diff --git a/.circleci/config.yml b/.circleci/config.yml index 3faca372c..b3b97363e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,9 @@ version: 2 jobs: build: - working_directory: /root/SRB2 + working_directory: /home/circleci/SRB2 docker: - - image: debian:stretch + - image: cimg/base:current environment: CC: ccache gcc -m32 PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig @@ -11,7 +11,7 @@ jobs: LIBGME_LDFLAGS: -lgme CCACHE_COMPRESS: true WFLAGS: -Wno-unsuffixed-float-constants - GCC49: true + GCC81: true #- image: ubuntu:trusty # environment: # CC: ccache gcc -m32 @@ -25,39 +25,42 @@ jobs: steps: - run: name: Add i386 arch - command: dpkg --add-architecture i386 + command: sudo dpkg --add-architecture i386 - run: name: Add STJr PPA command: | - apt-get -qq update - apt-get -qq -y install dirmngr - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0B1702D71499D9C25F986507F240F4449D3B0EC6 - echo "deb http://ppa.launchpad.net/stjr/srb2/ubuntu trusty main" >> /etc/apt/sources.list + sudo apt-get -qq update + sudo apt-get -qq -y install dirmngr + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0B1702D71499D9C25F986507F240F4449D3B0EC6 + echo "deb http://ppa.launchpad.net/stjr/srb2/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list - run: name: Make APT cache folder - command: mkdir -p /root/.cache/apt/archives/partial + command: mkdir -p /home/circleci/.cache/apt/archives/partial - run: name: Make APT cache usage by _apt - command: chown -Rv _apt:root /root/.cache/apt/archives/partial + command: sudo chown -Rv _apt:root /home/circleci/.cache/apt/archives/partial - run: name: Update APT listing - command: apt-get -qq update + command: sudo apt-get -qq update - run: name: Support S3 upload - command: apt-get -qq -y install ca-certificates + command: sudo apt-get -qq -y install ca-certificates - restore_cache: keys: - v1-SRB2-APT - run: - name: Install SDK - command: apt-get -o Dir::Cache="/root/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client + name: Uninstall amd64 SDK + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends remove libcurl4-openssl-dev:amd64 + - run: + name: Install i386 SDK + command: sudo apt-get -o Dir::Cache="/home/circleci/.cache/apt" -qq -y --no-install-recommends install git build-essential libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client - run: name: make md5sum - command: find /root/.cache/apt/archives -type f -print0 | sort -z | xargs -r0 md5sum > /root/.cache/apt_archives.md5 + command: sudo find /home/circleci/.cache/apt/archives -type f -print0 | sort -z | sudo xargs -r0 md5sum > /home/circleci/.cache/apt_archives.md5 - save_cache: - key: v1-SRB2-APT-{{ checksum "/root/.cache/apt_archives.md5" }} + key: v1-SRB2-APT-{{ checksum "/home/circleci/.cache/apt_archives.md5" }} paths: - - /root/.cache/apt + - /home/circleci/.cache/apt - checkout - run: name: Compile without network support @@ -78,9 +81,9 @@ jobs: name: Compile command: make -C src LINUX=1 ERRORMODE=1 -k -j4 - store_artifacts: - path: /root/SRB2/bin/ + path: /home/circleci/SRB2/bin/ destination: bin - save_cache: key: v1-SRB2-{{ .Branch }}-{{ checksum "make/linux/SDL.deps" }} paths: - - /root/.ccache + - /home/circleci/.ccache diff --git a/.gitattributes b/.gitattributes index 7751149ac..c2e507352 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,17 @@ #Source code +/Makefile text=auto /src/*.c text=auto /src/*.h text=auto /src/*.s text=auto /src/*.m text=auto /src/*.xpm text=auto /src/Makefile text=auto +/tools/Makefile text=auto /src/Make*.cfg text=auto /src/CMakeLists.txt text=auto +*.mk -whitespace text=auto # Windows EOL *.cs -crlf -whitespace -*.mk -crlf -whitespace *.bat -crlf -whitespace *.dev -crlf -whitespace *.dsp -crlf -whitespace diff --git a/.gitignore b/.gitignore index 2b4d9d2fb..268e36329 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ Win32_LIB_ASM_Release /bin /build /build/* +/CMakeUserPresets.json \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..b292bfc06 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,434 @@ +variables: + GIT_STRATEGY: clone + GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH + +default: + image: debian:stable-slim + + cache: + - key: ccache-$CI_PROJECT_PATH_SLUG-$CI_JOB_NAME_SLUG + fallback_keys: + - cache-$CI_PROJECT_PATH_SLUG-$CI_DEFAULT_BRANCH + - cache-$CI_PROJECT_PATH_SLUG-default + paths: + - ccache + - ccache_statslog + + - key: apt-$CI_JOB_IMAGE + paths: + - apt-cache + unprotect: true + + before_script: + - - | + # debconf + echo -e "\e[0Ksection_start:`date +%s`:debconf[collapsed=true]\r\e[0KSetup debconf's environment" + - export DEBIAN_FRONTEND="noninteractive" + - export DEBIAN_PRIORITY="low" + - export DEBCONF_NONINTERACTIVE_SEEN="true" + - | + # debconf + echo -e "\e[0Ksection_end:`date +%s`:debconf\r\e[0K" + - - | + # dpkg_aa + echo -e "\e[0Ksection_start:`date +%s`:dpkg_aa[collapsed=true]\r\e[0KAdding architectures to dpkg" + - dpkg --add-architecture i386 + - dpkg --add-architecture amd64 + - dpkg --add-architecture arm64 + - | + # dpkg_aa + echo -e "\e[0Ksection_end:`date +%s`:dpkg_aa\r\e[0K" + - - | + # apt_conf + echo -e "\e[0Ksection_start:`date +%s`:apt_conf[collapsed=true]\r\e[0KSetting up APT conf" + - export APT_CACHE_DIR=`pwd`/apt-cache + - mkdir --parents --verbose $APT_CACHE_DIR/partial/ + - touch /etc/apt/apt.conf.d/99build + - | + # apt.conf + echo Adding options to apt.conf':' + - | + # APT::Install-Recommends + echo APT::Install-Recommends "false"\; | tee --append /etc/apt/apt.conf.d/99build + - | + # quit + echo quiet "1"\; | tee --append /etc/apt/apt.conf.d/99build + - | + # APT::Get::Assume-Yes + echo APT::Get::Assume-Yes "true"\; | tee --append /etc/apt/apt.conf.d/99build + - | + # Dir::Cache::Archives + echo Dir::Cache::Archives "$APT_CACHE_DIR"\; | tee --append /etc/apt/apt.conf.d/99build + - | + # apt_conf + echo -e "\e[0Ksection_end:`date +%s`:apt_conf\r\e[0K" + - - | + # apt_update + echo -e "\e[0Ksection_start:`date +%s`:apt_update[collapsed=true]\r\e[0KUpdating APT listing" + - apt-get update + - | + # apt_update + echo -e "\e[0Ksection_end:`date +%s`:apt_update\r\e[0K" + + - - | + # apt_pre + echo -e "\e[0Ksection_start:`date +%s`:apt_pre[collapsed=true]\r\e[0KInstalling pre packages" + - apt-get install apt-utils + - | + # apt_pre + echo -e "\e[0Ksection_end:`date +%s`:apt_pre\r\e[0K" + + - - | + # apt_upgrade + echo -e "\e[0Ksection_start:`date +%s`:apt_upgrade[collapsed=true]\r\e[0KUpdating existing packages" + - apt-get upgrade + - | + # apt_update + echo -e "\e[0Ksection_end:`date +%s`:apt_upgrade\r\e[0K" + + - - | + # apt_common + echo -e "\e[0Ksection_start:`date +%s`:apt_common[collapsed=true]\r\e[0KInstalling common packages" + - apt-get install make git ccache nasm + - | + # apt_common + echo -e "\e[0Ksection_end:`date +%s`:apt_common\r\e[0K" + + - - | + # 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 --append ~/.ccache/ccache.conf + - | + # cache_dir + echo cache_dir = $PWD/ccache | tee --append ~/.ccache/ccache.conf + - | + # compiler_check + echo compiler_check = content | tee --append ~/.ccache/ccache.conf + - | + # stats_log + echo stats_log = $PWD/ccache_statslog | tee --append ~/.ccache/ccache.conf + - | + # max_size + echo max_size = 50M | tee --append ~/.ccache/ccache.conf + - | + # ccache_config + echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K" + + - - | + # cache_reset + echo -e "\e[0Ksection_start:`date +%s`:ccache_reset[collapsed=true]\r\e[0KResetting ccache statistics" + - ccache --zero-stats + - ccache --show-stats + - | + # ccache_reset + echo -e "\e[0Ksection_end:`date +%s`:ccache_reset\r\e[0K" + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-$CI_JOB_NAME_SLUG" + + 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" + + - - | + # ccache_stats + echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:" + - ccache --show-stats --verbose + - ccache --show-log-stats --verbose + - | + # ccahe_stats + echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K" + +stages: + - build + +Debian testing GCC: + stage: build + image: debian:testing-slim + + allow_failure: true + + artifacts: + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-gcc" + + variables: + CC: gcc + LDFLAGS: -Wl,-fuse-ld=gold + + script: + - - | + # apt_toolchain + echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" + - apt-get install gcc + - | + # apt_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # 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 + - | + # apt_development + echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Windows x86: + stage: build + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Win32" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win32" + + variables: + PREFIX: i686-w64-mingw32 + + script: + - - | + # 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_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 MINGW=1 SDL=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 MINGW=1 SDL=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Debian stable:amd64: + stage: build + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Debian amd64" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-x86-64" + + variables: + CC: x86_64-linux-gnu-gcc + LDFLAGS: -Wl,-fuse-ld=gold + OBJCOPY: x86_64-linux-gnu-objcopy + OBJDUMP: x86_64-linux-gnu-objdump + PKG_CONFIG_PATH: /usr/lib/x86_64-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-x86-64-linux-gnu || apt-get install gcc + - | + # apt_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # 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 + - | + # apt_development + echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Debian stable:i386: + stage: build + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Debian i386" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-i686" + + variables: + CC: i686-linux-gnu-gcc + OBJCOPY: i686-linux-gnu-objcopy + OBJDUMP: i686-linux-gnu-objdump + 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_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # 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 + - | + # apt_development + echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Debian stable:arm64: + stage: build + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Debian arm64" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-aarch64" + + variables: + CC: aarch64-linux-gnu-gcc + LDFLAGS: -Wl,-fuse-ld=gold + OBJCOPY: aarch64-linux-gnu-objcopy + OBJDUMP: aarch64-linux-gnu-objdump + 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_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # 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 + - | + # apt_development + echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 LINUX64=1 NONX86=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Windows x64: + stage: build + + artifacts: + paths: + - "bin/" + - "src/comptime.h" + expose_as: "Win64" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win64" + + variables: + PREFIX: x86_64-w64-mingw32 + + script: + - - | + # 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_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 MINGW64=1 SDL=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 MINGW64=1 SDL=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Debian stable Clang: + stage: build + + allow_failure: true + + artifacts: + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang" + + variables: + CC: clang + WFLAGS: -Wno-cast-align + CFLAGS: -Wno-cast-align + LDFLAGS: -Wl,-fuse-ld=gold + + script: + - - | + # apt_toolchain + echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" + - apt-get install clang + - | + # apt_toolchain + echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" + + - - | + # 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 + - | + # apt_development + echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" + + - - | + # make + echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" + - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + +Debian testing Clang: + extends: Debian stable Clang + + image: debian:testing-slim + + artifacts: + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-clang" + + variables: + CC: clang + WFLAGS: -Wno-cast-align -Wno-deprecated-non-prototype + CFLAGS: -Wno-cast-align -Wno-deprecated-non-prototype + LDFLAGS: -Wl,-fuse-ld=gold diff --git a/appveyor.yml b/appveyor.yml index 9770cb37d..63d801b73 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.2.11.{branch}-{build} +version: 2.2.13.{branch}-{build} os: MinGW environment: diff --git a/cpdebug.mk b/cpdebug.mk index 6baedf227..75f08c66f 100644 --- a/cpdebug.mk +++ b/cpdebug.mk @@ -3,12 +3,12 @@ ifdef ComSpec COMSPEC=$(ComSpec) endif ifdef COMSPEC -OBJCOPY=objcopy.exe -OBJDUMP=objdump.exe +OBJCOPY?=objcopy.exe +OBJDUMP?=objdump.exe GZIP?=gzip.exe else -OBJCOPY=objcopy -OBJDUMP=objdump +OBJCOPY?=objcopy +OBJDUMP?=objdump GZIP?=gzip endif DBGNAME=$(BIN).debug diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg index dd5cdb46b..41ad99889 100644 --- a/extras/conf/SRB2-22.cfg +++ b/extras/conf/SRB2-22.cfg @@ -4367,7 +4367,6 @@ thingtypes { color = 14; // Yellow title = "Rings and Weapon Panels"; - width = 24; height = 24; flags8height = 24; flags8text = "[8] Float"; @@ -4377,7 +4376,6 @@ thingtypes { title = "Ring"; sprite = "RINGA0"; - width = 16; } 301 { @@ -4393,6 +4391,7 @@ thingtypes { title = "Infinity Ring"; sprite = "RNGIA0"; + width = 24; } 304 { @@ -4418,43 +4417,53 @@ thingtypes { title = "CTF Team Ring (Red)"; sprite = "internal:TRNGA0R"; - width = 16; } 309 { title = "CTF Team Ring (Blue)"; sprite = "internal:TRNGA0B"; - width = 16; } 330 { title = "Bounce Ring Panel"; sprite = "PIKBA0"; + width = 24; + height = 40; } 331 { title = "Rail Ring Panel"; sprite = "PIKRA0"; + width = 24; + height = 40; } 332 { title = "Automatic Ring Panel"; sprite = "PIKAA0"; + width = 24; + height = 40; } 333 { title = "Explosion Ring Panel"; sprite = "PIKEA0"; + width = 24; + height = 40; } 334 { title = "Scatter Ring Panel"; sprite = "PIKSA0"; + width = 24; + height = 40; } 335 { title = "Grenade Ring Panel"; sprite = "PIKGA0"; + width = 24; + height = 40; } } @@ -4463,7 +4472,7 @@ thingtypes color = 10; // Light Green title = "Other Collectibles"; width = 16; - height = 32; + height = 24; sort = 1; sprite = "CEMGA0"; @@ -4529,6 +4538,7 @@ thingtypes { title = "Emerald Hunt Location"; sprite = "SHRDA0"; + height = 32; flags8height = 24; flags8text = "[8] Float"; } diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg index df08e3ac5..9eb227974 100644 --- a/extras/conf/udb/Includes/SRB222_things.cfg +++ b/extras/conf/udb/Includes/SRB222_things.cfg @@ -1185,7 +1185,7 @@ udmf { color = 14; // Yellow title = "Rings and Weapon Panels"; - width = 24; + width = 16; height = 24; sprite = "RINGA0"; @@ -1193,7 +1193,6 @@ udmf { title = "Ring"; sprite = "RINGA0"; - width = 16; arg0 { title = "Float?"; @@ -1227,6 +1226,7 @@ udmf { title = "Infinity Ring"; sprite = "RNGIA0"; + width = 24; arg0 { title = "Float?"; @@ -1282,7 +1282,6 @@ udmf { title = "CTF Team Ring (Red)"; sprite = "internal:TRNGA0R"; - width = 16; arg0 { title = "Float?"; @@ -1294,7 +1293,6 @@ udmf { title = "CTF Team Ring (Blue)"; sprite = "internal:TRNGA0B"; - width = 16; arg0 { title = "Float?"; @@ -1306,6 +1304,8 @@ udmf { title = "Bounce Ring Panel"; sprite = "PIKBA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1317,6 +1317,8 @@ udmf { title = "Rail Ring Panel"; sprite = "PIKRA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1328,6 +1330,8 @@ udmf { title = "Automatic Ring Panel"; sprite = "PIKAA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1339,6 +1343,8 @@ udmf { title = "Explosion Ring Panel"; sprite = "PIKEA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1350,6 +1356,8 @@ udmf { title = "Scatter Ring Panel"; sprite = "PIKSA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1361,6 +1369,8 @@ udmf { title = "Grenade Ring Panel"; sprite = "PIKGA0"; + width = 24; + height = 40; arg0 { title = "Float?"; @@ -1375,7 +1385,7 @@ udmf color = 10; // Light_Green title = "Other Collectibles"; width = 16; - height = 32; + height = 24; sort = 1; sprite = "CEMGA0"; @@ -1445,6 +1455,7 @@ udmf { title = "Emerald Hunt Location"; sprite = "SHRDA0"; + height = 32; arg0 { title = "Float?"; diff --git a/src/Android.mk b/src/Android.mk index 035d48887..235c4b81e 100644 --- a/src/Android.mk +++ b/src/Android.mk @@ -8,11 +8,7 @@ LOCAL_SRC_FILES := am_map.c \ command.c \ comptime.c \ console.c \ - d_clisrv.c \ d_main.c \ - d_net.c \ - d_netcmd.c \ - d_netfil.c \ dehacked.c \ f_finale.c \ f_wipe.c \ @@ -20,7 +16,6 @@ LOCAL_SRC_FILES := am_map.c \ g_game.c \ g_input.c \ hu_stuff.c \ - i_tcp.c \ info.c \ lzf.c \ m_argv.c \ @@ -32,7 +27,6 @@ LOCAL_SRC_FILES := am_map.c \ m_queue.c \ m_random.c \ md5.c \ - mserv.c \ p_ceilng.c \ p_enemy.c \ p_fab.c \ @@ -61,6 +55,7 @@ LOCAL_SRC_FILES := am_map.c \ r_things.c \ s_sound.c \ screen.c \ + snake.c \ sounds.c \ st_stuff.c \ string.c \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92cc11637..15d845c29 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,10 +6,6 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 config.h.in string.c d_main.c - d_clisrv.c - d_net.c - d_netfil.c - d_netcmd.c dehacked.c deh_soc.c deh_lua.c @@ -83,12 +79,10 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 sounds.c w_wad.c filesrch.c - mserv.c - http-mserv.c - i_tcp.c lzf.c b_bot.c u_list.c + snake.c lua_script.c lua_baselib.c lua_mathlib.c @@ -150,6 +144,7 @@ set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL "Compile a development build of SRB2.") add_subdirectory(blua) +add_subdirectory(netcode) # OS macros if (UNIX) diff --git a/src/Makefile b/src/Makefile index e1d9cb384..a0a18be76 100644 --- a/src/Makefile +++ b/src/Makefile @@ -62,7 +62,6 @@ # # Netplay incompatible # -------------------- -# NONET=1 - Disable online capability. # NOMD5=1 - Disable MD5 checksum (validation tool). # NOPOSTPROCESSING=1 - ? # MOBJCONSISTANCY=1 - ?? @@ -141,9 +140,9 @@ endif OBJDUMP_OPTS?=--wide --source --line-numbers -OBJCOPY:=$(call Prefix,objcopy) -OBJDUMP:=$(call Prefix,objdump) -WINDRES:=$(call Prefix,windres) +OBJCOPY?=$(call Prefix,objcopy) +OBJDUMP?=$(call Prefix,objdump) +WINDRES?=$(call Prefix,windres) GZIP?=gzip GZIP_OPTS?=-9 -f -n @@ -187,6 +186,7 @@ objdir:=$(makedir)/objs sources+=\ $(call List,Sourcefile)\ $(call List,blua/Sourcefile)\ + $(call List,netcode/Sourcefile)\ depends:=$(basename $(filter %.c %.s,$(sources))) objects:=$(basename $(filter %.c %.s %.nas,$(sources))) @@ -392,4 +392,4 @@ else @: endif -$(warning The handwritten GNU Makefile for SRB2 is deprecated, and may be removed in the future. Please consider switching to CMake.) +#$(warning The handwritten GNU Makefile for SRB2 is deprecated, and may be removed in the future. Please consider switching to CMake.) diff --git a/src/Makefile.d/detect.mk b/src/Makefile.d/detect.mk index aca498721..9e2736946 100644 --- a/src/Makefile.d/detect.mk +++ b/src/Makefile.d/detect.mk @@ -56,15 +56,18 @@ endif # This must have high to low order. gcc_versions:=\ - 102 101\ - 93 92 91\ - 84 83 82 81\ - 75 74 73 72 71\ - 64 63 62 61\ - 55 54 53 52 51\ + 132 131 130\ + 123 122 121 120\ + 114 113 112 111 110\ + 105 104 103 102 101 100\ + 95 94 93 92 91 90\ + 85 84 83 82 81 80\ + 75 74 73 72 71 70\ + 64 63 62 61 60\ + 55 54 53 52 51 50\ 49 48 47 46 45 44 43 42 41 40 -latest_gcc_version:=10.2 +latest_gcc_version:=13.2 # Automatically set version flag, but not if one was # manually set. And don't bother if this is a clean only @@ -74,13 +77,18 @@ ifeq (,$(call Wildvar,GCC% destructive)) # can't use $(CC) --version here since that uses argv[0] to display the name # also gcc outputs the information to stderr, so I had to do 2>&1 # this program really doesn't like identifying itself -version:=$(shell $(CC) -v 2>&1) +shellversion:=$(shell $(CC) -v 2>&1) +# Try to remove "-win32" +version:=$(subst -win32,.0,$(shellversion)) # check if this is in fact GCC ifneq (,$(findstring gcc version,$(version))) # in stark contrast to the name, gcc will give me a nicely formatted version number for free -version:=$(shell $(CC) -dumpfullversion) +shellversion:=$(shell $(CC) -dumpfullversion) + +# Try to remove "-win32" +version:=$(subst -win32,.0,$(shellversion)) # Turn version into words of major, minor v:=$(subst ., ,$(version)) diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 1787f94cb..8b713718c 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -1,68 +1,66 @@ -# -# Makefile for feature flags. -# - -passthru_opts+=\ - NONET NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\ - MOBJCONSISTANCY PACKETDROP ZDEBUG\ - HAVE_MINIUPNPC\ - -# build with debugging information -ifdef DEBUGMODE -PACKETDROP=1 -opts+=-DPARANOIA -DRANGECHECK -endif - -ifndef NOHW -opts+=-DHWRENDER -sources+=$(call List,hardware/Sourcefile) -endif - -ifndef NOMD5 -sources+=md5.c -endif - -ifndef NOZLIB -ifndef NOPNG -ifdef PNG_PKGCONFIG -$(eval $(call Use_pkg_config,PNG_PKGCONFIG)) -else -PNG_CONFIG?=$(call Prefix,libpng-config) -$(eval $(call Configure,PNG,$(PNG_CONFIG) \ - $(if $(PNG_STATIC),--static),,--ldflags)) -endif -ifdef LINUX -opts+=-D_LARGEFILE64_SOURCE -endif -opts+=-DHAVE_PNG -sources+=apng.c -endif -endif - -ifndef NONET -ifndef NOCURL -CURLCONFIG?=curl-config -$(eval $(call Configure,CURL,$(CURLCONFIG))) -opts+=-DHAVE_CURL -endif -endif - -ifdef HAVE_MINIUPNPC -libs+=-lminiupnpc -endif - -# (Valgrind is a memory debugger.) -ifdef VALGRIND -VALGRIND_PKGCONFIG?=valgrind -$(eval $(call Use_pkg_config,VALGRIND)) -ZDEBUG=1 -opts+=-DHAVE_VALGRIND -endif - -default_packages:=\ - GME/libgme/LIBGME\ - OPENMPT/libopenmpt/LIBOPENMPT\ - ZLIB/zlib\ - -$(foreach p,$(default_packages),\ - $(eval $(call Check_pkg_config,$(p)))) +# +# Makefile for feature flags. +# + +passthru_opts+=\ + NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\ + MOBJCONSISTANCY PACKETDROP ZDEBUG\ + HAVE_MINIUPNPC\ + +# build with debugging information +ifdef DEBUGMODE +PACKETDROP=1 +opts+=-DPARANOIA -DRANGECHECK +endif + +ifndef NOHW +opts+=-DHWRENDER +sources+=$(call List,hardware/Sourcefile) +endif + +ifndef NOMD5 +sources+=md5.c +endif + +ifndef NOZLIB +ifndef NOPNG +ifdef PNG_PKGCONFIG +$(eval $(call Use_pkg_config,PNG_PKGCONFIG)) +else +PNG_CONFIG?=$(call Prefix,libpng-config) +$(eval $(call Configure,PNG,$(PNG_CONFIG) \ + $(if $(PNG_STATIC),--static),,--ldflags)) +endif +ifdef LINUX +opts+=-D_LARGEFILE64_SOURCE +endif +opts+=-DHAVE_PNG +sources+=apng.c +endif +endif + +ifndef NOCURL +CURLCONFIG?=curl-config +$(eval $(call Configure,CURL,$(CURLCONFIG))) +opts+=-DHAVE_CURL +endif + +ifdef HAVE_MINIUPNPC +libs+=-lminiupnpc +endif + +# (Valgrind is a memory debugger.) +ifdef VALGRIND +VALGRIND_PKGCONFIG?=valgrind +$(eval $(call Use_pkg_config,VALGRIND)) +ZDEBUG=1 +opts+=-DHAVE_VALGRIND +endif + +default_packages:=\ + GME/libgme/LIBGME\ + OPENMPT/libopenmpt/LIBOPENMPT\ + ZLIB/zlib\ + +$(foreach p,$(default_packages),\ + $(eval $(call Check_pkg_config,$(p)))) diff --git a/src/Makefile.d/nix.mk b/src/Makefile.d/nix.mk index aa2e96df7..9adf3f0f1 100644 --- a/src/Makefile.d/nix.mk +++ b/src/Makefile.d/nix.mk @@ -25,7 +25,7 @@ endif # Tested by Steel, as of release 2.2.8. ifdef FREEBSD opts+=-I/usr/X11R6/include -DLINUX -DFREEBSD -libs+=-L/usr/X11R6/lib -lipx -lkvm +libs+=-L/usr/X11R6/lib -lkvm -lexecinfo endif # FIXME: UNTESTED diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk index 73a3d9e45..a858881c0 100644 --- a/src/Makefile.d/win32.mk +++ b/src/Makefile.d/win32.mk @@ -33,12 +33,10 @@ libs+=-lws2_32 endif endif -ifndef NONET ifndef MINGW64 # miniupnc is broken with MINGW64 opts+=-I../libs -DSTATIC_MINIUPNPC libs+=-L../libs/miniupnpc/mingw$(32) -lws2_32 -liphlpapi endif -endif ifndef MINGW64 32=32 diff --git a/src/Sourcefile b/src/Sourcefile index f2b408c66..6ed1f3b4c 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -1,9 +1,5 @@ string.c d_main.c -d_clisrv.c -d_net.c -d_netfil.c -d_netcmd.c dehacked.c deh_soc.c deh_lua.c @@ -77,12 +73,10 @@ s_sound.c sounds.c w_wad.c filesrch.c -mserv.c -http-mserv.c -i_tcp.c lzf.c b_bot.c u_list.c +snake.c lua_script.c lua_baselib.c lua_mathlib.c diff --git a/src/am_map.c b/src/am_map.c index 331d1a5e0..df3a45cff 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -1071,7 +1071,6 @@ static inline void AM_drawPlayers(void) return; } - // multiplayer (how??) for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) diff --git a/src/android/i_net.c b/src/android/i_net.c index f6e642022..4c30dc767 100644 --- a/src/android/i_net.c +++ b/src/android/i_net.c @@ -1,4 +1,4 @@ -#include "../i_net.h" +#include "../netcode/i_net.h" boolean I_InitNetwork(void) { diff --git a/src/blua/liolib.c b/src/blua/liolib.c index 545f9c144..6b44e2eb8 100644 --- a/src/blua/liolib.c +++ b/src/blua/liolib.c @@ -19,7 +19,8 @@ #include "lualib.h" #include "../i_system.h" #include "../g_game.h" -#include "../d_netfil.h" +#include "../netcode/d_netfil.h" +#include "../netcode/net_command.h" #include "../lua_libs.h" #include "../byteptr.h" #include "../lua_script.h" diff --git a/src/command.c b/src/command.c index e1a43522d..e0deff8e1 100644 --- a/src/command.c +++ b/src/command.c @@ -28,11 +28,12 @@ #include "byteptr.h" #include "p_saveg.h" #include "g_game.h" // for player_names -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" +#include "netcode/net_command.h" #include "hu_stuff.h" #include "p_setup.h" #include "lua_script.h" -#include "d_netfil.h" // findfile +#include "netcode/d_netfil.h" // findfile #include "r_data.h" // Color_cons_t #include "d_main.h" // D_IsPathAllowed diff --git a/src/config.h.in b/src/config.h.in index 0ca0f26e9..6d49a6989 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -38,12 +38,14 @@ * Last updated 2021 / 05 / 06 - v2.2.9 - patch.pk3 & zones.pk3 * Last updated 2022 / 03 / 06 - v2.2.10 - main assets * Last updated 2023 / 05 / 02 - v2.2.11 - patch.pk3 & zones.pk3 + * Last updated 2023 / 09 / 06 - v2.2.12 - patch.pk3 + * Last updated 2023 / 09 / 09 - v2.2.13 - none */ #define ASSET_HASH_SRB2_PK3 "ad911f29a28a18968ee5b2d11c2acb39" #define ASSET_HASH_ZONES_PK3 "1c8adf8d079ecb87d00081f158acf3c7" #define ASSET_HASH_PLAYER_DTA "2e7aaae8a6b1b77d90ffe7606ceadb6c" #ifdef USE_PATCH_DTA -#define ASSET_HASH_PATCH_PK3 "2e69558bce3b9610624549a75e29e19b" +#define ASSET_HASH_PATCH_PK3 "3c7b73f34af7e9a7bceb2d5260f76172" #endif #endif diff --git a/src/d_clisrv.c b/src/d_clisrv.c deleted file mode 100755 index 83482b527..000000000 --- a/src/d_clisrv.c +++ /dev/null @@ -1,5841 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2023 by Sonic Team Junior. -// -// 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 d_clisrv.c -/// \brief SRB2 Network game communication and protocol, all OS independent parts. - -#include -#ifdef __GNUC__ -#include //for unlink -#endif - -#include "i_time.h" -#include "i_net.h" -#include "i_system.h" -#include "i_video.h" -#include "d_net.h" -#include "d_main.h" -#include "g_game.h" -#include "st_stuff.h" -#include "hu_stuff.h" -#include "keys.h" -#include "g_input.h" // JOY1 -#include "m_menu.h" -#include "console.h" -#include "d_netfil.h" -#include "byteptr.h" -#include "p_saveg.h" -#include "z_zone.h" -#include "p_local.h" -#include "m_misc.h" -#include "am_map.h" -#include "m_random.h" -#include "mserv.h" -#include "y_inter.h" -#include "r_local.h" -#include "m_argv.h" -#include "p_setup.h" -#include "lzf.h" -#include "lua_script.h" -#include "lua_hook.h" -#include "lua_libs.h" -#include "md5.h" -#include "m_perfstats.h" - -// aaaaaa -#include "i_joy.h" - -#ifndef NONET -// cl loading screen -#include "v_video.h" -#include "f_finale.h" -#endif - -// -// NETWORKING -// -// gametic is the tic about to (or currently being) run -// Server: -// maketic is the tic that hasn't had control made for it yet -// nettics is the tic for each node -// firstticstosend is the lowest value of nettics -// Client: -// neededtic is the tic needed by the client to run the game -// firstticstosend is used to optimize a condition -// Normally maketic >= gametic > 0 - -#define PREDICTIONQUEUE BACKUPTICS -#define PREDICTIONMASK (PREDICTIONQUEUE-1) -#define MAX_REASONLENGTH 30 - -boolean server = true; // true or false but !server == client -#define client (!server) -boolean nodownload = false; -boolean serverrunning = false; -INT32 serverplayer = 0; -char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) - -// Server specific vars -UINT8 playernode[MAXPLAYERS]; -char playeraddress[MAXPLAYERS][64]; - -// Minimum timeout for sending the savegame -// The actual timeout will be longer depending on the savegame length -tic_t jointimeout = (10*TICRATE); -static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame? -static boolean resendingsavegame[MAXNETNODES]; // Are we resending the savegame? -static tic_t savegameresendcooldown[MAXNETNODES]; // How long before we can resend again? -static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout? - -// Incremented by cv_joindelay when a client joins, decremented each tic. -// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled. -static tic_t joindelay = 0; - -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. -SINT8 nodetoplayer[MAXNETNODES]; -SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen) -UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen -boolean nodeingame[MAXNETNODES]; // set false as nodes leave game -tic_t servermaxping = 800; // server's max ping. Defaults to 800 -static tic_t nettics[MAXNETNODES]; // what tic the client have received -static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet -static UINT8 nodewaiting[MAXNETNODES]; -static tic_t firstticstosend; // min of the nettics -static tic_t tictoclear = 0; // optimize d_clearticcmd -static tic_t maketic; - -static INT16 consistancy[BACKUPTICS]; - -static UINT8 player_joining = false; -UINT8 hu_redownloadinggamestate = 0; - -// true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks -boolean hu_stopped = false; - -consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL); - -UINT8 adminpassmd5[16]; -boolean adminpasswordset = false; - -// Client specific -static ticcmd_t localcmds; -static ticcmd_t localcmds2; -static boolean cl_packetmissed; -// here it is for the secondary local player (splitscreen) -static UINT8 mynode; // my address pointofview server -static boolean cl_redownloadinggamestate = false; - -static UINT8 localtextcmd[MAXTEXTCMD]; -static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen -static tic_t neededtic; -SINT8 servernode = 0; // the number of the server node - -/// \brief do we accept new players? -/// \todo WORK! -boolean acceptnewnode = true; - -static 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 -static tic_t firstconnectattempttime = 0; - -// engine - -// Must be a power of two -#define TEXTCMD_HASH_SIZE 4 - -typedef struct textcmdplayer_s -{ - INT32 playernum; - UINT8 cmd[MAXTEXTCMD]; - struct textcmdplayer_s *next; -} textcmdplayer_t; - -typedef struct textcmdtic_s -{ - tic_t tic; - textcmdplayer_t *playercmds[TEXTCMD_HASH_SIZE]; - struct textcmdtic_s *next; -} textcmdtic_t; - -ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS]; -static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL}; - - -consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); - -static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}}; -consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL); - -static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n) -{ - const size_t d = n / sizeof(ticcmd_t); - const size_t r = n % sizeof(ticcmd_t); - UINT8 *ret = dest; - - if (r) - M_Memcpy(dest, src, n); - else if (d) - G_MoveTiccmd(dest, src, d); - return ret+n; -} - -static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n) -{ - const size_t d = n / sizeof(ticcmd_t); - const size_t r = n % sizeof(ticcmd_t); - UINT8 *ret = src; - - if (r) - M_Memcpy(dest, src, n); - else if (d) - G_MoveTiccmd(dest, src, d); - return ret+n; -} - - - -// Some software don't support largest packet -// (original sersetup, not exactely, but the probability of sending a packet -// of 512 bytes is like 0.1) -UINT16 software_MAXPACKETLENGTH; - -/** Guesses the full value of a tic from its lowest byte, for a specific node - * - * \param low The lowest byte of the tic value - * \param node The node to deduce the tic for - * \return The full tic value - * - */ -tic_t ExpandTics(INT32 low, INT32 node) -{ - INT32 delta; - - delta = low - (nettics[node] & UINT8_MAX); - - if (delta >= -64 && delta <= 64) - return (nettics[node] & ~UINT8_MAX) + low; - else if (delta > 64) - return (nettics[node] & ~UINT8_MAX) - 256 + low; - else //if (delta < -64) - return (nettics[node] & ~UINT8_MAX) + 256 + low; -} - -// ----------------------------------------------------------------- -// Some extra data function for handle textcmd buffer -// ----------------------------------------------------------------- - -static void (*listnetxcmd[MAXNETXCMD])(UINT8 **p, INT32 playernum); - -void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum)) -{ -#ifdef PARANOIA - if (id >= MAXNETXCMD) - I_Error("Command id %d too big", id); - if (listnetxcmd[id] != 0) - I_Error("Command id %d already used", id); -#endif - listnetxcmd[id] = cmd_f; -} - -void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam) -{ - if (localtextcmd[0]+2+nparam > MAXTEXTCMD) - { - // for future reference: if (cv_debug) != debug disabled. - CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[0], sizeu1(nparam)); - return; - } - localtextcmd[0]++; - localtextcmd[localtextcmd[0]] = (UINT8)id; - if (param && nparam) - { - M_Memcpy(&localtextcmd[localtextcmd[0]+1], param, nparam); - localtextcmd[0] = (UINT8)(localtextcmd[0] + (UINT8)nparam); - } -} - -// splitscreen player -void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam) -{ - if (localtextcmd2[0]+2+nparam > MAXTEXTCMD) - { - I_Error("No more place in the buffer for netcmd %d\n",id); - return; - } - localtextcmd2[0]++; - localtextcmd2[localtextcmd2[0]] = (UINT8)id; - if (param && nparam) - { - M_Memcpy(&localtextcmd2[localtextcmd2[0]+1], param, nparam); - localtextcmd2[0] = (UINT8)(localtextcmd2[0] + (UINT8)nparam); - } -} - -UINT8 GetFreeXCmdSize(void) -{ - // -1 for the size and another -1 for the ID. - return (UINT8)(localtextcmd[0] - 2); -} - -// Frees all textcmd memory for the specified tic -static void D_FreeTextcmd(tic_t tic) -{ - textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; - textcmdtic_t *textcmdtic = *tctprev; - - while (textcmdtic && textcmdtic->tic != tic) - { - tctprev = &textcmdtic->next; - textcmdtic = textcmdtic->next; - } - - if (textcmdtic) - { - INT32 i; - - // Remove this tic from the list. - *tctprev = textcmdtic->next; - - // Free all players. - for (i = 0; i < TEXTCMD_HASH_SIZE; i++) - { - textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[i]; - - while (textcmdplayer) - { - textcmdplayer_t *tcpnext = textcmdplayer->next; - Z_Free(textcmdplayer); - textcmdplayer = tcpnext; - } - } - - // Free this tic's own memory. - Z_Free(textcmdtic); - } -} - -// Gets the buffer for the specified ticcmd, or NULL if there isn't one -static UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum) -{ - textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; - while (textcmdtic && textcmdtic->tic != tic) textcmdtic = textcmdtic->next; - - // Do we have an entry for the tic? If so, look for player. - if (textcmdtic) - { - textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)]; - while (textcmdplayer && textcmdplayer->playernum != playernum) textcmdplayer = textcmdplayer->next; - - if (textcmdplayer) return textcmdplayer->cmd; - } - - return NULL; -} - -// Gets the buffer for the specified ticcmd, creating one if necessary -static UINT8* D_GetTextcmd(tic_t tic, INT32 playernum) -{ - textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; - textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; - textcmdplayer_t *textcmdplayer, **tcpprev; - - // Look for the tic. - while (textcmdtic && textcmdtic->tic != tic) - { - tctprev = &textcmdtic->next; - textcmdtic = textcmdtic->next; - } - - // If we don't have an entry for the tic, make it. - if (!textcmdtic) - { - textcmdtic = *tctprev = Z_Calloc(sizeof (textcmdtic_t), PU_STATIC, NULL); - textcmdtic->tic = tic; - } - - tcpprev = &textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)]; - textcmdplayer = *tcpprev; - - // Look for the player. - while (textcmdplayer && textcmdplayer->playernum != playernum) - { - tcpprev = &textcmdplayer->next; - textcmdplayer = textcmdplayer->next; - } - - // If we don't have an entry for the player, make it. - if (!textcmdplayer) - { - textcmdplayer = *tcpprev = Z_Calloc(sizeof (textcmdplayer_t), PU_STATIC, NULL); - textcmdplayer->playernum = playernum; - } - - return textcmdplayer->cmd; -} - -static void ExtraDataTicker(void) -{ - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] || i == 0) - { - UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i); - - if (bufferstart) - { - UINT8 *curpos = bufferstart; - UINT8 *bufferend = &curpos[curpos[0]+1]; - - curpos++; - while (curpos < bufferend) - { - if (*curpos < MAXNETXCMD && listnetxcmd[*curpos]) - { - const UINT8 id = *curpos; - curpos++; - DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i)); - (listnetxcmd[id])(&curpos, i); - DEBFILE("done\n"); - } - else - { - if (server) - { - SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); - DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic)); - } - CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]); - break; - } - } - } - } - - // If you are a client, you can safely forget the net commands for this tic - // If you are the server, you need to remember them until every client has been acknowledged, - // because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it - if (client) - D_FreeTextcmd(gametic); -} - -static void D_Clearticcmd(tic_t tic) -{ - INT32 i; - - D_FreeTextcmd(tic); - - for (i = 0; i < MAXPLAYERS; i++) - netcmds[tic%BACKUPTICS][i].angleturn = 0; - - DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS)); -} - -void D_ResetTiccmds(void) -{ - INT32 i; - - memset(&localcmds, 0, sizeof(ticcmd_t)); - memset(&localcmds2, 0, sizeof(ticcmd_t)); - - // Reset the net command list - for (i = 0; i < TEXTCMD_HASH_SIZE; i++) - while (textcmds[i]) - D_Clearticcmd(textcmds[i]->tic); -} - -void SendKick(UINT8 playernum, UINT8 msg) -{ - UINT8 buf[2]; - - if (!(server && cv_rejointimeout.value)) - msg &= ~KICK_MSG_KEEP_BODY; - - buf[0] = playernum; - buf[1] = msg; - SendNetXCmd(XD_KICK, &buf, 2); -} - -// ----------------------------------------------------------------- -// end of extra data function -// ----------------------------------------------------------------- - -// ----------------------------------------------------------------- -// extra data function for lmps -// ----------------------------------------------------------------- - -// if extradatabit is set, after the ziped tic you find this: -// -// type | description -// ---------+-------------- -// byte | size of the extradata -// byte | the extradata (xd) bits: see XD_... -// with this byte you know what parameter folow -// if (xd & XDNAMEANDCOLOR) -// byte | color -// char[MAXPLAYERNAME] | name of the player -// endif -// if (xd & XD_WEAPON_PREF) -// byte | original weapon switch: boolean, true if use the old -// | weapon switch methode -// char[NUMWEAPONS] | the weapon switch priority -// byte | autoaim: true if use the old autoaim system -// endif -/*boolean AddLmpExtradata(UINT8 **demo_point, INT32 playernum) -{ - UINT8 *textcmd = D_GetExistingTextcmd(gametic, playernum); - - if (!textcmd) - return false; - - M_Memcpy(*demo_point, textcmd, textcmd[0]+1); - *demo_point += textcmd[0]+1; - return true; -} - -void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum) -{ - UINT8 nextra; - UINT8 *textcmd; - - if (!demo_pointer) - return; - - textcmd = D_GetTextcmd(gametic, playernum); - nextra = **demo_pointer; - M_Memcpy(textcmd, *demo_pointer, nextra + 1); - // increment demo pointer - *demo_pointer += nextra + 1; -}*/ - -// ----------------------------------------------------------------- -// end extra data function for lmps -// ----------------------------------------------------------------- - -static INT16 Consistancy(void); - -typedef enum -{ - CL_SEARCHING, - CL_CHECKFILES, - CL_DOWNLOADFILES, - CL_ASKJOIN, - CL_LOADFILES, - CL_WAITJOINRESPONSE, - CL_DOWNLOADSAVEGAME, - CL_CONNECTED, - CL_ABORTED, - CL_ASKFULLFILELIST, - CL_CONFIRMCONNECT -} cl_mode_t; - -static void GetPackets(void); - -static cl_mode_t cl_mode = CL_SEARCHING; - -static UINT16 cl_lastcheckedfilecount = 0; // used for full file list - -#ifndef NONET -#define SNAKE_SPEED 5 - -#define SNAKE_NUM_BLOCKS_X 20 -#define SNAKE_NUM_BLOCKS_Y 10 -#define SNAKE_BLOCK_SIZE 12 -#define SNAKE_BORDER_SIZE 12 - -#define SNAKE_MAP_WIDTH (SNAKE_NUM_BLOCKS_X * SNAKE_BLOCK_SIZE) -#define SNAKE_MAP_HEIGHT (SNAKE_NUM_BLOCKS_Y * SNAKE_BLOCK_SIZE) - -#define SNAKE_LEFT_X ((BASEVIDWIDTH - SNAKE_MAP_WIDTH) / 2 - SNAKE_BORDER_SIZE) -#define SNAKE_RIGHT_X (SNAKE_LEFT_X + SNAKE_MAP_WIDTH + SNAKE_BORDER_SIZE * 2 - 1) -#define SNAKE_BOTTOM_Y (BASEVIDHEIGHT - 48) -#define SNAKE_TOP_Y (SNAKE_BOTTOM_Y - SNAKE_MAP_HEIGHT - SNAKE_BORDER_SIZE * 2 + 1) - -enum snake_bonustype_s { - SNAKE_BONUS_NONE = 0, - SNAKE_BONUS_SLOW, - SNAKE_BONUS_FAST, - SNAKE_BONUS_GHOST, - SNAKE_BONUS_NUKE, - SNAKE_BONUS_SCISSORS, - SNAKE_BONUS_REVERSE, - SNAKE_BONUS_EGGMAN, - SNAKE_NUM_BONUSES, -}; - -static const char *snake_bonuspatches[] = { - NULL, - "DL_SLOW", - "TVSSC0", - "TVIVC0", - "TVARC0", - "DL_SCISSORS", - "TVRCC0", - "TVEGC0", -}; - -static const char *snake_backgrounds[] = { - "RVPUMICF", - "FRSTRCKF", - "TAR", - "MMFLRB4", - "RVDARKF1", - "RVZWALF1", - "RVZWALF4", - "RVZWALF5", - "RVZGRS02", - "RVZGRS04", -}; - -typedef struct snake_s -{ - boolean paused; - boolean pausepressed; - tic_t time; - tic_t nextupdate; - boolean gameover; - UINT8 background; - - UINT16 snakelength; - enum snake_bonustype_s snakebonus; - tic_t snakebonustime; - UINT8 snakex[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y]; - UINT8 snakey[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y]; - UINT8 snakedir[SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y]; - - UINT8 applex; - UINT8 appley; - - enum snake_bonustype_s bonustype; - UINT8 bonusx; - UINT8 bonusy; -} snake_t; - -static snake_t *snake = NULL; - -static void Snake_Initialise(void) -{ - if (!snake) - snake = malloc(sizeof(snake_t)); - - snake->paused = false; - snake->pausepressed = false; - snake->time = 0; - snake->nextupdate = SNAKE_SPEED; - snake->gameover = false; - snake->background = M_RandomKey(sizeof(snake_backgrounds) / sizeof(*snake_backgrounds)); - - snake->snakelength = 1; - snake->snakebonus = SNAKE_BONUS_NONE; - snake->snakex[0] = M_RandomKey(SNAKE_NUM_BLOCKS_X); - snake->snakey[0] = M_RandomKey(SNAKE_NUM_BLOCKS_Y); - snake->snakedir[0] = 0; - snake->snakedir[1] = 0; - - snake->applex = M_RandomKey(SNAKE_NUM_BLOCKS_X); - snake->appley = M_RandomKey(SNAKE_NUM_BLOCKS_Y); - - snake->bonustype = SNAKE_BONUS_NONE; -} - -static UINT8 Snake_GetOppositeDir(UINT8 dir) -{ - if (dir == 1 || dir == 3) - return dir + 1; - else if (dir == 2 || dir == 4) - return dir - 1; - else - return 12 + 5 - dir; -} - -event_t *snakejoyevents[MAXEVENTS]; -UINT16 joyeventcount = 0; - -// I'm screaming the hack is clean - ashi -static boolean Snake_Joy_Grabber(event_t *ev) -{ - if (ev->type == ev_joystick && ev->key == 0) - { - snakejoyevents[joyeventcount] = ev; - joyeventcount++; - return true; - } - else - return false; -} - -static void Snake_FindFreeSlot(UINT8 *freex, UINT8 *freey, UINT8 headx, UINT8 heady) -{ - UINT8 x, y; - UINT16 i; - - do - { - x = M_RandomKey(SNAKE_NUM_BLOCKS_X); - y = M_RandomKey(SNAKE_NUM_BLOCKS_Y); - - for (i = 0; i < snake->snakelength; i++) - if (x == snake->snakex[i] && y == snake->snakey[i]) - break; - } while (i < snake->snakelength || (x == headx && y == heady) - || (x == snake->applex && y == snake->appley) - || (snake->bonustype != SNAKE_BONUS_NONE && x == snake->bonusx && y == snake->bonusy)); - - *freex = x; - *freey = y; -} - -static void Snake_Handle(void) -{ - UINT8 x, y; - UINT8 oldx, oldy; - UINT16 i; - UINT16 j; - UINT16 joystate = 0; - static INT32 pjoyx = 0, pjoyy = 0; - - // Handle retry - if (snake->gameover && (PLAYER1INPUTDOWN(GC_JUMP) || gamekeydown[KEY_ENTER])) - { - Snake_Initialise(); - snake->pausepressed = true; // Avoid accidental pause on respawn - } - - // Handle pause - if (PLAYER1INPUTDOWN(GC_PAUSE) || gamekeydown[KEY_ENTER]) - { - if (!snake->pausepressed) - snake->paused = !snake->paused; - snake->pausepressed = true; - } - else - snake->pausepressed = false; - - if (snake->paused) - return; - - snake->time++; - - x = snake->snakex[0]; - y = snake->snakey[0]; - oldx = snake->snakex[1]; - oldy = snake->snakey[1]; - - // process the input events in here dear lord - for (j = 0; j < joyeventcount; j++) - { - event_t *ev = snakejoyevents[j]; - const INT32 jdeadzone = (JOYAXISRANGE * cv_digitaldeadzone.value) / FRACUNIT; - if (ev->y != INT32_MAX) - { - if (Joystick.bGamepadStyle || abs(ev->y) > jdeadzone) - { - if (ev->y < 0 && pjoyy >= 0) - joystate = 1; - else if (ev->y > 0 && pjoyy <= 0) - joystate = 2; - pjoyy = ev->y; - } - else - pjoyy = 0; - } - - if (ev->x != INT32_MAX) - { - if (Joystick.bGamepadStyle || abs(ev->x) > jdeadzone) - { - if (ev->x < 0 && pjoyx >= 0) - joystate = 3; - else if (ev->x > 0 && pjoyx <= 0) - joystate = 4; - pjoyx = ev->x; - } - else - pjoyx = 0; - } - } - joyeventcount = 0; - - // Update direction - if (PLAYER1INPUTDOWN(GC_STRAFELEFT) || gamekeydown[KEY_LEFTARROW] || joystate == 3) - { - if (snake->snakelength < 2 || x <= oldx) - snake->snakedir[0] = 1; - } - else if (PLAYER1INPUTDOWN(GC_STRAFERIGHT) || gamekeydown[KEY_RIGHTARROW] || joystate == 4) - { - if (snake->snakelength < 2 || x >= oldx) - snake->snakedir[0] = 2; - } - else if (PLAYER1INPUTDOWN(GC_FORWARD) || gamekeydown[KEY_UPARROW] || joystate == 1) - { - if (snake->snakelength < 2 || y <= oldy) - snake->snakedir[0] = 3; - } - else if (PLAYER1INPUTDOWN(GC_BACKWARD) || gamekeydown[KEY_DOWNARROW] || joystate == 2) - { - if (snake->snakelength < 2 || y >= oldy) - snake->snakedir[0] = 4; - } - - if (snake->snakebonustime) - { - snake->snakebonustime--; - if (!snake->snakebonustime) - snake->snakebonus = SNAKE_BONUS_NONE; - } - - snake->nextupdate--; - if (snake->nextupdate) - return; - if (snake->snakebonus == SNAKE_BONUS_SLOW) - snake->nextupdate = SNAKE_SPEED * 2; - else if (snake->snakebonus == SNAKE_BONUS_FAST) - snake->nextupdate = SNAKE_SPEED * 2 / 3; - else - snake->nextupdate = SNAKE_SPEED; - - if (snake->gameover) - return; - - // Find new position - switch (snake->snakedir[0]) - { - case 1: - if (x > 0) - x--; - else - snake->gameover = true; - break; - case 2: - if (x < SNAKE_NUM_BLOCKS_X - 1) - x++; - else - snake->gameover = true; - break; - case 3: - if (y > 0) - y--; - else - snake->gameover = true; - break; - case 4: - if (y < SNAKE_NUM_BLOCKS_Y - 1) - y++; - else - snake->gameover = true; - break; - } - - // Check collision with snake - if (snake->snakebonus != SNAKE_BONUS_GHOST) - for (i = 1; i < snake->snakelength - 1; i++) - if (x == snake->snakex[i] && y == snake->snakey[i]) - { - if (snake->snakebonus == SNAKE_BONUS_SCISSORS) - { - snake->snakebonus = SNAKE_BONUS_NONE; - snake->snakelength = i; - S_StartSound(NULL, sfx_adderr); - } - else - snake->gameover = true; - } - - if (snake->gameover) - { - S_StartSound(NULL, sfx_lose); - return; - } - - // Check collision with apple - if (x == snake->applex && y == snake->appley) - { - if (snake->snakelength + 3 < SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y) - { - snake->snakelength++; - snake->snakex [snake->snakelength - 1] = snake->snakex [snake->snakelength - 2]; - snake->snakey [snake->snakelength - 1] = snake->snakey [snake->snakelength - 2]; - snake->snakedir[snake->snakelength - 1] = snake->snakedir[snake->snakelength - 2]; - } - - // Spawn new apple - Snake_FindFreeSlot(&snake->applex, &snake->appley, x, y); - - // Spawn new bonus - if (!(snake->snakelength % 5)) - { - do - { - snake->bonustype = M_RandomKey(SNAKE_NUM_BONUSES - 1) + 1; - } while (snake->snakelength > SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y * 3 / 4 - && (snake->bonustype == SNAKE_BONUS_EGGMAN || snake->bonustype == SNAKE_BONUS_FAST || snake->bonustype == SNAKE_BONUS_REVERSE)); - - Snake_FindFreeSlot(&snake->bonusx, &snake->bonusy, x, y); - } - - S_StartSound(NULL, sfx_s3k6b); - } - - if (snake->snakelength > 1 && snake->snakedir[0]) - { - UINT8 dir = snake->snakedir[0]; - - oldx = snake->snakex[1]; - oldy = snake->snakey[1]; - - // Move - for (i = snake->snakelength - 1; i > 0; i--) - { - snake->snakex[i] = snake->snakex[i - 1]; - snake->snakey[i] = snake->snakey[i - 1]; - snake->snakedir[i] = snake->snakedir[i - 1]; - } - - // Handle corners - if (x < oldx && dir == 3) - dir = 5; - else if (x > oldx && dir == 3) - dir = 6; - else if (x < oldx && dir == 4) - dir = 7; - else if (x > oldx && dir == 4) - dir = 8; - else if (y < oldy && dir == 1) - dir = 9; - else if (y < oldy && dir == 2) - dir = 10; - else if (y > oldy && dir == 1) - dir = 11; - else if (y > oldy && dir == 2) - dir = 12; - snake->snakedir[1] = dir; - } - - snake->snakex[0] = x; - snake->snakey[0] = y; - - // Check collision with bonus - if (snake->bonustype != SNAKE_BONUS_NONE && x == snake->bonusx && y == snake->bonusy) - { - S_StartSound(NULL, sfx_ncchip); - - switch (snake->bonustype) - { - case SNAKE_BONUS_SLOW: - snake->snakebonus = SNAKE_BONUS_SLOW; - snake->snakebonustime = 20 * TICRATE; - break; - case SNAKE_BONUS_FAST: - snake->snakebonus = SNAKE_BONUS_FAST; - snake->snakebonustime = 20 * TICRATE; - break; - case SNAKE_BONUS_GHOST: - snake->snakebonus = SNAKE_BONUS_GHOST; - snake->snakebonustime = 10 * TICRATE; - break; - case SNAKE_BONUS_NUKE: - for (i = 0; i < snake->snakelength; i++) - { - snake->snakex [i] = snake->snakex [0]; - snake->snakey [i] = snake->snakey [0]; - snake->snakedir[i] = snake->snakedir[0]; - } - - S_StartSound(NULL, sfx_bkpoof); - break; - case SNAKE_BONUS_SCISSORS: - snake->snakebonus = SNAKE_BONUS_SCISSORS; - snake->snakebonustime = 60 * TICRATE; - break; - case SNAKE_BONUS_REVERSE: - for (i = 0; i < (snake->snakelength + 1) / 2; i++) - { - UINT16 i2 = snake->snakelength - 1 - i; - UINT8 tmpx = snake->snakex [i]; - UINT8 tmpy = snake->snakey [i]; - UINT8 tmpdir = snake->snakedir[i]; - - // Swap first segment with last segment - snake->snakex [i] = snake->snakex [i2]; - snake->snakey [i] = snake->snakey [i2]; - snake->snakedir[i] = Snake_GetOppositeDir(snake->snakedir[i2]); - snake->snakex [i2] = tmpx; - snake->snakey [i2] = tmpy; - snake->snakedir[i2] = Snake_GetOppositeDir(tmpdir); - } - - snake->snakedir[0] = 0; - - S_StartSound(NULL, sfx_gravch); - break; - default: - if (snake->snakebonus != SNAKE_BONUS_GHOST) - { - snake->gameover = true; - S_StartSound(NULL, sfx_lose); - } - } - - snake->bonustype = SNAKE_BONUS_NONE; - } -} - -static void Snake_Draw(void) -{ - INT16 i; - - // Background - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - - V_DrawFlatFill( - SNAKE_LEFT_X + SNAKE_BORDER_SIZE, - SNAKE_TOP_Y + SNAKE_BORDER_SIZE, - SNAKE_MAP_WIDTH, - SNAKE_MAP_HEIGHT, - W_GetNumForName(snake_backgrounds[snake->background]) - ); - - // Borders - V_DrawFill(SNAKE_LEFT_X, SNAKE_TOP_Y, SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_BORDER_SIZE, 242); // Top - V_DrawFill(SNAKE_LEFT_X + SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_TOP_Y, SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, 242); // Right - V_DrawFill(SNAKE_LEFT_X + SNAKE_BORDER_SIZE, SNAKE_TOP_Y + SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, SNAKE_BORDER_SIZE + SNAKE_MAP_WIDTH, SNAKE_BORDER_SIZE, 242); // Bottom - V_DrawFill(SNAKE_LEFT_X, SNAKE_TOP_Y + SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE, SNAKE_BORDER_SIZE + SNAKE_MAP_HEIGHT, 242); // Left - - // Apple - V_DrawFixedPatch( - (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->applex * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT, - (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->appley * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT, - FRACUNIT / 4, - 0, - W_CachePatchLongName("DL_APPLE", PU_HUDGFX), - NULL - ); - - // Bonus - if (snake->bonustype != SNAKE_BONUS_NONE) - V_DrawFixedPatch( - (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->bonusx * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2 ) * FRACUNIT, - (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->bonusy * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2 + 4) * FRACUNIT, - FRACUNIT / 2, - 0, - W_CachePatchLongName(snake_bonuspatches[snake->bonustype], PU_HUDGFX), - NULL - ); - - // Snake - if (!snake->gameover || snake->time % 8 < 8 / 2) // Blink if game over - { - for (i = snake->snakelength - 1; i >= 0; i--) - { - const char *patchname; - UINT8 dir = snake->snakedir[i]; - - if (i == 0) // Head - { - switch (dir) - { - case 1: patchname = "DL_SNAKEHEAD_L"; break; - case 2: patchname = "DL_SNAKEHEAD_R"; break; - case 3: patchname = "DL_SNAKEHEAD_T"; break; - case 4: patchname = "DL_SNAKEHEAD_B"; break; - default: patchname = "DL_SNAKEHEAD_M"; - } - } - else // Body - { - switch (dir) - { - case 1: patchname = "DL_SNAKEBODY_L"; break; - case 2: patchname = "DL_SNAKEBODY_R"; break; - case 3: patchname = "DL_SNAKEBODY_T"; break; - case 4: patchname = "DL_SNAKEBODY_B"; break; - case 5: patchname = "DL_SNAKEBODY_LT"; break; - case 6: patchname = "DL_SNAKEBODY_RT"; break; - case 7: patchname = "DL_SNAKEBODY_LB"; break; - case 8: patchname = "DL_SNAKEBODY_RB"; break; - case 9: patchname = "DL_SNAKEBODY_TL"; break; - case 10: patchname = "DL_SNAKEBODY_TR"; break; - case 11: patchname = "DL_SNAKEBODY_BL"; break; - case 12: patchname = "DL_SNAKEBODY_BR"; break; - default: patchname = "DL_SNAKEBODY_B"; - } - } - - V_DrawFixedPatch( - (SNAKE_LEFT_X + SNAKE_BORDER_SIZE + snake->snakex[i] * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT, - (SNAKE_TOP_Y + SNAKE_BORDER_SIZE + snake->snakey[i] * SNAKE_BLOCK_SIZE + SNAKE_BLOCK_SIZE / 2) * FRACUNIT, - i == 0 && dir == 0 ? FRACUNIT / 5 : FRACUNIT / 2, - snake->snakebonus == SNAKE_BONUS_GHOST ? V_TRANSLUCENT : 0, - W_CachePatchLongName(patchname, PU_HUDGFX), - NULL - ); - } - } - - // Length - V_DrawString(SNAKE_RIGHT_X + 4, SNAKE_TOP_Y, V_MONOSPACE, va("%u", snake->snakelength)); - - // Bonus - if (snake->snakebonus != SNAKE_BONUS_NONE - && (snake->snakebonustime >= 3 * TICRATE || snake->time % 4 < 4 / 2)) - V_DrawFixedPatch( - (SNAKE_RIGHT_X + 10) * FRACUNIT, - (SNAKE_TOP_Y + 24) * FRACUNIT, - FRACUNIT / 2, - 0, - W_CachePatchLongName(snake_bonuspatches[snake->snakebonus], PU_HUDGFX), - NULL - ); -} - -static void CL_DrawConnectionStatusBox(void) -{ - M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1); - if (cl_mode != CL_CONFIRMCONNECT) - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort"); -} - -// -// CL_DrawConnectionStatus -// -// Keep the local client informed of our status. -// -static inline void CL_DrawConnectionStatus(void) -{ - INT32 ccstime = I_GetTime(); - - // Draw background fade - V_DrawFadeScreen(0xFF00, 16); // force default - - if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES) - { - INT32 i, animtime = ((ccstime / 4) & 15) + 16; - UINT8 palstart; - const char *cltext; - - // Draw the bottom box. - CL_DrawConnectionStatusBox(); - - if (cl_mode == CL_SEARCHING) - palstart = 32; // Red - else if (cl_mode == CL_CONFIRMCONNECT) - palstart = 48; // Orange - else - palstart = 96; // Green - - if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1)) - for (i = 0; i < 16; ++i) // 15 pal entries total. - V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15)); - - switch (cl_mode) - { - case CL_DOWNLOADSAVEGAME: - if (fileneeded && lastfilenum != -1) - { - UINT32 currentsize = fileneeded[lastfilenum].currentsize; - UINT32 totalsize = fileneeded[lastfilenum].totalsize; - INT32 dldlength; - - cltext = M_GetText("Downloading game state..."); - Net_GetNetStat(); - - dldlength = (INT32)((currentsize/(double)totalsize) * 256); - if (dldlength > 256) - dldlength = 256; - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96); - - V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, - va(" %4uK/%4uK",currentsize>>10,totalsize>>10)); - - V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, - va("%3.1fK/s ", ((double)getbps)/1024)); - } - else - cltext = M_GetText("Waiting to download game state..."); - break; - case CL_ASKFULLFILELIST: - case CL_CHECKFILES: - cltext = M_GetText("Checking server addon list..."); - break; - case CL_CONFIRMCONNECT: - cltext = ""; - break; - case CL_LOADFILES: - cltext = M_GetText("Loading server addons..."); - break; - case CL_ASKJOIN: - case CL_WAITJOINRESPONSE: - if (serverisfull) - cltext = M_GetText("Server full, waiting for a slot..."); - else - cltext = M_GetText("Requesting to join..."); - break; - default: - cltext = M_GetText("Connecting to server..."); - break; - } - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, cltext); - } - else - { - if (cl_mode == CL_LOADFILES) - { - INT32 totalfileslength; - INT32 loadcompletednum = 0; - INT32 i; - - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort"); - - //ima just count files here - if (fileneeded) - { - for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status == FS_OPEN) - loadcompletednum++; - } - - // Loading progress - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, "Loading server addons..."); - totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256); - M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, totalfileslength, 8, 96); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, - va(" %2u/%2u Files",loadcompletednum,fileneedednum)); - } - else if (lastfilenum != -1) - { - INT32 dldlength; - static char tempname[28]; - fileneeded_t *file; - char *filename; - - if (snake) - Snake_Draw(); - - // Draw the bottom box. - CL_DrawConnectionStatusBox(); - - if (fileneeded) - { - file = &fileneeded[lastfilenum]; - filename = file->filename; - } - else - return; - - Net_GetNetStat(); - dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256); - if (dldlength > 256) - dldlength = 256; - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96); - - memset(tempname, 0, sizeof(tempname)); - // offset filename to just the name only part - filename += strlen(filename) - nameonlylength(filename); - - if (strlen(filename) > sizeof(tempname)-1) // too long to display fully - { - size_t endhalfpos = strlen(filename)-10; - // display as first 14 chars + ... + last 10 chars - // which should add up to 27 if our math(s) is correct - snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos); - } - else // we can copy the whole thing in safely - { - strncpy(tempname, filename, sizeof(tempname)-1); - } - - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, - va(M_GetText("Downloading \"%s\""), tempname)); - V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, - va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10)); - V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, - va("%3.1fK/s ", ((double)getbps)/1024)); - } - else - { - if (snake) - Snake_Draw(); - - CL_DrawConnectionStatusBox(); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, - M_GetText("Waiting to download files...")); - } - } -} -#endif - -static boolean CL_AskFileList(INT32 firstfile) -{ - netbuffer->packettype = PT_TELLFILESNEEDED; - netbuffer->u.filesneedednum = firstfile; - - return HSendPacket(servernode, false, 0, sizeof (INT32)); -} - -/** Sends a special packet to declare how many players in local - * Used only in arbitratrenetstart() - * Sends a PT_CLIENTJOIN packet to the server - * - * \return True if the packet was successfully sent - * \todo Improve the description... - * - */ -static boolean CL_SendJoin(void) -{ - UINT8 localplayers = 1; - char const *player2name; - if (netgame) - CONS_Printf(M_GetText("Sending join request...\n")); - netbuffer->packettype = PT_CLIENTJOIN; - - netbuffer->u.clientcfg.modversion = MODVERSION; - strncpy(netbuffer->u.clientcfg.application, - SRB2APPLICATION, - sizeof netbuffer->u.clientcfg.application); - - if (splitscreen || botingame) - localplayers++; - netbuffer->u.clientcfg.localplayers = localplayers; - - CleanupPlayerName(consoleplayer, cv_playername.zstring); - if (splitscreen) - CleanupPlayerName(1, cv_playername2.zstring);/* 1 is a HACK? oh no */ - // Avoid empty string on bots to avoid softlocking in singleplayer - if (botingame) - player2name = strcmp(cv_playername.zstring, "Tails") == 0 ? "Tail" : "Tails"; - else - player2name = cv_playername2.zstring; - - strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME); - strncpy(netbuffer->u.clientcfg.names[1], player2name, MAXPLAYERNAME); - - return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak)); -} - -static INT32 FindRejoinerNum(SINT8 node) -{ - char addressbuffer[64]; - const char *nodeaddress; - const char *strippednodeaddress; - INT32 i; - - // Make sure there is no dead dress before proceeding to the stripping - if (!I_GetNodeAddress) - return -1; - nodeaddress = I_GetNodeAddress(node); - if (!nodeaddress) - return -1; - - // Strip the address of its port - strcpy(addressbuffer, nodeaddress); - strippednodeaddress = I_NetSplitAddress(addressbuffer, NULL); - - // Check if any player matches the stripped address - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX - && !strcmp(playeraddress[i], strippednodeaddress)) - return i; - } - - return -1; -} - -static UINT8 -GetRefuseReason (INT32 node) -{ - if (!node || FindRejoinerNum(node) != -1) - return 0; - else if (bannednode && bannednode[node]) - return REFUSE_BANNED; - else if (!cv_allownewplayer.value) - return REFUSE_JOINS_DISABLED; - else if (D_NumPlayers() >= cv_maxplayers.value) - return REFUSE_SLOTS_FULL; - else - return 0; -} - -static void SV_SendServerInfo(INT32 node, tic_t servertime) -{ - UINT8 *p; - - netbuffer->packettype = PT_SERVERINFO; - netbuffer->u.serverinfo._255 = 255; - netbuffer->u.serverinfo.packetversion = PACKETVERSION; - netbuffer->u.serverinfo.version = VERSION; - netbuffer->u.serverinfo.subversion = SUBVERSION; - strncpy(netbuffer->u.serverinfo.application, SRB2APPLICATION, - sizeof netbuffer->u.serverinfo.application); - // return back the time value so client can compute their ping - netbuffer->u.serverinfo.time = (tic_t)LONG(servertime); - netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); - - // Exclude bots from both counts - netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumPlayers() - D_NumBots()); - netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots()); - - netbuffer->u.serverinfo.refusereason = GetRefuseReason(node); - - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], - sizeof netbuffer->u.serverinfo.gametypename); - netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; - netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); - netbuffer->u.serverinfo.flags = (dedicated ? SV_DEDICATED : 0); - strncpy(netbuffer->u.serverinfo.servername, cv_servername.string, - MAXSERVERNAME); - strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7); - - M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16); - - memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle); - - if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl) - { - char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle; - while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0') - { - if (!(*read & 0x80)) - { - *writ = toupper(*read); - writ++; - } - read++; - } - *writ = '\0'; - //strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33); - } - else - strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32); - - if (mapheaderinfo[gamemap-1] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) - netbuffer->u.serverinfo.iszone = 1; - else - netbuffer->u.serverinfo.iszone = 0; - - if (mapheaderinfo[gamemap-1]) - netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum; - - p = PutFileNeeded(0); - - HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u)); -} - -static void SV_SendPlayerInfo(INT32 node) -{ - UINT8 i; - netbuffer->packettype = PT_PLAYERINFO; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - { - netbuffer->u.playerinfo[i].num = 255; // This slot is empty. - continue; - } - - netbuffer->u.playerinfo[i].num = i; - strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1); - netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0'; - - //fetch IP address - //No, don't do that, you fuckface. - memset(netbuffer->u.playerinfo[i].address, 0, 4); - - if (G_GametypeHasTeams()) - { - if (!players[i].ctfteam) - netbuffer->u.playerinfo[i].team = 255; - else - netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam; - } - else - { - if (players[i].spectator) - netbuffer->u.playerinfo[i].team = 255; - 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 - ); - - // Extra data - netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor; - - if (players[i].pflags & PF_TAGIT) - netbuffer->u.playerinfo[i].data |= 0x20; - - if (players[i].gotflag) - netbuffer->u.playerinfo[i].data |= 0x40; - - if (players[i].powers[pw_super]) - netbuffer->u.playerinfo[i].data |= 0x80; - } - - HSendPacket(node, false, 0, sizeof(plrinfo) * MAXPLAYERS); -} - -/** Sends a PT_SERVERCFG packet - * - * \param node The destination - * \return True if the packet was successfully sent - * - */ -static boolean SV_SendServerConfig(INT32 node) -{ - boolean waspacketsent; - - netbuffer->packettype = PT_SERVERCFG; - - netbuffer->u.servercfg.serverplayer = (UINT8)serverplayer; - netbuffer->u.servercfg.totalslotnum = (UINT8)(doomcom->numslots); - netbuffer->u.servercfg.gametic = (tic_t)LONG(gametic); - netbuffer->u.servercfg.clientnode = (UINT8)node; - netbuffer->u.servercfg.gamestate = (UINT8)gamestate; - netbuffer->u.servercfg.gametype = (UINT8)gametype; - netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame; - netbuffer->u.servercfg.usedCheats = (UINT8)usedCheats; - - memcpy(netbuffer->u.servercfg.server_context, server_context, 8); - - { - const size_t len = sizeof (serverconfig_pak); - -#ifdef DEBUGFILE - if (debugfile) - { - fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n", - sizeu1(len), node); - } -#endif - - waspacketsent = HSendPacket(node, true, 0, len); - } - -#ifdef DEBUGFILE - if (debugfile) - { - if (waspacketsent) - { - fprintf(debugfile, "ServerConfig Packet was sent\n"); - } - else - { - fprintf(debugfile, "ServerConfig Packet could not be sent right now\n"); - } - } -#endif - - return waspacketsent; -} - -#ifndef NONET -#define SAVEGAMESIZE (768*1024) - -static boolean SV_ResendingSavegameToAnyone(void) -{ - INT32 i; - - for (i = 0; i < MAXNETNODES; i++) - if (resendingsavegame[i]) - return true; - return false; -} - -static void SV_SendSaveGame(INT32 node, boolean resending) -{ - size_t length, compressedlen; - UINT8 *savebuffer; - UINT8 *compressedsave; - UINT8 *buffertosend; - - // first save it in a malloced buffer - savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); - if (!savebuffer) - { - CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); - return; - } - - // Leave room for the uncompressed length. - save_p = savebuffer + sizeof(UINT32); - - P_SaveNetGame(resending); - - length = save_p - savebuffer; - if (length > SAVEGAMESIZE) - { - free(savebuffer); - save_p = NULL; - I_Error("Savegame buffer overrun"); - } - - // Allocate space for compressed save: one byte fewer than for the - // uncompressed data to ensure that the compression is worthwhile. - compressedsave = malloc(length - 1); - if (!compressedsave) - { - CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); - return; - } - - // Attempt to compress it. - if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) - { - // Compressing succeeded; send compressed data - - free(savebuffer); - - // State that we're compressed. - buffertosend = compressedsave; - WRITEUINT32(compressedsave, length - sizeof(UINT32)); - length = compressedlen + sizeof(UINT32); - } - else - { - // Compression failed to make it smaller; send original - - free(compressedsave); - - // State that we're not compressed - buffertosend = savebuffer; - WRITEUINT32(savebuffer, 0); - } - - AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); - save_p = NULL; - - // Remember when we started sending the savegame so we can handle timeouts - sendingsavegame[node] = true; - freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte -} - -#ifdef DUMPCONSISTENCY -#define TMPSAVENAME "badmath.sav" -static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); - -static void SV_SavedGame(void) -{ - size_t length; - UINT8 *savebuffer; - char tmpsave[256]; - - if (!cv_dumpconsistency.value) - return; - - sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - - // first save it in a malloced buffer - save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); - if (!save_p) - { - CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); - return; - } - - P_SaveNetGame(false); - - length = save_p - savebuffer; - if (length > SAVEGAMESIZE) - { - free(savebuffer); - save_p = NULL; - I_Error("Savegame buffer overrun"); - } - - // then save it! - if (!FIL_WriteFile(tmpsave, savebuffer, length)) - CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); - - free(savebuffer); - save_p = NULL; -} - -#undef TMPSAVENAME -#endif -#define TMPSAVENAME "$$$.sav" - - -static void CL_LoadReceivedSavegame(boolean reloading) -{ - UINT8 *savebuffer = NULL; - size_t length, decompressedlen; - char tmpsave[256]; - - FreeFileNeeded(); - - sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - - length = FIL_ReadFile(tmpsave, &savebuffer); - - CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); - if (!length) - { - I_Error("Can't read savegame sent"); - return; - } - - save_p = savebuffer; - - // Decompress saved game if necessary. - decompressedlen = READUINT32(save_p); - if(decompressedlen > 0) - { - UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); - lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); - Z_Free(savebuffer); - save_p = savebuffer = decompressedbuffer; - } - - paused = false; - demoplayback = false; - titlemapinaction = TITLEMAP_OFF; - titledemo = false; - automapactive = false; - - // load a base level - if (P_LoadNetGame(reloading)) - { - const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; - CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap)); - if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, "")) - { - CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl); - if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) - CONS_Printf(M_GetText(" Zone")); - if (actnum > 0) - CONS_Printf(" %2d", actnum); - } - CONS_Printf("\"\n"); - } - - // done - Z_Free(savebuffer); - save_p = NULL; - if (unlink(tmpsave) == -1) - CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); - consistancy[gametic%BACKUPTICS] = Consistancy(); - CON_ToggleOff(); - - // Tell the server we have received and reloaded the gamestate - // so they know they can resume the game - netbuffer->packettype = PT_RECEIVEDGAMESTATE; - HSendPacket(servernode, true, 0, 0); -} - -static void CL_ReloadReceivedSavegame(void) -{ - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - LUA_InvalidatePlayer(&players[i]); - sprintf(player_names[i], "Player %d", i + 1); - } - - CL_LoadReceivedSavegame(true); - - if (neededtic < gametic) - neededtic = gametic; - maketic = neededtic; - - ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn; - P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16)); - if (splitscreen) - { - ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn; - P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16)); - } - - camera.subsector = R_PointInSubsector(camera.x, camera.y); - camera2.subsector = R_PointInSubsector(camera2.x, camera2.y); - - cl_redownloadinggamestate = false; - - CONS_Printf(M_GetText("Game state reloaded\n")); -} -#endif - -#ifndef NONET -static void SendAskInfo(INT32 node) -{ - const tic_t asktime = I_GetTime(); - netbuffer->packettype = PT_ASKINFO; - netbuffer->u.askinfo.version = VERSION; - netbuffer->u.askinfo.time = (tic_t)LONG(asktime); - - // Even if this never arrives due to the host being firewalled, we've - // now allowed traffic from the host to us in, so once the MS relays - // our address to the host, it'll be able to speak to us. - HSendPacket(node, false, 0, sizeof (askinfo_pak)); -} - -serverelem_t serverlist[MAXSERVERLIST]; -UINT32 serverlistcount = 0; - -#define FORCECLOSE 0x8000 - -static void SL_ClearServerList(INT32 connectedserver) -{ - UINT32 i; - - for (i = 0; i < serverlistcount; i++) - if (connectedserver != serverlist[i].node) - { - Net_CloseConnection(serverlist[i].node|FORCECLOSE); - serverlist[i].node = 0; - } - serverlistcount = 0; -} - -static UINT32 SL_SearchServer(INT32 node) -{ - UINT32 i; - for (i = 0; i < serverlistcount; i++) - if (serverlist[i].node == node) - return i; - - return UINT32_MAX; -} - -static void SL_InsertServer(serverinfo_pak* info, SINT8 node) -{ - UINT32 i; - - // search if not already on it - i = SL_SearchServer(node); - if (i == UINT32_MAX) - { - // not found add it - if (serverlistcount >= MAXSERVERLIST) - return; // list full - - /* check it later if connecting to this one */ - if (node != servernode) - { - if (info->_255 != 255) - return;/* old packet format */ - - if (info->packetversion != PACKETVERSION) - return;/* old new packet format */ - - if (info->version != VERSION) - return; // Not same version. - - if (info->subversion != SUBVERSION) - return; // Close, but no cigar. - - if (strcmp(info->application, SRB2APPLICATION)) - return;/* that's a different mod */ - } - - i = serverlistcount++; - } - - serverlist[i].info = *info; - serverlist[i].node = node; - - // resort server list - M_SortServerList(); -} - -#if defined (MASTERSERVER) && defined (HAVE_THREADS) -struct Fetch_servers_ctx -{ - int room; - int id; -}; - -static void -Fetch_servers_thread (struct Fetch_servers_ctx *ctx) -{ - msg_server_t *server_list; - - server_list = GetShortServersList(ctx->room, ctx->id); - - if (server_list) - { - I_lock_mutex(&ms_QueryId_mutex); - { - if (ctx->id != ms_QueryId) - { - free(server_list); - server_list = NULL; - } - } - I_unlock_mutex(ms_QueryId_mutex); - - if (server_list) - { - I_lock_mutex(&m_menu_mutex); - { - if (m_waiting_mode == M_WAITING_SERVERS) - m_waiting_mode = M_NOT_WAITING; - } - I_unlock_mutex(m_menu_mutex); - - I_lock_mutex(&ms_ServerList_mutex); - { - ms_ServerList = server_list; - } - I_unlock_mutex(ms_ServerList_mutex); - } - } - - free(ctx); -} -#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/ - -void CL_QueryServerList (msg_server_t *server_list) -{ - INT32 i; - - for (i = 0; server_list[i].header.buffer[0]; i++) - { - // Make sure MS version matches our own, to - // thwart nefarious servers who lie to the MS. - - /* lol bruh, that version COMES from the servers */ - //if (strcmp(version, server_list[i].version) == 0) - { - INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); - if (node == -1) - break; // no more node free - SendAskInfo(node); - // Force close the connection so that servers can't eat - // up nodes forever if we never get a reply back from them - // (usually when they've not forwarded their ports). - // - // Don't worry, we'll get in contact with the working - // servers again when they send SERVERINFO to us later! - // - // (Note: as a side effect this probably means every - // server in the list will probably be using the same node (e.g. node 1), - // not that it matters which nodes they use when - // the connections are closed afterwards anyway) - // -- Monster Iestyn 12/11/18 - Net_CloseConnection(node|FORCECLOSE); - } - } -} - -void CL_UpdateServerList(boolean internetsearch, INT32 room) -{ - (void)internetsearch; - (void)room; - - SL_ClearServerList(0); - - if (!netgame && I_NetOpenSocket) - { - if (I_NetOpenSocket()) - { - netgame = true; - multiplayer = true; - } - } - - // search for local servers - if (netgame) - SendAskInfo(BROADCASTADDR); - -#ifdef MASTERSERVER - if (internetsearch) - { -#ifdef HAVE_THREADS - struct Fetch_servers_ctx *ctx; - - ctx = malloc(sizeof *ctx); - - /* This called from M_Refresh so I don't use a mutex */ - m_waiting_mode = M_WAITING_SERVERS; - - I_lock_mutex(&ms_QueryId_mutex); - { - ctx->id = ms_QueryId; - } - I_unlock_mutex(ms_QueryId_mutex); - - ctx->room = room; - - I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx); -#else - msg_server_t *server_list; - - server_list = GetShortServersList(room, 0); - - if (server_list) - { - CL_QueryServerList(server_list); - free(server_list); - } -#endif - } -#endif/*MASTERSERVER*/ -} - -#endif // ifndef NONET - -static void M_ConfirmConnect(event_t *ev) -{ -#ifndef NONET - if (ev->type == ev_keydown) - { - if (ev->key == ' ' || ev->key == 'y' || ev->key == KEY_ENTER || ev->key == KEY_JOY1) - { - if (totalfilesrequestednum > 0) - { - if (CL_SendFileRequest()) - { - cl_mode = CL_DOWNLOADFILES; - Snake_Initialise(); - } - } - else - cl_mode = CL_LOADFILES; - - M_ClearMenus(true); - } - else if (ev->key == 'n' || ev->key == KEY_ESCAPE || ev->key == KEY_JOY1 + 3) - { - cl_mode = CL_ABORTED; - M_ClearMenus(true); - } - } -#else - (void)ev; -#endif -} - -static boolean CL_FinishedFileList(void) -{ - INT32 i; - char *downloadsize = NULL; - - //CONS_Printf(M_GetText("Checking files...\n")); - i = CL_CheckFiles(); - if (i == 4) // still checking ... - { - return true; - } - else if (i == 3) // too many files - { - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText( - "You have too many WAD files loaded\n" - "to add ones the server is using.\n" - "Please restart SRB2 before connecting.\n\n" - "Press ESC\n" - ), NULL, MM_NOTHING); - return false; - } - else if (i == 2) // cannot join for some reason - { - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText( - "You have the wrong addons loaded.\n\n" - "To play on this server, restart\n" - "the game and don't load any addons.\n" - "SRB2 will automatically add\n" - "everything you need when you join.\n\n" - "Press ESC\n" - ), NULL, MM_NOTHING); - return false; - } - else if (i == 1) - { - if (serverisfull) - { - M_StartMessage(M_GetText( - "This server is full!\n" - "\n" - "You may load server addons (if any), and wait for a slot.\n" - "\n" - "Press ENTER to continue\nor ESC to cancel.\n\n" - ), M_ConfirmConnect, MM_EVENTHANDLER); - cl_mode = CL_CONFIRMCONNECT; - curfadevalue = 0; - } - else - cl_mode = CL_LOADFILES; - } - else - { - // must download something - // can we, though? - if (!CL_CheckDownloadable()) // nope! - { - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText( - "An error occured when trying to\n" - "download missing addons.\n" - "(This is almost always a problem\n" - "with the server, not your game.)\n\n" - "See the console or log file\n" - "for additional details.\n\n" - "Press ESC\n" - ), NULL, MM_NOTHING); - return false; - } - -#ifndef NONET - downloadcompletednum = 0; - downloadcompletedsize = 0; - totalfilesrequestednum = 0; - totalfilesrequestedsize = 0; - - if (fileneeded == NULL) - I_Error("CL_FinishedFileList: fileneeded == NULL"); - - for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) - { - totalfilesrequestednum++; - totalfilesrequestedsize += fileneeded[i].totalsize; - } - - if (totalfilesrequestedsize>>20 >= 100) - downloadsize = Z_StrDup(va("%uM",totalfilesrequestedsize>>20)); - else - downloadsize = Z_StrDup(va("%uK",totalfilesrequestedsize>>10)); -#endif - - if (serverisfull) - M_StartMessage(va(M_GetText( - "This server is full!\n" - "Download of %s additional content\nis required to join.\n" - "\n" - "You may download, load server addons,\nand wait for a slot.\n" - "\n" - "Press ENTER to continue\nor ESC to cancel.\n" - ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); - else - M_StartMessage(va(M_GetText( - "Download of %s additional content\nis required to join.\n" - "\n" - "Press ENTER to continue\nor ESC to cancel.\n" - ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); - - Z_Free(downloadsize); - cl_mode = CL_CONFIRMCONNECT; - curfadevalue = 0; - } - return true; -} - -#ifndef NONET -static const char * InvalidServerReason (serverinfo_pak *info) -{ -#define EOT "\nPress ESC\n" - - /* magic number for new packet format */ - if (info->_255 != 255) - { - return - "Outdated server (version unknown).\n" EOT; - } - - if (strncmp(info->application, SRB2APPLICATION, sizeof - info->application)) - { - return va( - "%s cannot connect\n" - "to %s servers.\n" EOT, - SRB2APPLICATION, - info->application); - } - - if ( - info->packetversion != PACKETVERSION || - info->version != VERSION || - info->subversion != SUBVERSION - ){ - return va( - "Incompatible %s versions.\n" - "(server version %d.%d.%d)\n" EOT, - SRB2APPLICATION, - info->version / 100, - info->version % 100, - info->subversion); - } - - switch (info->refusereason) - { - case REFUSE_BANNED: - return - "You have been banned\n" - "from the server.\n" EOT; - case REFUSE_JOINS_DISABLED: - return - "The server is not accepting\n" - "joins for the moment.\n" EOT; - case REFUSE_SLOTS_FULL: - return va( - "Maximum players reached: %d\n" EOT, - info->maxplayer); - default: - if (info->refusereason) - { - return - "You can't join.\n" - "I don't know why,\n" - "but you can't join.\n" EOT; - } - } - - return NULL; - -#undef EOT -} -#endif // ifndef NONET - -/** Called by CL_ServerConnectionTicker - * - * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. - * \return False if the connection was aborted - * \sa CL_ServerConnectionTicker - * \sa CL_ConnectToServer - * - */ -static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) -{ -#ifndef NONET - INT32 i; - - // serverlist is updated by GetPacket function - if (serverlistcount > 0) - { - // this can be a responce to our broadcast request - if (servernode == -1 || servernode >= MAXNETNODES) - { - i = 0; - servernode = serverlist[i].node; - CONS_Printf(M_GetText("Found, ")); - } - else - { - i = SL_SearchServer(servernode); - if (i < 0) - return true; - } - - if (client) - { - serverinfo_pak *info = &serverlist[i].info; - - if (info->refusereason == REFUSE_SLOTS_FULL) - serverisfull = true; - else - { - const char *reason = InvalidServerReason(info); - - // Quit here rather than downloading files - // and being refused later. - if (reason) - { - char *message = Z_StrDup(reason); - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(message, NULL, MM_NOTHING); - Z_Free(message); - return false; - } - } - - D_ParseFileneeded(info->fileneedednum, info->fileneeded, 0); - - if (info->flags & SV_LOTSOFADDONS) - { - cl_mode = CL_ASKFULLFILELIST; - cl_lastcheckedfilecount = 0; - return true; - } - - cl_mode = CL_CHECKFILES; - } - else - { - cl_mode = CL_ASKJOIN; // files need not be checked for the server. - *asksent = 0; - } - - return true; - } - - // Ask the info to the server (askinfo packet) - if (*asksent + NEWTICRATE < I_GetTime()) - { - SendAskInfo(servernode); - *asksent = I_GetTime(); - } -#else - (void)asksent; - // No netgames, so we skip this state. - cl_mode = CL_ASKJOIN; -#endif // ifndef NONET/else - - return true; -} - -/** Called by CL_ConnectToServer - * - * \param tmpsave The name of the gamestate file??? - * \param oldtic Used for knowing when to poll events and redraw - * \param asksent ??? - * \return False if the connection was aborted - * \sa CL_ServerConnectionSearchTicker - * \sa CL_ConnectToServer - * - */ -static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent) -{ - boolean waitmore; - INT32 i; - -#ifdef NONET - (void)tmpsave; -#endif - - switch (cl_mode) - { - case CL_SEARCHING: - if (!CL_ServerConnectionSearchTicker(asksent)) - return false; - break; - - case CL_ASKFULLFILELIST: - if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved - cl_mode = CL_CHECKFILES; - else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent) - { - if (CL_AskFileList(fileneedednum)) - { - cl_lastcheckedfilecount = fileneedednum; - *asksent = I_GetTime() + NEWTICRATE; - } - } - break; - case CL_CHECKFILES: - if (!CL_FinishedFileList()) - return false; - break; - case CL_DOWNLOADFILES: - waitmore = false; - for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status == FS_DOWNLOADING - || fileneeded[i].status == FS_REQUESTED) - { - waitmore = true; - break; - } - if (waitmore) - break; // exit the case - -#ifndef NONET - if (snake) - { - free(snake); - snake = NULL; - } -#endif - - cl_mode = CL_LOADFILES; - break; - case CL_LOADFILES: - if (CL_LoadServerFiles()) - { - FreeFileNeeded(); - *asksent = 0; //This ensure the first join ask is right away - firstconnectattempttime = I_GetTime(); - cl_mode = CL_ASKJOIN; - } - break; - case CL_ASKJOIN: - if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server) - { - CONS_Printf(M_GetText("5 minute wait time exceeded.\n")); - CONS_Printf(M_GetText("Network game synchronization aborted.\n")); - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText( - "5 minute wait time exceeded.\n" - "You may retry connection.\n" - "\n" - "Press ESC\n" - ), NULL, MM_NOTHING); - return false; - } -#ifndef NONET - // prepare structures to save the file - // WARNING: this can be useless in case of server not in GS_LEVEL - // but since the network layer doesn't provide ordered packets... - CL_PrepareDownloadSaveGame(tmpsave); -#endif - if (I_GetTime() >= *asksent && CL_SendJoin()) - { - *asksent = I_GetTime() + NEWTICRATE*3; - cl_mode = CL_WAITJOINRESPONSE; - } - break; - case CL_WAITJOINRESPONSE: - if (I_GetTime() >= *asksent) - { - cl_mode = CL_ASKJOIN; - } - break; -#ifndef NONET - case CL_DOWNLOADSAVEGAME: - // At this state, the first (and only) needed file is the gamestate - if (fileneeded[0].status == FS_FOUND) - { - // Gamestate is now handled within CL_LoadReceivedSavegame() - CL_LoadReceivedSavegame(false); - cl_mode = CL_CONNECTED; - } // don't break case continue to CL_CONNECTED - else - break; -#endif - - case CL_CONNECTED: - case CL_CONFIRMCONNECT: //logic is handled by M_ConfirmConnect - default: - break; - - // Connection closed by cancel, timeout or refusal. - case CL_ABORTED: - cl_mode = CL_SEARCHING; - return false; - } - - GetPackets(); - Net_AckTicker(); - - // Call it only once by tic - if (*oldtic != I_GetTime()) - { - I_OsPolling(); - - if (cl_mode == CL_CONFIRMCONNECT) - D_ProcessEvents(); //needed for menu system to receive inputs - else - { - // my hand has been forced and I am dearly sorry for this awful hack :vomit: - for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) - { -#ifndef NONET - if (!Snake_Joy_Grabber(&events[eventtail])) -#endif - G_MapEventsToControls(&events[eventtail]); - } - } - - if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1] || cl_mode == CL_ABORTED) - { - CONS_Printf(M_GetText("Network game synchronization aborted.\n")); - M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING); - -#ifndef NONET - if (snake) - { - free(snake); - snake = NULL; - } -#endif - - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - memset(gamekeydown, 0, NUMKEYS); - return false; - } -#ifndef NONET - else if (cl_mode == CL_DOWNLOADFILES && snake) - Snake_Handle(); -#endif - - if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME)) - FileReceiveTicker(); - - // why are these here? this is for servers, we're a client - //if (key == 's' && server) - // doomcom->numnodes = (INT16)pnumnodes; - //FileSendTicker(); - *oldtic = I_GetTime(); - -#ifndef NONET - if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED) - { - if (!snake) - { - F_MenuPresTicker(); // title sky - F_TitleScreenTicker(true); - F_TitleScreenDrawer(); - } - CL_DrawConnectionStatus(); -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - M_Drawer(); //Needed for drawing messageboxes on the connection screen -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif - I_UpdateNoVsync(); // page flip or blit buffer - if (moviemode) - M_SaveFrame(); - S_UpdateSounds(); - S_UpdateClosedCaptions(); - } -#else - CON_Drawer(); - I_UpdateNoVsync(); -#endif - } - else - { - I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); - } - - return true; -} - -/** Use adaptive send using net_bandwidth and stat.sendbytes - * - * \todo Better description... - * - */ -static void CL_ConnectToServer(void) -{ - INT32 pnumnodes, nodewaited = doomcom->numnodes, i; - tic_t oldtic; -#ifndef NONET - tic_t asksent; - char tmpsave[256]; - - sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - - lastfilenum = -1; -#endif - - cl_mode = CL_SEARCHING; - -#ifndef NONET - // Don't get a corrupt savegame error because tmpsave already exists - if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) - I_Error("Can't delete %s\n", tmpsave); -#endif - - if (netgame) - { - if (servernode < 0 || servernode >= MAXNETNODES) - CONS_Printf(M_GetText("Searching for a server...\n")); - else - CONS_Printf(M_GetText("Contacting the server...\n")); - } - - if (gamestate == GS_INTERMISSION) - Y_EndIntermission(); // clean up intermission graphics etc - - DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); - G_SetGamestate(GS_WAITINGPLAYERS); - wipegamestate = GS_WAITINGPLAYERS; - - ClearAdminPlayers(); - pnumnodes = 1; - oldtic = I_GetTime() - 1; - -#ifndef NONET - asksent = (tic_t) - TICRATE; - firstconnectattempttime = I_GetTime(); - - i = SL_SearchServer(servernode); - - if (i != -1) - { - char *gametypestr = serverlist[i].info.gametypename; - CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername); - gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0'; - CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr); - CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100, - serverlist[i].info.version%100, serverlist[i].info.subversion); - } - SL_ClearServerList(servernode); -#endif - - do - { - // If the connection was aborted for some reason, leave -#ifndef NONET - if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent)) -#else - if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL)) -#endif - return; - - if (server) - { - pnumnodes = 0; - for (i = 0; i < MAXNETNODES; i++) - if (nodeingame[i]) - pnumnodes++; - } - } - while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes)))); - - if (netgame) - F_StartWaitingPlayers(); - DEBFILE(va("Synchronisation Finished\n")); - - displayplayer = consoleplayer; -} - -#ifndef NONET -typedef struct banreason_s -{ - char *reason; - struct banreason_s *prev; //-1 - struct banreason_s *next; //+1 -} banreason_t; - -static banreason_t *reasontail = NULL; //last entry, use prev -static banreason_t *reasonhead = NULL; //1st entry, use next - -static void Command_ShowBan(void) //Print out ban list -{ - size_t i; - const char *address, *mask; - banreason_t *reasonlist = reasonhead; - - if (I_GetBanAddress) - CONS_Printf(M_GetText("Ban List:\n")); - else - return; - - for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++) - { - if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) - CONS_Printf("%s: %s ", sizeu1(i+1), address); - else - CONS_Printf("%s: %s/%s ", sizeu1(i+1), address, mask); - - if (reasonlist && reasonlist->reason) - CONS_Printf("(%s)\n", reasonlist->reason); - else - CONS_Printf("\n"); - - if (reasonlist) reasonlist = reasonlist->next; - } - - if (i == 0 && !address) - CONS_Printf(M_GetText("(empty)\n")); -} - -void D_SaveBan(void) -{ - FILE *f; - size_t i; - banreason_t *reasonlist = reasonhead; - const char *address, *mask; - const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt"); - - if (!reasonhead) - { - remove(path); - return; - } - - f = fopen(path, "w"); - - if (!f) - { - CONS_Alert(CONS_WARNING, M_GetText("Could not save ban list into ban.txt\n")); - return; - } - - for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++) - { - if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) - fprintf(f, "%s 0", address); - else - fprintf(f, "%s %s", address, mask); - - if (reasonlist && reasonlist->reason) - fprintf(f, " %s\n", reasonlist->reason); - else - fprintf(f, " %s\n", "NA"); - - if (reasonlist) reasonlist = reasonlist->next; - } - - fclose(f); -} - -static void Ban_Add(const char *reason) -{ - banreason_t *reasonlist = malloc(sizeof(*reasonlist)); - - if (!reasonlist) - return; - if (!reason) - reason = "NA"; - - reasonlist->next = NULL; - reasonlist->reason = Z_StrDup(reason); - if ((reasonlist->prev = reasontail) == NULL) - reasonhead = reasonlist; - else - reasontail->next = reasonlist; - reasontail = reasonlist; -} - -static void Ban_Clear(void) -{ - banreason_t *temp; - - I_ClearBans(); - - reasontail = NULL; - - while (reasonhead) - { - temp = reasonhead->next; - Z_Free(reasonhead->reason); - free(reasonhead); - reasonhead = temp; - } -} - -static void Command_ClearBans(void) -{ - if (!I_ClearBans) - return; - - Ban_Clear(); - D_SaveBan(); -} - -static void Ban_Load_File(boolean warning) -{ - FILE *f; - size_t i; - const char *address, *mask; - char buffer[MAX_WADPATH]; - - if (!I_ClearBans) - return; - - f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r"); - - if (!f) - { - if (warning) - CONS_Alert(CONS_WARNING, M_GetText("Could not open ban.txt for ban list\n")); - return; - } - - Ban_Clear(); - - for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++) - { - address = strtok(buffer, " \t\r\n"); - mask = strtok(NULL, " \t\r\n"); - - I_SetBanAddress(address, mask); - - Ban_Add(strtok(NULL, "\r\n")); - } - - fclose(f); -} - -static void Command_ReloadBan(void) //recheck ban.txt -{ - Ban_Load_File(true); -} - -static void Command_connect(void) -{ - if (COM_Argc() < 2 || *COM_Argv(1) == 0) - { - CONS_Printf(M_GetText( - "Connect (port): connect to a server\n" - "Connect ANY: connect to the first lan server found\n" - //"Connect SELF: connect to your own server.\n" - )); - return; - } - - if (Playing() || titledemo) - { - CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); - return; - } - - // modified game check: no longer handled - // we don't request a restart unless the filelist differs - - server = false; -/* - if (!stricmp(COM_Argv(1), "self")) - { - servernode = 0; - server = true; - /// \bug should be but... - //SV_SpawnServer(); - } - else -*/ - { - // used in menu to connect to a server in the list - if (netgame && !stricmp(COM_Argv(1), "node")) - { - servernode = (SINT8)atoi(COM_Argv(2)); - } - else if (netgame) - { - CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); - return; - } - else if (I_NetOpenSocket) - { - I_NetOpenSocket(); - netgame = true; - multiplayer = true; - - if (!stricmp(COM_Argv(1), "any")) - servernode = BROADCASTADDR; - else if (I_NetMakeNodewPort) - { - if (COM_Argc() >= 3) // address AND port - servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2)); - else // address only, or address:port - servernode = I_NetMakeNode(COM_Argv(1)); - } - else - { - CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n")); - D_CloseConnection(); - return; - } - } - else - CONS_Alert(CONS_ERROR, M_GetText("There is no network driver\n")); - } - - splitscreen = false; - SplitScreen_OnChange(); - botingame = false; - botskin = 0; - CL_ConnectToServer(); -} -#endif - -static void ResetNode(INT32 node); - -// -// CL_ClearPlayer -// -// Clears the player data so that a future client can use this slot -// -void CL_ClearPlayer(INT32 playernum) -{ - if (players[playernum].mo) - P_RemoveMobj(players[playernum].mo); - memset(&players[playernum], 0, sizeof (player_t)); - memset(playeraddress[playernum], 0, sizeof(*playeraddress)); -} - -// -// CL_RemovePlayer -// -// Removes a player from the current game -// -void CL_RemovePlayer(INT32 playernum, kickreason_t reason) -{ - // Sanity check: exceptional cases (i.e. c-fails) can cause multiple - // kick commands to be issued for the same player. - if (!playeringame[playernum]) - return; - - if (server && !demoplayback && playernode[playernum] != UINT8_MAX) - { - INT32 node = playernode[playernum]; - playerpernode[node]--; - if (playerpernode[node] <= 0) - { - nodeingame[node] = false; - Net_CloseConnection(node); - ResetNode(node); - } - } - - if (gametyperules & GTR_TEAMFLAGS) - P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you! - - // If in a special stage, redistribute the player's spheres across - // the remaining players. - if (G_IsSpecialStage(gamemap)) - { - INT32 i, count, sincrement, spheres, rincrement, rings; - - for (i = 0, count = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - count++; - } - - count--; - sincrement = spheres = players[playernum].spheres; - rincrement = rings = players[playernum].rings; - - if (count) - { - sincrement /= count; - rincrement /= count; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && i != playernum) - { - if (spheres < 2*sincrement) - { - P_GivePlayerSpheres(&players[i], spheres); - spheres = 0; - } - else - { - P_GivePlayerSpheres(&players[i], sincrement); - spheres -= sincrement; - } - - if (rings < 2*rincrement) - { - P_GivePlayerRings(&players[i], rings); - rings = 0; - } - else - { - P_GivePlayerRings(&players[i], rincrement); - rings -= rincrement; - } - } - } - } - - LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting - - // don't look through someone's view who isn't there - if (playernum == displayplayer) - { - // Call ViewpointSwitch hooks here. - // The viewpoint was forcibly changed. - LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true); - displayplayer = consoleplayer; - } - - // Reset player data - CL_ClearPlayer(playernum); - - // remove avatar of player - playeringame[playernum] = false; - playernode[playernum] = UINT8_MAX; - while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1) - doomcom->numslots--; - - // Reset the name - sprintf(player_names[playernum], "Player %d", playernum+1); - - player_name_changes[playernum] = 0; - - if (IsPlayerAdmin(playernum)) - { - RemoveAdminPlayer(playernum); // don't stay admin after you're gone - } - - LUA_InvalidatePlayer(&players[playernum]); - - if (G_TagGametype()) //Check if you still have a game. Location flexible. =P - P_CheckSurvivors(); - else if (gametyperules & GTR_RACE) - P_CheckRacers(); -} - -void CL_Reset(void) -{ - if (metalrecording) - G_StopMetalRecording(false); - if (metalplayback) - G_StopMetalDemo(); - if (demorecording) - G_CheckDemoStatus(); - - // reset client/server code - DEBFILE(va("\n-=-=-=-=-=-=-= Client reset =-=-=-=-=-=-=-\n\n")); - - if (servernode > 0 && servernode < MAXNETNODES) - { - nodeingame[(UINT8)servernode] = false; - Net_CloseConnection(servernode); - } - D_CloseConnection(); // netgame = false - multiplayer = false; - servernode = 0; - server = true; - doomcom->numnodes = 1; - doomcom->numslots = 1; - SV_StopServer(); - SV_ResetServer(); - - // make sure we don't leave any fileneeded gunk over from a failed join - FreeFileNeeded(); - fileneedednum = 0; - -#ifndef NONET - totalfilesrequestednum = 0; - totalfilesrequestedsize = 0; -#endif - firstconnectattempttime = 0; - serverisfull = false; - connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack - - // D_StartTitle should get done now, but the calling function will handle it -} - -#ifndef NONET -static void Command_GetPlayerNum(void) -{ - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - { - if (serverplayer == i) - CONS_Printf(M_GetText("num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]); - else - CONS_Printf(M_GetText("\x82num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]); - } -} - -SINT8 nametonum(const char *name) -{ - INT32 playernum, i; - - if (!strcmp(name, "0")) - return 0; - - playernum = (SINT8)atoi(name); - - if (playernum < 0 || playernum >= MAXPLAYERS) - return -1; - - if (playernum) - { - if (playeringame[playernum]) - return (SINT8)playernum; - else - return -1; - } - - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && !stricmp(player_names[i], name)) - return (SINT8)i; - - CONS_Printf(M_GetText("There is no player named \"%s\"\n"), name); - - return -1; -} - -/** Lists all players and their player numbers. - * - * \sa Command_GetPlayerNum - */ -static void Command_Nodes(void) -{ - INT32 i; - size_t maxlen = 0; - const char *address; - - for (i = 0; i < MAXPLAYERS; i++) - { - const size_t plen = strlen(player_names[i]); - if (playeringame[i] && plen > maxlen) - maxlen = plen; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - CONS_Printf("%.2u: %*s", i, (int)maxlen, player_names[i]); - - if (playernode[i] != UINT8_MAX) - { - CONS_Printf(" - node %.2d", playernode[i]); - if (I_GetNodeAddress && (address = I_GetNodeAddress(playernode[i])) != NULL) - CONS_Printf(" - %s", address); - } - - if (IsPlayerAdmin(i)) - CONS_Printf(M_GetText(" (verified admin)")); - - if (players[i].spectator) - CONS_Printf(M_GetText(" (spectator)")); - - CONS_Printf("\n"); - } - } -} - -static void Command_Ban(void) -{ - if (COM_Argc() < 2) - { - CONS_Printf(M_GetText("Ban : ban and kick a player\n")); - return; - } - - if (!netgame) // Don't kick Tails in splitscreen! - { - CONS_Printf(M_GetText("This only works in a netgame.\n")); - return; - } - - if (server || IsPlayerAdmin(consoleplayer)) - { - UINT8 buf[3 + MAX_REASONLENGTH]; - UINT8 *p = buf; - const SINT8 pn = nametonum(COM_Argv(1)); - const INT32 node = playernode[(INT32)pn]; - - if (pn == -1 || pn == 0) - return; - - WRITEUINT8(p, pn); - - if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now - { - CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); - WRITEUINT8(p, KICK_MSG_GO_AWAY); - SendNetXCmd(XD_KICK, &buf, 2); - } - else - { - if (server) // only the server is allowed to do this right now - { - Ban_Add(COM_Argv(2)); - D_SaveBan(); // save the ban list - } - - if (COM_Argc() == 2) - { - WRITEUINT8(p, KICK_MSG_BANNED); - SendNetXCmd(XD_KICK, &buf, 2); - } - else - { - size_t i, j = COM_Argc(); - char message[MAX_REASONLENGTH]; - - //Steal from the motd code so you don't have to put the reason in quotes. - strlcpy(message, COM_Argv(2), sizeof message); - for (i = 3; i < j; i++) - { - strlcat(message, " ", sizeof message); - strlcat(message, COM_Argv(i), sizeof message); - } - - WRITEUINT8(p, KICK_MSG_CUSTOM_BAN); - WRITESTRINGN(p, message, MAX_REASONLENGTH); - SendNetXCmd(XD_KICK, &buf, p - buf); - } - } - } - else - CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); - -} - -static void Command_BanIP(void) -{ - if (COM_Argc() < 2) - { - CONS_Printf(M_GetText("banip : ban an ip address\n")); - return; - } - - if (server) // Only the server can use this, otherwise does nothing. - { - const char *address = (COM_Argv(1)); - const char *reason; - - if (COM_Argc() == 2) - reason = NULL; - else - reason = COM_Argv(2); - - - if (I_SetBanAddress && I_SetBanAddress(address, NULL)) - { - if (reason) - CONS_Printf("Banned IP address %s for: %s\n", address, reason); - else - CONS_Printf("Banned IP address %s\n", address); - - Ban_Add(reason); - D_SaveBan(); - } - else - { - return; - } - } -} - -static void Command_Kick(void) -{ - if (COM_Argc() < 2) - { - CONS_Printf(M_GetText("kick : kick a player\n")); - return; - } - - if (!netgame) // Don't kick Tails in splitscreen! - { - CONS_Printf(M_GetText("This only works in a netgame.\n")); - return; - } - - if (server || IsPlayerAdmin(consoleplayer)) - { - UINT8 buf[3 + MAX_REASONLENGTH]; - UINT8 *p = buf; - const SINT8 pn = nametonum(COM_Argv(1)); - - if (pn == -1 || pn == 0) - return; - - // Special case if we are trying to kick a player who is downloading the game state: - // trigger a timeout instead of kicking them, because a kick would only - // take effect after they have finished downloading - if (server && playernode[pn] != UINT8_MAX && sendingsavegame[playernode[pn]]) - { - Net_ConnectionTimeout(playernode[pn]); - return; - } - - WRITESINT8(p, pn); - - if (COM_Argc() == 2) - { - WRITEUINT8(p, KICK_MSG_GO_AWAY); - SendNetXCmd(XD_KICK, &buf, 2); - } - else - { - size_t i, j = COM_Argc(); - char message[MAX_REASONLENGTH]; - - //Steal from the motd code so you don't have to put the reason in quotes. - strlcpy(message, COM_Argv(2), sizeof message); - for (i = 3; i < j; i++) - { - strlcat(message, " ", sizeof message); - strlcat(message, COM_Argv(i), sizeof message); - } - - WRITEUINT8(p, KICK_MSG_CUSTOM_KICK); - WRITESTRINGN(p, message, MAX_REASONLENGTH); - SendNetXCmd(XD_KICK, &buf, p - buf); - } - } - else - CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); -} - -static void Command_ResendGamestate(void) -{ - SINT8 playernum; - - if (COM_Argc() == 1) - { - CONS_Printf(M_GetText("resendgamestate : resend the game state to 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 || playernum == 0) - return; - - // Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on - netbuffer->packettype = PT_WILLRESENDGAMESTATE; - if (!HSendPacket(playernode[playernum], true, 0, 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n")); - return; - } -} -#endif - -static void Got_KickCmd(UINT8 **p, INT32 playernum) -{ - INT32 pnum, msg; - char buf[3 + MAX_REASONLENGTH]; - char *reason = buf; - kickreason_t kickreason = KR_KICK; - boolean keepbody; - - pnum = READUINT8(*p); - msg = READUINT8(*p); - keepbody = (msg & KICK_MSG_KEEP_BODY) != 0; - msg &= ~KICK_MSG_KEEP_BODY; - - if (pnum == serverplayer && IsPlayerAdmin(playernum)) - { - CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n")); - - if (server) - COM_BufAddText("quit\n"); - - return; - } - - // Is playernum authorized to make this kick? - if (playernum != serverplayer && !IsPlayerAdmin(playernum) - && !(playernode[playernum] != UINT8_MAX && playerpernode[playernode[playernum]] == 2 - && nodetoplayer2[playernode[playernum]] == pnum)) - { - // We received a kick command from someone who isn't the - // server or admin, and who isn't in splitscreen removing - // player 2. Thus, it must be someone with a modified - // binary, trying to kick someone but without having - // authorization. - - // We deal with this by changing the kick reason to - // "consistency failure" and kicking the offending user - // instead. - - // Note: Splitscreen in netgames is broken because of - // this. Only the server has any idea of which players - // are using splitscreen on the same computer, so - // clients cannot always determine if a kick is - // legitimate. - - CONS_Alert(CONS_WARNING, M_GetText("Illegal kick command received from %s for player %d\n"), player_names[playernum], pnum); - - // In debug, print a longer message with more details. - // TODO Callum: Should we translate this? -/* - CONS_Debug(DBG_NETPLAY, - "So, you must be asking, why is this an illegal kick?\n" - "Well, let's take a look at the facts, shall we?\n" - "\n" - "playernum (this is the guy who did it), he's %d.\n" - "pnum (the guy he's trying to kick) is %d.\n" - "playernum's node is %d.\n" - "That node has %d players.\n" - "Player 2 on that node is %d.\n" - "pnum's node is %d.\n" - "That node has %d players.\n" - "Player 2 on that node is %d.\n" - "\n" - "If you think this is a bug, please report it, including all of the details above.\n", - playernum, pnum, - playernode[playernum], playerpernode[playernode[playernum]], - nodetoplayer2[playernode[playernum]], - playernode[pnum], playerpernode[playernode[pnum]], - nodetoplayer2[playernode[pnum]]); -*/ - pnum = playernum; - msg = KICK_MSG_CON_FAIL; - keepbody = true; - } - - //CONS_Printf("\x82%s ", player_names[pnum]); - - // If a verified admin banned someone, the server needs to know about it. - // If the playernum isn't zero (the server) then the server needs to record the ban. - if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN)) - { - if (I_Ban && !I_Ban(playernode[(INT32)pnum])) - CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); -#ifndef NONET - else - Ban_Add(reason); -#endif - } - - switch (msg) - { - case KICK_MSG_GO_AWAY: - if (!players[pnum].quittime) - HU_AddChatText(va("\x82*%s has been kicked (No reason given)", player_names[pnum]), false); - kickreason = KR_KICK; - break; - case KICK_MSG_PING_HIGH: - HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false); - kickreason = KR_PINGLIMIT; - break; - case KICK_MSG_CON_FAIL: - HU_AddChatText(va("\x82*%s left the game (Synch failure)", player_names[pnum]), false); - kickreason = KR_SYNCH; - - if (M_CheckParm("-consisdump")) // Helps debugging some problems - { - INT32 i; - - CONS_Printf(M_GetText("Player kicked is #%d, dumping consistency...\n"), pnum); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - CONS_Printf("-------------------------------------\n"); - CONS_Printf("Player %d: %s\n", i, player_names[i]); - CONS_Printf("Skin: %d\n", players[i].skin); - CONS_Printf("Color: %d\n", players[i].skincolor); - CONS_Printf("Speed: %d\n",players[i].speed>>FRACBITS); - if (players[i].mo) - { - if (!players[i].mo->skin) - CONS_Printf("Mobj skin: NULL!\n"); - else - CONS_Printf("Mobj skin: %s\n", ((skin_t *)players[i].mo->skin)->name); - CONS_Printf("Position: %d, %d, %d\n", players[i].mo->x, players[i].mo->y, players[i].mo->z); - if (!players[i].mo->state) - CONS_Printf("State: S_NULL\n"); - else - CONS_Printf("State: %d\n", (statenum_t)(players[i].mo->state-states)); - } - else - CONS_Printf("Mobj: NULL\n"); - CONS_Printf("-------------------------------------\n"); - } - } - break; - case KICK_MSG_TIMEOUT: - HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false); - kickreason = KR_TIMEOUT; - break; - case KICK_MSG_PLAYER_QUIT: - if (netgame && !players[pnum].quittime) // not splitscreen/bots or soulless body - HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); - kickreason = KR_LEAVE; - break; - case KICK_MSG_BANNED: - HU_AddChatText(va("\x82*%s has been banned (No reason given)", player_names[pnum]), false); - kickreason = KR_BAN; - break; - case KICK_MSG_CUSTOM_KICK: - READSTRINGN(*p, reason, MAX_REASONLENGTH+1); - HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false); - kickreason = KR_KICK; - break; - case KICK_MSG_CUSTOM_BAN: - READSTRINGN(*p, reason, MAX_REASONLENGTH+1); - HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false); - kickreason = KR_BAN; - break; - } - - if (pnum == consoleplayer) - { - LUA_HookBool(false, HOOK(GameQuit)); -#ifdef DUMPCONSISTENCY - if (msg == KICK_MSG_CON_FAIL) SV_SavedGame(); -#endif - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - if (msg == KICK_MSG_CON_FAIL) - M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING); - else if (msg == KICK_MSG_PING_HIGH) - M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING); - else if (msg == KICK_MSG_BANNED) - M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING); - else if (msg == KICK_MSG_CUSTOM_KICK) - M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); - else if (msg == KICK_MSG_CUSTOM_BAN) - M_StartMessage(va(M_GetText("You have been banned\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); - else - M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING); - } - else if (keepbody) - { - if (server && !demoplayback && playernode[pnum] != UINT8_MAX) - { - INT32 node = playernode[pnum]; - playerpernode[node]--; - if (playerpernode[node] <= 0) - { - nodeingame[node] = false; - Net_CloseConnection(node); - ResetNode(node); - } - } - - playernode[pnum] = UINT8_MAX; - - players[pnum].quittime = 1; - } - else - CL_RemovePlayer(pnum, kickreason); -} - -static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}}; -consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL); - -consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_OnOff, NULL); -consvar_t cv_joinnextround = CVAR_INIT ("joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); /// \todo not done -static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}}; -consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, maxplayers_cons_t, NULL); -static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}}; -consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL); -static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}}; -consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL); - -static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}}; -consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL); -consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); - -// max file size to send to a player (in kilobytes) -static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {204800, "MAX"}, {0, NULL}}; -consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL); -consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); - -// Speed of file downloading (in packets per tic) -static CV_PossibleValue_t downloadspeed_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; -consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL); - -static void Got_AddPlayer(UINT8 **p, INT32 playernum); - -// called one time at init -void D_ClientServerInit(void) -{ - DEBFILE(va("- - -== SRB2 v%d.%.2d.%d "VERSIONSTRING" debugfile ==- - -\n", - VERSION/100, VERSION%100, SUBVERSION)); - -#ifndef NONET - COM_AddCommand("getplayernum", Command_GetPlayerNum, COM_LUA); - COM_AddCommand("kick", Command_Kick, COM_LUA); - COM_AddCommand("ban", Command_Ban, COM_LUA); - COM_AddCommand("banip", Command_BanIP, COM_LUA); - COM_AddCommand("clearbans", Command_ClearBans, COM_LUA); - COM_AddCommand("showbanlist", Command_ShowBan, COM_LUA); - COM_AddCommand("reloadbans", Command_ReloadBan, COM_LUA); - COM_AddCommand("connect", Command_connect, COM_LUA); - COM_AddCommand("nodes", Command_Nodes, COM_LUA); - COM_AddCommand("resendgamestate", Command_ResendGamestate, COM_LUA); -#ifdef PACKETDROP - COM_AddCommand("drop", Command_Drop, COM_LUA); - COM_AddCommand("droprate", Command_Droprate, COM_LUA); -#endif -#ifdef _DEBUG - COM_AddCommand("numnodes", Command_Numnodes, COM_LUA); -#endif -#endif - - RegisterNetXCmd(XD_KICK, Got_KickCmd); - RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer); -#ifndef NONET -#ifdef DUMPCONSISTENCY - CV_RegisterVar(&cv_dumpconsistency); -#endif - Ban_Load_File(false); -#endif - - gametic = 0; - localgametic = 0; - - // do not send anything before the real begin - SV_StopServer(); - SV_ResetServer(); - if (dedicated) - SV_SpawnServer(); -} - -static void ResetNode(INT32 node) -{ - nodeingame[node] = false; - nodewaiting[node] = 0; - - nettics[node] = gametic; - supposedtics[node] = gametic; - - nodetoplayer[node] = -1; - nodetoplayer2[node] = -1; - playerpernode[node] = 0; - - sendingsavegame[node] = false; - resendingsavegame[node] = false; - savegameresendcooldown[node] = 0; -} - -void SV_ResetServer(void) -{ - INT32 i; - - // +1 because this command will be executed in com_executebuffer in - // tryruntic so gametic will be incremented, anyway maketic > gametic - // is not an issue - - maketic = gametic + 1; - neededtic = maketic; - tictoclear = maketic; - - joindelay = 0; - - for (i = 0; i < MAXNETNODES; i++) - ResetNode(i); - - for (i = 0; i < MAXPLAYERS; i++) - { - LUA_InvalidatePlayer(&players[i]); - playeringame[i] = false; - playernode[i] = UINT8_MAX; - memset(playeraddress[i], 0, sizeof(*playeraddress)); - sprintf(player_names[i], "Player %d", i + 1); - adminplayers[i] = -1; // Populate the entire adminplayers array with -1. - } - - memset(player_name_changes, 0, sizeof player_name_changes); - - mynode = 0; - cl_packetmissed = false; - cl_redownloadinggamestate = false; - - if (dedicated) - { - nodeingame[0] = true; - serverplayer = 0; - } - else - serverplayer = consoleplayer; - - if (server) - servernode = 0; - - doomcom->numslots = 0; - - // clear server_context - memset(server_context, '-', 8); - - CV_RevertNetVars(); - - // Ensure synched when creating a new server - M_CopyGameData(serverGamedata, clientGamedata); - - DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n"); -} - -static inline void SV_GenContext(void) -{ - UINT8 i; - // generate server_context, as exactly 8 bytes of randomly mixed A-Z and a-z - // (hopefully M_Random is initialized!! if not this will be awfully silly!) - for (i = 0; i < 8; i++) - { - const char a = M_RandomKey(26*2); - if (a < 26) // uppercase - server_context[i] = 'A'+a; - else // lowercase - server_context[i] = 'a'+(a-26); - } -} - -// -// D_QuitNetGame -// Called before quitting to leave a net game -// without hanging the other players -// -void D_QuitNetGame(void) -{ - mousegrabbedbylua = true; - I_UpdateMouseGrab(); - - if (!netgame || !netbuffer) - return; - - DEBFILE("===========================================================================\n" - " Quitting Game, closing connection\n" - "===========================================================================\n"); - - // abort send/receive of files - CloseNetFile(); - RemoveAllLuaFileTransfers(); - waitingforluafiletransfer = false; - waitingforluafilecommand = false; - - if (server) - { - INT32 i; - - netbuffer->packettype = PT_SERVERSHUTDOWN; - for (i = 0; i < MAXNETNODES; i++) - if (nodeingame[i]) - HSendPacket(i, true, 0, 0); -#ifdef MASTERSERVER - if (serverrunning && ms_RoomId > 0) - UnregisterServer(); -#endif - } - else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode]) - { - netbuffer->packettype = PT_CLIENTQUIT; - HSendPacket(servernode, true, 0, 0); - } - - D_CloseConnection(); - ClearAdminPlayers(); - - DEBFILE("===========================================================================\n" - " Log finish\n" - "===========================================================================\n"); -#ifdef DEBUGFILE - if (debugfile) - { - fclose(debugfile); - debugfile = NULL; - } -#endif -} - -// Adds a node to the game (player will follow at map change or at savegame....) -static inline void SV_AddNode(INT32 node) -{ - nettics[node] = gametic; - supposedtics[node] = gametic; - // little hack because the server connects to itself and puts - // nodeingame when connected not here - if (node) - nodeingame[node] = true; -} - -// Xcmd XD_ADDPLAYER -static void Got_AddPlayer(UINT8 **p, INT32 playernum) -{ - INT16 node, newplayernum; - boolean splitscreenplayer; - boolean rejoined; - player_t *newplayer; - - if (playernum != serverplayer && !IsPlayerAdmin(playernum)) - { - // protect against hacked/buggy client - CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]); - if (server) - SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); - return; - } - - node = READUINT8(*p); - newplayernum = READUINT8(*p); - splitscreenplayer = newplayernum & 0x80; - newplayernum &= ~0x80; - - rejoined = playeringame[newplayernum]; - - if (!rejoined) - { - // Clear player before joining, lest some things get set incorrectly - // HACK: don't do this for splitscreen, it relies on preset values - if (!splitscreen && !botingame) - CL_ClearPlayer(newplayernum); - playeringame[newplayernum] = true; - G_AddPlayer(newplayernum); - if (newplayernum+1 > doomcom->numslots) - doomcom->numslots = (INT16)(newplayernum+1); - - if (server && I_GetNodeAddress) - { - char addressbuffer[64]; - const char *address = I_GetNodeAddress(node); - if (address) // MI: fix msvcrt.dll!_mbscat crash? - { - strcpy(addressbuffer, address); - strcpy(playeraddress[newplayernum], - I_NetSplitAddress(addressbuffer, NULL)); - } - } - } - - newplayer = &players[newplayernum]; - - newplayer->jointime = 0; - newplayer->quittime = 0; - - READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); - - // the server is creating my player - if (node == mynode) - { - playernode[newplayernum] = 0; // for information only - if (!splitscreenplayer) - { - consoleplayer = newplayernum; - displayplayer = newplayernum; - secondarydisplayplayer = newplayernum; - DEBFILE("spawning me\n"); - ticcmd_oldangleturn[0] = newplayer->oldrelangleturn; - } - else - { - secondarydisplayplayer = newplayernum; - DEBFILE("spawning my brother\n"); - if (botingame) - newplayer->bot = 1; - ticcmd_oldangleturn[1] = newplayer->oldrelangleturn; - } - P_ForceLocalAngle(newplayer, (angle_t)(newplayer->angleturn << 16)); - D_SendPlayerConfig(); - addedtogame = true; - - if (rejoined) - { - if (newplayer->mo) - { - newplayer->viewheight = 41*newplayer->height/48; - - if (newplayer->mo->eflags & MFE_VERTICALFLIP) - newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight; - else - newplayer->viewz = newplayer->mo->z + newplayer->viewheight; - } - - // wake up the status bar - ST_Start(); - // wake up the heads up text - HU_Start(); - - if (camera.chase && !splitscreenplayer) - P_ResetCamera(newplayer, &camera); - if (camera2.chase && splitscreenplayer) - P_ResetCamera(newplayer, &camera2); - } - } - - if (netgame) - { - char joinmsg[256]; - - if (rejoined) - strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)")); - else - strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)")); - strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum)); - - // Merge join notification + IP to avoid clogging console/chat - if (server && cv_showjoinaddress.value && I_GetNodeAddress) - { - const char *address = I_GetNodeAddress(node); - if (address) - strcat(joinmsg, va(" (%s)", address)); - } - - HU_AddChatText(joinmsg, false); - } - - if (server && multiplayer && motd[0] != '\0') - COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); - - if (!rejoined) - LUA_HookInt(newplayernum, HOOK(PlayerJoin)); -} - -static boolean SV_AddWaitingPlayers(const char *name, const char *name2) -{ - INT32 node, n, newplayer = false; - UINT8 buf[2 + MAXPLAYERNAME]; - UINT8 *p; - INT32 newplayernum; - - for (node = 0; node < MAXNETNODES; node++) - { - // splitscreen can allow 2 player in one node - for (; nodewaiting[node] > 0; nodewaiting[node]--) - { - newplayer = true; - - newplayernum = FindRejoinerNum(node); - if (newplayernum == -1) - { - // search for a free playernum - // we can't use playeringame since it is not updated here - for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++) - { - if (playeringame[newplayernum]) - continue; - for (n = 0; n < MAXNETNODES; n++) - if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum) - break; - if (n == MAXNETNODES) - break; - } - } - - // should never happen since we check the playernum - // before accepting the join - I_Assert(newplayernum < MAXPLAYERS); - - playernode[newplayernum] = (UINT8)node; - - p = buf + 2; - buf[0] = (UINT8)node; - buf[1] = newplayernum; - if (playerpernode[node] < 1) - { - nodetoplayer[node] = newplayernum; - WRITESTRINGN(p, name, MAXPLAYERNAME); - } - else - { - nodetoplayer2[node] = newplayernum; - buf[1] |= 0x80; - WRITESTRINGN(p, name2, MAXPLAYERNAME); - } - playerpernode[node]++; - - SendNetXCmd(XD_ADDPLAYER, &buf, p - buf); - - DEBFILE(va("Server added player %d node %d\n", newplayernum, node)); - } - } - - return newplayer; -} - -void CL_AddSplitscreenPlayer(void) -{ - if (cl_mode == CL_CONNECTED) - CL_SendJoin(); -} - -void CL_RemoveSplitscreenPlayer(void) -{ - if (cl_mode != CL_CONNECTED) - return; - - SendKick(secondarydisplayplayer, KICK_MSG_PLAYER_QUIT); -} - -// is there a game running -boolean Playing(void) -{ - return (server && serverrunning) || (client && cl_mode == CL_CONNECTED); -} - -boolean SV_SpawnServer(void) -{ - if (demoplayback) - G_StopDemo(); // reset engine parameter - if (metalplayback) - G_StopMetalDemo(); - - if (!serverrunning) - { - CONS_Printf(M_GetText("Starting Server....\n")); - serverrunning = true; - SV_ResetServer(); - SV_GenContext(); - if (netgame && I_NetOpenSocket) - { - I_NetOpenSocket(); -#ifdef MASTERSERVER - if (ms_RoomId > 0) - RegisterServer(); -#endif - } - - // non dedicated server just connect to itself - if (!dedicated) - CL_ConnectToServer(); - else doomcom->numslots = 1; - } - - return SV_AddWaitingPlayers(cv_playername.zstring, cv_playername2.zstring); -} - -void SV_StopServer(void) -{ - tic_t i; - - if (gamestate == GS_INTERMISSION) - Y_EndIntermission(); - gamestate = wipegamestate = GS_NULL; - - localtextcmd[0] = 0; - localtextcmd2[0] = 0; - - for (i = firstticstosend; i < firstticstosend + BACKUPTICS; i++) - D_Clearticcmd(i); - - consoleplayer = 0; - cl_mode = CL_SEARCHING; - maketic = gametic+1; - neededtic = maketic; - serverrunning = false; -} - -// called at singleplayer start and stopdemo -void SV_StartSinglePlayerServer(void) -{ - server = true; - netgame = false; - multiplayer = false; - G_SetGametype(GT_COOP); - - // no more tic the game with this settings! - SV_StopServer(); - - if (splitscreen) - multiplayer = true; -} - -static void SV_SendRefuse(INT32 node, const char *reason) -{ - strcpy(netbuffer->u.serverrefuse.reason, reason); - - netbuffer->packettype = PT_SERVERREFUSE; - HSendPacket(node, true, 0, strlen(netbuffer->u.serverrefuse.reason) + 1); - Net_CloseConnection(node); -} - -// used at txtcmds received to check packetsize bound -static size_t TotalTextCmdPerTic(tic_t tic) -{ - INT32 i; - size_t total = 1; // num of textcmds in the tic (ntextcmd byte) - - for (i = 0; i < MAXPLAYERS; i++) - { - UINT8 *textcmd = D_GetExistingTextcmd(tic, i); - if ((!i || playeringame[i]) && textcmd) - total += 2 + textcmd[0]; // "+2" for size and playernum - } - - return total; -} - -static const char * -ConnectionRefused (SINT8 node, INT32 rejoinernum) -{ - clientconfig_pak *cc = &netbuffer->u.clientcfg; - - boolean rejoining = (rejoinernum != -1); - - if (!node)/* server connecting to itself */ - return NULL; - - if ( - cc->modversion != MODVERSION || - strncmp(cc->application, SRB2APPLICATION, - sizeof cc->application) - ){ - return/* this is probably client's fault */ - "Incompatible."; - } - else if (bannednode && bannednode[node]) - { - return - "You have been banned\n" - "from the server."; - } - else if (cc->localplayers != 1) - { - return - "Wrong player count."; - } - - if (!rejoining) - { - if (!cv_allownewplayer.value) - { - return - "The server is not accepting\n" - "joins for the moment."; - } - else if (D_NumPlayers() >= cv_maxplayers.value) - { - return va( - "Maximum players reached: %d", - cv_maxplayers.value - D_NumBots()); - } - } - - if (luafiletransfers) - { - return - "The serveris broadcasting a file\n" - "requested by a Lua script.\n" - "Please wait a bit and then\n" - "try rejoining."; - } - - if (netgame) - { - const tic_t th = 2 * cv_joindelay.value * TICRATE; - - if (joindelay > th) - { - return va( - "Too many people are connecting.\n" - "Please wait %d seconds and then\n" - "try rejoining.", - (joindelay - th) / TICRATE); - } - } - - return NULL; -} - -/** Called when a PT_CLIENTJOIN packet is received - * - * \param node The packet sender - * - */ -static void HandleConnect(SINT8 node) -{ - char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; - INT32 rejoinernum; - INT32 i; - const char *refuse; - - rejoinernum = FindRejoinerNum(node); - - refuse = ConnectionRefused(node, rejoinernum); - - if (refuse) - SV_SendRefuse(node, refuse); - else - { -#ifndef NONET - boolean newnode = false; -#endif - - for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++) - { - strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1); - if (!EnsurePlayerNameIsGood(names[i], rejoinernum)) - { - SV_SendRefuse(node, "Bad player name"); - return; - } - } - - // client authorised to join - nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]); - if (!nodeingame[node]) - { - gamestate_t backupstate = gamestate; -#ifndef NONET - newnode = true; -#endif - SV_AddNode(node); - - if (cv_joinnextround.value && gameaction == ga_nothing) - G_SetGamestate(GS_WAITINGPLAYERS); - if (!SV_SendServerConfig(node)) - { - G_SetGamestate(backupstate); - /// \note Shouldn't SV_SendRefuse be called before ResetNode? - ResetNode(node); - SV_SendRefuse(node, M_GetText("Server couldn't send info, please try again")); - /// \todo fix this !!! - return; // restart the while - } - //if (gamestate != GS_LEVEL) // GS_INTERMISSION, etc? - // SV_SendPlayerConfigs(node); // send bare minimum player info - G_SetGamestate(backupstate); - DEBFILE("new node joined\n"); - } -#ifndef NONET - if (nodewaiting[node]) - { - if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode) - { - SV_SendSaveGame(node, false); // send a complete game state - DEBFILE("send savegame\n"); - } - SV_AddWaitingPlayers(names[0], names[1]); - joindelay += cv_joindelay.value * TICRATE; - player_joining = true; - } -#endif - } -} - -/** Called when a PT_SERVERSHUTDOWN packet is received - * - * \param node The packet sender (should be the server) - * - */ -static void HandleShutdown(SINT8 node) -{ - (void)node; - LUA_HookBool(false, HOOK(GameQuit)); - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText("Server has shutdown\n\nPress Esc\n"), NULL, MM_NOTHING); -} - -/** Called when a PT_NODETIMEOUT packet is received - * - * \param node The packet sender (should be the server) - * - */ -static void HandleTimeout(SINT8 node) -{ - (void)node; - LUA_HookBool(false, HOOK(GameQuit)); - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING); -} - -#ifndef NONET -/** Called when a PT_SERVERINFO packet is received - * - * \param node The packet sender - * \note What happens if the packet comes from a client or something like that? - * - */ -static void HandleServerInfo(SINT8 node) -{ - // compute ping in ms - const tic_t ticnow = I_GetTime(); - const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time); - const tic_t ticdiff = (ticnow - ticthen)*1000/NEWTICRATE; - netbuffer->u.serverinfo.time = (tic_t)LONG(ticdiff); - netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0; - netbuffer->u.serverinfo.application - [sizeof netbuffer->u.serverinfo.application - 1] = '\0'; - netbuffer->u.serverinfo.gametypename - [sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0'; - - SL_InsertServer(&netbuffer->u.serverinfo, node); -} -#endif - -static void PT_WillResendGamestate(void) -{ -#ifndef NONET - char tmpsave[256]; - - if (server || cl_redownloadinggamestate) - return; - - // Send back a PT_CANRECEIVEGAMESTATE packet to the server - // so they know they can start sending the game state - netbuffer->packettype = PT_CANRECEIVEGAMESTATE; - if (!HSendPacket(servernode, true, 0, 0)) - return; - - CONS_Printf(M_GetText("Reloading game state...\n")); - - sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - - // Don't get a corrupt savegame error because tmpsave already exists - if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) - I_Error("Can't delete %s\n", tmpsave); - - CL_PrepareDownloadSaveGame(tmpsave); - - cl_redownloadinggamestate = true; -#endif -} - -static void PT_CanReceiveGamestate(SINT8 node) -{ -#ifndef NONET - if (client || sendingsavegame[node]) - return; - - CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]); - - SV_SendSaveGame(node, true); // Resend a complete game state - resendingsavegame[node] = true; -#else - (void)node; -#endif -} - -/** Handles a packet received from a node that isn't in game - * - * \param node The packet sender - * \todo Choose a better name, as the packet can also come from the server apparently? - * \sa HandlePacketFromPlayer - * \sa GetPackets - * - */ -static void HandlePacketFromAwayNode(SINT8 node) -{ - if (node != servernode) - DEBFILE(va("Received packet from unknown host %d\n", node)); - -// macro for packets that should only be sent by the server -// if it is NOT from the server, bail out and close the connection! -#define SERVERONLY \ - if (node != servernode) \ - { \ - Net_CloseConnection(node); \ - break; \ - } - switch (netbuffer->packettype) - { - case PT_ASKINFOVIAMS: - Net_CloseConnection(node); - break; - - case PT_TELLFILESNEEDED: - if (server && serverrunning) - { - UINT8 *p; - INT32 firstfile = netbuffer->u.filesneedednum; - - netbuffer->packettype = PT_MOREFILESNEEDED; - netbuffer->u.filesneededcfg.first = firstfile; - netbuffer->u.filesneededcfg.more = 0; - - p = PutFileNeeded(firstfile); - - HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u)); - } - else // Shouldn't get this if you aren't the server...? - Net_CloseConnection(node); - break; - - case PT_MOREFILESNEEDED: - if (server && serverrunning) - { // But wait I thought I'm the server? - Net_CloseConnection(node); - break; - } - SERVERONLY - if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum) - { - D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first); - if (!netbuffer->u.filesneededcfg.more) - cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list - } - break; - - case PT_ASKINFO: - if (server && serverrunning) - { - SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time)); - SV_SendPlayerInfo(node); // Send extra info - } - Net_CloseConnection(node); - break; - - case PT_SERVERREFUSE: // Negative response of client join request - if (server && serverrunning) - { // But wait I thought I'm the server? - Net_CloseConnection(node); - break; - } - SERVERONLY - if (cl_mode == CL_WAITJOINRESPONSE) - { - // Save the reason so it can be displayed after quitting the netgame - char *reason = strdup(netbuffer->u.serverrefuse.reason); - if (!reason) - I_Error("Out of memory!\n"); - - if (strstr(reason, "Maximum players reached")) - { - serverisfull = true; - //Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer - //We set it back to the value of cv_nettimeout.value in CL_Reset - connectiontimeout = NEWTICRATE*7; - cl_mode = CL_ASKJOIN; - free(reason); - break; - } - - M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), - reason), NULL, MM_NOTHING); - - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - - free(reason); - - // Will be reset by caller. Signals refusal. - cl_mode = CL_ABORTED; - } - break; - - case PT_SERVERCFG: // Positive response of client join request - { - if (server && serverrunning && node != servernode) - { // but wait I thought I'm the server? - Net_CloseConnection(node); - break; - } - SERVERONLY - /// \note how would this happen? and is it doing the right thing if it does? - if (cl_mode != CL_WAITJOINRESPONSE) - break; - - if (client) - { - maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic); - G_SetGametype(netbuffer->u.servercfg.gametype); - modifiedgame = netbuffer->u.servercfg.modifiedgame; - if (netbuffer->u.servercfg.usedCheats) - G_SetUsedCheats(true); - memcpy(server_context, netbuffer->u.servercfg.server_context, 8); - } - - nodeingame[(UINT8)servernode] = true; - serverplayer = netbuffer->u.servercfg.serverplayer; - doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum); - mynode = netbuffer->u.servercfg.clientnode; - if (serverplayer >= 0) - playernode[(UINT8)serverplayer] = servernode; - - if (netgame) -#ifndef NONET - CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n")); -#else - CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n")); -#endif - DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode)); - -#ifndef NONET - /// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook? - /// Shouldn't them be downloaded even at intermission time? - /// Also, according to HandleConnect, the server will send the savegame even during intermission... - if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* || - netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/) - cl_mode = CL_DOWNLOADSAVEGAME; - else -#endif - cl_mode = CL_CONNECTED; - break; - } - - // Handled in d_netfil.c - case PT_FILEFRAGMENT: - if (server) - { // But wait I thought I'm the server? - Net_CloseConnection(node); - break; - } - SERVERONLY - PT_FileFragment(); - break; - - case PT_FILEACK: - if (server) - PT_FileAck(); - break; - - case PT_FILERECEIVED: - if (server) - PT_FileReceived(); - break; - - case PT_REQUESTFILE: - if (server) - { - if (!cv_downloading.value || !PT_RequestFile(node)) - Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway - } - else - Net_CloseConnection(node); // nope - break; - - case PT_NODETIMEOUT: - case PT_CLIENTQUIT: - if (server) - Net_CloseConnection(node); - break; - - case PT_CLIENTCMD: - break; // This is not an "unknown packet" - - case PT_SERVERTICS: - // Do not remove my own server (we have just get a out of order packet) - if (node == servernode) - break; - /* FALLTHRU */ - - default: - DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype)); - Net_CloseConnection(node); - break; // Ignore it - - } -#undef SERVERONLY -} - -/** Handles a packet received from a node that is in game - * - * \param node The packet sender - * \todo Choose a better name - * \sa HandlePacketFromAwayNode - * \sa GetPackets - * - */ -static void HandlePacketFromPlayer(SINT8 node) -{ - INT32 netconsole; - tic_t realend, realstart; - UINT8 *pak, *txtpak, numtxtpak; -#ifndef NOMD5 - UINT8 finalmd5[16];/* Well, it's the cool thing to do? */ -#endif - - txtpak = NULL; - - if (dedicated && node == 0) - netconsole = 0; - else - netconsole = nodetoplayer[node]; - -#ifdef PARANOIA - if (netconsole >= MAXPLAYERS) - I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole); -#endif - - switch (netbuffer->packettype) - { -// -------------------------------------------- SERVER RECEIVE ---------- - case PT_CLIENTCMD: - case PT_CLIENT2CMD: - case PT_CLIENTMIS: - case PT_CLIENT2MIS: - case PT_NODEKEEPALIVE: - case PT_NODEKEEPALIVEMIS: - if (client) - break; - - // To save bytes, only the low byte of tic numbers are sent - // Use ExpandTics to figure out what the rest of the bytes are - realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node); - realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node); - - if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS - || netbuffer->packettype == PT_NODEKEEPALIVEMIS - || supposedtics[node] < realend) - { - supposedtics[node] = realend; - } - // Discard out of order packet - if (nettics[node] > realend) - { - DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node])); - break; - } - - // Update the nettics - nettics[node] = realend; - - // This should probably still timeout though, as the node should always have a player 1 number - if (netconsole == -1) - break; - - // As long as clients send valid ticcmds, the server can keep running, so reset the timeout - /// \todo Use a separate cvar for that kind of timeout? - freezetimeout[node] = I_GetTime() + connectiontimeout; - - // Don't do anything for packets of type NODEKEEPALIVE? - // Sryder 2018/07/01: Update the freezetimeout still! - if (netbuffer->packettype == PT_NODEKEEPALIVE - || netbuffer->packettype == PT_NODEKEEPALIVEMIS) - break; - - // If we've alredy received a ticcmd for this tic, just submit it for the next one. - tic_t faketic = maketic; - if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED)) - && (maketic - firstticstosend < BACKUPTICS - 1)) - faketic++; - - // Copy ticcmd - G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1); - - // Check ticcmd for "speed hacks" - if (netcmds[faketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[faketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE - || netcmds[faketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[faketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole); - //D_Clearticcmd(k); - - SendKick(netconsole, KICK_MSG_CON_FAIL); - break; - } - - // Splitscreen cmd - if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS) - && nodetoplayer2[node] >= 0) - G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][(UINT8)nodetoplayer2[node]], - &netbuffer->u.client2pak.cmd2, 1); - - - // Check player consistancy during the level - if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL - && consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy) -#ifndef NONET - && !SV_ResendingSavegameToAnyone() -#endif - && !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime()) - { - if (cv_resynchattempts.value) - { - // Tell the client we are about to resend them the gamestate - netbuffer->packettype = PT_WILLRESENDGAMESTATE; - HSendPacket(node, true, 0, 0); - - 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 | KICK_MSG_KEEP_BODY); - DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n", - netconsole, realstart, consistancy[realstart%BACKUPTICS], - SHORT(netbuffer->u.clientpak.consistancy))); - break; - } - } - break; - case PT_BASICKEEPALIVE: - if (client) - break; - - // This should probably still timeout though, as the node should always have a player 1 number - if (netconsole == -1) - break; - - // If a client sends this it should mean they are done receiving the savegame - sendingsavegame[node] = false; - - // As long as clients send keep alives, the server can keep running, so reset the timeout - /// \todo Use a separate cvar for that kind of timeout? - freezetimeout[node] = I_GetTime() + connectiontimeout; - break; - case PT_TEXTCMD2: // splitscreen special - netconsole = nodetoplayer2[node]; - /* FALLTHRU */ - case PT_TEXTCMD: - if (client) - break; - - if (netconsole < 0 || netconsole >= MAXPLAYERS) - Net_UnAcknowledgePacket(node); - else - { - size_t j; - tic_t tic = maketic; - UINT8 *textcmd; - - // ignore if the textcmd has a reported size of zero - // this shouldn't be sent at all - if (!netbuffer->u.textcmd[0]) - { - DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n", - node, netconsole)); - Net_UnAcknowledgePacket(node); - break; - } - - // ignore if the textcmd size var is actually larger than it should be - // BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength - if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1) - { - DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n", - netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1), - node, netconsole)); - Net_UnAcknowledgePacket(node); - break; - } - - // check if tic that we are making isn't too large else we cannot send it :( - // doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time - j = software_MAXPACKETLENGTH - - (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE - + (doomcom->numslots+1)*sizeof(ticcmd_t)); - - // search a tic that have enougth space in the ticcmd - while ((textcmd = D_GetExistingTextcmd(tic, netconsole)), - (TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD) - && tic < firstticstosend + BACKUPTICS) - tic++; - - if (tic >= firstticstosend + BACKUPTICS) - { - DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, " - "tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)), - maketic, firstticstosend, node, netconsole)); - Net_UnAcknowledgePacket(node); - break; - } - - // Make sure we have a buffer - if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole); - - DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n", - tic, textcmd[0]+1, netconsole, firstticstosend, maketic)); - - M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]); - textcmd[0] += (UINT8)netbuffer->u.textcmd[0]; - } - break; - case PT_LOGIN: - if (client) - break; - -#ifndef NOMD5 - if (doomcom->datalength < 16)/* ignore partial sends */ - break; - - if (!adminpasswordset) - { - CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]); - break; - } - - // Do the final pass to compare with the sent md5 - D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5); - - if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) - { - CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); - COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately - } - else - CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); -#endif - break; - case PT_NODETIMEOUT: - case PT_CLIENTQUIT: - if (client) - break; - - // nodeingame will be put false in the execution of kick command - // this allow to send some packets to the quitting client to have their ack back - nodewaiting[node] = 0; - if (netconsole != -1 && playeringame[netconsole]) - { - UINT8 kickmsg; - - if (netbuffer->packettype == PT_NODETIMEOUT) - kickmsg = KICK_MSG_TIMEOUT; - else - kickmsg = KICK_MSG_PLAYER_QUIT; - kickmsg |= KICK_MSG_KEEP_BODY; - - SendKick(netconsole, kickmsg); - nodetoplayer[node] = -1; - - if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0 - && playeringame[(UINT8)nodetoplayer2[node]]) - { - SendKick(nodetoplayer2[node], kickmsg); - nodetoplayer2[node] = -1; - } - } - Net_CloseConnection(node); - nodeingame[node] = false; - break; - case PT_CANRECEIVEGAMESTATE: - PT_CanReceiveGamestate(node); - break; - case PT_ASKLUAFILE: - if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED) - AddLuaFileToSendQueue(node, luafiletransfers->realfilename); - break; - case PT_HASLUAFILE: - if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING) - SV_HandleLuaFileSent(node); - break; - case PT_RECEIVEDGAMESTATE: - sendingsavegame[node] = false; - resendingsavegame[node] = false; - savegameresendcooldown[node] = I_GetTime() + 5 * TICRATE; - break; -// -------------------------------------------- CLIENT RECEIVE ---------- - case PT_SERVERTICS: - // Only accept PT_SERVERTICS from the server. - if (node != servernode) - { - CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node); - if (server) - SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); - break; - } - - realstart = netbuffer->u.serverpak.starttic; - realend = realstart + netbuffer->u.serverpak.numtics; - - if (!txtpak) - txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots - * netbuffer->u.serverpak.numtics]; - - if (realend > gametic + CLIENTBACKUPTICS) - realend = gametic + CLIENTBACKUPTICS; - cl_packetmissed = realstart > neededtic; - - if (realstart <= neededtic && realend > neededtic) - { - tic_t i, j; - pak = (UINT8 *)&netbuffer->u.serverpak.cmds; - - for (i = realstart; i < realend; i++) - { - // clear first - D_Clearticcmd(i); - - // copy the tics - pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak, - netbuffer->u.serverpak.numslots*sizeof (ticcmd_t)); - - // copy the textcmds - numtxtpak = *txtpak++; - for (j = 0; j < numtxtpak; j++) - { - INT32 k = *txtpak++; // playernum - const size_t txtsize = txtpak[0]+1; - - if (i >= gametic) // Don't copy old net commands - M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize); - txtpak += txtsize; - } - } - - neededtic = realend; - } - else - { - DEBFILE(va("frame not in bound: %u\n", neededtic)); - /*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart) - I_Error("Received an out of order PT_SERVERTICS packet!\n" - "Got tics %d-%d, needed tic %d\n\n" - "Please report this crash on the Master Board,\n" - "IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/ - } - break; - case PT_PING: - // Only accept PT_PING from the server. - if (node != servernode) - { - CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node); - if (server) - SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); - break; - } - - //Update client ping table from the server. - if (client) - { - UINT8 i; - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i]; - - servermaxping = (tic_t)netbuffer->u.pingtable[MAXPLAYERS]; - } - - break; - case PT_SERVERCFG: - break; - case PT_FILEFRAGMENT: - // Only accept PT_FILEFRAGMENT from the server. - if (node != servernode) - { - CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node); - if (server) - SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); - break; - } - if (client) - PT_FileFragment(); - break; - case PT_FILEACK: - if (server) - PT_FileAck(); - break; - case PT_FILERECEIVED: - if (server) - PT_FileReceived(); - break; - case PT_WILLRESENDGAMESTATE: - PT_WillResendGamestate(); - break; - case PT_SENDINGLUAFILE: - if (client) - CL_PrepareDownloadLuaFile(); - break; - default: - DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n", - netbuffer->packettype, node)); - } // end switch -} - -/** Handles all received packets, if any - * - * \todo Add details to this description (lol) - * - */ -static void GetPackets(void) -{ - SINT8 node; // The packet sender - - player_joining = false; - - while (HGetPacket()) - { - node = (SINT8)doomcom->remotenode; - - if (netbuffer->packettype == PT_CLIENTJOIN && server) - { - HandleConnect(node); - continue; - } - if (node == servernode && client && cl_mode != CL_SEARCHING) - { - if (netbuffer->packettype == PT_SERVERSHUTDOWN) - { - HandleShutdown(node); - continue; - } - if (netbuffer->packettype == PT_NODETIMEOUT) - { - HandleTimeout(node); - continue; - } - } - -#ifndef NONET - if (netbuffer->packettype == PT_SERVERINFO) - { - HandleServerInfo(node); - continue; - } -#endif - - if (netbuffer->packettype == PT_PLAYERINFO) - continue; // We do nothing with PLAYERINFO, that's for the MS browser. - - // Packet received from someone already playing - if (nodeingame[node]) - HandlePacketFromPlayer(node); - // Packet received from someone not playing - else - HandlePacketFromAwayNode(node); - } -} - -// -// NetUpdate -// Builds ticcmds for console player, -// sends out a packet -// -// no more use random generator, because at very first tic isn't yet synchronized -// Note: It is called consistAncy on purpose. -// -static INT16 Consistancy(void) -{ - INT32 i; - UINT32 ret = 0; -#ifdef MOBJCONSISTANCY - thinker_t *th; - mobj_t *mo; -#endif - - DEBFILE(va("TIC %u ", gametic)); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - ret ^= 0xCCCC; - else if (!players[i].mo); - else - { - ret += players[i].mo->x; - ret -= players[i].mo->y; - ret += players[i].powers[pw_shield]; - ret *= i+1; - } - } - // I give up - // Coop desynching enemies is painful - if (!G_PlatformGametype()) - ret += P_GetRandSeed(); - -#ifdef MOBJCONSISTANCY - if (gamestate == GS_LEVEL) - { - for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) - { - if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) - continue; - - mo = (mobj_t *)th; - - if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY)) - { - ret -= mo->type; - ret += mo->x; - ret -= mo->y; - ret += mo->z; - ret -= mo->momx; - ret += mo->momy; - ret -= mo->momz; - ret += mo->angle; - ret -= mo->flags; - ret += mo->flags2; - ret -= mo->eflags; - if (mo->target) - { - ret += mo->target->type; - ret -= mo->target->x; - ret += mo->target->y; - ret -= mo->target->z; - ret += mo->target->momx; - ret -= mo->target->momy; - ret += mo->target->momz; - ret -= mo->target->angle; - ret += mo->target->flags; - ret -= mo->target->flags2; - ret += mo->target->eflags; - ret -= mo->target->state - states; - ret += mo->target->tics; - ret -= mo->target->sprite; - ret += mo->target->frame; - } - else - ret ^= 0x3333; - if (mo->tracer && mo->tracer->type != MT_OVERLAY) - { - ret += mo->tracer->type; - ret -= mo->tracer->x; - ret += mo->tracer->y; - ret -= mo->tracer->z; - ret += mo->tracer->momx; - ret -= mo->tracer->momy; - ret += mo->tracer->momz; - ret -= mo->tracer->angle; - ret += mo->tracer->flags; - ret -= mo->tracer->flags2; - ret += mo->tracer->eflags; - ret -= mo->tracer->state - states; - ret += mo->tracer->tics; - ret -= mo->tracer->sprite; - ret += mo->tracer->frame; - } - else - ret ^= 0xAAAA; - ret -= mo->state - states; - ret += mo->tics; - ret -= mo->sprite; - ret += mo->frame; - } - } - } -#endif - - DEBFILE(va("Consistancy = %u\n", (ret & 0xFFFF))); - - return (INT16)(ret & 0xFFFF); -} - -// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_BASICKEEPALIVE -// used during wipes to tell the server that a node is still connected -static void CL_SendClientKeepAlive(void) -{ - netbuffer->packettype = PT_BASICKEEPALIVE; - - HSendPacket(servernode, false, 0, 0); -} - -static void SV_SendServerKeepAlive(void) -{ - INT32 n; - - for (n = 1; n < MAXNETNODES; n++) - { - if (nodeingame[n]) - { - netbuffer->packettype = PT_BASICKEEPALIVE; - HSendPacket(n, false, 0, 0); - } - } -} - -// send the client packet to the server -static void CL_SendClientCmd(void) -{ - size_t packetsize = 0; - boolean mis = false; - - netbuffer->packettype = PT_CLIENTCMD; - - if (cl_packetmissed) - { - netbuffer->packettype = PT_CLIENTMIS; - mis = true; - } - - netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX); - netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX); - - if (gamestate == GS_WAITINGPLAYERS) - { - // Send PT_NODEKEEPALIVE packet - netbuffer->packettype = (mis ? PT_NODEKEEPALIVEMIS : PT_NODEKEEPALIVE); - packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16); - HSendPacket(servernode, false, 0, packetsize); - } - else if (gamestate != GS_NULL && (addedtogame || dedicated)) - { - packetsize = sizeof (clientcmd_pak); - G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1); - netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]); - - // Send a special packet with 2 cmd for splitscreen - if (splitscreen || botingame) - { - netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD); - packetsize = sizeof (client2cmd_pak); - G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1); - } - - HSendPacket(servernode, false, 0, packetsize); - } - - if (cl_mode == CL_CONNECTED || dedicated) - { - // Send extra data if needed - if (localtextcmd[0]) - { - netbuffer->packettype = PT_TEXTCMD; - M_Memcpy(netbuffer->u.textcmd, localtextcmd, localtextcmd[0]+1); - // All extra data have been sent - if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail... - localtextcmd[0] = 0; - } - - // Send extra data if needed for player 2 (splitscreen) - if (localtextcmd2[0]) - { - netbuffer->packettype = PT_TEXTCMD2; - M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1); - // All extra data have been sent - if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail... - localtextcmd2[0] = 0; - } - } -} - -// send the server packet -// send tic from firstticstosend to maketic-1 -static void SV_SendTics(void) -{ - tic_t realfirsttic, lasttictosend, i; - UINT32 n; - INT32 j; - size_t packsize; - UINT8 *bufpos; - UINT8 *ntextcmd; - - // send to all client but not to me - // for each node create a packet with x tics and send it - // x is computed using supposedtics[n], max packet size and maketic - for (n = 1; n < MAXNETNODES; n++) - if (nodeingame[n]) - { - // assert supposedtics[n]>=nettics[n] - realfirsttic = supposedtics[n]; - lasttictosend = min(maketic, nettics[n] + CLIENTBACKUPTICS); - - if (realfirsttic >= lasttictosend) - { - // well we have sent all tics we will so use extrabandwidth - // to resent packet that are supposed lost (this is necessary since lost - // packet detection work when we have received packet with firsttic > neededtic - // (getpacket servertics case) - DEBFILE(va("Nothing to send node %u mak=%u sup=%u net=%u \n", - n, maketic, supposedtics[n], nettics[n])); - realfirsttic = nettics[n]; - if (realfirsttic >= lasttictosend || (I_GetTime() + n)&3) - // all tic are ok - continue; - DEBFILE(va("Sent %d anyway\n", realfirsttic)); - } - if (realfirsttic < firstticstosend) - realfirsttic = firstticstosend; - - // compute the length of the packet and cut it if too large - packsize = BASESERVERTICSSIZE; - for (i = realfirsttic; i < lasttictosend; i++) - { - packsize += sizeof (ticcmd_t) * doomcom->numslots; - packsize += TotalTextCmdPerTic(i); - - if (packsize > software_MAXPACKETLENGTH) - { - DEBFILE(va("packet too large (%s) at tic %d (should be from %d to %d)\n", - sizeu1(packsize), i, realfirsttic, lasttictosend)); - lasttictosend = i; - - // too bad: too much player have send extradata and there is too - // much data in one tic. - // To avoid it put the data on the next tic. (see getpacket - // textcmd case) but when numplayer changes the computation can be different - if (lasttictosend == realfirsttic) - { - if (packsize > MAXPACKETLENGTH) - I_Error("Too many players: can't send %s data for %d players to node %d\n" - "Well sorry nobody is perfect....\n", - sizeu1(packsize), doomcom->numslots, n); - else - { - lasttictosend++; // send it anyway! - DEBFILE("sending it anyway\n"); - } - } - break; - } - } - - // Send the tics - netbuffer->packettype = PT_SERVERTICS; - netbuffer->u.serverpak.starttic = realfirsttic; - netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic); - netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots); - bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds; - - for (i = realfirsttic; i < lasttictosend; i++) - { - bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t)); - } - - // add textcmds - for (i = realfirsttic; i < lasttictosend; i++) - { - ntextcmd = bufpos++; - *ntextcmd = 0; - for (j = 0; j < MAXPLAYERS; j++) - { - UINT8 *textcmd = D_GetExistingTextcmd(i, j); - INT32 size = textcmd ? textcmd[0] : 0; - - if ((!j || playeringame[j]) && size) - { - (*ntextcmd)++; - WRITEUINT8(bufpos, j); - M_Memcpy(bufpos, textcmd, size + 1); - bufpos += size + 1; - } - } - } - packsize = bufpos - (UINT8 *)&(netbuffer->u); - - HSendPacket(n, false, 0, packsize); - // when tic are too large, only one tic is sent so don't go backward! - if (lasttictosend-doomcom->extratics > realfirsttic) - supposedtics[n] = lasttictosend-doomcom->extratics; - else - supposedtics[n] = lasttictosend; - if (supposedtics[n] < nettics[n]) supposedtics[n] = nettics[n]; - } - // node 0 is me! - supposedtics[0] = maketic; -} - -// -// TryRunTics -// -static void Local_Maketic(INT32 realtics) -{ - I_OsPolling(); // I_Getevent - D_ProcessEvents(); // menu responder, cons responder, - // game responder calls HU_Responder, AM_Responder, - // and G_MapEventsToControls - if (!dedicated) rendergametic = gametic; - // translate inputs (keyboard/mouse/joystick) into game controls - G_BuildTiccmd(&localcmds, realtics, 1); - if (splitscreen || botingame) - G_BuildTiccmd(&localcmds2, realtics, 2); - - localcmds.angleturn |= TICCMD_RECEIVED; - localcmds2.angleturn |= TICCMD_RECEIVED; -} - -// create missed tic -static void SV_Maketic(void) -{ - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - - // We didn't receive this tic - if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0) - { - ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i]; - ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i]; - - if (players[i].quittime) - { - // Copy the angle/aiming from the previous tic - // and empty the other inputs - memset(ticcmd, 0, sizeof(netcmds[0][0])); - ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED; - ticcmd->aiming = prevticcmd->aiming; - } - else - { - DEBFILE(va("MISS tic%4d for player %d\n", maketic, i)); - // Copy the input from the previous tic - *ticcmd = *prevticcmd; - ticcmd->angleturn &= ~TICCMD_RECEIVED; - } - } - } - - // all tic are now proceed make the next - maketic++; -} - -boolean TryRunTics(tic_t realtics) -{ - boolean ticking; - - // the machine has lagged but it is not so bad - if (realtics > TICRATE/7) // FIXME: consistency failure!! - { - if (server) - realtics = 1; - else - realtics = TICRATE/7; - } - - if (singletics) - realtics = 1; - - if (realtics >= 1) - { - COM_BufTicker(); - if (mapchangepending) - D_MapChange(-1, 0, ultimatemode, false, 2, false, fromlevelselect); // finish the map change - } - - NetUpdate(); - - if (demoplayback) - { - neededtic = gametic + realtics; - // start a game after a demo - maketic += realtics; - firstticstosend = maketic; - tictoclear = firstticstosend; - } - - GetPackets(); - -#ifdef DEBUGFILE - if (debugfile && (realtics || neededtic > gametic)) - { - //SoM: 3/30/2000: Need long INT32 in the format string for args 4 & 5. - //Shut up stupid warning! - fprintf(debugfile, "------------ Tryruntic: REAL:%d NEED:%d GAME:%d LOAD: %d\n", - realtics, neededtic, gametic, debugload); - debugload = 100000; - } -#endif - - ticking = neededtic > gametic; - - if (ticking) - { - if (realtics) - hu_stopped = false; - } - - if (player_joining) - { - if (realtics) - hu_stopped = true; - return false; - } - - if (ticking) - { - if (advancedemo) - { - if (timedemo_quit) - COM_ImmedExecute("quit"); - else - D_StartTitle(); - } - else - // run the count * tics - while (neededtic > gametic) - { - boolean update_stats = !(paused || P_AutoPause()); - - DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic)); - - if (update_stats) - PS_START_TIMING(ps_tictime); - - G_Ticker((gametic % NEWTICRATERATIO) == 0); - ExtraDataTicker(); - gametic++; - consistancy[gametic%BACKUPTICS] = Consistancy(); - - if (update_stats) - { - PS_STOP_TIMING(ps_tictime); - PS_UpdateTickStats(); - } - - // Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame. - if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value) - break; - } - } - else - { - if (realtics) - hu_stopped = true; - } - - return ticking; -} - -/* -Ping Update except better: -We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out. -If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave. -*/ - -static INT32 pingtimeout[MAXPLAYERS]; - -static inline void PingUpdate(void) -{ - INT32 i; - boolean laggers[MAXPLAYERS]; - UINT8 numlaggers = 0; - memset(laggers, 0, sizeof(boolean) * MAXPLAYERS); - - netbuffer->packettype = PT_PING; - - //check for ping limit breakage. - if (cv_maxping.value) - { - for (i = 1; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].quittime - && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value)) - { - if (players[i].jointime > 30 * TICRATE) - laggers[i] = true; - numlaggers++; - } - else - pingtimeout[i] = 0; - } - - //kick lagging players... unless everyone but the server's ping sucks. - //in that case, it is probably the server's fault. - if (numlaggers < D_NumPlayers() - 1) - { - for (i = 1; i < MAXPLAYERS; i++) - { - if (playeringame[i] && laggers[i]) - { - pingtimeout[i]++; - // ok your net has been bad for too long, you deserve to die. - if (pingtimeout[i] > cv_pingtimeout.value) - { - pingtimeout[i] = 0; - SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY); - } - } - /* - you aren't lagging, - but you aren't free yet. - In case you'll keep spiking, - we just make the timer go back down. (Very unstable net must still get kicked). - */ - else - pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1); - } - } - } - - //make the ping packet and clear server data for next one - for (i = 0; i < MAXPLAYERS; i++) - { - netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount; - //server takes a snapshot of the real ping for display. - //otherwise, pings fluctuate a lot and would be odd to look at. - playerpingtable[i] = realpingtable[i] / pingmeasurecount; - realpingtable[i] = 0; //Reset each as we go. - } - - // send the server's maxping as last element of our ping table. This is useful to let us know when we're about to get kicked. - netbuffer->u.pingtable[MAXPLAYERS] = cv_maxping.value; - - //send out our ping packets - for (i = 0; i < MAXNETNODES; i++) - if (nodeingame[i]) - HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1)); - - pingmeasurecount = 1; //Reset count -} - -static tic_t gametime = 0; - -static void UpdatePingTable(void) -{ - INT32 i; - - if (server) - { - if (netgame && !(gametime % 35)) // update once per second. - PingUpdate(); - // update node latency values so we can take an average later. - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && playernode[i] != UINT8_MAX) - realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i])); - pingmeasurecount++; - } -} - -// Handle timeouts to prevent definitive freezes from happenning -static void HandleNodeTimeouts(void) -{ - INT32 i; - - if (server) - { - for (i = 1; i < MAXNETNODES; i++) - if (nodeingame[i] && freezetimeout[i] < I_GetTime()) - Net_ConnectionTimeout(i); - - // In case the cvar value was lowered - if (joindelay) - joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE); - } -} - -// Keep the network alive while not advancing tics! -void NetKeepAlive(void) -{ - tic_t nowtime; - INT32 realtics; - - nowtime = I_GetTime(); - realtics = nowtime - gametime; - - // return if there's no time passed since the last call - if (realtics <= 0) // nothing new to update - return; - - UpdatePingTable(); - - GetPackets(); - -#ifdef MASTERSERVER - MasterClient_Ticker(); -#endif - - if (client) - { - // send keep alive - CL_SendClientKeepAlive(); - // No need to check for resynch because we aren't running any tics - } - else - { - SV_SendServerKeepAlive(); - } - - // No else because no tics are being run and we can't resynch during this - - Net_AckTicker(); - HandleNodeTimeouts(); - FileSendTicker(); -} - -void NetUpdate(void) -{ - static tic_t resptime = 0; - tic_t nowtime; - INT32 i; - INT32 realtics; - - nowtime = I_GetTime(); - realtics = nowtime - gametime; - - if (realtics <= 0) // nothing new to update - return; - - if (realtics > 5) - { - if (server) - realtics = 1; - else - realtics = 5; - } - - if (server && dedicated && gamestate == GS_LEVEL) - { - const tic_t dedicatedidletime = cv_dedicatedidletime.value * TICRATE; - static tic_t dedicatedidletimeprev = 0; - static tic_t dedicatedidle = 0; - - if (dedicatedidletime > 0) - { - for (i = 1; i < MAXNETNODES; ++i) - if (nodeingame[i]) - { - if (dedicatedidle >= dedicatedidletime) - { - CONS_Printf("DEDICATED: Awakening from idle (Node %d detected...)\n", i); - dedicatedidle = 0; - } - break; - } - - if (i == MAXNETNODES) - { - if (leveltime == 2) - { - // On next tick... - dedicatedidle = dedicatedidletime-1; - } - else if (dedicatedidle >= dedicatedidletime) - { - if (D_GetExistingTextcmd(gametic, 0) || D_GetExistingTextcmd(gametic+1, 0)) - { - CONS_Printf("DEDICATED: Awakening from idle (Netxcmd detected...)\n"); - dedicatedidle = 0; - } - else - { - realtics = 0; - } - } - else if ((dedicatedidle += realtics) >= dedicatedidletime) - { - const char *idlereason = "at round start"; - if (leveltime > 3) - idlereason = va("for %d seconds", dedicatedidle/TICRATE); - - CONS_Printf("DEDICATED: No nodes %s, idling...\n", idlereason); - realtics = 0; - dedicatedidle = dedicatedidletime; - } - } - } - else - { - if (dedicatedidletimeprev > 0 && dedicatedidle >= dedicatedidletimeprev) - { - CONS_Printf("DEDICATED: Awakening from idle (Idle disabled...)\n"); - } - dedicatedidle = 0; - } - - dedicatedidletimeprev = dedicatedidletime; - } - - gametime = nowtime; - - UpdatePingTable(); - - if (client) - maketic = neededtic; - - Local_Maketic(realtics); // make local tic, and call menu? - - if (server) - CL_SendClientCmd(); // send it - - GetPackets(); // get packet from client or from server - - // client send the command after a receive of the server - // the server send before because in single player is beter - -#ifdef MASTERSERVER - MasterClient_Ticker(); // Acking the Master Server -#endif - - if (client) - { -#ifndef NONET - // If the client just finished redownloading the game state, load it - if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND) - CL_ReloadReceivedSavegame(); -#endif - - CL_SendClientCmd(); // Send tic cmd - hu_redownloadinggamestate = cl_redownloadinggamestate; - } - else - { - if (!demoplayback && realtics > 0) - { - INT32 counts; - - hu_redownloadinggamestate = false; - - // Don't erase tics not acknowledged - counts = realtics; - - firstticstosend = gametic; - for (i = 0; i < MAXNETNODES; i++) - { - if (!nodeingame[i]) - continue; - if (nettics[i] < firstticstosend) - firstticstosend = nettics[i]; - if (maketic + counts >= nettics[i] + (BACKUPTICS - TICRATE)) - Net_ConnectionTimeout(i); - } - - if (maketic + counts >= firstticstosend + BACKUPTICS) - counts = firstticstosend+BACKUPTICS-maketic-1; - - for (i = 0; i < counts; i++) - SV_Maketic(); // Create missed tics and increment maketic - - for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged - D_Clearticcmd(tictoclear); // Clear the maketic the new tic - - SV_SendTics(); - - neededtic = maketic; // The server is a client too - } - } - - Net_AckTicker(); - HandleNodeTimeouts(); - - nowtime /= NEWTICRATERATIO; - - if (nowtime > resptime) - { - resptime = nowtime; -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - M_Ticker(); -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif - CON_Ticker(); - } - - FileSendTicker(); -} - -/** Returns the number of players playing. - * \return Number of players. Can be zero if we're running a ::dedicated - * server. - * \author Graue - */ -INT32 D_NumPlayers(void) -{ - INT32 num = 0, ix; - for (ix = 0; ix < MAXPLAYERS; ix++) - if (playeringame[ix]) - num++; - return num; -} - -/** Similar to the above, but counts only bots. - * Purpose is to remove bots from both the player count and the - * max player count on the server view -*/ -INT32 D_NumBots(void) -{ - INT32 num = 0, ix; - for (ix = 0; ix < MAXPLAYERS; ix++) - if (playeringame[ix] && players[ix].bot) - num++; - return num; -} - -tic_t GetLag(INT32 node) -{ - return gametic - nettics[node]; -} - -void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest) -{ -#ifdef NOMD5 - (void)buffer; - (void)len; - (void)salt; - memset(dest, 0, 16); -#else - char tmpbuf[256]; - const size_t sl = strlen(salt); - - if (len > 256-sl) - len = 256-sl; - - memcpy(tmpbuf, buffer, len); - memmove(&tmpbuf[len], salt, sl); - //strcpy(&tmpbuf[len], salt); - len += strlen(salt); - if (len < 256) - memset(&tmpbuf[len],0,256-len); - - // Yes, we intentionally md5 the ENTIRE buffer regardless of size... - md5_buffer(tmpbuf, 256, dest); -#endif -} diff --git a/src/d_main.c b/src/d_main.c index 22a676255..80907a013 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -34,7 +34,7 @@ #include "doomdef.h" #include "am_map.h" #include "console.h" -#include "d_net.h" +#include "netcode/d_net.h" #include "f_finale.h" #include "g_game.h" #include "hu_stuff.h" @@ -56,11 +56,11 @@ #include "w_wad.h" #include "z_zone.h" #include "d_main.h" -#include "d_netfil.h" +#include "netcode/d_netfil.h" #include "m_cheat.h" #include "y_inter.h" #include "p_local.h" // chasecam -#include "mserv.h" // ms_RoomId +#include "netcode/mserv.h" // ms_RoomId #include "m_misc.h" // screenshot functionality #include "deh_tables.h" // Dehacked list test #include "m_cond.h" // condition initialization @@ -981,6 +981,7 @@ void D_StartTitle(void) emeralds = 0; memset(&luabanks, 0, sizeof(luabanks)); lastmaploaded = 0; + pickedchar = R_SkinAvailable(cv_defaultskin.string); // In case someone exits out at the same time they start a time attack run, // reset modeattacking @@ -1626,6 +1627,8 @@ void D_SRB2Main(void) autostart = true; } + pickedchar = R_SkinAvailable(cv_defaultskin.string); + // user settings come before "+" parameters. if (dedicated) COM_ImmedExecute(va("exec \"%s"PATHSEP"adedserv.cfg\"\n", srb2home)); @@ -1737,14 +1740,15 @@ void D_SRB2Main(void) // Prevent warping to nonexistent levels if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR) I_Error("Could not warp to %s (map not found)\n", G_BuildMapName(pstartmap)); - // Prevent warping to locked levels - // ... unless you're in a dedicated server. Yes, technically this means you can view any level by - // running a dedicated server and joining it yourself, but that's better than making dedicated server's - // lives hell. - else if (!dedicated && M_MapLocked(pstartmap, serverGamedata)) - I_Error("You need to unlock this level before you can warp to it!\n"); else { + if (M_CampaignWarpIsCheat(gametype, pstartmap, serverGamedata)) + { + // If you're warping via command line, you know what you're doing. + // No need to I_Error over this. + G_SetUsedCheats(false); + } + D_MapChange(pstartmap, gametype, ultimatemode, true, 0, false, false); } } diff --git a/src/deh_lua.c b/src/deh_lua.c index 1b177eb25..0b789547b 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -277,8 +277,8 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) } else if (fastncmp("MTF_", word, 4)) { p = word+4; - for (i = 0; i < 4; i++) - if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) { + for (i = 0; MAPTHINGFLAG_LIST[i]; i++) + if (fastcmp(p, MAPTHINGFLAG_LIST[i])) { CacheAndPushConstant(L, word, ((lua_Integer)1< \t"\1", // \2 extern const char *const MOBJEFLAG_LIST[]; -extern const char *const MAPTHINGFLAG_LIST[4]; +extern const char *const MAPTHINGFLAG_LIST[]; extern const char *const PLAYERFLAG_LIST[]; extern const char *const GAMETYPERULE_LIST[]; extern const char *const ML_LIST[]; // Linedef flags diff --git a/src/doomdata.h b/src/doomdata.h index 4c5bdefaf..276e03297 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -62,6 +62,10 @@ enum #define MTF_AMBUSH 8 // Do not use bit five or after, as they are used for object z-offsets. +// Unless it's exclusive to UDMF. + +// Flag to use Z as absolute spawn height, ignoring the floor and ceiling. +#define MTF_ABSOLUTEZ 16 #if defined(_MSC_VER) #pragma pack(1) @@ -211,6 +215,7 @@ typedef struct UINT8 extrainfo; taglist_t tags; fixed_t scale; + fixed_t spritexscale, spriteyscale; INT32 args[NUMMAPTHINGARGS]; char *stringargs[NUMMAPTHINGSTRINGARGS]; struct mobj_s *mobj; diff --git a/src/doomdef.h b/src/doomdef.h index f82b0a4cd..b382d0ecb 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -56,7 +56,6 @@ #endif #ifdef _WINDOWS -#define NONET #if !defined (HWRENDER) && !defined (NOHW) #define HWRENDER #endif @@ -269,8 +268,7 @@ typedef enum // Desaturated SKINCOLOR_AETHER, SKINCOLOR_SLATE, - SKINCOLOR_METEORITE, - SKINCOLOR_MERCURY, + SKINCOLOR_MOONSTONE, SKINCOLOR_BLUEBELL, SKINCOLOR_PINK, SKINCOLOR_ROSEWOOD, @@ -307,10 +305,10 @@ typedef enum SKINCOLOR_COPPER, SKINCOLOR_APRICOT, SKINCOLOR_ORANGE, - SKINCOLOR_PUMPKIN, SKINCOLOR_RUST, - SKINCOLOR_GOLD, + SKINCOLOR_TANGERINE, SKINCOLOR_TOPAZ, + SKINCOLOR_GOLD, SKINCOLOR_SANDY, SKINCOLOR_GOLDENROD, SKINCOLOR_YELLOW, @@ -320,20 +318,21 @@ typedef enum SKINCOLOR_LIME, SKINCOLOR_PERIDOT, SKINCOLOR_APPLE, + SKINCOLOR_HEADLIGHT, SKINCOLOR_CHARTREUSE, SKINCOLOR_GREEN, SKINCOLOR_FOREST, SKINCOLOR_SHAMROCK, SKINCOLOR_JADE, - SKINCOLOR_HEADLIGHT, SKINCOLOR_MINT, SKINCOLOR_MASTER, SKINCOLOR_EMERALD, - SKINCOLOR_BOTTLE, SKINCOLOR_SEAFOAM, SKINCOLOR_ISLAND, + SKINCOLOR_BOTTLE, SKINCOLOR_AQUA, SKINCOLOR_TEAL, + SKINCOLOR_OCEAN, SKINCOLOR_WAVE, SKINCOLOR_CYAN, SKINCOLOR_TURQUOISE, @@ -359,7 +358,7 @@ typedef enum SKINCOLOR_NOBLE, SKINCOLOR_FUCHSIA, SKINCOLOR_BUBBLEGUM, - SKINCOLOR_CRYSTAL, + SKINCOLOR_SIBERITE, SKINCOLOR_MAGENTA, SKINCOLOR_NEON, SKINCOLOR_VIOLET, @@ -724,7 +723,7 @@ extern int /// Maintain compatibility with older 2.2 demos #define OLD22DEMOCOMPAT -#if defined (HAVE_CURL) && ! defined (NONET) +#ifdef HAVE_CURL #define MASTERSERVER #else #undef UPDATE_ALERT diff --git a/src/doomstat.h b/src/doomstat.h index a812cc304..6a2d6acf0 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -249,6 +249,7 @@ extern textprompt_t *textprompts[MAX_PROMPTS]; // For the Custom Exit linedef. extern INT16 nextmapoverride; extern UINT8 skipstats; +extern INT16 nextgametype; extern UINT32 ssspheres; // Total # of spheres in a level @@ -633,7 +634,7 @@ extern boolean singletics; // Netgame stuff // ============= -#include "d_clisrv.h" +#include "netcode/d_clisrv.h" extern consvar_t cv_timetic; // display high resolution timer extern consvar_t cv_powerupdisplay; // display powerups diff --git a/src/doomtype.h b/src/doomtype.h index 617fd4d47..4070e346a 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -113,6 +113,15 @@ int endswith (const char *base, const char *tag); #define HAVE_DOSSTR_FUNCS #endif +#if defined (__APPLE__) + #define SRB2_HAVE_STRLCPY +#elif defined (__GLIBC_PREREQ) + // glibc 2.38: added strlcpy and strlcat to _DEFAULT_SOURCE + #if __GLIBC_PREREQ(2, 38) + #define SRB2_HAVE_STRLCPY + #endif +#endif + #ifndef HAVE_DOSSTR_FUNCS int strupr(char *n); // from dosstr.c int strlwr(char *n); // from dosstr.c @@ -120,7 +129,7 @@ int strlwr(char *n); // from dosstr.c #include // for size_t -#ifndef __APPLE__ +#ifndef SRB2_HAVE_STRLCPY size_t strlcat(char *dst, const char *src, size_t siz); size_t strlcpy(char *dst, const char *src, size_t siz); #endif diff --git a/src/dummy/i_net.c b/src/dummy/i_net.c index f6e642022..4c30dc767 100644 --- a/src/dummy/i_net.c +++ b/src/dummy/i_net.c @@ -1,4 +1,4 @@ -#include "../i_net.h" +#include "../netcode/i_net.h" boolean I_InitNetwork(void) { diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c index 125d2e8ae..70e1ef4ec 100644 --- a/src/dummy/i_system.c +++ b/src/dummy/i_system.c @@ -14,13 +14,18 @@ size_t I_GetFreeMem(size_t *total) return 0; } -void I_Sleep(UINT32 ms){} +void I_Sleep(UINT32 ms) +{ + (void)ms; +} -precise_t I_GetPreciseTime(void) { +precise_t I_GetPreciseTime(void) +{ return 0; } -UINT64 I_GetPrecisePrecision(void) { +UINT64 I_GetPrecisePrecision(void) +{ return 1000000; } @@ -182,10 +187,12 @@ const char *I_ClipboardPaste(void) size_t I_GetRandomBytes(char *destination, size_t amount) { + (void)destination; + (void)amount; return 0; } -void I_RegisterSysCommands(void) {} +void I_RegisterSysCommands(void){} void I_GetCursorPosition(INT32 *x, INT32 *y) { diff --git a/src/f_finale.c b/src/f_finale.c index d03795dc4..7afed27d0 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -14,7 +14,7 @@ #include "doomdef.h" #include "doomstat.h" #include "d_main.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" #include "f_finale.h" #include "g_game.h" #include "hu_stuff.h" @@ -1062,12 +1062,14 @@ static const char *credits[] = { "\"Golden\"", "Vivian \"toaster\" Grannell", "Julio \"Chaos Zero 64\" Guir", + "\"Hanicef\"", "\"Hannu_Hanhi\"", // For many OpenGL performance improvements! "Kepa \"Nev3r\" Iceta", "Thomas \"Shadow Hog\" Igoe", "Iestyn \"Monster Iestyn\" Jealous", "\"Kaito Sinclaire\"", "\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog + "\"katsy\"", "Ronald \"Furyhunter\" Kinard", // The SDL2 port "\"Lat'\"", // SRB2-CHAT, the chat window from Kart "\"LZA\"", @@ -1090,6 +1092,7 @@ static const char *credits[] = { "Ben \"Cue\" Woodford", "Lachlan \"Lach\" Wright", "Marco \"mazmazz\" Zafra", + "\"Zwip-Zwap Zapony\"", "", "\1Art", "Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D: @@ -1197,6 +1200,7 @@ static const char *credits[] = { "FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak "Kart Krew", "Alex \"MistaED\" Fuller", + "Howard Drossin", // Virtual Sonic - Sonic & Knuckles Theme "Pascal \"CodeImp\" vd Heiden", // Doom Builder developer "Randi Heit ()", // For their MSPaint sprite that we nicked "Simon \"sirjuddington\" Judd", // SLADE developer @@ -1644,7 +1648,7 @@ void F_GameEvaluationTicker(void) sparklloop = 0; } - if (finalecount == 5*TICRATE) + if (G_CoopGametype() && !stagefailed && finalecount == 5*TICRATE) { serverGamedata->timesBeaten++; clientGamedata->timesBeaten++; @@ -2256,7 +2260,7 @@ void F_InitMenuPresValues(void) curfadevalue = 16; curbgcolor = -1; curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed; - curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed; + curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed; curbghide = (gamestate == GS_TIMEATTACK) ? false : true; curhidepics = hidetitlepics; diff --git a/src/filesrch.c b/src/filesrch.c index 9ee64f5ba..313f286e1 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -26,7 +26,7 @@ #include #include "filesrch.h" -#include "d_netfil.h" +#include "netcode/d_netfil.h" #include "m_misc.h" #include "z_zone.h" #include "m_menu.h" // Addons_option_Onchange diff --git a/src/filesrch.h b/src/filesrch.h index 59ef5269b..a934c48d6 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -5,7 +5,7 @@ #define __FILESRCH_H__ #include "doomdef.h" -#include "d_netfil.h" +#include "netcode/d_netfil.h" #include "m_menu.h" // MAXSTRINGLENGTH #include "w_wad.h" diff --git a/src/g_demo.c b/src/g_demo.c index adb8e891d..cb168dfd9 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -15,7 +15,7 @@ #include "console.h" #include "d_main.h" #include "d_player.h" -#include "d_clisrv.h" +#include "netcode/d_clisrv.h" #include "p_setup.h" #include "i_time.h" #include "i_system.h" @@ -39,7 +39,7 @@ #include "v_video.h" #include "lua_hook.h" #include "md5.h" // demo checksums -#include "d_netfil.h" // G_CheckDemoExtraFiles +#include "netcode/d_netfil.h" // G_CheckDemoExtraFiles boolean timingdemo; // if true, exit with report on completion boolean nodrawers; // for comparative timing purposes @@ -1500,8 +1500,12 @@ void G_BeginRecording(void) demo_p += 16; // Color - for (i = 0; i < MAXCOLORNAME && cv_playercolor.string[i]; i++) - name[i] = cv_playercolor.string[i]; + UINT16 skincolor = players[0].skincolor; + if (skincolor >= numskincolors) + skincolor = SKINCOLOR_NONE; + const char *skincolor_name = skincolors[skincolor].name; + for (i = 0; i < MAXCOLORNAME && skincolor_name[i]; i++) + name[i] = skincolor_name[i]; for (; i < MAXCOLORNAME; i++) name[i] = '\0'; M_Memcpy(demo_p,name,MAXCOLORNAME); @@ -1617,7 +1621,7 @@ void G_BeginMetal(void) oldmetal.angle = mo->angle>>24; } -static void G_LoadDemoExtraFiles(UINT8 **pp) +static void G_LoadDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version) { UINT16 totalfiles; char filename[MAX_WADPATH]; @@ -1627,6 +1631,12 @@ static void G_LoadDemoExtraFiles(UINT8 **pp) boolean alreadyloaded; UINT16 i, j; + if (this_demo_version < 0x0010) + { + // demo has no file list + return; + } + totalfiles = READUINT16((*pp)); for (i = 0; i < totalfiles; ++i) { @@ -1686,12 +1696,12 @@ static void G_LoadDemoExtraFiles(UINT8 **pp) } } -static void G_SkipDemoExtraFiles(UINT8 **pp) +static void G_SkipDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version) { UINT16 totalfiles; UINT16 i; - if (demoversion < 0x0010) + if (this_demo_version < 0x0010) { // demo has no file list return; @@ -1707,7 +1717,7 @@ static void G_SkipDemoExtraFiles(UINT8 **pp) // G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's. // Enabling quick prevents filesystem checks to see if needed files are available to load. -static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick) +static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick, UINT16 this_demo_version) { UINT16 totalfiles, filesloaded, nmusfilecount; char filename[MAX_WADPATH]; @@ -1717,7 +1727,7 @@ static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick) UINT16 i, j; UINT8 error = DFILE_ERROR_NONE; - if (demoversion < 0x0010) + if (this_demo_version < 0x0010) { // demo has no file list return DFILE_ERROR_NONE; @@ -1816,10 +1826,9 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) UINT8 *buffer,*p; UINT8 flags; UINT32 oldtime, newtime, oldscore, newscore; - UINT16 oldrings, newrings, oldversion; + UINT16 oldrings, newrings, oldversion, newversion; size_t bufsize ATTRUNUSED; UINT8 c; - UINT16 s ATTRUNUSED; UINT8 aflags = 0; // load the new file @@ -1835,15 +1844,15 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) I_Assert(c == VERSION); c = READUINT8(p); // SUBVERSION I_Assert(c == SUBVERSION); - s = READUINT16(p); - I_Assert(s >= 0x000c); + newversion = READUINT16(p); + I_Assert(newversion == DEMOVERSION); p += 16; // demo checksum I_Assert(!memcmp(p, "PLAY", 4)); p += 4; // PLAY p += 2; // gamemap p += 16; // map md5 flags = READUINT8(p); // demoflags - G_SkipDemoExtraFiles(&p); + G_SkipDemoExtraFiles(&p, newversion); aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK); I_Assert(aflags); if (flags & DF_RECORDATTACK) @@ -1882,16 +1891,9 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) p++; // VERSION p++; // SUBVERSION oldversion = READUINT16(p); - switch(oldversion) // demoversion + if (oldversion < 0x000c || oldversion > DEMOVERSION) { - case DEMOVERSION: // latest always supported - case 0x000f: // The previous demoversions also supported - case 0x000e: - case 0x000d: // all that changed between then and now was longer color name - case 0x000c: - break; - // too old, cannot support. - default: + // too old (or new), cannot support CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname); Z_Free(buffer); return UINT8_MAX; @@ -1909,7 +1911,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) p += 2; // gamemap p += 16; // mapmd5 flags = READUINT8(p); - G_SkipDemoExtraFiles(&p); + G_SkipDemoExtraFiles(&p, oldversion); if (!(flags & aflags)) { CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname); @@ -1964,14 +1966,11 @@ void G_DoPlayDemo(char *defdemoname) UINT8 i; lumpnum_t l; char skin[17],color[MAXCOLORNAME+1],*n,*pdemoname; - UINT8 version,subversion,charability,charability2,thrustfactor,accelstart,acceleration,cnamelen; + UINT8 version,subversion,charability,charability2,thrustfactor,accelstart,acceleration; pflags_t pflags; UINT32 randseed, followitem; fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight; char msg[1024]; -#ifdef OLD22DEMOCOMPAT - boolean use_old_demo_vars = false; -#endif skin[16] = '\0'; color[MAXCOLORNAME] = '\0'; @@ -2030,23 +2029,13 @@ void G_DoPlayDemo(char *defdemoname) subversion = READUINT8(demo_p); demoversion = READUINT16(demo_p); demo_forwardmove_rng = (demoversion < 0x0010); - switch(demoversion) - { - case 0x000f: - case 0x000d: - case 0x000e: - case DEMOVERSION: // latest always supported - cnamelen = MAXCOLORNAME; - break; #ifdef OLD22DEMOCOMPAT - // all that changed between then and now was longer color name - case 0x000c: - cnamelen = 16; - use_old_demo_vars = true; - break; + if (demoversion < 0x000c || demoversion > DEMOVERSION) +#else + if (demoversion < 0x000d || demoversion > DEMOVERSION) #endif - // too old, cannot support. - default: + { + // too old (or new), cannot support snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); @@ -2074,26 +2063,22 @@ void G_DoPlayDemo(char *defdemoname) demoflags = READUINT8(demo_p); - if (demoversion < 0x0010) - { - ; // Don't do anything with files. - } - else if (titledemo) + if (titledemo) { // Titledemos should always play and ought to always be compatible with whatever wadlist is running. - G_SkipDemoExtraFiles(&demo_p); + G_SkipDemoExtraFiles(&demo_p, demoversion); } else if (demofileoverride == DFILE_OVERRIDE_LOAD) { - G_LoadDemoExtraFiles(&demo_p); + G_LoadDemoExtraFiles(&demo_p, demoversion); } else if (demofileoverride == DFILE_OVERRIDE_SKIP) { - G_SkipDemoExtraFiles(&demo_p); + G_SkipDemoExtraFiles(&demo_p, demoversion); } else { - UINT8 error = G_CheckDemoExtraFiles(&demo_p, false); + UINT8 error = G_CheckDemoExtraFiles(&demo_p, false, demoversion); if (error) { @@ -2177,8 +2162,8 @@ void G_DoPlayDemo(char *defdemoname) demo_p += 16; // Color - M_Memcpy(color,demo_p,cnamelen); - demo_p += cnamelen; + M_Memcpy(color, demo_p, (demoversion < 0x000d) ? 16 : MAXCOLORNAME); + demo_p += (demoversion < 0x000d) ? 16 : MAXCOLORNAME; charability = READUINT8(demo_p); charability2 = READUINT8(demo_p); @@ -2214,7 +2199,7 @@ void G_DoPlayDemo(char *defdemoname) // net var data #ifdef OLD22DEMOCOMPAT - if (use_old_demo_vars) + if (demoversion < 0x000d) CV_LoadOldDemoVars(&demo_p); else #endif @@ -2262,7 +2247,6 @@ void G_DoPlayDemo(char *defdemoname) players[0].skincolor = i; break; } - CV_StealthSetValue(&cv_playercolor, players[0].skincolor); if (players[0].mo) { players[0].mo->color = players[0].skincolor; @@ -2302,6 +2286,13 @@ UINT8 G_CheckDemoForError(char *defdemoname) { lumpnum_t l; char *n,*pdemoname; + UINT16 our_demo_version; + + if (titledemo) + { + // Don't do anything with files for these. + return DFILE_ERROR_NONE; + } n = defdemoname+strlen(defdemoname); while (*n != '/' && *n != '\\' && n != defdemoname) @@ -2340,21 +2331,14 @@ UINT8 G_CheckDemoForError(char *defdemoname) demo_p++; // version demo_p++; // subversion - demoversion = READUINT16(demo_p); - demo_forwardmove_rng = (demoversion < 0x0010); - switch(demoversion) - { - case 0x000d: - case 0x000e: - case 0x000f: - case DEMOVERSION: // latest always supported - break; + our_demo_version = READUINT16(demo_p); #ifdef OLD22DEMOCOMPAT - case 0x000c: - break; + if (our_demo_version < 0x000c || our_demo_version > DEMOVERSION) +#else + if (our_demo_version < 0x000d || our_demo_version > DEMOVERSION) #endif - // too old, cannot support. - default: + { + // too old (or new), cannot support return DFILE_ERROR_NOTDEMO; } demo_p += 16; // demo checksum @@ -2368,17 +2352,7 @@ UINT8 G_CheckDemoForError(char *defdemoname) demo_p++; // demoflags - // Don't do anything with files. - if (demoversion < 0x0010) - { - return DFILE_ERROR_NONE; - } - else if (titledemo) - { - return DFILE_ERROR_NONE; - } - - return G_CheckDemoExtraFiles(&demo_p, true); + return G_CheckDemoExtraFiles(&demo_p, true, our_demo_version); } void G_AddGhost(char *defdemoname) @@ -2386,7 +2360,6 @@ void G_AddGhost(char *defdemoname) INT32 i; lumpnum_t l; char name[17],skin[17],color[MAXCOLORNAME+1],*n,*pdemoname,md5[16]; - UINT8 cnamelen; demoghost *gh; UINT8 flags, subversion; UINT8 *buffer,*p; @@ -2438,20 +2411,9 @@ void G_AddGhost(char *defdemoname) p++; // VERSION subversion = READUINT8(p); // SUBVERSION ghostversion = READUINT16(p); - switch(ghostversion) + if (ghostversion < 0x000c || ghostversion > DEMOVERSION) { - case 0x000f: - case 0x000d: - case 0x000e: - case DEMOVERSION: // latest always supported - cnamelen = MAXCOLORNAME; - break; - // all that changed between then and now was longer color name - case 0x000c: - cnamelen = 16; - break; - // too old, cannot support. - default: + // too old (or new), cannot support CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo version incompatible.\n"), pdemoname); Z_Free(pdemoname); Z_Free(buffer); @@ -2487,7 +2449,7 @@ void G_AddGhost(char *defdemoname) return; } - G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. + G_SkipDemoExtraFiles(&p, ghostversion); // Don't wanna modify the file list for ghosts. switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { @@ -2514,8 +2476,8 @@ void G_AddGhost(char *defdemoname) p += 16; // Color - M_Memcpy(color, p,cnamelen); - p += cnamelen; + M_Memcpy(color, p, (ghostversion < 0x000d) ? 16 : MAXCOLORNAME); + p += (ghostversion < 0x000d) ? 16 : MAXCOLORNAME; // Ghosts do not have a player structure to put this in. p++; // charability @@ -2698,16 +2660,9 @@ void G_DoPlayMetal(void) metal_p++; // VERSION metal_p++; // SUBVERSION metalversion = READUINT16(metal_p); - switch(metalversion) + if (metalversion < 0x000c || metalversion > DEMOVERSION) { - case DEMOVERSION: // latest always supported - case 0x000f: - case 0x000e: // There are checks wheter the momentum is from older demo versions or not - case 0x000d: // all that changed between then and now was longer color name - case 0x000c: - break; - // too old, cannot support. - default: + // too old (or new), cannot support CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version incompatible.\n")); Z_Free(metalbuffer); return; diff --git a/src/g_game.c b/src/g_game.c index 04b1ffe4d..fae311694 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -15,7 +15,8 @@ #include "console.h" #include "d_main.h" #include "d_player.h" -#include "d_clisrv.h" +#include "netcode/d_clisrv.h" +#include "netcode/net_command.h" #include "f_finale.h" #include "p_setup.h" #include "p_saveg.h" @@ -55,6 +56,8 @@ gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; +INT32 pickedchar; + boolean botingame; UINT8 botskin; UINT16 botcolor; @@ -156,6 +159,7 @@ textprompt_t *textprompts[MAX_PROMPTS]; INT16 nextmapoverride; UINT8 skipstats; +INT16 nextgametype = -1; // Pointers to each CTF flag mobj_t *redflag; @@ -315,6 +319,8 @@ consvar_t cv_consolechat = CVAR_INIT ("chatmode", "Window", CV_SAVE, consolechat // Pause game upon window losing focus consvar_t cv_pauseifunfocused = CVAR_INIT ("pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL); +consvar_t cv_instantretry = CVAR_INIT ("instantretry", "No", CV_SAVE, CV_YesNo, NULL); + consvar_t cv_crosshair = CVAR_INIT ("crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL); consvar_t cv_crosshair2 = CVAR_INIT ("crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL); consvar_t cv_invertmouse = CVAR_INIT ("invertmouse", "Off", CV_SAVE, CV_OnOff, NULL); @@ -2079,7 +2085,7 @@ boolean G_Responder(event_t *ev) if (gameaction == ga_nothing && !singledemo && ((demoplayback && !modeattacking && !titledemo) || gamestate == GS_TITLESCREEN)) { - if (ev->type == ev_keydown && ev->key != 301 && !(gamestate == GS_TITLESCREEN && finalecount < TICRATE)) + if (ev->type == ev_keydown && ev->key != 301 && !(gamestate == GS_TITLESCREEN && finalecount < (cv_tutorialprompt.value ? TICRATE : 0))) { M_StartControlPanel(); return true; @@ -2137,7 +2143,7 @@ boolean G_Responder(event_t *ev) if (! netgame) F_StartGameEvaluation(); else if (server || IsPlayerAdmin(consoleplayer)) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); return true; } } @@ -2172,9 +2178,9 @@ boolean G_Responder(event_t *ev) if (menuactive || pausedelay < 0 || leveltime < 2) return true; - if (pausedelay < 1+(NEWTICRATE/2)) + if (!cv_instantretry.value && pausedelay < 1+(NEWTICRATE/2)) pausedelay = 1+(NEWTICRATE/2); - else if (++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3)) + else if (cv_instantretry.value || ++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3)) { G_SetModeAttackRetryFlag(); return true; @@ -2748,25 +2754,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) //if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there //p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent - // Check to make sure their color didn't change somehow... - if (G_GametypeHasTeams()) - { - if (p->ctfteam == 1 && p->skincolor != skincolor_redteam) - { - if (p == &players[consoleplayer]) - CV_SetValue(&cv_playercolor, skincolor_redteam); - else if (p == &players[secondarydisplayplayer]) - CV_SetValue(&cv_playercolor2, skincolor_redteam); - } - else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) - { - if (p == &players[consoleplayer]) - CV_SetValue(&cv_playercolor, skincolor_blueteam); - else if (p == &players[secondarydisplayplayer]) - CV_SetValue(&cv_playercolor2, skincolor_blueteam); - } - } - if (betweenmaps) return; @@ -3461,9 +3448,7 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] = }; // -// G_SetGametype -// -// Set a new gametype, also setting gametype rules accordingly. Yay! +// Sets a new gametype. // void G_SetGametype(INT16 gtype) { @@ -3861,7 +3846,7 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap) for (ix = 0; ix < NUMMAPS; ix++) if (mapheaderinfo[ix] && (mapheaderinfo[ix]->typeoflevel & tolflags) == tolflags && ix != pprevmap // Don't pick the same map. - && (dedicated || !M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps. + && (!M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps. ) okmaps[numokmaps++] = ix; @@ -4053,6 +4038,13 @@ static void G_DoCompleted(void) nextmap = 1100-1; // No infinite loop for you } + INT16 gametype_to_use; + + if (nextgametype >= 0 && nextgametype < gametypecount) + gametype_to_use = nextgametype; + else + gametype_to_use = gametype; + // If nextmap is actually going to get used, make sure it points to // a map of the proper gametype -- skip levels that don't support // the current gametype. (Helps avoid playing boss levels in Race, @@ -4061,8 +4053,8 @@ static void G_DoCompleted(void) { if (nextmap >= 0 && nextmap < NUMMAPS) { - register INT16 cm = nextmap; - UINT32 tolflag = G_TOLFlag(gametype); + INT16 cm = nextmap; + UINT32 tolflag = G_TOLFlag(gametype_to_use); UINT8 visitedmap[(NUMMAPS+7)/8]; memset(visitedmap, 0, sizeof (visitedmap)); @@ -4117,7 +4109,7 @@ static void G_DoCompleted(void) { token--; - if (!nextmapoverride) +// if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933) for (i = 0; i < 7; i++) if (!(emeralds & (1<= 0 && nextgametype < gametypecount) + gametype_to_use = nextgametype; + else + gametype_to_use = gametype; + if (gametyperules & GTR_CAMPAIGN) // don't reset player between maps - D_MapChange(nextmap+1, gametype, ultimatemode, false, 0, false, false); + D_MapChange(nextmap+1, gametype_to_use, ultimatemode, false, 0, false, false); else // resetplayer in match/chaos/tag/CTF/race for more equality - D_MapChange(nextmap+1, gametype, ultimatemode, true, 0, false, false); + D_MapChange(nextmap+1, gametype_to_use, ultimatemode, true, 0, false, false); + + nextgametype = -1; } gameaction = ga_nothing; @@ -4789,12 +4789,9 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) Z_Free(savebuffer); save_p = savebuffer = NULL; -// gameaction = ga_nothing; -// G_SetGamestate(GS_LEVEL); displayplayer = consoleplayer; multiplayer = splitscreen = false; -// G_DeferedInitNew(sk_medium, G_BuildMapName(1), 0, 0, 1); if (setsizeneeded) R_ExecuteSetViewSize(); @@ -4987,9 +4984,9 @@ cleanup: // Can be called by the startup code or the menu task, // consoleplayer, displayplayer, playeringame[] should be set. // -void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, boolean SSSG, boolean FLS) +void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 character, boolean SSSG, boolean FLS) { - UINT16 color = skins[pickedchar].prefcolor; + pickedchar = character; paused = false; if (demoplayback) @@ -5010,10 +5007,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b SplitScreen_OnChange(); } - color = skins[pickedchar].prefcolor; - SetPlayerSkinByNum(consoleplayer, pickedchar); - CV_StealthSet(&cv_skin, skins[pickedchar].name); - CV_StealthSetValue(&cv_playercolor, color); + SetPlayerSkinByNum(consoleplayer, character); if (mapname) D_MapChange(M_MapNumber(mapname[3], mapname[4]), gametype, pultmode, true, 1, false, FLS); @@ -5093,6 +5087,10 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean CV_StealthSetValue(&cv_itemfinder, 0); } + // Restore each player's skin if it was previously forced to be a specific one + // (Looks a bit silly, but it works.) + boolean reset_skin = netgame && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'; + // internal game map // well this check is useless because it is done before (d_netcmd.c::command_map_f) // but in case of for demos.... @@ -5120,6 +5118,9 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean automapactive = false; imcontinuing = false; + if (reset_skin) + D_SendPlayerConfig(); + // fetch saved data if available if (savedata.lives > 0) { diff --git a/src/g_game.h b/src/g_game.h index a8c285f79..4d86a0455 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -49,6 +49,8 @@ extern boolean promptactive; extern consvar_t cv_pauseifunfocused; +extern consvar_t cv_instantretry; + // used in game menu extern consvar_t cv_tutorialprompt; extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard; @@ -174,8 +176,7 @@ void G_SpawnPlayer(INT32 playernum); // Can be called by the startup code or M_Responder. // A normal game starts at map 1, but a warp test can start elsewhere -void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, - boolean SSSG, boolean FLS); +void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 character, boolean SSSG, boolean FLS); void G_DoLoadLevel(boolean resetplayer); void G_StartTitleCard(void); void G_PreLevelTitleCard(void); diff --git a/src/g_input.c b/src/g_input.c index 826dcecbd..fa30c1984 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -16,7 +16,7 @@ #include "g_input.h" #include "keys.h" #include "hu_stuff.h" // need HUFONT start & end -#include "d_net.h" +#include "netcode/d_net.h" #include "console.h" #define MAXMOUSESENSITIVITY 100 // sensitivity steps diff --git a/src/g_state.h b/src/g_state.h index 8f97930bb..4b07f3d90 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -53,9 +53,11 @@ typedef enum extern gamestate_t gamestate; extern UINT8 titlemapinaction; -extern UINT8 ultimatemode; // was sk_insane +extern UINT8 ultimatemode; extern gameaction_t gameaction; +extern INT32 pickedchar; + extern boolean botingame; extern UINT8 botskin; extern UINT16 botcolor; diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c index d1b84a5ee..dc0b5ee5b 100644 --- a/src/hardware/hw_batching.c +++ b/src/hardware/hw_batching.c @@ -42,10 +42,10 @@ int unsortedVertexArrayAllocSize = 65536; // Call HWR_RenderBatches to render all the collected geometry. void HWR_StartBatching(void) { - if (currently_batching) - I_Error("Repeat call to HWR_StartBatching without HWR_RenderBatches"); + if (currently_batching) + I_Error("Repeat call to HWR_StartBatching without HWR_RenderBatches"); - // init arrays if that has not been done yet + // init arrays if that has not been done yet if (!finalVertexArray) { finalVertexArray = malloc(finalVertexArrayAllocSize * sizeof(FOutVector)); @@ -55,7 +55,7 @@ void HWR_StartBatching(void) unsortedVertexArray = malloc(unsortedVertexArrayAllocSize * sizeof(FOutVector)); } - currently_batching = true; + currently_batching = true; } // This replaces the direct calls to pfnSetTexture in cases where batching is available. diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 568161425..090ddc53c 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -29,7 +29,7 @@ #include "../r_patch.h" #include "../r_picformats.h" #include "../r_bsp.h" -#include "../d_clisrv.h" +#include "../netcode/d_clisrv.h" #include "../w_wad.h" #include "../z_zone.h" #include "../r_splats.h" @@ -1699,7 +1699,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) { blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent; - Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1; + Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255)); } if (gl_frontsector->numlights) @@ -1822,7 +1822,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) { blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent; - Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1; + Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255)); } if (gl_backsector->numlights) @@ -3095,7 +3095,7 @@ static void HWR_Subsector(size_t num) false, *rover->bottomheight, *gl_frontsector->lightlist[light].lightlevel, - rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, + max(0, min(rover->alpha, 255)), rover->master->frontsector, HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent), false, *gl_frontsector->lightlist[light].extra_colormap); } @@ -3141,7 +3141,7 @@ static void HWR_Subsector(size_t num) true, *rover->topheight, *gl_frontsector->lightlist[light].lightlevel, - rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, + max(0, min(rover->alpha, 255)), rover->master->frontsector, HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent), false, *gl_frontsector->lightlist[light].extra_colormap); } @@ -3595,7 +3595,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) return; } - floordiff = abs((flip < 0 ? thing->height : 0) + interp.z - groundz); + floordiff = abs((flip < 0 ? interp.height : 0) + interp.z - groundz); alpha = floordiff / (4*FRACUNIT) + 75; if (alpha >= 255) return; @@ -3606,9 +3606,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) HWR_GetPatch(gpatch); scalemul = FixedMul(FRACUNIT - floordiff/640, scale); - scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height); - if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs - scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale)); + scalemul = FixedMul(scalemul, (interp.radius*2) / gpatch->height); fscale = FIXED_TO_FLOAT(scalemul); fx = FIXED_TO_FLOAT(interp.x); @@ -3720,7 +3718,7 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts if (P_MobjFlip(spr->mobj) == -1) { - basey = FIXED_TO_FLOAT(interp.z + spr->mobj->height); + basey = FIXED_TO_FLOAT(interp.z + interp.height); } else { @@ -4055,32 +4053,32 @@ static void HWR_DrawBoundingBox(gl_vissprite_t *vis) // repeat this 4 times (overhead) // // - // 17 20 21 11 - // 16 15 14 10 - // 27 22 *--* 07 12 + // 15 16 17 09 + // 14 13 12 08 + // 23 18 *--* 07 10 // | | - // 26 23 *--* 06 13 - // 24 00 01 02 - // 25 05 04 03 + // 22 19 *--* 06 11 + // 20 00 01 02 + // 21 05 04 03 // - v[000].x = v[005].x = v[015].x = v[016].x = v[017].x = v[020].x = - v[022].x = v[023].x = v[024].x = v[025].x = v[026].x = v[027].x = vis->x1; // west + v[ 0].x = v[ 5].x = v[13].x = v[14].x = v[15].x = v[16].x = + v[18].x = v[19].x = v[20].x = v[21].x = v[22].x = v[23].x = vis->x1; // west - v[001].x = v[002].x = v[003].x = v[004].x = v[006].x = v[007].x = - v[010].x = v[011].x = v[012].x = v[013].x = v[014].x = v[021].x = vis->x2; // east + v[ 1].x = v[ 2].x = v[ 3].x = v[ 4].x = v[ 6].x = v[ 7].x = + v[ 8].x = v[ 9].x = v[10].x = v[11].x = v[12].x = v[17].x = vis->x2; // east - v[000].z = v[001].z = v[002].z = v[003].z = v[004].z = v[005].z = - v[006].z = v[013].z = v[023].z = v[024].z = v[025].z = v[026].z = vis->z1; // south + v[ 0].z = v[ 1].z = v[ 2].z = v[ 3].z = v[ 4].z = v[ 5].z = + v[ 6].z = v[11].z = v[19].z = v[20].z = v[21].z = v[22].z = vis->z1; // south - v[007].z = v[010].z = v[011].z = v[012].z = v[014].z = v[015].z = - v[016].z = v[017].z = v[020].z = v[021].z = v[022].z = v[027].z = vis->z2; // north + v[ 7].z = v[ 8].z = v[ 9].z = v[10].z = v[12].z = v[13].z = + v[14].z = v[15].z = v[16].z = v[17].z = v[18].z = v[23].z = vis->z2; // north - v[000].y = v[001].y = v[002].y = v[006].y = v[007].y = v[010].y = - v[014].y = v[015].y = v[016].y = v[022].y = v[023].y = v[024].y = vis->gz; // bottom + v[ 0].y = v[ 1].y = v[ 2].y = v[ 6].y = v[ 7].y = v[ 8].y = + v[12].y = v[13].y = v[14].y = v[18].y = v[19].y = v[20].y = vis->gz; // bottom - v[003].y = v[004].y = v[005].y = v[011].y = v[012].y = v[013].y = - v[017].y = v[020].y = v[021].y = v[025].y = v[026].y = v[027].y = vis->gzt; // top + v[ 3].y = v[ 4].y = v[ 5].y = v[ 9].y = v[10].y = v[11].y = + v[15].y = v[16].y = v[17].y = v[21].y = v[22].y = v[23].y = vis->gzt; // top Surf.PolyColor = V_GetColor(R_GetBoundingBoxColor(vis->mobj)); @@ -4143,14 +4141,11 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) float xscale, yscale; float xoffset, yoffset; float leftoffset, topoffset; - float scale = spr->scale; float zoffset = (P_MobjFlip(spr->mobj) * 0.05f); pslope_t *splatslope = NULL; INT32 i; renderflags_t renderflags = spr->renderflags; - if (renderflags & RF_SHADOWEFFECTS) - scale *= spr->shadowscale; if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD) angle = spr->mobj->angle; @@ -5326,7 +5321,7 @@ static void HWR_ProjectSprite(mobj_t *thing) } groundz = R_GetShadowZ(thing, NULL); - floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? caster->height : 0) + casterinterp.z - groundz); + floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? casterinterp.height : 0) + casterinterp.z - groundz); shadowheight = FIXED_TO_FLOAT(floordiff); shadowscale = FIXED_TO_FLOAT(FixedMul(FRACUNIT - floordiff/640, casterinterp.scale)); @@ -5378,7 +5373,7 @@ static void HWR_ProjectSprite(mobj_t *thing) if (vflip) { - gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); + gz = FIXED_TO_FLOAT(interp.z + interp.height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); } else @@ -5684,7 +5679,6 @@ static void HWR_ProjectBoundingBox(mobj_t *thing) gl_vissprite_t *vis; float tr_x, tr_y; float tz; - float rad; if (!thing) return; @@ -5719,15 +5713,13 @@ static void HWR_ProjectBoundingBox(mobj_t *thing) tr_x += gl_viewx; tr_y += gl_viewy; - rad = FIXED_TO_FLOAT(thing->radius); - vis = HWR_NewVisSprite(); - vis->x1 = tr_x - rad; - vis->x2 = tr_x + rad; - vis->z1 = tr_y - rad; - vis->z2 = tr_y + rad; + vis->x1 = tr_x - FIXED_TO_FLOAT(interp.radius); + vis->x2 = tr_x + FIXED_TO_FLOAT(interp.radius); + vis->z1 = tr_y - FIXED_TO_FLOAT(interp.radius); + vis->z2 = tr_y + FIXED_TO_FLOAT(interp.radius); vis->gz = FIXED_TO_FLOAT(interp.z); - vis->gzt = vis->gz + FIXED_TO_FLOAT(thing->height); + vis->gzt = vis->gz + FIXED_TO_FLOAT(interp.height); vis->mobj = thing; vis->precip = false; diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 6d1f12664..3e080105c 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -486,7 +486,7 @@ void HWR_InitModels(void) size_t i; INT32 s; FILE *f; - char name[24], filename[32]; + char name[26], filename[32]; float scale, offset; size_t prefixlen; @@ -585,7 +585,7 @@ modelfound: void HWR_AddPlayerModel(int skin) // For skins that were added after startup { FILE *f; - char name[24], filename[32]; + char name[26], filename[32]; float scale, offset; size_t prefixlen; @@ -644,7 +644,7 @@ void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after s // name[24] is used to check for names in the models.dat file that match with sprites or player skins // sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long // PLAYERMODELPREFIX is 6 characters long - char name[24], filename[32]; + char name[26], filename[32]; float scale, offset; if (nomd2s) @@ -1582,7 +1582,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) p.y = FIXED_TO_FLOAT(interp.y)+md2->offset; if (flip) - p.z = FIXED_TO_FLOAT(interp.z + spr->mobj->height); + p.z = FIXED_TO_FLOAT(interp.z + interp.height); else p.z = FIXED_TO_FLOAT(interp.z); @@ -1618,8 +1618,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) p.roll = true; // rotation pivot - p.centerx = FIXED_TO_FLOAT(spr->mobj->radius / 2); - p.centery = FIXED_TO_FLOAT(spr->mobj->height / 2); + p.centerx = FIXED_TO_FLOAT(interp.radius / 2); + p.centery = FIXED_TO_FLOAT(interp.height / 2); // rotation axes relative to camera p.rollx = FIXED_TO_FLOAT(FINECOSINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT)); diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c index b69bce0e2..4b6bce6f7 100644 --- a/src/hardware/hw_model.c +++ b/src/hardware/hw_model.c @@ -672,6 +672,9 @@ void GeneratePolygonNormals(model_t *model, int ztag) for (k = 0; k < mesh->numTriangles; k++) { + /// TODO: normalize vectors + (void)vertices; + (void)polyNormals; // Vector::Normal(vertices, polyNormals); vertices += 3 * 3; polyNormals++; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 091e2b2fb..eb2915d80 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -19,7 +19,9 @@ #include "m_cond.h" // emblems #include "m_misc.h" // word jumping -#include "d_clisrv.h" +#include "netcode/d_clisrv.h" +#include "netcode/net_command.h" +#include "netcode/gamestate.h" #include "g_game.h" #include "g_input.h" @@ -175,14 +177,12 @@ static huddrawlist_h luahuddrawlist_scores; static tic_t resynch_ticker = 0; -#ifndef NONET // just after static void Command_Say_f(void); static void Command_Sayto_f(void); static void Command_Sayteam_f(void); static void Command_CSay_f(void); static void Got_Saycmd(UINT8 **p, INT32 playernum); -#endif void HU_LoadGraphics(void) { @@ -327,13 +327,11 @@ void HU_LoadGraphics(void) // void HU_Init(void) { -#ifndef NONET COM_AddCommand("say", Command_Say_f, COM_LUA); COM_AddCommand("sayto", Command_Sayto_f, COM_LUA); COM_AddCommand("sayteam", Command_Sayteam_f, COM_LUA); COM_AddCommand("csay", Command_CSay_f, COM_LUA); RegisterNetXCmd(XD_SAY, Got_Saycmd); -#endif // set shift translation table shiftxform = english_shiftxform; @@ -363,8 +361,6 @@ void HU_Start(void) // EXECUTION //====================================================================== -#ifndef NONET - // EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM static UINT32 chat_nummsg_log = 0; @@ -412,11 +408,9 @@ static void HU_removeChatText_Log(void) } chat_nummsg_log--; // lost 1 msg. } -#endif void HU_AddChatText(const char *text, boolean playsound) { -#ifndef NONET if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat. S_StartSound(NULL, sfx_radio); // reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game. @@ -438,14 +432,8 @@ void HU_AddChatText(const char *text, boolean playsound) CONS_Printf("%s\n", text); else // if we aren't, still save the message to log.txt CON_LogMessage(va("%s\n", text)); -#else - (void)playsound; - CONS_Printf("%s\n", text); -#endif } -#ifndef NONET - /** Runs a say command, sending an ::XD_SAY message. * A say command consists of a signed 8-bit integer for the target, an * unsigned 8-bit flag variable, and then the message itself. @@ -865,8 +853,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) #endif } -#endif - // // void HU_Ticker(void) @@ -882,7 +868,6 @@ void HU_Ticker(void) else hu_showscores = false; -#ifndef NONET if (chat_on) { // count down the scroll timer. @@ -910,7 +895,6 @@ void HU_Ticker(void) HU_removeChatText_Mini(); } } -#endif if (cechotimer > 0) --cechotimer; @@ -918,8 +902,6 @@ void HU_Ticker(void) resynch_ticker++; } -#ifndef NONET - static boolean teamtalk = false; static boolean justscrolleddown; static boolean justscrolledup; @@ -1027,8 +1009,6 @@ static void HU_sendChatMessage(void) } } -#endif - void HU_clearChatChars(void) { memset(w_chat, '\0', sizeof(w_chat)); @@ -1043,9 +1023,7 @@ void HU_clearChatChars(void) // boolean HU_Responder(event_t *ev) { -#ifndef NONET INT32 c=0; -#endif if (ev->type != ev_keydown) return false; @@ -1072,7 +1050,6 @@ boolean HU_Responder(event_t *ev) return false; }*/ //We don't actually care about that unless we get splitscreen netgames. :V -#ifndef NONET c = (INT32)ev->key; if (!chat_on) @@ -1222,7 +1199,6 @@ boolean HU_Responder(event_t *ev) return true; } -#endif return false; } @@ -1232,8 +1208,6 @@ boolean HU_Responder(event_t *ev) // HEADS UP DRAWING //====================================================================== -#ifndef NONET - // Precompile a wordwrapped string to any given width. // This is a muuuch better method than V_WORDWRAP. // again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day. @@ -1813,7 +1787,6 @@ static void HU_DrawChat_Old(void) if (hu_tick < 4) V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true); } -#endif // Draw crosshairs at the exact center of the view. // In splitscreen, crosshairs are stretched vertically to compensate for V_PERPLAYER squishing them. @@ -1953,7 +1926,6 @@ static void HU_DrawDemoInfo(void) // void HU_Drawer(void) { -#ifndef NONET // draw chat string plus cursor if (chat_on) { @@ -1970,7 +1942,6 @@ void HU_Drawer(void) if (!OLDCHAT && cv_consolechat.value < 2 && netgame) // Don't display minimized chat if you set the mode to Window (Hidden) HU_drawMiniChat(); // draw messages in a cool fashion. } -#endif if (cechotimer) HU_DrawCEcho(); @@ -2025,7 +1996,7 @@ void HU_Drawer(void) V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text); } - if (modeattacking && pausedelay > 0 && !pausebreakkey) + if (modeattacking && pausedelay > 0 && !(pausebreakkey || cv_instantretry.value)) { INT32 strength = ((pausedelay - 1 - NEWTICRATE/2)*10)/(NEWTICRATE/3); INT32 y = hudinfo[HUD_LIVES].y - 13; diff --git a/src/i_time.c b/src/i_time.c index 2a22503f1..fae26abed 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -17,7 +17,7 @@ #include "command.h" #include "doomtype.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" #include "m_fixed.h" #include "i_system.h" diff --git a/src/info.c b/src/info.c index abcf4b499..36389d849 100644 --- a/src/info.c +++ b/src/info.c @@ -7194,7 +7194,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD1, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7220,7 +7220,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD2, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7246,7 +7246,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD3, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7272,7 +7272,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD4, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7298,7 +7298,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD5, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7324,7 +7324,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD6, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -7350,7 +7350,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_cgot, // deathsound EMERALD7, // speed 16*FRACUNIT, // radius - 32*FRACUNIT, // height + 24*FRACUNIT, // height 0, // display offset 16, // mass 0, // damage @@ -18344,7 +18344,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_bouncering, // mass @@ -18371,7 +18371,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_railring, // mass @@ -18425,7 +18425,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_automaticring, // mass @@ -18452,7 +18452,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_explosionring, // mass @@ -18479,7 +18479,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_scatterring, // mass @@ -18506,7 +18506,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_itemup, // deathsound 60*FRACUNIT, // speed - 24*FRACUNIT, // radius + 16*FRACUNIT, // radius 24*FRACUNIT, // height 0, // display offset pw_grenadering, // mass @@ -18535,7 +18535,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_bouncering, // mass 2*TICRATE, // damage @@ -18562,7 +18562,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_railring, // mass 2*TICRATE, // damage @@ -18589,7 +18589,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_automaticring, // mass 2*TICRATE, // damage @@ -18616,7 +18616,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_explosionring, // mass 2*TICRATE, // damage @@ -18643,7 +18643,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_scatterring, // mass 2*TICRATE, // damage @@ -18670,7 +18670,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = sfx_ncitem, // deathsound 60*FRACUNIT, // speed 24*FRACUNIT, // radius - 24*FRACUNIT, // height + 40*FRACUNIT, // height 0, // display offset pw_grenadering, // mass 2*TICRATE, // damage @@ -21584,10 +21584,9 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Black", {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1e, 0x1f, 0x1f}, SKINCOLOR_WHITE, 7, V_GRAYMAP, true}, // SKINCOLOR_BLACK // Desaturated - {"Aether", {0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x91, 0x91, 0x91, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xaf}, SKINCOLOR_GREY, 15, 0, true}, // SKINCOLOR_AETHER + {"Aether", {0x00, 0x00, 0x01, 0x01, 0x90, 0x90, 0x91, 0x91, 0x92, 0xaa, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xae}, SKINCOLOR_GREY, 15, 0, true}, // SKINCOLOR_AETHER {"Slate", {0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0xaa, 0xaa, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xad, 0xae, 0xaf}, SKINCOLOR_SILVER, 12, 0, true}, // SKINCOLOR_SLATE - {"Meteorite", { 0, 4, 8, 9, 11, 12, 14, 15, 171, 172, 173, 174, 175, 27, 29, 31}, SKINCOLOR_TOPAZ, 15, V_GRAYMAP, true}, // SKINCOLOR_METEORITE - {"Mercury", { 0, 3, 4, 7, 11, 12, 14, 15, 171, 172, 173, 155, 157, 159, 253, 254}, SKINCOLOR_ECRU, 15, V_AZUREMAP, true}, // SKINCOLOR_MERCURY + {"Moonstone", { 0, 4, 8, 9, 11, 12, 14, 15, 171, 172, 173, 174, 175, 27, 29, 31}, SKINCOLOR_TOPAZ, 15, V_GRAYMAP, true}, // SKINCOLOR_MOONSTONE {"Bluebell", {0x90, 0x91, 0x92, 0x93, 0x94, 0x94, 0x95, 0xac, 0xac, 0xad, 0xad, 0xa8, 0xa8, 0xa9, 0xfd, 0xfe}, SKINCOLOR_COPPER, 4, V_BLUEMAP, true}, // SKINCOLOR_BLUEBELL {"Pink", {0xd0, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0x2b, 0x2c, 0x2e}, SKINCOLOR_AZURE, 9, V_REDMAP, true}, // SKINCOLOR_PINK {"Rosewood", { 209, 210, 211, 212, 213, 214, 228, 230, 232, 234, 235, 237, 26, 27, 28, 29}, SKINCOLOR_SEPIA, 5, V_BROWNMAP, true}, // SKINCOLOR_ROSEWOOD @@ -21597,37 +21596,37 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Boulder", {0xde, 0xe0, 0xe1, 0xe4, 0xe7, 0xe9, 0xeb, 0xec, 0xed, 0xed, 0xed, 0x19, 0x19, 0x1b, 0x1d, 0x1e}, SKINCOLOR_KETCHUP, 0, V_BROWNMAP, true}, // SKINCOLOR_BOULDER {"Bronze", { 82, 84, 50, 51, 223, 228, 230, 232, 234, 236, 237, 238, 239, 239, 30, 31}, SKINCOLOR_VOLCANIC, 9, V_BROWNMAP, true}, // SKINCOLOR_BRONZE {"Sepia", { 88, 84, 85, 86, 224, 226, 228, 230, 232, 235, 236, 237, 238, 239, 28, 28}, SKINCOLOR_ROSEWOOD, 5, V_BROWNMAP, true}, // SKINCOLOR_SEPIA - {"Ecru", { 80, 83, 84, 85, 86, 242, 243, 245, 230, 232, 234, 236, 238, 239, 47, 47}, SKINCOLOR_MERCURY, 15, V_BROWNMAP, true}, // SKINCOLOR_ECRU + {"Ecru", { 80, 83, 84, 85, 86, 242, 243, 245, 230, 232, 234, 236, 238, 239, 47, 47}, SKINCOLOR_ARCTIC, 12, V_BROWNMAP, true}, // SKINCOLOR_ECRU {"Tan", {0x51, 0x51, 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0xf5, 0xf5, 0xf9, 0xf9, 0xed, 0xed}, SKINCOLOR_BROWN, 12, V_BROWNMAP, true}, // SKINCOLOR_TAN {"Beige", {0x54, 0x55, 0x56, 0x56, 0xf2, 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xed, 0xed}, SKINCOLOR_MOSS, 5, V_BROWNMAP, true}, // SKINCOLOR_BEIGE {"Rosebush", { 208, 216, 209, 85, 90, 91, 91, 92, 191, 93, 94, 107, 109, 110, 111, 111}, SKINCOLOR_EGGPLANT, 5, V_GREENMAP, true}, // SKINCOLOR_ROSEBUSH {"Moss", {0x58, 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5c, 0x5d, 0x5d, 0x5e, 0x5e, 0x5f, 0x5f}, SKINCOLOR_BEIGE, 13, V_GREENMAP, true}, // SKINCOLOR_MOSS {"Azure", {0x90, 0x90, 0x91, 0x91, 0xaa, 0xaa, 0xab, 0xab, 0xab, 0xac, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf}, SKINCOLOR_PINK, 5, V_AZUREMAP, true}, // SKINCOLOR_AZURE {"Eggplant", { 4, 8, 11, 11, 16, 195, 195, 195, 196, 186, 187, 187, 254, 254, 30, 31}, SKINCOLOR_ROSEBUSH, 5, V_PURPLEMAP, true}, // SKINCOLOR_EGGPLANT - {"Lavender", {0xc0, 0xc0, 0xc1, 0xc1, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7}, SKINCOLOR_HEADLIGHT, 8, V_PURPLEMAP, true}, // SKINCOLOR_LAVENDER + {"Lavender", {0xc0, 0xc0, 0xc1, 0xc1, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7}, SKINCOLOR_GOLD, 4, V_PURPLEMAP, true}, // SKINCOLOR_LAVENDER // Viv's vivid colours (toast 21/07/17) - // Tweaks & additions (Lach, sphere, Alice, MotorRoach 26/10/22) + // Tweaks & additions (Lach, Chrispy, sphere, Alice, MotorRoach & Saneko 26/10/22) {"Ruby", {0xb0, 0xb0, 0xc9, 0xca, 0xcc, 0x26, 0x27, 0x28, 0x29, 0x2a, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xfd}, SKINCOLOR_EMERALD, 10, V_REDMAP, true}, // SKINCOLOR_RUBY {"Cherry", { 202, 203, 204, 205, 206, 40, 41, 42, 43, 44, 186, 187, 28, 29, 30, 31}, SKINCOLOR_MIDNIGHT, 10, V_REDMAP, true}, // SKINCOLOR_CHERRY {"Salmon", {0xd0, 0xd0, 0xd1, 0xd2, 0x20, 0x21, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e}, SKINCOLOR_FOREST, 6, V_REDMAP, true}, // SKINCOLOR_SALMON - {"Pepper", { 210, 32, 33, 34, 35, 35, 36, 37, 38, 39, 41, 43, 45, 45, 46, 47}, SKINCOLOR_GREEN, 10, V_REDMAP, true}, // SKINCOLOR_PEPPER - {"Red", {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x47, 0x2e, 0x2f}, SKINCOLOR_MASTER, 8, V_REDMAP, true}, // SKINCOLOR_RED + {"Pepper", { 210, 32, 33, 34, 35, 35, 36, 37, 38, 39, 41, 43, 45, 45, 46, 47}, SKINCOLOR_MASTER, 8, V_REDMAP, true}, // SKINCOLOR_PEPPER + {"Red", {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x47, 0x2e, 0x2f}, SKINCOLOR_GREEN, 10, V_REDMAP, true}, // SKINCOLOR_RED {"Crimson", {0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x1f}, SKINCOLOR_ICY, 10, V_REDMAP, true}, // SKINCOLOR_CRIMSON {"Flame", {0x31, 0x32, 0x33, 0x36, 0x22, 0x22, 0x25, 0x25, 0x25, 0xcd, 0xcf, 0xcf, 0xc5, 0xc5, 0xc7, 0xc7}, SKINCOLOR_PURPLE, 8, V_REDMAP, true}, // SKINCOLOR_FLAME {"Garnet", { 0, 83, 50, 53, 34, 35, 37, 38, 39, 40, 42, 44, 45, 46, 47, 47}, SKINCOLOR_AQUAMARINE, 6, V_REDMAP, true}, // SKINCOLOR_GARNET {"Ketchup", {0x48, 0x49, 0x40, 0x33, 0x34, 0x36, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2b, 0x2c, 0x47, 0x2e, 0x2f}, SKINCOLOR_BOULDER, 8, V_REDMAP, true}, // SKINCOLOR_KETCHUP {"Peachy", {0xd0, 0x30, 0x31, 0x31, 0x32, 0x32, 0xdc, 0xdc, 0xdc, 0xd3, 0xd4, 0xd4, 0xcc, 0xcd, 0xce, 0xcf}, SKINCOLOR_TEAL, 7, V_ROSYMAP, true}, // SKINCOLOR_PEACHY {"Quail", {0xd8, 0xd9, 0xdb, 0xdc, 0xde, 0xdf, 0xd5, 0xd5, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0x1d, 0x1f}, SKINCOLOR_WAVE, 5, V_BROWNMAP, true}, // SKINCOLOR_QUAIL - {"Foundation", { 80, 81, 82, 84, 219, 221, 221, 212, 213, 214, 215, 195, 196, 186, 187, 30}, SKINCOLOR_DREAM, 6, V_ORANGEMAP, true}, // SKINCOLOR_FOUNDATION + {"Foundation", { 80, 81, 82, 84, 219, 221, 221, 212, 213, 214, 215, 197, 186, 187, 187, 30}, SKINCOLOR_DREAM, 6, V_ORANGEMAP, true}, // SKINCOLOR_FOUNDATION {"Sunset", {0x51, 0x52, 0x40, 0x40, 0x34, 0x36, 0xd5, 0xd5, 0xd6, 0xd7, 0xcf, 0xcf, 0xc6, 0xc6, 0xc7, 0xfe}, SKINCOLOR_SAPPHIRE, 5, V_ORANGEMAP, true}, // SKINCOLOR_SUNSET {"Copper", {0x58, 0x54, 0x40, 0x34, 0x35, 0x38, 0x3a, 0x3c, 0x3d, 0x2a, 0x2b, 0x2c, 0x2c, 0xba, 0xba, 0xbb}, SKINCOLOR_BLUEBELL, 5, V_ORANGEMAP, true}, // SKINCOLOR_COPPER {"Apricot", {0x00, 0xd8, 0xd9, 0xda, 0xdb, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e}, SKINCOLOR_CYAN, 4, V_ORANGEMAP, true}, // SKINCOLOR_APRICOT {"Orange", { 49, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 42, 44, 45, 46, 46}, SKINCOLOR_BLUE, 4, V_ORANGEMAP, true}, // SKINCOLOR_ORANGE - {"Pumpkin", { 51, 52, 53, 54, 56, 58, 59, 59, 61, 61, 63, 45, 46, 47, 47, 31}, SKINCOLOR_ARCTIC, 12, V_ORANGEMAP, true}, // SKINCOLOR_PUMPKIN {"Rust", {0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3f, 0x2c, 0x2d, 0x47, 0x2e, 0x2f, 0x2f}, SKINCOLOR_YOGURT, 8, V_ORANGEMAP, true}, // SKINCOLOR_RUST - {"Gold", {0x51, 0x51, 0x54, 0x54, 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x3f, 0x2d, 0x2e, 0x2f, 0x2f}, SKINCOLOR_MAUVE, 8, V_YELLOWMAP, true}, // SKINCOLOR_GOLD - {"Topaz", { 0, 81, 83, 73, 74, 74, 65, 52, 53, 54, 56, 58, 60, 42, 43, 45}, SKINCOLOR_METEORITE, 10, V_YELLOWMAP, true}, // SKINCOLOR_TOPAZ + {"Tangerine", { 81, 83, 64, 64, 51, 52, 53, 54, 56, 58, 60, 61, 63, 45, 46, 47}, SKINCOLOR_OCEAN, 12, V_ORANGEMAP, true}, // SKINCOLOR_TANGERINE + {"Topaz", { 0, 81, 83, 73, 74, 74, 65, 52, 53, 54, 56, 58, 60, 42, 43, 45}, SKINCOLOR_MOONSTONE, 10, V_YELLOWMAP, true}, // SKINCOLOR_TOPAZ + {"Gold", {0x51, 0x51, 0x54, 0x54, 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x3f, 0x2d, 0x2e, 0x2f, 0x2f}, SKINCOLOR_LAVENDER, 10, V_YELLOWMAP, true}, // SKINCOLOR_GOLD {"Sandy", {0x53, 0x40, 0x41, 0x42, 0x43, 0xe6, 0xe9, 0xe9, 0xea, 0xec, 0xec, 0xc6, 0xc6, 0xc7, 0xc7, 0xfe}, SKINCOLOR_SKY, 8, V_YELLOWMAP, true}, // SKINCOLOR_SANDY {"Goldenrod", { 0, 80, 81, 81, 83, 73, 73, 64, 65, 66, 67, 68, 69, 62, 44, 45}, SKINCOLOR_MAJESTY, 8, V_YELLOWMAP, true}, // SKINCOLOR_GOLDENROD {"Yellow", {0x52, 0x53, 0x49, 0x49, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4e, 0x4f, 0xed}, SKINCOLOR_CORNFLOWER, 8, V_YELLOWMAP, true}, // SKINCOLOR_YELLOW @@ -21637,20 +21636,21 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Lime", {0x50, 0x51, 0x52, 0x53, 0x48, 0xbc, 0xbd, 0xbe, 0xbe, 0xbf, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_MAGENTA, 9, V_PERIDOTMAP, true}, // SKINCOLOR_LIME {"Peridot", {0x58, 0x58, 0xbc, 0xbc, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0x5e, 0x5e, 0x5f, 0x5f, 0x77, 0x77}, SKINCOLOR_COBALT, 2, V_PERIDOTMAP, true}, // SKINCOLOR_PERIDOT {"Apple", {0x49, 0x49, 0xbc, 0xbd, 0xbe, 0xbe, 0xbe, 0x67, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6d}, SKINCOLOR_RASPBERRY, 13, V_PERIDOTMAP, true}, // SKINCOLOR_APPLE + {"Headlight", { 0, 80, 81, 82, 73, 84, 64, 65, 91, 91, 124, 125, 126, 137, 138, 139}, SKINCOLOR_MAUVE, 8, V_YELLOWMAP, true}, // SKINCOLOR_HEADLIGHT {"Chartreuse", { 80, 82, 72, 73, 188, 188, 113, 114, 114, 125, 126, 137, 138, 139, 253, 254}, SKINCOLOR_NOBLE, 9, V_PERIDOTMAP, true}, // SKINCOLOR_CHARTREUSE - {"Green", {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_PEPPER, 8, V_GREENMAP, true}, // SKINCOLOR_GREEN + {"Green", {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, SKINCOLOR_RED, 6, V_GREENMAP, true}, // SKINCOLOR_GREEN {"Forest", {0x65, 0x66, 0x67, 0x68, 0x69, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f}, SKINCOLOR_SALMON, 9, V_GREENMAP, true}, // SKINCOLOR_FOREST - {"Shamrock", {0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, SKINCOLOR_CRYSTAL, 10, V_GREENMAP, true}, // SKINCOLOR_SHAMROCK - {"Jade", { 128, 120, 121, 122, 122, 113, 114, 114, 115, 116, 117, 118, 119, 110, 111, 30}, SKINCOLOR_ROSY, 7, V_GREENMAP, true}, // SKINCOLOR_JADE - {"Headlight", { 0, 80, 81, 82, 73, 84, 64, 65, 91, 91, 124, 125, 126, 137, 138, 139}, SKINCOLOR_LAVENDER, 10, V_YELLOWMAP, true}, // SKINCOLOR_HEADLIGHT + {"Shamrock", {0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, SKINCOLOR_SIBERITE, 10, V_GREENMAP, true}, // SKINCOLOR_SHAMROCK + {"Jade", { 128, 120, 121, 122, 122, 113, 114, 114, 115, 116, 117, 118, 119, 110, 111, 30}, SKINCOLOR_TAFFY, 10, V_GREENMAP, true}, // SKINCOLOR_JADE {"Mint", {0x00, 0x00, 0x58, 0x58, 0x59, 0x62, 0x62, 0x62, 0x64, 0x67, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a}, SKINCOLOR_VIOLET, 5, V_GREENMAP, true}, // SKINCOLOR_MINT - {"Master", { 0, 80, 88, 96, 112, 113, 99, 100, 124, 125, 126, 117, 107, 118, 119, 111}, SKINCOLOR_RED, 6, V_GREENMAP, true}, // SKINCOLOR_MASTER + {"Master", { 0, 80, 88, 96, 112, 113, 99, 100, 124, 125, 126, 117, 107, 118, 119, 111}, SKINCOLOR_PEPPER, 8, V_GREENMAP, true}, // SKINCOLOR_MASTER {"Emerald", { 80, 96, 112, 113, 114, 114, 125, 125, 126, 126, 137, 137, 138, 138, 139, 139}, SKINCOLOR_RUBY, 9, V_GREENMAP, true}, // SKINCOLOR_EMERALD - {"Bottle", { 0, 1, 3, 4, 5, 140, 141, 141, 124, 125, 126, 127, 118, 119, 111, 111}, SKINCOLOR_LATTE, 14, V_AQUAMAP, true}, // SKINCOLOR_BOTTLE {"Seafoam", {0x01, 0x58, 0x59, 0x5a, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a, 0x8b, 0xfd, 0xfd}, SKINCOLOR_PLUM, 6, V_AQUAMAP, true}, // SKINCOLOR_SEAFOAM {"Island", { 96, 97, 113, 113, 114, 124, 142, 136, 136, 150, 151, 153, 168, 168, 169, 169}, SKINCOLOR_GALAXY, 7, V_AQUAMAP, true}, // SKINCOLOR_ISLAND - {"Aqua", {0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x76, 0x77}, SKINCOLOR_TAFFY, 10, V_AQUAMAP, true}, // SKINCOLOR_AQUA + {"Bottle", { 0, 1, 3, 4, 5, 140, 141, 141, 124, 125, 126, 127, 118, 119, 111, 111}, SKINCOLOR_LATTE, 14, V_AQUAMAP, true}, // SKINCOLOR_BOTTLE + {"Aqua", {0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x76, 0x77}, SKINCOLOR_ROSY, 7, V_AQUAMAP, true}, // SKINCOLOR_AQUA {"Teal", {0x78, 0x78, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8a, 0x8a, 0x8a, 0x8a}, SKINCOLOR_PEACHY, 7, V_SKYMAP, true}, // SKINCOLOR_TEAL + {"Ocean", { 120, 121, 122, 122, 123, 141, 142, 142, 136, 137, 138, 138, 139, 139, 253, 253}, SKINCOLOR_TANGERINE, 4, V_AQUAMAP, true}, // SKINCOLOR_OCEAN {"Wave", {0x00, 0x78, 0x78, 0x79, 0x8d, 0x87, 0x88, 0x89, 0x89, 0xae, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfd}, SKINCOLOR_QUAIL, 5, V_SKYMAP, true}, // SKINCOLOR_WAVE {"Cyan", {0x80, 0x81, 0xff, 0xff, 0x83, 0x83, 0x8d, 0x8d, 0x8d, 0x8e, 0x7e, 0x7f, 0x76, 0x76, 0x77, 0x6e}, SKINCOLOR_APRICOT, 6, V_SKYMAP, true}, // SKINCOLOR_CYAN {"Turquoise", { 0, 120, 121, 122, 123, 141, 141, 135, 136, 136, 150, 153, 155, 157, 159, 253}, SKINCOLOR_SANGRIA, 12, V_SKYMAP, true}, // SKINCOLOR_TURQUOISE @@ -21661,12 +21661,12 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Dream", { 80, 208, 200, 200, 146, 146, 133, 134, 135, 136, 137, 138, 139, 139, 254, 254}, SKINCOLOR_FOUNDATION, 9, V_SKYMAP, true}, // SKINCOLOR_DREAM {"Icy", {0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0x83, 0x83, 0x86, 0x87, 0x95, 0x95, 0xad, 0xad, 0xae, 0xaf}, SKINCOLOR_CRIMSON, 0, V_SKYMAP, true}, // SKINCOLOR_ICY {"Daybreak", { 80, 81, 82, 72, 64, 9, 11, 171, 149, 150, 151, 153, 156, 157, 159, 253}, SKINCOLOR_EVENTIDE, 12, V_BLUEMAP, true}, // SKINCOLOR_DAYBREAK - {"Sapphire", {0x80, 0x82, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_SUNSET, 5, V_SKYMAP, true}, // SKINCOLOR_SAPPHIRE - {"Arctic", { 0, 1, 3, 4, 146, 146, 147, 148, 148, 149, 150, 153, 156, 159, 253, 254}, SKINCOLOR_PUMPKIN, 6, V_BLUEMAP, true}, // SKINCOLOR_ARCTIC + {"Sapphire", {0x80, 0x82, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_SUNSET, 5, V_BLUEMAP, true}, // SKINCOLOR_SAPPHIRE + {"Arctic", { 0, 1, 3, 4, 145, 146, 147, 148, 148, 149, 150, 153, 156, 159, 253, 254}, SKINCOLOR_ECRU, 15, V_BLUEMAP, true}, // SKINCOLOR_ARCTIC {"Cornflower", {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x9a, 0x9c, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e}, SKINCOLOR_YELLOW, 4, V_BLUEMAP, true}, // SKINCOLOR_CORNFLOWER {"Blue", {0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_ORANGE, 5, V_BLUEMAP, true}, // SKINCOLOR_BLUE {"Cobalt", { 145, 147, 149, 150, 151, 153, 154, 155, 156, 157, 158, 159, 253, 253, 254, 254}, SKINCOLOR_PERIDOT, 5, V_BLUEMAP, true}, // SKINCOLOR_COBALT - {"Midnight", { 171, 171, 172, 173, 173, 174, 155, 156, 157, 159, 253, 253, 254, 254, 31, 31}, SKINCOLOR_CHERRY, 10, V_GRAYMAP, true}, // SKINCOLOR_MIDNIGHT + {"Midnight", { 171, 171, 172, 173, 173, 174, 175, 157, 158, 159, 253, 253, 254, 254, 31, 31}, SKINCOLOR_CHERRY, 10, V_GRAYMAP, true}, // SKINCOLOR_MIDNIGHT {"Galaxy", { 160, 161, 162, 163, 164, 165, 166, 166, 154, 155, 156, 157, 159, 253, 254, 31}, SKINCOLOR_ISLAND, 7, V_PURPLEMAP, true}, // SKINCOLOR_GALAXY {"Vapor", {0x80, 0x81, 0x83, 0x86, 0x94, 0x94, 0xa3, 0xa3, 0xa4, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa9, 0xa9}, SKINCOLOR_LILAC, 4, V_SKYMAP, true}, // SKINCOLOR_VAPOR {"Dusk", {0x92, 0x93, 0x94, 0x94, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf, 0xa9, 0xa9, 0xfd, 0xfd}, SKINCOLOR_OLIVE, 0, V_BLUEMAP, true}, // SKINCOLOR_DUSK @@ -21676,21 +21676,21 @@ skincolor_t skincolors[MAXSKINCOLORS] = { {"Noble", { 144, 146, 147, 148, 149, 164, 164, 165, 166, 185, 186, 186, 187, 187, 28, 29}, SKINCOLOR_CHARTREUSE, 12, V_PURPLEMAP, true}, // SKINCOLOR_NOBLE {"Fuchsia", { 200, 201, 203, 204, 204, 183, 184, 184, 165, 166, 167, 168, 169, 159, 253, 254}, SKINCOLOR_LEMON, 10, V_PURPLEMAP, true}, // SKINCOLOR_FUCHSIA {"Bubblegum", { 0, 208, 208, 176, 177, 178, 179, 180, 181, 182, 164, 166, 167, 168, 169, 253}, SKINCOLOR_PASTEL, 8, V_MAGENTAMAP, true}, // SKINCOLOR_BUBBLEGUM - {"Crystal", { 252, 177, 179, 180, 181, 181, 182, 182, 183, 164, 166, 167, 167, 168, 169, 159}, SKINCOLOR_EMERALD, 8, V_MAGENTAMAP, true}, // SKINCOLOR_CRYSTAL + {"Siberite", { 252, 177, 179, 180, 181, 181, 182, 182, 183, 164, 166, 167, 167, 168, 169, 159}, SKINCOLOR_EMERALD, 8, V_MAGENTAMAP, true}, // SKINCOLOR_SIBERITE {"Magenta", {0xb3, 0xb3, 0xb4, 0xb5, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb}, SKINCOLOR_LIME, 6, V_MAGENTAMAP, true}, // SKINCOLOR_MAGENTA {"Neon", {0xb3, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb, 0xc7, 0xc7, 0x1d, 0x1d, 0x1e}, SKINCOLOR_CERULEAN, 2, V_MAGENTAMAP, true}, // SKINCOLOR_NEON {"Violet", {0xd0, 0xd1, 0xd2, 0xca, 0xcc, 0xb8, 0xb9, 0xb9, 0xba, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfe, 0xfe}, SKINCOLOR_MINT, 6, V_MAGENTAMAP, true}, // SKINCOLOR_VIOLET {"Royal", { 208, 209, 192, 192, 192, 193, 193, 194, 194, 172, 173, 174, 175, 175, 139, 139}, SKINCOLOR_FANCY, 9, V_PURPLEMAP, true}, // SKINCOLOR_ROYAL {"Lilac", {0x00, 0xd0, 0xd1, 0xd2, 0xd3, 0xc1, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xfe, 0x1f}, SKINCOLOR_VAPOR, 4, V_ROSYMAP, true}, // SKINCOLOR_LILAC - {"Mauve", { 176, 177, 178, 192, 193, 194, 195, 195, 196, 185, 185, 186, 186, 187, 187, 253}, SKINCOLOR_GOLD, 4, V_PURPLEMAP, true}, // SKINCOLOR_MAUVE + {"Mauve", { 176, 177, 178, 192, 193, 194, 195, 195, 196, 185, 185, 186, 186, 187, 187, 253}, SKINCOLOR_HEADLIGHT, 8, V_PURPLEMAP, true}, // SKINCOLOR_MAUVE {"Eventide", { 51, 52, 53, 33, 34, 204, 183, 183, 184, 184, 166, 167, 168, 169, 253, 254}, SKINCOLOR_DAYBREAK, 13, V_MAGENTAMAP, true}, // SKINCOLOR_EVENTIDE {"Plum", {0xc8, 0xd3, 0xd5, 0xd6, 0xd7, 0xce, 0xcf, 0xb9, 0xb9, 0xba, 0xba, 0xa9, 0xa9, 0xa9, 0xfd, 0xfe}, SKINCOLOR_MINT, 7, V_ROSYMAP, true}, // SKINCOLOR_PLUM {"Raspberry", {0xc8, 0xc9, 0xca, 0xcb, 0xcb, 0xcc, 0xcd, 0xcd, 0xce, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xfe, 0xfe}, SKINCOLOR_APPLE, 13, V_ROSYMAP, true}, // SKINCOLOR_RASPBERRY - {"Taffy", { 1, 176, 176, 177, 178, 179, 202, 203, 204, 204, 205, 206, 207, 44, 45, 46}, SKINCOLOR_AQUA, 1, V_ROSYMAP, true}, // SKINCOLOR_TAFFY - {"Rosy", {0xfc, 0xc8, 0xc8, 0xc9, 0xc9, 0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf}, SKINCOLOR_JADE, 8, V_ROSYMAP, true}, // SKINCOLOR_ROSY + {"Taffy", { 1, 176, 176, 177, 178, 179, 202, 203, 204, 204, 205, 206, 207, 44, 45, 46}, SKINCOLOR_JADE, 8, V_ROSYMAP, true}, // SKINCOLOR_TAFFY + {"Rosy", {0xfc, 0xc8, 0xc8, 0xc9, 0xc9, 0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf}, SKINCOLOR_AQUA, 1, V_ROSYMAP, true}, // SKINCOLOR_ROSY {"Fancy", { 0, 208, 49, 210, 210, 202, 202, 203, 204, 204, 205, 206, 207, 207, 186, 186}, SKINCOLOR_ROYAL, 9, V_ROSYMAP, true}, // SKINCOLOR_FANCY {"Sangria", { 210, 32, 33, 34, 34, 215, 215, 207, 207, 185, 186, 186, 186, 169, 169, 253}, SKINCOLOR_TURQUOISE, 12, V_ROSYMAP, true}, // SKINCOLOR_SANGRIA - {"Volcanic", { 35, 38, 41, 42, 44, 46, 46, 169, 169, 159, 253, 254, 30, 30, 31, 31}, SKINCOLOR_BRONZE, 9, V_REDMAP, true}, // SKINCOLOR_VOLCANIC + {"Volcanic", { 54, 36, 42, 44, 45, 46, 46, 47, 28, 253, 253, 254, 254, 30, 31, 31}, SKINCOLOR_BRONZE, 9, V_REDMAP, true}, // SKINCOLOR_VOLCANIC // super {"Super Silver 1", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03}, SKINCOLOR_BLACK, 15, 0, false}, // SKINCOLOR_SUPERSILVER1 diff --git a/src/lua_baselib.c b/src/lua_baselib.c index cdda48e8f..b5c72fdd4 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -26,11 +26,11 @@ #include "y_inter.h" #include "hu_stuff.h" // HU_AddChatText #include "console.h" -#include "d_netcmd.h" // IsPlayerAdmin +#include "netcode/d_netcmd.h" // IsPlayerAdmin #include "m_menu.h" // Player Setup menu color stuff #include "m_misc.h" // M_MapNumber #include "b_bot.h" // B_UpdateBotleader -#include "d_clisrv.h" // CL_RemovePlayer +#include "netcode/d_clisrv.h" // CL_RemovePlayer #include "i_system.h" // I_GetPreciseTime, I_GetPrecisePrecision #include "lua_script.h" @@ -3566,7 +3566,7 @@ static int lib_gAddGametype(lua_State *L) // Partly lifted from Got_AddPlayer static int lib_gAddPlayer(lua_State *L) { - INT16 i, newplayernum, botcount = 1; + INT16 i, newplayernum; player_t *newplayer; SINT8 skinnum = 0, bot; @@ -3574,10 +3574,8 @@ static int lib_gAddPlayer(lua_State *L) { if (!playeringame[i]) break; - - if (players[i].bot) - botcount++; // How many of us are there already? } + if (i >= MAXPLAYERS) { lua_pushnil(L); @@ -3848,7 +3846,7 @@ static int lib_gDoReborn(lua_State *L) } // Another Lua function that doesn't actually exist! -// Sets nextmapoverride & skipstats without instantly ending the level, for instances where other sources should be exiting the level, like normal signposts. +// Sets nextmapoverride, skipstats and nextgametype without instantly ending the level, for instances where other sources should be exiting the level, like normal signposts. static int lib_gSetCustomExitVars(lua_State *L) { int n = lua_gettop(L); // Num arguments @@ -3857,18 +3855,21 @@ static int lib_gSetCustomExitVars(lua_State *L) // LUA EXTENSION: Custom exit like support // Supported: - // G_SetCustomExitVars(); [reset to defaults] - // G_SetCustomExitVars(int) [nextmap override only] - // G_SetCustomExitVars(nil, int) [skipstats only] - // G_SetCustomExitVars(int, int) [both of the above] + // G_SetCustomExitVars(); [reset to defaults] + // G_SetCustomExitVars(int) [nextmap override only] + // G_SetCustomExitVars(nil, int) [skipstats only] + // G_SetCustomExitVars(int, int) [both of the above] + // G_SetCustomExitVars(int, int, int) [nextmapoverride, skipstats and nextgametype] nextmapoverride = 0; skipstats = 0; + nextgametype = -1; if (n >= 1) { nextmapoverride = (INT16)luaL_optinteger(L, 1, 0); skipstats = (INT16)luaL_optinteger(L, 2, 0); + nextgametype = (INT16)luaL_optinteger(L, 3, -1); } return 0; diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c index 9e5d1e2ee..0ab8ad9c3 100644 --- a/src/lua_consolelib.c +++ b/src/lua_consolelib.c @@ -16,6 +16,7 @@ #include "g_game.h" #include "byteptr.h" #include "z_zone.h" +#include "netcode/net_command.h" #include "lua_script.h" #include "lua_libs.h" @@ -615,7 +616,7 @@ static int cvar_get(lua_State *L) break; default: if (devparm) - return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field); + return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS ".", lua_tostring(L, 2)); else return 0; } diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 039a9677f..0fc25ee6c 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -24,7 +24,7 @@ #include "lua_hud.h" // hud_running errors #include "m_perfstats.h" -#include "d_netcmd.h" // for cv_perfstats +#include "netcode/d_netcmd.h" // for cv_perfstats #include "i_system.h" // I_GetPreciseTime /* ========================================================================= diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 2e3bb9c68..6eec91273 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -282,7 +282,6 @@ static int patch_get(lua_State *L) patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH)); enum patch field = Lua_optoption(L, 2, -1, patch_fields_ref); - // patches are invalidated when switching renderers if (!patch) { if (field == patch_valid) { lua_pushboolean(L, 0); @@ -436,7 +435,7 @@ static int camera_set(lua_State *L) cam->momz = luaL_checkfixed(L, 3); break; default: - return luaL_error(L, LUA_QL("camera_t") " has no field named " LUA_QS, camera_opt[field]); + return luaL_error(L, LUA_QL("camera_t") " has no field named " LUA_QS ".", lua_tostring(L, 2)); } return 0; } @@ -1487,7 +1486,6 @@ void LUA_SetHudHook(int hook, huddrawlist_h list) break; case HUD_HOOK(intermission): - lua_pushboolean(gL, intertype == int_spec && - stagefailed); + lua_pushboolean(gL, stagefailed); } } diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 440d1fce4..dad38a591 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -805,8 +805,9 @@ static int sector_set(lua_State *L) case sector_fslope: // f_slope case sector_cslope: // c_slope case sector_friction: // friction - default: return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]); + default: + return luaL_error(L, "sector_t has no field named " LUA_QS ".", lua_tostring(L, 2)); case sector_floorheight: { // floorheight boolean flag; mobj_t *ptmthing = tmthing; @@ -1284,8 +1285,9 @@ static int side_set(lua_State *L) case side_sector: case side_special: case side_text: - default: return luaL_error(L, "side_t field " LUA_QS " cannot be set.", side_opt[field]); + default: + return luaL_error(L, "side_t has no field named " LUA_QS ".", lua_tostring(L, 2)); case side_textureoffset: side->textureoffset = luaL_checkfixed(L, 3); break; @@ -2296,8 +2298,9 @@ static int ffloor_set(lua_State *L) case ffloor_target: // target case ffloor_next: // next case ffloor_prev: // prev - default: return luaL_error(L, "ffloor_t field " LUA_QS " cannot be set.", ffloor_opt[field]); + default: + return luaL_error(L, "ffloor_t has no field named " LUA_QS ".", lua_tostring(L, 2)); case ffloor_topheight: { // topheight boolean flag; fixed_t lastpos = *ffloor->topheight; @@ -2431,8 +2434,9 @@ static int slope_set(lua_State *L) case slope_d: // d case slope_flags: // flags case slope_normal: // normal - default: return luaL_error(L, "pslope_t field " LUA_QS " cannot be set.", slope_opt[field]); + default: + return luaL_error(L, "pslope_t has no field named " LUA_QS ".", lua_tostring(L, 2)); case slope_o: { // o luaL_checktype(L, 3, LUA_TTABLE); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index fd8dcec92..fddf958be 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -925,6 +925,8 @@ enum mapthing_e { mapthing_type, mapthing_options, mapthing_scale, + mapthing_spritexscale, + mapthing_spriteyscale, mapthing_z, mapthing_extrainfo, mapthing_tag, @@ -944,6 +946,8 @@ const char *const mapthing_opt[] = { "type", "options", "scale", + "spritexscale", + "spriteyscale", "z", "extrainfo", "tag", @@ -999,7 +1003,13 @@ static int mapthing_get(lua_State *L) lua_pushinteger(L, mt->options); break; case mapthing_scale: - lua_pushinteger(L, mt->scale); + lua_pushfixed(L, mt->scale); + break; + case mapthing_spritexscale: + lua_pushfixed(L, mt->spritexscale); + break; + case mapthing_spriteyscale: + lua_pushfixed(L, mt->spriteyscale); break; case mapthing_z: lua_pushinteger(L, mt->z); @@ -1072,6 +1082,12 @@ static int mapthing_set(lua_State *L) case mapthing_scale: mt->scale = luaL_checkfixed(L, 3); break; + case mapthing_spritexscale: + mt->spritexscale = luaL_checkfixed(L, 3); + break; + case mapthing_spriteyscale: + mt->spriteyscale = luaL_checkfixed(L, 3); + break; case mapthing_z: mt->z = (INT16)luaL_checkinteger(L, 3); break; diff --git a/src/lua_script.c b/src/lua_script.c index 8b081c587..c9884ebd5 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -28,7 +28,7 @@ #include "p_slopes.h" // for P_SlopeById and slopelist #include "p_polyobj.h" // polyobj_t, PolyObjects #ifdef LUA_ALLOW_BYTECODE -#include "d_netfil.h" // for LUA_DumpFile +#include "netcode/d_netfil.h" // for LUA_DumpFile #endif #include "lua_script.h" diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index 13e0dd987..041c5d598 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -25,6 +25,7 @@ enum skin { skin_flags, skin_realname, skin_hudname, + skin_supername, skin_ability, skin_ability2, skin_thokitem, @@ -63,6 +64,7 @@ static const char *const skin_opt[] = { "flags", "realname", "hudname", + "supername", "ability", "ability2", "thokitem", @@ -126,6 +128,9 @@ static int skin_get(lua_State *L) case skin_hudname: lua_pushstring(L, skin->hudname); break; + case skin_supername: + lua_pushstring(L, skin->supername); + break; case skin_ability: lua_pushinteger(L, skin->ability); break; diff --git a/src/m_cheat.c b/src/m_cheat.c index e370335f8..2bcf43ad1 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -19,7 +19,7 @@ #include "r_local.h" #include "p_local.h" #include "p_setup.h" -#include "d_net.h" +#include "netcode/d_net.h" #include "m_cheat.h" #include "m_menu.h" @@ -1102,6 +1102,8 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value; mt->scale = player->mo->scale; + mt->spritexscale = player->mo->spritexscale; + mt->spriteyscale = player->mo->spriteyscale; memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); mt->pitch = mt->roll = 0; diff --git a/src/m_cond.c b/src/m_cond.c index 13a483ea2..706a1b510 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -467,6 +467,15 @@ UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data) UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data) { + if (dedicated) + { + // If you're in a dedicated server, every level is unlocked. + // Yes, technically this means you can view any level by + // running a dedicated server and joining it yourself, but + // that's better than making dedicated server's lives hell. + return false; + } + if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) { return false; @@ -480,6 +489,48 @@ UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data) return false; } +UINT8 M_CampaignWarpIsCheat(INT32 gt, INT32 mapnum, gamedata_t *data) +{ + if (M_MapLocked(mapnum, data) == true) + { + // Warping to locked maps is definitely always a cheat + return true; + } + + if ((gametypedefaultrules[gt] & GTR_CAMPAIGN) == 0) + { + // Not a campaign, do whatever you want. + return false; + } + + if (G_IsSpecialStage(mapnum)) + { + // Warping to special stages is a cheat + return true; + } + + if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->menuflags & LF2_HIDEINMENU) + { + // You're never allowed to warp to this level. + return true; + } + + if (mapheaderinfo[mapnum-1]->menuflags & LF2_NOVISITNEEDED) + { + // You're always allowed to warp to this level. + return false; + } + + if (mapnum == spstage_start) + { + // Warping to the first level is never a cheat + return false; + } + + // It's only a cheat if you've never been there. + return (!(data->mapvisited[mapnum-1])); +} + INT32 M_CountEmblems(gamedata_t *data) { INT32 found = 0, i; diff --git a/src/m_cond.h b/src/m_cond.h index 2be8d564b..2491a384c 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -252,6 +252,7 @@ void M_SilentUpdateSkinAvailabilites(void); UINT8 M_AnySecretUnlocked(gamedata_t *data); UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data); UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data); +UINT8 M_CampaignWarpIsCheat(INT32 gt, INT32 mapnum, gamedata_t *data); INT32 M_CountEmblems(gamedata_t *data); // Emblem shit diff --git a/src/m_menu.c b/src/m_menu.c index 3451b90d6..523430ed8 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -20,7 +20,7 @@ #include "doomdef.h" #include "d_main.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" #include "console.h" #include "r_fps.h" #include "r_local.h" @@ -53,8 +53,10 @@ #include "hardware/hw_main.h" #endif -#include "d_net.h" -#include "mserv.h" +#include "netcode/d_net.h" +#include "netcode/mserv.h" +#include "netcode/server_connection.h" +#include "netcode/client_connection.h" #include "m_misc.h" #include "m_anigif.h" #include "byteptr.h" @@ -149,9 +151,7 @@ levellist_mode_t levellistmode = LLM_CREATESERVER; UINT8 maplistoption = 0; static char joystickInfo[MAX_JOYSTICKS+1][29]; -#ifndef NONET static UINT32 serverlistpage; -#endif static UINT8 numsaves = 0; static saveinfo_t* savegameinfo = NULL; // Extra info about the save games. @@ -190,10 +190,8 @@ static void M_GoBack(INT32 choice); static void M_StopMessage(INT32 choice); static boolean stopstopmessage = false; -#ifndef NONET static void M_HandleServerPage(INT32 choice); static void M_RoomMenu(INT32 choice); -#endif // Prototyping is fun, innit? // ========================================================================== @@ -296,7 +294,6 @@ static void M_SetupMultiPlayer2(INT32 choice); static void M_StartSplitServerMenu(INT32 choice); static void M_StartServer(INT32 choice); static void M_ServerOptions(INT32 choice); -#ifndef NONET static void M_StartServerMenu(INT32 choice); static void M_ConnectMenu(INT32 choice); static void M_ConnectMenuModChecks(INT32 choice); @@ -304,7 +301,6 @@ static void M_Refresh(INT32 choice); static void M_Connect(INT32 choice); static void M_ChooseRoom(INT32 choice); menu_t MP_MainDef; -#endif // Options // Split into multiple parts due to size @@ -382,11 +378,9 @@ static void M_DrawVideoMode(void); static void M_DrawColorMenu(void); static void M_DrawScreenshotMenu(void); static void M_DrawMonitorToggles(void); -#ifndef NONET static void M_DrawConnectMenu(void); static void M_DrawMPMainMenu(void); static void M_DrawRoomMenu(void); -#endif static void M_DrawJoystick(void); static void M_DrawSetupMultiPlayerMenu(void); static void M_DrawColorRamp(INT32 x, INT32 y, INT32 w, INT32 h, skincolor_t color); @@ -401,10 +395,8 @@ static void M_HandleImageDef(INT32 choice); static void M_HandleLoadSave(INT32 choice); static void M_HandleLevelStats(INT32 choice); static void M_HandlePlaystyleMenu(INT32 choice); -#ifndef NONET static boolean M_CancelConnect(void); static void M_HandleConnectIP(INT32 choice); -#endif static void M_HandleSetupMultiPlayer(INT32 choice); static void M_HandleVideoMode(INT32 choice); @@ -503,11 +495,7 @@ consvar_t cv_dummyloadless = CVAR_INIT ("dummyloadless", "In-game", CV_HIDEN, lo static menuitem_t MainMenu[] = { {IT_STRING|IT_CALL, NULL, "1 Player", M_SinglePlayerMenu, 76}, -#ifndef NONET {IT_STRING|IT_SUBMENU, NULL, "Multiplayer", &MP_MainDef, 84}, -#else - {IT_STRING|IT_CALL, NULL, "Multiplayer", M_StartSplitServerMenu, 84}, -#endif {IT_STRING|IT_CALL, NULL, "Extras", M_SecretsMenu, 92}, {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 100}, {IT_STRING|IT_CALL, NULL, "Options", M_Options, 108}, @@ -930,16 +918,10 @@ static menuitem_t SP_PlayerMenu[] = static menuitem_t MP_SplitServerMenu[] = { {IT_STRING|IT_CALL, NULL, "Select Gametype/Level...", M_MapChange, 100}, -#ifdef NONET // In order to keep player setup accessible. - {IT_STRING|IT_CALL, NULL, "Player 1 setup...", M_SetupMultiPlayer, 110}, - {IT_STRING|IT_CALL, NULL, "Player 2 setup...", M_SetupMultiPlayer2, 120}, -#endif {IT_STRING|IT_CALL, NULL, "More Options...", M_ServerOptions, 130}, {IT_WHITESTRING|IT_CALL, NULL, "Start", M_StartServer, 140}, }; -#ifndef NONET - static menuitem_t MP_MainMenu[] = { {IT_HEADER, NULL, "Join a game", NULL, 0}, @@ -1026,8 +1008,6 @@ menuitem_t MP_RoomMenu[] = {IT_DISABLED, NULL, "", M_ChooseRoom, 162}, }; -#endif - static menuitem_t MP_PlayerSetupMenu[] = { {IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // name @@ -1586,14 +1566,12 @@ enum static menuitem_t OP_ServerOptionsMenu[] = { {IT_HEADER, NULL, "General", NULL, 0}, -#ifndef NONET {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Server name", &cv_servername, 7}, {IT_STRING | IT_CVAR, NULL, "Max Players", &cv_maxplayers, 21}, {IT_STRING | IT_CVAR, NULL, "Allow Add-on Downloading", &cv_downloading, 26}, {IT_STRING | IT_CVAR, NULL, "Allow players to join", &cv_allownewplayer, 31}, {IT_STRING | IT_CVAR, NULL, "Minutes for reconnecting", &cv_rejointimeout, 36}, -#endif {IT_STRING | IT_CVAR, NULL, "Map progression", &cv_advancemap, 41}, {IT_STRING | IT_CVAR, NULL, "Intermission Timer", &cv_inttime, 46}, @@ -1632,7 +1610,6 @@ static menuitem_t OP_ServerOptionsMenu[] = {IT_STRING | IT_CVAR, NULL, "Autobalance sizes", &cv_autobalance, 216}, {IT_STRING | IT_CVAR, NULL, "Scramble on Map Change", &cv_scrambleonchange, 221}, -#ifndef NONET {IT_HEADER, NULL, "Advanced", NULL, 230}, {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server", &cv_masterserver, 236}, @@ -1640,7 +1617,6 @@ static menuitem_t OP_ServerOptionsMenu[] = {IT_STRING | IT_CVAR, NULL, "Attempts to resynchronise", &cv_resynchattempts, 256}, {IT_STRING | IT_CVAR, NULL, "Show IP Address of Joiners", &cv_showjoinaddress, 261}, -#endif }; static menuitem_t OP_MonitorToggleMenu[] = @@ -1954,11 +1930,7 @@ menu_t MP_SplitServerDef = MTREE2(MN_MP_MAIN, MN_MP_SPLITSCREEN), "M_MULTI", sizeof (MP_SplitServerMenu)/sizeof (menuitem_t), -#ifndef NONET &MP_MainDef, -#else - &MainDef, -#endif MP_SplitServerMenu, M_DrawServerMenu, 27, 30 - 50, @@ -1966,8 +1938,6 @@ menu_t MP_SplitServerDef = NULL }; -#ifndef NONET - menu_t MP_MainDef = { MN_MP_MAIN, @@ -2019,15 +1989,10 @@ menu_t MP_RoomDef = 0, NULL }; -#endif menu_t MP_PlayerSetupDef = { -#ifdef NONET - MTREE2(MN_MP_MAIN, MN_MP_PLAYERSETUP), -#else MTREE3(MN_MP_MAIN, MN_MP_SPLITSCREEN, MN_MP_PLAYERSETUP), -#endif "M_SPLAYR", sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t), &MainDef, // doesn't matter @@ -2649,7 +2614,7 @@ static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval, { strncpy(curbgname, defaultname, 9); curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed; - curbgyspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollyspeed; + curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed; } } return false; @@ -2787,6 +2752,7 @@ void M_SetMenuCurBackground(const char *defaultname) { char name[9]; strncpy(name, defaultname, 8); + name[8] = '\0'; M_IterateMenuTree(MIT_SetCurBackground, &name); } @@ -2843,8 +2809,8 @@ static void M_HandleMenuPresState(menu_t *newMenu) curfadevalue = 16; curhidepics = hidetitlepics; curbgcolor = -1; - curbgxspeed = titlescrollxspeed; - curbgyspeed = titlescrollyspeed; + curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed; + curbgyspeed = (gamestate == GS_TIMEATTACK) ? 18 : titlescrollyspeed; curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus curttmode = ttmode; @@ -3196,7 +3162,7 @@ boolean M_Responder(event_t *ev) || gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND) return false; - if (gamestate == GS_TITLESCREEN && finalecount < TICRATE) + if (gamestate == GS_TITLESCREEN && finalecount < (cv_tutorialprompt.value ? TICRATE : 0)) return false; if (CON_Ready() && gamestate != GS_WAITINGPLAYERS) @@ -3956,9 +3922,7 @@ void M_Init(void) OP_JoystickSetMenu[i].itemaction = M_AssignJoystick; } -#ifndef NONET CV_RegisterVar(&cv_serversort); -#endif } void M_InitCharacterTables(void) @@ -11108,7 +11072,6 @@ static void M_EndGame(INT32 choice) #define S_LINEY(n) currentMenu->y + SERVERHEADERHEIGHT + (n * SERVERLINEHEIGHT) -#ifndef NONET static UINT32 localservercount; static void M_HandleServerPage(INT32 choice) @@ -11380,11 +11343,9 @@ static int ServerListEntryComparator_modified(const void *entry1, const void *en // Default to strcmp. return strcmp(sa->info.servername, sb->info.servername); } -#endif void M_SortServerList(void) { -#ifndef NONET switch(cv_serversort.value) { case 0: // Ping. @@ -11406,10 +11367,8 @@ void M_SortServerList(void) qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename); break; } -#endif } -#ifndef NONET #ifdef UPDATE_ALERT static boolean M_CheckMODVersion(int id) { @@ -11608,7 +11567,6 @@ static void M_ChooseRoom(INT32 choice) if (currentMenu == &MP_ConnectDef) M_Refresh(0); } -#endif //NONET //=========================================================================== // Start Server Menu @@ -11656,7 +11614,6 @@ static void M_DrawServerMenu(void) { M_DrawGenericMenu(); -#ifndef NONET // Room name if (currentMenu == &MP_ServerDef) { @@ -11668,15 +11625,10 @@ static void M_DrawServerMenu(void) V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey, V_YELLOWMAP, room_list[menuRoomIndex].name); } -#endif if (cv_nextmap.value) { -#ifndef NONET #define imgheight MP_ServerMenu[mp_server_levelgt].alphaKey -#else -#define imgheight 100 -#endif patch_t *PictureOfLevel; lumpnum_t lumpnum; char headerstr[40]; @@ -11728,7 +11680,6 @@ static void M_ServerOptions(INT32 choice) { (void)choice; -#ifndef NONET if ((splitscreen && !netgame) || currentMenu == &MP_SplitServerDef) { OP_ServerOptionsMenu[ 1].status = IT_GRAYEDOUT; // Server name @@ -11749,7 +11700,6 @@ static void M_ServerOptions(INT32 choice) OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR; OP_ServerOptionsMenu[38].status = IT_STRING | IT_CVAR; } -#endif /* Disable fading because of different menu head. */ if (currentMenu == &OP_MainDef)/* from Options menu */ @@ -11761,7 +11711,6 @@ static void M_ServerOptions(INT32 choice) M_SetupNextMenu(&OP_ServerOptionsDef); } -#ifndef NONET static void M_StartServerMenu(INT32 choice) { (void)choice; @@ -12028,7 +11977,6 @@ static void M_HandleConnectIP(INT32 choice) M_ClearMenus(true); } } -#endif //!NONET // ======================== // MULTIPLAYER PLAYER SETUP @@ -12054,6 +12002,136 @@ static INT32 setupm_fakeskin; static menucolor_t *setupm_fakecolor; static boolean colorgrid; +#define COLOR_GRID_ROW_SIZE (16) + +static UINT16 M_GetColorGridIndex(UINT16 color) +{ + menucolor_t *look; + UINT16 i = 0; + + if (!skincolors[color].accessible) + { + return 0; + } + + for (look = menucolorhead; ; i++, look = look->next) + { + while (!skincolors[look->color].accessible) // skip inaccessible colors + { + if (look == menucolortail) + { + return 0; + } + + look = look->next; + } + + if (look->color == color) + { + return i; + } + + if (look == menucolortail) + { + return 0; + } + } +} + +static INT32 M_GridIndexToX(UINT16 index) +{ + return (index % COLOR_GRID_ROW_SIZE); +} + +static INT32 M_GridIndexToY(UINT16 index) +{ + return (index / COLOR_GRID_ROW_SIZE); +} + +static UINT16 M_ColorGridLen(void) +{ + menucolor_t *look; + UINT16 i = 0; + + for (look = menucolorhead; ; i++) + { + do + { + if (look == menucolortail) + { + return i; + } + + look = look->next; + } + while (!skincolors[look->color].accessible); // skip inaccessible colors + } +} + +static UINT16 M_GridPosToGridIndex(INT32 x, INT32 y) +{ + const UINT16 grid_len = M_ColorGridLen(); + const UINT16 grid_height = ((grid_len - 1) / COLOR_GRID_ROW_SIZE) + 1; + const UINT16 last_row_len = COLOR_GRID_ROW_SIZE - ((grid_height * COLOR_GRID_ROW_SIZE) - grid_len); + + UINT16 row_len = COLOR_GRID_ROW_SIZE; + UINT16 new_index = 0; + + while (y < 0) + { + y += grid_height; + } + y = (y % grid_height); + + if (y >= grid_height-1 && last_row_len > 0) + { + row_len = last_row_len; + } + + while (x < 0) + { + x += row_len; + } + x = (x % row_len); + + new_index = (y * COLOR_GRID_ROW_SIZE) + x; + if (new_index >= grid_len) + { + new_index = grid_len - 1; + } + + return new_index; +} + +static menucolor_t *M_GridIndexToMenuColor(UINT16 index) +{ + menucolor_t *look = menucolorhead; + UINT16 i = 0; + + for (look = menucolorhead; ; i++, look = look->next) + { + while (!skincolors[look->color].accessible) // skip inaccessible colors + { + if (look == menucolortail) + { + return menucolorhead; + } + + look = look->next; + } + + if (i == index) + { + return look; + } + + if (look == menucolortail) + { + return menucolorhead; + } + } +} + static void M_DrawSetupMultiPlayerMenu(void) { INT32 x, y, cursory = 0, flags = 0; @@ -12172,32 +12250,54 @@ colordraw: boolean stoprow = false; menucolor_t *mc; // Last accessed color + const UINT16 grid_len = M_ColorGridLen(); + const UINT16 grid_end_y = M_GridIndexToY(grid_len - 1); + + INT32 grid_select = M_GetColorGridIndex(setupm_fakecolor->color); + INT32 grid_select_y = M_GridIndexToY(grid_select); + x = 132; y = 66; - pos = min(max(0, 16*((M_GetColorIndex(setupm_fakecolor->color)-1)/16) - 64), 16*(M_GetColorIndex(menucolortail->color)/16-1) - 128); - mc = M_GetColorFromIndex(pos); + + pos = M_GridPosToGridIndex(0, max(0, min(grid_select_y - 3, grid_end_y - 7))); + mc = M_GridIndexToMenuColor(pos); // Draw grid V_DrawFill(x-2, y-2, 132, 132, 159); for (j = 0; j < 8; j++) { - for (i = 0; i < 16; i++) + for (i = 0; i < COLOR_GRID_ROW_SIZE; i++) { - if (skincolors[mc->color].accessible && !stoprow) + if (skincolors[mc->color].accessible) { M_DrawColorRamp(x + i*w, y + j*16, w, 1, skincolors[mc->color]); - if (mc->color == setupm_fakecolor->color) // store current color position - { - cx = x + i*w; - cy = y + j*16; - } + + if (mc == setupm_fakecolor) // store current color position + { + cx = x + i*w; + cy = y + j*16; + } } - mc = mc->next; - while (!skincolors[mc->color].accessible && !stoprow) // Find accessible color after this one + + if (stoprow) + { + break; + } + + // Find accessible color after this one + do { mc = mc->next; - if (mc == menucolortail) stoprow = true; - } + if (mc == menucolortail) + { + stoprow = true; + } + } while (!skincolors[mc->color].accessible && !stoprow); + } + + if (stoprow) + { + break; } } @@ -12318,15 +12418,16 @@ static void M_HandleSetupMultiPlayer(INT32 choice) case KEY_DOWNARROW: case KEY_UPARROW: { - UINT8 i; if (itemOn == 2 && colorgrid) { - for (i = 0; i < 16; i++) - { - setupm_fakecolor = (choice == KEY_UPARROW) ? setupm_fakecolor->prev : setupm_fakecolor->next; - while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors - setupm_fakecolor = (choice == KEY_UPARROW) ? setupm_fakecolor->prev : setupm_fakecolor->next; - } + UINT16 index = M_GetColorGridIndex(setupm_fakecolor->color); + INT32 x = M_GridIndexToX(index); + INT32 y = M_GridIndexToY(index); + + y += (choice == KEY_UPARROW) ? -1 : 1; + + index = M_GridPosToGridIndex(x, y); + setupm_fakecolor = M_GridIndexToMenuColor(index); } else if (choice == KEY_UPARROW) M_PrevOpt(); @@ -12353,8 +12454,8 @@ static void M_HandleSetupMultiPlayer(INT32 choice) } else if (itemOn == 2) // player color { - S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor = setupm_fakecolor->prev; + S_StartSound(NULL,sfx_menu1); // Tails } break; @@ -12393,8 +12494,8 @@ static void M_HandleSetupMultiPlayer(INT32 choice) } else if (itemOn == 2) // player color { - S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor = setupm_fakecolor->next; + S_StartSound(NULL,sfx_menu1); // Tails } break; @@ -12404,13 +12505,28 @@ static void M_HandleSetupMultiPlayer(INT32 choice) UINT8 i; if (itemOn == 2) // player color { - S_StartSound(NULL,sfx_menu1); - for (i = 0; i < (colorgrid ? 64 : 13); i++) // or (282-charw)/(2*indexwidth) + if (colorgrid) { - setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next; - while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors - setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next; + UINT16 index = M_GetColorGridIndex(setupm_fakecolor->color); + INT32 x = M_GridIndexToX(index); + INT32 y = M_GridIndexToY(index); + + y += (choice == KEY_UPARROW) ? -4 : 4; + + index = M_GridPosToGridIndex(x, y); + setupm_fakecolor = M_GridIndexToMenuColor(index); } + else + { + for (i = 0; i < 13; i++) // or (282-charw)/(2*indexwidth) + { + setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next; + while (!skincolors[setupm_fakecolor->color].accessible) // skip inaccessible colors + setupm_fakecolor = (choice == KEY_PGUP) ? setupm_fakecolor->prev : setupm_fakecolor->next; + } + } + + S_StartSound(NULL, sfx_menu1); // Tails } } break; @@ -12440,7 +12556,6 @@ static void M_HandleSetupMultiPlayer(INT32 choice) } } break; - break; case KEY_DEL: if (itemOn == 0 && (l = strlen(setupm_name))!=0) @@ -12523,11 +12638,7 @@ static void M_SetupMultiPlayer(INT32 choice) else MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER|IT_STRING); - // ditto with colour - if (Playing() && G_GametypeHasTeams()) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); + MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); @@ -12542,7 +12653,7 @@ static void M_SetupMultiPlayer2(INT32 choice) multi_frame = 0; multi_tics = 4*FRACUNIT; - + strcpy (setupm_name, cv_playername2.string); // set for splitscreen secondary player @@ -12568,11 +12679,7 @@ static void M_SetupMultiPlayer2(INT32 choice) else MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER | IT_STRING); - // ditto with colour - if (Playing() && G_GametypeHasTeams()) - MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT); - else - MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); + MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING); multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL); diff --git a/src/m_menu.h b/src/m_menu.h index c925c7f49..b8fe3b808 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -20,7 +20,7 @@ #include "command.h" #include "f_finale.h" // for ttmode_enum #include "i_threads.h" -#include "mserv.h" +#include "netcode/mserv.h" #include "r_things.h" // for SKINNAMESIZE // Compatibility with old-style named NiGHTS replay files. @@ -74,7 +74,7 @@ typedef enum MN_MP_SERVER, MN_MP_CONNECT, MN_MP_ROOM, - MN_MP_PLAYERSETUP, // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET + MN_MP_PLAYERSETUP, MN_MP_SERVER_OPTIONS, // Options diff --git a/src/m_perfstats.c b/src/m_perfstats.c index 17e026b3e..151185932 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -12,7 +12,7 @@ #include "m_perfstats.h" #include "v_video.h" #include "i_video.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" #include "r_main.h" #include "i_system.h" #include "z_zone.h" diff --git a/src/netcode/CMakeLists.txt b/src/netcode/CMakeLists.txt new file mode 100644 index 000000000..69b976d17 --- /dev/null +++ b/src/netcode/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(SRB2SDL2 PRIVATE + d_clisrv.c + server_connection.c + client_connection.c + tic_command.c + net_command.c + gamestate.c + commands.c + d_net.c + d_netcmd.c + d_netfil.c + http-mserv.c + i_tcp.c + mserv.c +) diff --git a/src/netcode/Sourcefile b/src/netcode/Sourcefile new file mode 100644 index 000000000..7c0354714 --- /dev/null +++ b/src/netcode/Sourcefile @@ -0,0 +1,13 @@ +d_clisrv.c +server_connection.c +client_connection.c +tic_command.c +net_command.c +gamestate.c +commands.c +d_net.c +d_netcmd.c +d_netfil.c +http-mserv.c +i_tcp.c +mserv.c diff --git a/src/netcode/client_connection.c b/src/netcode/client_connection.c new file mode 100644 index 000000000..907021e7d --- /dev/null +++ b/src/netcode/client_connection.c @@ -0,0 +1,1178 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 client_connection.h +/// \brief Client connection handling + +#include "client_connection.h" +#include "gamestate.h" +#include "d_clisrv.h" +#include "d_netfil.h" +#include "../d_main.h" +#include "../f_finale.h" +#include "../g_game.h" +#include "../g_input.h" +#include "i_net.h" +#include "../i_system.h" +#include "../i_time.h" +#include "../i_video.h" +#include "../keys.h" +#include "../m_menu.h" +#include "../m_misc.h" +#include "../snake.h" +#include "../s_sound.h" +#include "../v_video.h" +#include "../y_inter.h" +#include "../z_zone.h" +#include "../doomtype.h" +#include "../doomstat.h" +#if defined (__GNUC__) || defined (__unix__) +#include +#endif + +cl_mode_t cl_mode = CL_SEARCHING; +static UINT16 cl_lastcheckedfilecount = 0; // used for full file list +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; +UINT8 mynode; +static void *snake = NULL; + +static void CL_DrawConnectionStatusBox(void) +{ + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1); + if (cl_mode != CL_CONFIRMCONNECT) + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort"); +} + +// +// CL_DrawConnectionStatus +// +// Keep the local client informed of our status. +// +static inline void CL_DrawConnectionStatus(void) +{ + INT32 ccstime = I_GetTime(); + + // Draw background fade + V_DrawFadeScreen(0xFF00, 16); // force default + + if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES) + { + INT32 animtime = ((ccstime / 4) & 15) + 16; + UINT8 palstart; + const char *cltext; + + // Draw the bottom box. + CL_DrawConnectionStatusBox(); + + if (cl_mode == CL_SEARCHING) + palstart = 32; // Red + else if (cl_mode == CL_CONFIRMCONNECT) + palstart = 48; // Orange + else + palstart = 96; // Green + + if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1)) + for (INT32 i = 0; i < 16; ++i) // 15 pal entries total. + V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15)); + + switch (cl_mode) + { + case CL_DOWNLOADSAVEGAME: + if (fileneeded && lastfilenum != -1) + { + UINT32 currentsize = fileneeded[lastfilenum].currentsize; + UINT32 totalsize = fileneeded[lastfilenum].totalsize; + INT32 dldlength; + + cltext = M_GetText("Downloading game state..."); + Net_GetNetStat(); + + dldlength = (INT32)((currentsize/(double)totalsize) * 256); + if (dldlength > 256) + dldlength = 256; + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96); + + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + va(" %4uK/%4uK",currentsize>>10,totalsize>>10)); + + V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + va("%3.1fK/s ", ((double)getbps)/1024)); + } + else + cltext = M_GetText("Waiting to download game state..."); + break; + case CL_ASKFULLFILELIST: + case CL_CHECKFILES: + cltext = M_GetText("Checking server addon list..."); + break; + case CL_CONFIRMCONNECT: + cltext = ""; + break; + case CL_LOADFILES: + cltext = M_GetText("Loading server addons..."); + break; + case CL_ASKJOIN: + case CL_WAITJOINRESPONSE: + if (serverisfull) + cltext = M_GetText("Server full, waiting for a slot..."); + else + cltext = M_GetText("Requesting to join..."); + break; + default: + cltext = M_GetText("Connecting to server..."); + break; + } + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, cltext); + } + else + { + if (cl_mode == CL_LOADFILES) + { + INT32 totalfileslength; + INT32 loadcompletednum = 0; + + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort"); + + // ima just count files here + if (fileneeded) + { + for (INT32 i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_OPEN) + loadcompletednum++; + } + + // Loading progress + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, "Loading server addons..."); + totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256); + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, totalfileslength, 8, 96); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + va(" %2u/%2u Files",loadcompletednum,fileneedednum)); + } + else if (lastfilenum != -1) + { + INT32 dldlength; + static char tempname[28]; + fileneeded_t *file; + char *filename; + + if (snake) + Snake_Draw(snake); + + // Draw the bottom box. + CL_DrawConnectionStatusBox(); + + if (fileneeded) + { + file = &fileneeded[lastfilenum]; + filename = file->filename; + } + else + return; + + Net_GetNetStat(); + dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256); + if (dldlength > 256) + dldlength = 256; + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96); + + memset(tempname, 0, sizeof(tempname)); + // offset filename to just the name only part + filename += strlen(filename) - nameonlylength(filename); + + if (strlen(filename) > sizeof(tempname)-1) // too long to display fully + { + size_t endhalfpos = strlen(filename)-10; + // display as first 14 chars + ... + last 10 chars + // which should add up to 27 if our math(s) is correct + snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos); + } + else // we can copy the whole thing in safely + { + strncpy(tempname, filename, sizeof(tempname)-1); + } + + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, + va(M_GetText("Downloading \"%s\""), tempname)); + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10)); + V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + va("%3.1fK/s ", ((double)getbps)/1024)); + } + else + { + if (snake) + Snake_Draw(snake); + + CL_DrawConnectionStatusBox(); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, + M_GetText("Waiting to download files...")); + } + } +} + +static boolean CL_AskFileList(INT32 firstfile) +{ + netbuffer->packettype = PT_TELLFILESNEEDED; + netbuffer->u.filesneedednum = firstfile; + + return HSendPacket(servernode, false, 0, sizeof (INT32)); +} + +/** Sends a PT_CLIENTJOIN packet to the server + * + * \return True if the packet was successfully sent + * + */ +boolean CL_SendJoin(void) +{ + UINT8 localplayers = 1; + char const *player2name; + if (netgame) + CONS_Printf(M_GetText("Sending join request...\n")); + netbuffer->packettype = PT_CLIENTJOIN; + + netbuffer->u.clientcfg.modversion = MODVERSION; + strncpy(netbuffer->u.clientcfg.application, + SRB2APPLICATION, + sizeof netbuffer->u.clientcfg.application); + + if (splitscreen || botingame) + localplayers++; + netbuffer->u.clientcfg.localplayers = localplayers; + + CleanupPlayerName(consoleplayer, cv_playername.zstring); + if (splitscreen) + CleanupPlayerName(1, cv_playername2.zstring); // 1 is a HACK? oh no + + // Avoid empty string on bots to avoid softlocking in singleplayer + if (botingame) + player2name = strcmp(cv_playername.zstring, "Tails") == 0 ? "Tail" : "Tails"; + else + player2name = cv_playername2.zstring; + + strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME); + strncpy(netbuffer->u.clientcfg.names[1], player2name, MAXPLAYERNAME); + + return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak)); +} + +static void SendAskInfo(INT32 node) +{ + const tic_t asktime = I_GetTime(); + netbuffer->packettype = PT_ASKINFO; + netbuffer->u.askinfo.version = VERSION; + netbuffer->u.askinfo.time = (tic_t)LONG(asktime); + + // Even if this never arrives due to the host being firewalled, we've + // now allowed traffic from the host to us in, so once the MS relays + // our address to the host, it'll be able to speak to us. + HSendPacket(node, false, 0, sizeof (askinfo_pak)); +} + +serverelem_t serverlist[MAXSERVERLIST]; +UINT32 serverlistcount = 0; + +#define FORCECLOSE 0x8000 + +static void SL_ClearServerList(INT32 connectedserver) +{ + for (UINT32 i = 0; i < serverlistcount; i++) + if (connectedserver != serverlist[i].node) + { + Net_CloseConnection(serverlist[i].node|FORCECLOSE); + serverlist[i].node = 0; + } + serverlistcount = 0; +} + +static UINT32 SL_SearchServer(INT32 node) +{ + for (UINT32 i = 0; i < serverlistcount; i++) + if (serverlist[i].node == node) + return i; + + return UINT32_MAX; +} + +static void SL_InsertServer(serverinfo_pak* info, SINT8 node) +{ + UINT32 i; + + // search if not already on it + i = SL_SearchServer(node); + if (i == UINT32_MAX) + { + // not found add it + if (serverlistcount >= MAXSERVERLIST) + return; // list full + + // check it later if connecting to this one + if (node != servernode) + { + if (info->_255 != 255) + return; // Old packet format + + if (info->packetversion != PACKETVERSION) + return; // Old new packet format + + if (info->version != VERSION) + return; // Not same version. + + if (info->subversion != SUBVERSION) + return; // Close, but no cigar. + + if (strcmp(info->application, SRB2APPLICATION)) + return; // That's a different mod + } + + i = serverlistcount++; + } + + serverlist[i].info = *info; + serverlist[i].node = node; + + // resort server list + M_SortServerList(); +} + +#if defined (MASTERSERVER) && defined (HAVE_THREADS) +struct Fetch_servers_ctx +{ + int room; + int id; +}; + +static void +Fetch_servers_thread (struct Fetch_servers_ctx *ctx) +{ + msg_server_t *server_list; + + server_list = GetShortServersList(ctx->room, ctx->id); + + if (server_list) + { + I_lock_mutex(&ms_QueryId_mutex); + { + if (ctx->id != ms_QueryId) + { + free(server_list); + server_list = NULL; + } + } + I_unlock_mutex(ms_QueryId_mutex); + + if (server_list) + { + I_lock_mutex(&m_menu_mutex); + { + if (m_waiting_mode == M_WAITING_SERVERS) + m_waiting_mode = M_NOT_WAITING; + } + I_unlock_mutex(m_menu_mutex); + + I_lock_mutex(&ms_ServerList_mutex); + { + ms_ServerList = server_list; + } + I_unlock_mutex(ms_ServerList_mutex); + } + } + + free(ctx); +} +#endif // defined (MASTERSERVER) && defined (HAVE_THREADS) + +void CL_QueryServerList (msg_server_t *server_list) +{ + for (INT32 i = 0; server_list[i].header.buffer[0]; i++) + { + // Make sure MS version matches our own, to + // thwart nefarious servers who lie to the MS. + + // lol bruh, that version COMES from the servers + //if (strcmp(version, server_list[i].version) == 0) + { + INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); + if (node == -1) + break; // no more node free + SendAskInfo(node); + // Force close the connection so that servers can't eat + // up nodes forever if we never get a reply back from them + // (usually when they've not forwarded their ports). + // + // Don't worry, we'll get in contact with the working + // servers again when they send SERVERINFO to us later! + // + // (Note: as a side effect this probably means every + // server in the list will probably be using the same node (e.g. node 1), + // not that it matters which nodes they use when + // the connections are closed afterwards anyway) + // -- Monster Iestyn 12/11/18 + Net_CloseConnection(node|FORCECLOSE); + } + } +} + +void CL_UpdateServerList(boolean internetsearch, INT32 room) +{ + (void)internetsearch; + (void)room; + + SL_ClearServerList(0); + + if (!netgame && I_NetOpenSocket) + { + if (I_NetOpenSocket()) + { + netgame = true; + multiplayer = true; + } + } + + // search for local servers + if (netgame) + SendAskInfo(BROADCASTADDR); + +#ifdef MASTERSERVER + if (internetsearch) + { +#ifdef HAVE_THREADS + struct Fetch_servers_ctx *ctx; + + ctx = malloc(sizeof *ctx); + + // This called from M_Refresh so I don't use a mutex + m_waiting_mode = M_WAITING_SERVERS; + + I_lock_mutex(&ms_QueryId_mutex); + { + ctx->id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + ctx->room = room; + + I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx); +#else + msg_server_t *server_list; + + server_list = GetShortServersList(room, 0); + + if (server_list) + { + CL_QueryServerList(server_list); + free(server_list); + } +#endif + } +#endif // MASTERSERVER +} + +static void M_ConfirmConnect(event_t *ev) +{ + if (ev->type == ev_keydown) + { + if (ev->key == ' ' || ev->key == 'y' || ev->key == KEY_ENTER || ev->key == KEY_JOY1) + { + if (totalfilesrequestednum > 0) + { + if (CL_SendFileRequest()) + { + cl_mode = CL_DOWNLOADFILES; + Snake_Allocate(&snake); + } + } + else + cl_mode = CL_LOADFILES; + + M_ClearMenus(true); + } + else if (ev->key == 'n' || ev->key == KEY_ESCAPE || ev->key == KEY_JOY1 + 3) + { + cl_mode = CL_ABORTED; + M_ClearMenus(true); + } + } +} + +static boolean CL_FinishedFileList(void) +{ + INT32 i; + char *downloadsize = NULL; + + //CONS_Printf(M_GetText("Checking files...\n")); + i = CL_CheckFiles(); + if (i == 4) // still checking ... + { + return true; + } + else if (i == 3) // too many files + { + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "You have too many WAD files loaded\n" + "to add ones the server is using.\n" + "Please restart SRB2 before connecting.\n\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } + else if (i == 2) // cannot join for some reason + { + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "You have the wrong addons loaded.\n\n" + "To play on this server, restart\n" + "the game and don't load any addons.\n" + "SRB2 will automatically add\n" + "everything you need when you join.\n\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } + else if (i == 1) + { + if (serverisfull) + { + M_StartMessage(M_GetText( + "This server is full!\n" + "\n" + "You may load server addons (if any), and wait for a slot.\n" + "\n" + "Press ENTER to continue\nor ESC to cancel.\n\n" + ), M_ConfirmConnect, MM_EVENTHANDLER); + cl_mode = CL_CONFIRMCONNECT; + curfadevalue = 0; + } + else + cl_mode = CL_LOADFILES; + } + else + { + // must download something + // can we, though? + if (!CL_CheckDownloadable()) // nope! + { + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "An error occurred when trying to\n" + "download missing addons.\n" + "(This is almost always a problem\n" + "with the server, not your game.)\n\n" + "See the console or log file\n" + "for additional details.\n\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } + + downloadcompletednum = 0; + downloadcompletedsize = 0; + totalfilesrequestednum = 0; + totalfilesrequestedsize = 0; + + if (fileneeded == NULL) + I_Error("CL_FinishedFileList: fileneeded == NULL"); + + for (i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) + { + totalfilesrequestednum++; + totalfilesrequestedsize += fileneeded[i].totalsize; + } + + if (totalfilesrequestedsize>>20 >= 100) + downloadsize = Z_StrDup(va("%uM",totalfilesrequestedsize>>20)); + else + downloadsize = Z_StrDup(va("%uK",totalfilesrequestedsize>>10)); + + if (serverisfull) + M_StartMessage(va(M_GetText( + "This server is full!\n" + "Download of %s additional content\nis required to join.\n" + "\n" + "You may download, load server addons,\nand wait for a slot.\n" + "\n" + "Press ENTER to continue\nor ESC to cancel.\n" + ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); + else + M_StartMessage(va(M_GetText( + "Download of %s additional content\nis required to join.\n" + "\n" + "Press ENTER to continue\nor ESC to cancel.\n" + ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); + + Z_Free(downloadsize); + cl_mode = CL_CONFIRMCONNECT; + curfadevalue = 0; + } + return true; +} + +static const char * InvalidServerReason (serverinfo_pak *info) +{ +#define EOT "\nPress ESC\n" + + // Magic number for new packet format + if (info->_255 != 255) + { + return + "Outdated server (version unknown).\n" EOT; + } + + if (strncmp(info->application, SRB2APPLICATION, sizeof + info->application)) + { + return va( + "%s cannot connect\n" + "to %s servers.\n" EOT, + SRB2APPLICATION, + info->application); + } + + if ( + info->packetversion != PACKETVERSION || + info->version != VERSION || + info->subversion != SUBVERSION + ){ + return va( + "Incompatible %s versions.\n" + "(server version %d.%d.%d)\n" EOT, + SRB2APPLICATION, + info->version / 100, + info->version % 100, + info->subversion); + } + + switch (info->refusereason) + { + case REFUSE_BANNED: + return + "You have been banned\n" + "from the server.\n" EOT; + case REFUSE_JOINS_DISABLED: + return + "The server is not accepting\n" + "joins for the moment.\n" EOT; + case REFUSE_SLOTS_FULL: + return va( + "Maximum players reached: %d\n" EOT, + info->maxplayer - D_NumBots()); + default: + if (info->refusereason) + { + return + "You can't join.\n" + "I don't know why,\n" + "but you can't join.\n" EOT; + } + } + + return NULL; + +#undef EOT +} + +/** Called by CL_ServerConnectionTicker + * + * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. + * \return False if the connection was aborted + * \sa CL_ServerConnectionTicker + * \sa CL_ConnectToServer + * + */ +static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) +{ + INT32 i; + + // serverlist is updated by GetPackets + if (serverlistcount > 0) + { + // This can be a response to our broadcast request + if (servernode == -1 || servernode >= MAXNETNODES) + { + i = 0; + servernode = serverlist[i].node; + CONS_Printf(M_GetText("Found, ")); + } + else + { + i = SL_SearchServer(servernode); + if (i < 0) + return true; + } + + if (client) + { + serverinfo_pak *info = &serverlist[i].info; + + if (info->refusereason == REFUSE_SLOTS_FULL) + serverisfull = true; + else + { + const char *reason = InvalidServerReason(info); + + // Quit here rather than downloading files + // and being refused later. + if (reason) + { + char *message = Z_StrDup(reason); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(message, NULL, MM_NOTHING); + Z_Free(message); + return false; + } + } + + D_ParseFileneeded(info->fileneedednum, info->fileneeded, 0); + + if (info->flags & SV_LOTSOFADDONS) + { + cl_mode = CL_ASKFULLFILELIST; + cl_lastcheckedfilecount = 0; + return true; + } + + cl_mode = CL_CHECKFILES; + } + else + { + cl_mode = CL_ASKJOIN; // files need not be checked for the server. + *asksent = 0; + } + + return true; + } + + // Ask the info to the server (askinfo packet) + if (*asksent + NEWTICRATE < I_GetTime()) + { + SendAskInfo(servernode); + *asksent = I_GetTime(); + } + + return true; +} + +/** Called by CL_ConnectToServer + * + * \param tmpsave The name of the gamestate file??? + * \param oldtic Used for knowing when to poll events and redraw + * \param asksent ??? + * \return False if the connection was aborted + * \sa CL_ServerConnectionSearchTicker + * \sa CL_ConnectToServer + * + */ +static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent) +{ + boolean waitmore; + + switch (cl_mode) + { + case CL_SEARCHING: + if (!CL_ServerConnectionSearchTicker(asksent)) + return false; + break; + + case CL_ASKFULLFILELIST: + if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved + cl_mode = CL_CHECKFILES; + else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent) + { + if (CL_AskFileList(fileneedednum)) + { + cl_lastcheckedfilecount = fileneedednum; + *asksent = I_GetTime() + NEWTICRATE; + } + } + break; + case CL_CHECKFILES: + if (!CL_FinishedFileList()) + return false; + break; + case CL_DOWNLOADFILES: + waitmore = false; + for (INT32 i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_DOWNLOADING + || fileneeded[i].status == FS_REQUESTED) + { + waitmore = true; + break; + } + if (waitmore) + break; // exit the case + + Snake_Free(&snake); + + cl_mode = CL_LOADFILES; + break; + case CL_LOADFILES: + if (CL_LoadServerFiles()) + { + FreeFileNeeded(); + *asksent = 0; // This ensures the first join request is right away + firstconnectattempttime = I_GetTime(); + cl_mode = CL_ASKJOIN; + } + break; + case CL_ASKJOIN: + if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server) + { + CONS_Printf(M_GetText("5 minute wait time exceeded.\n")); + CONS_Printf(M_GetText("Network game synchronization aborted.\n")); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "5 minute wait time exceeded.\n" + "You may retry connection.\n" + "\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } + + // Prepare structures to save the file + CL_PrepareDownloadSaveGame(tmpsave); + + if (I_GetTime() >= *asksent && CL_SendJoin()) + { + *asksent = I_GetTime() + NEWTICRATE*3; + cl_mode = CL_WAITJOINRESPONSE; + } + break; + case CL_WAITJOINRESPONSE: + if (I_GetTime() >= *asksent) + { + cl_mode = CL_ASKJOIN; + } + break; + case CL_DOWNLOADSAVEGAME: + // At this state, the first (and only) needed file is the gamestate + if (fileneeded[0].status == FS_FOUND) + { + // Gamestate is now handled within CL_LoadReceivedSavegame() + CL_LoadReceivedSavegame(false); + cl_mode = CL_CONNECTED; + } // don't break case continue to CL_CONNECTED + else + break; + + case CL_CONNECTED: + case CL_CONFIRMCONNECT: //logic is handled by M_ConfirmConnect + default: + break; + + // Connection closed by cancel, timeout or refusal. + case CL_ABORTED: + cl_mode = CL_SEARCHING; + return false; + } + + GetPackets(); + Net_AckTicker(); + + // Call it only once by tic + if (*oldtic != I_GetTime()) + { + I_OsPolling(); + + if (cl_mode == CL_CONFIRMCONNECT) + D_ProcessEvents(); //needed for menu system to receive inputs + else + { + // my hand has been forced and I am dearly sorry for this awful hack :vomit: + for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) + { + if (!Snake_JoyGrabber(snake, &events[eventtail])) + G_MapEventsToControls(&events[eventtail]); + } + } + + if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1] || cl_mode == CL_ABORTED) + { + CONS_Printf(M_GetText("Network game synchronization aborted.\n")); + M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING); + + Snake_Free(&snake); + + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + memset(gamekeydown, 0, NUMKEYS); + return false; + } + else if (cl_mode == CL_DOWNLOADFILES && snake) + Snake_Update(snake); + + if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME)) + FileReceiveTicker(); + + *oldtic = I_GetTime(); + + if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED) + { + if (!snake) + { + F_MenuPresTicker(); // title sky + F_TitleScreenTicker(true); + F_TitleScreenDrawer(); + } + CL_DrawConnectionStatus(); +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + M_Drawer(); //Needed for drawing messageboxes on the connection screen +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + I_UpdateNoVsync(); // page flip or blit buffer + if (moviemode) + M_SaveFrame(); + S_UpdateSounds(); + S_UpdateClosedCaptions(); + } + } + else + { + I_Sleep(cv_sleep.value); + I_UpdateTime(cv_timescale.value); + } + + return true; +} + +#define TMPSAVENAME "$$$.sav" + +void CL_ConnectToServer(void) +{ + INT32 pnumnodes, nodewaited = doomcom->numnodes, i; + tic_t oldtic; + tic_t asksent; + char tmpsave[256]; + + sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + + lastfilenum = -1; + + cl_mode = CL_SEARCHING; + + // Don't get a corrupt savegame error because tmpsave already exists + if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) + I_Error("Can't delete %s\n", tmpsave); + + if (netgame) + { + if (servernode < 0 || servernode >= MAXNETNODES) + CONS_Printf(M_GetText("Searching for a server...\n")); + else + CONS_Printf(M_GetText("Contacting the server...\n")); + } + + if (gamestate == GS_INTERMISSION) + Y_EndIntermission(); // clean up intermission graphics etc + + DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); + G_SetGamestate(GS_WAITINGPLAYERS); + wipegamestate = GS_WAITINGPLAYERS; + + ClearAdminPlayers(); + pnumnodes = 1; + oldtic = I_GetTime() - 1; + + asksent = (tic_t) - TICRATE; + firstconnectattempttime = I_GetTime(); + + i = SL_SearchServer(servernode); + + if (i != -1) + { + char *gametypestr = serverlist[i].info.gametypename; + CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername); + gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0'; + CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr); + CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100, + serverlist[i].info.version%100, serverlist[i].info.subversion); + } + SL_ClearServerList(servernode); + + do + { + // If the connection was aborted for some reason, leave + if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent)) + return; + + if (server) + { + pnumnodes = 0; + for (i = 0; i < MAXNETNODES; i++) + if (netnodes[i].ingame) + pnumnodes++; + } + } + while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes)))); + + if (netgame) + F_StartWaitingPlayers(); + + DEBFILE(va("Synchronisation Finished\n")); + + displayplayer = consoleplayer; +} + +/** Called when a PT_SERVERINFO packet is received + * + * \param node The packet sender + * \note What happens if the packet comes from a client or something like that? + * + */ +void PT_ServerInfo(SINT8 node) +{ + // compute ping in ms + const tic_t ticnow = I_GetTime(); + const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time); + const tic_t ticdiff = (ticnow - ticthen)*1000/NEWTICRATE; + netbuffer->u.serverinfo.time = (tic_t)LONG(ticdiff); + netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0; + netbuffer->u.serverinfo.application + [sizeof netbuffer->u.serverinfo.application - 1] = '\0'; + netbuffer->u.serverinfo.gametypename + [sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0'; + + SL_InsertServer(&netbuffer->u.serverinfo, node); +} + +// Helper function for packets that should only be sent by the server +// If it is NOT from the server, bail out and close the connection! +static boolean ServerOnly(SINT8 node) +{ + if (node == servernode) + return false; + + Net_CloseConnection(node); + return true; +} + +void PT_MoreFilesNeeded(SINT8 node) +{ + if (server && serverrunning) + { // But wait I thought I'm the server? + Net_CloseConnection(node); + return; + } + if (ServerOnly(node)) + return; + if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum) + { + D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first); + if (!netbuffer->u.filesneededcfg.more) + cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list + } +} + +// Negative response of client join request +void PT_ServerRefuse(SINT8 node) +{ + if (server && serverrunning) + { // But wait I thought I'm the server? + Net_CloseConnection(node); + return; + } + if (ServerOnly(node)) + return; + if (cl_mode == CL_WAITJOINRESPONSE) + { + // Save the reason so it can be displayed after quitting the netgame + char *reason = strdup(netbuffer->u.serverrefuse.reason); + if (!reason) + I_Error("Out of memory!\n"); + + if (strstr(reason, "Maximum players reached")) + { + serverisfull = true; + //Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer + //We set it back to the value of cv_nettimeout.value in CL_Reset + connectiontimeout = NEWTICRATE*7; + cl_mode = CL_ASKJOIN; + free(reason); + return; + } + + M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), + reason), NULL, MM_NOTHING); + + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + + free(reason); + + // Will be reset by caller. Signals refusal. + cl_mode = CL_ABORTED; + } +} + +// Positive response of client join request +void PT_ServerCFG(SINT8 node) +{ + if (server && serverrunning && node != servernode) + { // but wait I thought I'm the server? + Net_CloseConnection(node); + return; + } + if (ServerOnly(node)) + return; + /// \note how would this happen? and is it doing the right thing if it does? + if (cl_mode != CL_WAITJOINRESPONSE) + return; + + if (client) + { + maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic); + G_SetGametype(netbuffer->u.servercfg.gametype); + modifiedgame = netbuffer->u.servercfg.modifiedgame; + if (netbuffer->u.servercfg.usedCheats) + G_SetUsedCheats(true); + memcpy(server_context, netbuffer->u.servercfg.server_context, 8); + } + + netnodes[(UINT8)servernode].ingame = true; + serverplayer = netbuffer->u.servercfg.serverplayer; + doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum); + mynode = netbuffer->u.servercfg.clientnode; + if (serverplayer >= 0) + playernode[(UINT8)serverplayer] = servernode; + + if (netgame) + CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n")); + DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode)); + + /// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook? + /// Shouldn't they be downloaded even at intermission time? + /// Also, according to PT_ClientJoin, the server will send the savegame even during intermission... + if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* || + netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/) + cl_mode = CL_DOWNLOADSAVEGAME; + else + cl_mode = CL_CONNECTED; +} diff --git a/src/netcode/client_connection.h b/src/netcode/client_connection.h new file mode 100644 index 000000000..4d75160d4 --- /dev/null +++ b/src/netcode/client_connection.h @@ -0,0 +1,61 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 client_connection.h +/// \brief Client connection handling + +#ifndef __D_CLIENT_CONNECTION__ +#define __D_CLIENT_CONNECTION__ + +#include "../doomtype.h" +#include "d_clisrv.h" + +#define MAXSERVERLIST (MAXNETNODES-1) + +typedef struct +{ + SINT8 node; + serverinfo_pak info; +} serverelem_t; + +typedef enum +{ + CL_SEARCHING, + CL_CHECKFILES, + CL_DOWNLOADFILES, + CL_ASKJOIN, + CL_LOADFILES, + CL_WAITJOINRESPONSE, + CL_DOWNLOADSAVEGAME, + CL_CONNECTED, + CL_ABORTED, + CL_ASKFULLFILELIST, + CL_CONFIRMCONNECT +} cl_mode_t; + +extern serverelem_t serverlist[MAXSERVERLIST]; +extern UINT32 serverlistcount; + +extern cl_mode_t cl_mode; +extern boolean serverisfull; //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 +extern tic_t firstconnectattempttime; +extern UINT8 mynode; // my address pointofview server + +void CL_QueryServerList(msg_server_t *list); +void CL_UpdateServerList(boolean internetsearch, INT32 room); + +void CL_ConnectToServer(void); +boolean CL_SendJoin(void); + +void PT_ServerInfo(SINT8 node); +void PT_MoreFilesNeeded(SINT8 node); +void PT_ServerRefuse(SINT8 node); +void PT_ServerCFG(SINT8 node); + +#endif diff --git a/src/netcode/commands.c b/src/netcode/commands.c new file mode 100644 index 000000000..4b67198ba --- /dev/null +++ b/src/netcode/commands.c @@ -0,0 +1,484 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 commands.c +/// \brief Various netgame commands, such as kick and ban + +#include "commands.h" +#include "d_clisrv.h" +#include "client_connection.h" +#include "net_command.h" +#include "d_netcmd.h" +#include "d_net.h" +#include "i_net.h" +#include "protocol.h" +#include "../byteptr.h" +#include "../d_main.h" +#include "../g_game.h" +#include "../w_wad.h" +#include "../z_zone.h" +#include "../doomstat.h" +#include "../doomdef.h" +#include "../r_local.h" +#include +#include +#include +#include + +typedef struct banreason_s +{ + char *reason; + struct banreason_s *prev; //-1 + struct banreason_s *next; //+1 +} banreason_t; + +static banreason_t *reasontail = NULL; //last entry, use prev +static banreason_t *reasonhead = NULL; //1st entry, use next + +void Ban_Add(const char *reason) +{ + banreason_t *reasonlist = malloc(sizeof(*reasonlist)); + + if (!reasonlist) + return; + if (!reason) + reason = "NA"; + + reasonlist->next = NULL; + reasonlist->reason = Z_StrDup(reason); + if ((reasonlist->prev = reasontail) == NULL) + reasonhead = reasonlist; + else + reasontail->next = reasonlist; + reasontail = reasonlist; +} + +static void Ban_Clear(void) +{ + banreason_t *temp; + + I_ClearBans(); + + reasontail = NULL; + + while (reasonhead) + { + temp = reasonhead->next; + Z_Free(reasonhead->reason); + free(reasonhead); + reasonhead = temp; + } +} + +void Ban_Load_File(boolean warning) +{ + FILE *f; + const char *address, *mask; + char buffer[MAX_WADPATH]; + + if (!I_ClearBans) + return; + + f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r"); + + if (!f) + { + if (warning) + CONS_Alert(CONS_WARNING, M_GetText("Could not open ban.txt for ban list\n")); + return; + } + + Ban_Clear(); + + for (; fgets(buffer, (int)sizeof(buffer), f);) + { + address = strtok(buffer, " \t\r\n"); + mask = strtok(NULL, " \t\r\n"); + + I_SetBanAddress(address, mask); + + Ban_Add(strtok(NULL, "\r\n")); + } + + fclose(f); +} + +void D_SaveBan(void) +{ + FILE *f; + banreason_t *reasonlist = reasonhead; + const char *address, *mask; + const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt"); + + if (!reasonhead) + { + remove(path); + return; + } + + f = fopen(path, "w"); + + if (!f) + { + CONS_Alert(CONS_WARNING, M_GetText("Could not save ban list into ban.txt\n")); + return; + } + + for (size_t i = 0;(address = I_GetBanAddress(i)) != NULL;i++) + { + if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) + fprintf(f, "%s 0", address); + else + fprintf(f, "%s %s", address, mask); + + if (reasonlist && reasonlist->reason) + fprintf(f, " %s\n", reasonlist->reason); + else + fprintf(f, " %s\n", "NA"); + + if (reasonlist) reasonlist = reasonlist->next; + } + + fclose(f); +} + +void Command_ShowBan(void) //Print out ban list +{ + size_t i; + const char *address, *mask; + banreason_t *reasonlist = reasonhead; + + if (I_GetBanAddress) + CONS_Printf(M_GetText("Ban List:\n")); + else + return; + + for (i = 0;(address = I_GetBanAddress(i)) != NULL;i++) + { + if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL) + CONS_Printf("%s: %s ", sizeu1(i+1), address); + else + CONS_Printf("%s: %s/%s ", sizeu1(i+1), address, mask); + + if (reasonlist && reasonlist->reason) + CONS_Printf("(%s)\n", reasonlist->reason); + else + CONS_Printf("\n"); + + if (reasonlist) reasonlist = reasonlist->next; + } + + if (i == 0 && !address) + CONS_Printf(M_GetText("(empty)\n")); +} + +void Command_ClearBans(void) +{ + if (!I_ClearBans) + return; + + Ban_Clear(); + D_SaveBan(); +} + +void Command_Ban(void) +{ + if (COM_Argc() < 2) + { + CONS_Printf(M_GetText("Ban : ban and kick a player\n")); + return; + } + + if (!netgame) // Don't kick Tails in splitscreen! + { + CONS_Printf(M_GetText("This only works in a netgame.\n")); + return; + } + + if (server || IsPlayerAdmin(consoleplayer)) + { + UINT8 buf[3 + MAX_REASONLENGTH]; + UINT8 *p = buf; + const SINT8 pn = nametonum(COM_Argv(1)); + const INT32 node = playernode[(INT32)pn]; + + if (pn == -1 || pn == 0) + return; + + WRITEUINT8(p, pn); + + if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now + { + CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); + WRITEUINT8(p, KICK_MSG_GO_AWAY); + SendNetXCmd(XD_KICK, &buf, 2); + } + else + { + if (server) // only the server is allowed to do this right now + { + Ban_Add(COM_Argv(2)); + D_SaveBan(); // save the ban list + } + + if (COM_Argc() == 2) + { + WRITEUINT8(p, KICK_MSG_BANNED); + SendNetXCmd(XD_KICK, &buf, 2); + } + else + { + size_t j = COM_Argc(); + char message[MAX_REASONLENGTH]; + + //Steal from the motd code so you don't have to put the reason in quotes. + strlcpy(message, COM_Argv(2), sizeof message); + for (size_t i = 3; i < j; i++) + { + strlcat(message, " ", sizeof message); + strlcat(message, COM_Argv(i), sizeof message); + } + + WRITEUINT8(p, KICK_MSG_CUSTOM_BAN); + WRITESTRINGN(p, message, MAX_REASONLENGTH); + SendNetXCmd(XD_KICK, &buf, p - buf); + } + } + } + else + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + +} + +void Command_BanIP(void) +{ + if (COM_Argc() < 2) + { + CONS_Printf(M_GetText("banip : ban an ip address\n")); + return; + } + + if (server) // Only the server can use this, otherwise does nothing. + { + const char *address = (COM_Argv(1)); + const char *reason; + + if (COM_Argc() == 2) + reason = NULL; + else + reason = COM_Argv(2); + + + if (I_SetBanAddress && I_SetBanAddress(address, NULL)) + { + if (reason) + CONS_Printf("Banned IP address %s for: %s\n", address, reason); + else + CONS_Printf("Banned IP address %s\n", address); + + Ban_Add(reason); + D_SaveBan(); + } + else + { + return; + } + } +} + +void Command_ReloadBan(void) //recheck ban.txt +{ + Ban_Load_File(true); +} + +void Command_Kick(void) +{ + if (COM_Argc() < 2) + { + CONS_Printf(M_GetText("kick : kick a player\n")); + return; + } + + if (!netgame) // Don't kick Tails in splitscreen! + { + CONS_Printf(M_GetText("This only works in a netgame.\n")); + return; + } + + if (server || IsPlayerAdmin(consoleplayer)) + { + UINT8 buf[3 + MAX_REASONLENGTH]; + UINT8 *p = buf; + const SINT8 pn = nametonum(COM_Argv(1)); + + if (pn == -1 || pn == 0) + return; + + // Special case if we are trying to kick a player who is downloading the game state: + // trigger a timeout instead of kicking them, because a kick would only + // take effect after they have finished downloading + if (server && playernode[pn] != UINT8_MAX && netnodes[playernode[pn]].sendingsavegame) + { + Net_ConnectionTimeout(playernode[pn]); + return; + } + + WRITESINT8(p, pn); + + if (COM_Argc() == 2) + { + WRITEUINT8(p, KICK_MSG_GO_AWAY); + SendNetXCmd(XD_KICK, &buf, 2); + } + else + { + size_t j = COM_Argc(); + char message[MAX_REASONLENGTH]; + + //Steal from the motd code so you don't have to put the reason in quotes. + strlcpy(message, COM_Argv(2), sizeof message); + for (size_t i = 3; i < j; i++) + { + strlcat(message, " ", sizeof message); + strlcat(message, COM_Argv(i), sizeof message); + } + + WRITEUINT8(p, KICK_MSG_CUSTOM_KICK); + WRITESTRINGN(p, message, MAX_REASONLENGTH); + SendNetXCmd(XD_KICK, &buf, p - buf); + } + } + else + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); +} + +void Command_connect(void) +{ + if (COM_Argc() < 2 || *COM_Argv(1) == 0) + { + CONS_Printf(M_GetText( + "Connect (port): connect to a server\n" + "Connect ANY: connect to the first lan server found\n" + //"Connect SELF: connect to your own server.\n" + )); + return; + } + + if (Playing() || titledemo) + { + CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); + return; + } + + server = false; +/* + if (!stricmp(COM_Argv(1), "self")) + { + servernode = 0; + server = true; + /// \bug should be but... + //SV_SpawnServer(); + } + else +*/ + { + // used in menu to connect to a server in the list + if (netgame && !stricmp(COM_Argv(1), "node")) + { + servernode = (SINT8)atoi(COM_Argv(2)); + } + else if (netgame) + { + CONS_Printf(M_GetText("You cannot connect while in a game. End this game first.\n")); + return; + } + else if (I_NetOpenSocket) + { + I_NetOpenSocket(); + netgame = true; + multiplayer = true; + + if (!stricmp(COM_Argv(1), "any")) + servernode = BROADCASTADDR; + else if (I_NetMakeNodewPort) + { + if (COM_Argc() >= 3) // address AND port + servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2)); + else // address only, or address:port + servernode = I_NetMakeNode(COM_Argv(1)); + } + else + { + CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n")); + D_CloseConnection(); + return; + } + } + else + CONS_Alert(CONS_ERROR, M_GetText("There is no network driver\n")); + } + + splitscreen = false; + SplitScreen_OnChange(); + botingame = false; + botskin = 0; + CL_ConnectToServer(); +} + +void Command_GetPlayerNum(void) +{ + for (INT32 i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + { + if (serverplayer == i) + CONS_Printf(M_GetText("num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]); + else + CONS_Printf(M_GetText("\x82num:%2d node:%2d %s\n"), i, playernode[i], player_names[i]); + } +} + +/** Lists all players and their player numbers. + * + * \sa Command_GetPlayerNum + */ +void Command_Nodes(void) +{ + size_t maxlen = 0; + const char *address; + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + const size_t plen = strlen(player_names[i]); + if (playeringame[i] && plen > maxlen) + maxlen = plen; + } + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + CONS_Printf("%.2u: %*s", i, (int)maxlen, player_names[i]); + + if (playernode[i] != UINT8_MAX) + { + CONS_Printf(" - node %.2d", playernode[i]); + if (I_GetNodeAddress && (address = I_GetNodeAddress(playernode[i])) != NULL) + CONS_Printf(" - %s", address); + } + + if (IsPlayerAdmin(i)) + CONS_Printf(M_GetText(" (verified admin)")); + + if (players[i].spectator) + CONS_Printf(M_GetText(" (spectator)")); + + CONS_Printf("\n"); + } + } +} diff --git a/src/netcode/commands.h b/src/netcode/commands.h new file mode 100644 index 000000000..d328114ee --- /dev/null +++ b/src/netcode/commands.h @@ -0,0 +1,33 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 commands.h +/// \brief Various netgame commands, such as kick and ban + +#ifndef __COMMANDS__ +#define __COMMANDS__ + +#include "../doomdef.h" + +#define MAX_REASONLENGTH 30 + +void Ban_Add(const char *reason); +void D_SaveBan(void); +void Ban_Load_File(boolean warning); +void Command_ShowBan(void); +void Command_ClearBans(void); +void Command_Ban(void); +void Command_BanIP(void); +void Command_ReloadBan(void); +void Command_Kick(void); +void Command_connect(void); +void Command_GetPlayerNum(void); +void Command_Nodes(void); + +#endif diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c new file mode 100644 index 000000000..f06192f2c --- /dev/null +++ b/src/netcode/d_clisrv.c @@ -0,0 +1,1736 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 d_clisrv.c +/// \brief SRB2 Network game communication and protocol, all OS independent parts. + +#include +#ifdef __GNUC__ +#include //for unlink +#endif + +#include "../i_time.h" +#include "i_net.h" +#include "../i_system.h" +#include "../i_video.h" +#include "d_net.h" +#include "../d_main.h" +#include "../g_game.h" +#include "../st_stuff.h" +#include "../hu_stuff.h" +#include "../keys.h" +#include "../m_menu.h" +#include "../console.h" +#include "d_netfil.h" +#include "../byteptr.h" +#include "../p_saveg.h" +#include "../z_zone.h" +#include "../p_local.h" +#include "../m_misc.h" +#include "../am_map.h" +#include "../m_random.h" +#include "mserv.h" +#include "../y_inter.h" +#include "../r_local.h" +#include "../m_argv.h" +#include "../p_setup.h" +#include "../lzf.h" +#include "../lua_script.h" +#include "../lua_hook.h" +#include "../lua_libs.h" +#include "../md5.h" +#include "../m_perfstats.h" +#include "server_connection.h" +#include "client_connection.h" +#include "tic_command.h" +#include "net_command.h" +#include "gamestate.h" +#include "commands.h" +#include "protocol.h" + +// +// NETWORKING +// +// gametic is the tic about to (or currently being) run +// Server: +// maketic is the tic that hasn't had control made for it yet +// nettics is the tic for each node +// firstticstosend is the lowest value of nettics +// Client: +// neededtic is the tic needed by the client to run the game +// firstticstosend is used to optimize a condition +// Normally maketic >= gametic > 0 + +boolean server = true; // true or false but !server == client +boolean serverrunning = false; +INT32 serverplayer = 0; +char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support) + +netnode_t netnodes[MAXNETNODES]; + +// Server specific vars +UINT8 playernode[MAXPLAYERS]; + +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. +static INT32 pingtimeout[MAXPLAYERS]; +tic_t servermaxping = 800; // server's max ping. Defaults to 800 + +tic_t maketic; + +INT16 consistancy[BACKUPTICS]; + +// true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks +boolean hu_stopped = false; + +UINT8 adminpassmd5[16]; +boolean adminpasswordset = false; + +tic_t neededtic; +SINT8 servernode = 0; // the number of the server node + +boolean acceptnewnode = true; + +UINT16 software_MAXPACKETLENGTH; + +static tic_t gametime = 0; + +static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}}; +consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL); + +static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}}; +consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL); + +consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); + +static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}}; +consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL); + +consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL); + +void ResetNode(INT32 node) +{ + memset(&netnodes[node], 0, sizeof(*netnodes)); + netnodes[node].player = -1; + netnodes[node].player2 = -1; +} + +void CL_Reset(void) +{ + if (metalrecording) + G_StopMetalRecording(false); + if (metalplayback) + G_StopMetalDemo(); + if (demorecording) + G_CheckDemoStatus(); + + // reset client/server code + DEBFILE(va("\n-=-=-=-=-=-=-= Client reset =-=-=-=-=-=-=-\n\n")); + + if (servernode > 0 && servernode < MAXNETNODES) + { + netnodes[(UINT8)servernode].ingame = false; + Net_CloseConnection(servernode); + } + D_CloseConnection(); // netgame = false + multiplayer = false; + servernode = 0; + server = true; + doomcom->numnodes = 1; + doomcom->numslots = 1; + SV_StopServer(); + SV_ResetServer(); + + // make sure we don't leave any fileneeded gunk over from a failed join + FreeFileNeeded(); + fileneedednum = 0; + + totalfilesrequestednum = 0; + totalfilesrequestedsize = 0; + firstconnectattempttime = 0; + serverisfull = false; + connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack + + // D_StartTitle should get done now, but the calling function will handle it +} + +// +// CL_ClearPlayer +// +// Clears the player data so that a future client can use this slot +// +void CL_ClearPlayer(INT32 playernum) +{ + if (players[playernum].mo) + P_RemoveMobj(players[playernum].mo); + memset(&players[playernum], 0, sizeof (player_t)); + memset(playeraddress[playernum], 0, sizeof(*playeraddress)); +} + +// Xcmd XD_ADDPLAYER +static void Got_AddPlayer(UINT8 **p, INT32 playernum) +{ + INT16 node, newplayernum; + boolean splitscreenplayer; + boolean rejoined; + player_t *newplayer; + + if (playernum != serverplayer && !IsPlayerAdmin(playernum)) + { + // protect against hacked/buggy client + CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]); + if (server) + SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + return; + } + + node = READUINT8(*p); + newplayernum = READUINT8(*p); + splitscreenplayer = newplayernum & 0x80; + newplayernum &= ~0x80; + + rejoined = playeringame[newplayernum]; + + if (!rejoined) + { + // Clear player before joining, lest some things get set incorrectly + // HACK: don't do this for splitscreen, it relies on preset values + if (!splitscreen && !botingame) + CL_ClearPlayer(newplayernum); + playeringame[newplayernum] = true; + G_AddPlayer(newplayernum); + if (newplayernum+1 > doomcom->numslots) + doomcom->numslots = (INT16)(newplayernum+1); + + if (server && I_GetNodeAddress) + { + char addressbuffer[64]; + const char *address = I_GetNodeAddress(node); + if (address) // MI: fix msvcrt.dll!_mbscat crash? + { + strcpy(addressbuffer, address); + strcpy(playeraddress[newplayernum], + I_NetSplitAddress(addressbuffer, NULL)); + } + } + } + + newplayer = &players[newplayernum]; + + newplayer->jointime = 0; + newplayer->quittime = 0; + + READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME); + + // the server is creating my player + if (node == mynode) + { + playernode[newplayernum] = 0; // for information only + if (!splitscreenplayer) + { + consoleplayer = newplayernum; + displayplayer = newplayernum; + secondarydisplayplayer = newplayernum; + DEBFILE("spawning me\n"); + ticcmd_oldangleturn[0] = newplayer->oldrelangleturn; + } + else + { + secondarydisplayplayer = newplayernum; + DEBFILE("spawning my brother\n"); + if (botingame) + newplayer->bot = 1; + ticcmd_oldangleturn[1] = newplayer->oldrelangleturn; + } + P_ForceLocalAngle(newplayer, (angle_t)(newplayer->angleturn << 16)); + D_SendPlayerConfig(); + addedtogame = true; + + if (rejoined) + { + if (newplayer->mo) + { + newplayer->viewheight = 41*newplayer->height/48; + + if (newplayer->mo->eflags & MFE_VERTICALFLIP) + newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight; + else + newplayer->viewz = newplayer->mo->z + newplayer->viewheight; + } + + // wake up the status bar + ST_Start(); + // wake up the heads up text + HU_Start(); + + if (camera.chase && !splitscreenplayer) + P_ResetCamera(newplayer, &camera); + if (camera2.chase && splitscreenplayer) + P_ResetCamera(newplayer, &camera2); + } + } + + if (netgame) + { + char joinmsg[256]; + + if (rejoined) + strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)")); + else + strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)")); + strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum)); + + // Merge join notification + IP to avoid clogging console/chat + if (server && cv_showjoinaddress.value && I_GetNodeAddress) + { + const char *address = I_GetNodeAddress(node); + if (address) + strcat(joinmsg, va(" (%s)", address)); + } + + HU_AddChatText(joinmsg, false); + } + + if (server && multiplayer && motd[0] != '\0') + COM_BufAddText(va("sayto %d %s\n", newplayernum, motd)); + + if (!rejoined) + LUA_HookInt(newplayernum, HOOK(PlayerJoin)); +} + +static void UnlinkPlayerFromNode(INT32 playernum) +{ + INT32 node = playernode[playernum]; + + if (node == UINT8_MAX) + return; + + playernode[playernum] = UINT8_MAX; + + netnodes[node].numplayers--; + if (netnodes[node].numplayers <= 0) + { + netnodes[node].ingame = false; + Net_CloseConnection(node); + ResetNode(node); + } +} + +static void PT_ClientQuit(SINT8 node, INT32 netconsole) +{ + if (client) + return; + + if (netnodes[node].ingame && netconsole != -1 && playeringame[netconsole]) + SendKicksForNode(node, KICK_MSG_PLAYER_QUIT | KICK_MSG_KEEP_BODY); + + Net_CloseConnection(node); + netnodes[node].ingame = false; + netnodes[node].player = -1; + netnodes[node].player2 = -1; +} + +static void Got_KickCmd(UINT8 **p, INT32 playernum) +{ + INT32 pnum, msg; + char buf[3 + MAX_REASONLENGTH]; + char *reason = buf; + kickreason_t kickreason = KR_KICK; + boolean keepbody; + + pnum = READUINT8(*p); + msg = READUINT8(*p); + keepbody = (msg & KICK_MSG_KEEP_BODY) != 0; + msg &= ~KICK_MSG_KEEP_BODY; + + if (pnum == serverplayer && IsPlayerAdmin(playernum)) + { + CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n")); + + if (server) + COM_BufAddText("quit\n"); + + return; + } + + // Is playernum authorized to make this kick? + if (playernum != serverplayer && !IsPlayerAdmin(playernum) + && !(playernode[playernum] != UINT8_MAX && netnodes[playernode[playernum]].numplayers == 2 + && netnodes[playernode[playernum]].player2 == pnum)) + { + // We received a kick command from someone who isn't the + // server or admin, and who isn't in splitscreen removing + // player 2. Thus, it must be someone with a modified + // binary, trying to kick someone but without having + // authorization. + + // We deal with this by changing the kick reason to + // "consistency failure" and kicking the offending user + // instead. + + // Note: Splitscreen in netgames is broken because of + // this. Only the server has any idea of which players + // are using splitscreen on the same computer, so + // clients cannot always determine if a kick is + // legitimate. + + CONS_Alert(CONS_WARNING, M_GetText("Illegal kick command received from %s for player %d\n"), player_names[playernum], pnum); + + // In debug, print a longer message with more details. + // TODO Callum: Should we translate this? +/* + CONS_Debug(DBG_NETPLAY, + "So, you must be asking, why is this an illegal kick?\n" + "Well, let's take a look at the facts, shall we?\n" + "\n" + "playernum (this is the guy who did it), he's %d.\n" + "pnum (the guy he's trying to kick) is %d.\n" + "playernum's node is %d.\n" + "That node has %d players.\n" + "Player 2 on that node is %d.\n" + "pnum's node is %d.\n" + "That node has %d players.\n" + "Player 2 on that node is %d.\n" + "\n" + "If you think this is a bug, please report it, including all of the details above.\n", + playernum, pnum, + playernode[playernum], netnodes[playernode[playernum]].numplayers, + netnodes[playernode[playernum]].player2, + playernode[pnum], netnodes[playernode[pnum]].numplayers, + netnodes[playernode[pnum]].player2); +*/ + pnum = playernum; + msg = KICK_MSG_CON_FAIL; + keepbody = true; + } + + //CONS_Printf("\x82%s ", player_names[pnum]); + + // If a verified admin banned someone, the server needs to know about it. + // If the playernum isn't zero (the server) then the server needs to record the ban. + if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN)) + { + if (I_Ban && !I_Ban(playernode[(INT32)pnum])) + CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n")); + else + Ban_Add(reason); + } + + switch (msg) + { + case KICK_MSG_GO_AWAY: + if (!players[pnum].quittime) + HU_AddChatText(va("\x82*%s has been kicked (No reason given)", player_names[pnum]), false); + kickreason = KR_KICK; + break; + case KICK_MSG_PING_HIGH: + HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false); + kickreason = KR_PINGLIMIT; + break; + case KICK_MSG_CON_FAIL: + HU_AddChatText(va("\x82*%s left the game (Synch failure)", player_names[pnum]), false); + kickreason = KR_SYNCH; + + if (M_CheckParm("-consisdump")) // Helps debugging some problems + { + CONS_Printf(M_GetText("Player kicked is #%d, dumping consistency...\n"), pnum); + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + CONS_Printf("-------------------------------------\n"); + CONS_Printf("Player %d: %s\n", i, player_names[i]); + CONS_Printf("Skin: %d\n", players[i].skin); + CONS_Printf("Color: %d\n", players[i].skincolor); + CONS_Printf("Speed: %d\n",players[i].speed>>FRACBITS); + if (players[i].mo) + { + if (!players[i].mo->skin) + CONS_Printf("Mobj skin: NULL!\n"); + else + CONS_Printf("Mobj skin: %s\n", ((skin_t *)players[i].mo->skin)->name); + CONS_Printf("Position: %d, %d, %d\n", players[i].mo->x, players[i].mo->y, players[i].mo->z); + if (!players[i].mo->state) + CONS_Printf("State: S_NULL\n"); + else + CONS_Printf("State: %d\n", (statenum_t)(players[i].mo->state-states)); + } + else + CONS_Printf("Mobj: NULL\n"); + CONS_Printf("-------------------------------------\n"); + } + } + break; + case KICK_MSG_TIMEOUT: + HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false); + kickreason = KR_TIMEOUT; + break; + case KICK_MSG_PLAYER_QUIT: + if (netgame && !players[pnum].quittime) // not splitscreen/bots or soulless body + HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); + kickreason = KR_LEAVE; + break; + case KICK_MSG_BANNED: + HU_AddChatText(va("\x82*%s has been banned (No reason given)", player_names[pnum]), false); + kickreason = KR_BAN; + break; + case KICK_MSG_CUSTOM_KICK: + READSTRINGN(*p, reason, MAX_REASONLENGTH+1); + HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false); + kickreason = KR_KICK; + break; + case KICK_MSG_CUSTOM_BAN: + READSTRINGN(*p, reason, MAX_REASONLENGTH+1); + HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false); + kickreason = KR_BAN; + break; + } + + if (pnum == consoleplayer) + { + LUA_HookBool(false, HOOK(GameQuit)); +#ifdef DUMPCONSISTENCY + if (msg == KICK_MSG_CON_FAIL) SV_SavedGame(); +#endif + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + if (msg == KICK_MSG_CON_FAIL) + M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING); + else if (msg == KICK_MSG_PING_HIGH) + M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING); + else if (msg == KICK_MSG_BANNED) + M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING); + else if (msg == KICK_MSG_CUSTOM_KICK) + M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); + else if (msg == KICK_MSG_CUSTOM_BAN) + M_StartMessage(va(M_GetText("You have been banned\n(%s)\nPress ESC\n"), reason), NULL, MM_NOTHING); + else + M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING); + } + else if (keepbody) + { + if (server) + UnlinkPlayerFromNode(pnum); + players[pnum].quittime = 1; + } + else + CL_RemovePlayer(pnum, kickreason); +} + +// If in a special stage, redistribute the player's +// spheres across the remaining players. +// I feel like this shouldn't even be in this file at all, but well. +static void RedistributeSpecialStageSpheres(INT32 playernum) +{ + if (!G_IsSpecialStage(gamemap) || D_NumPlayers() <= 1) + return; + + INT32 count = D_NumPlayers() - 1; + INT32 spheres = players[playernum].spheres; + INT32 rings = players[playernum].rings; + + while (spheres || rings) + { + INT32 sincrement = max(spheres / count, 1); + INT32 rincrement = max(rings / count, 1); + + INT32 n; + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || i == playernum) + continue; + + n = min(spheres, sincrement); + P_GivePlayerSpheres(&players[i], n); + spheres -= n; + + n = min(rings, rincrement); + P_GivePlayerRings(&players[i], n); + rings -= n; + } + } +} + +// +// CL_RemovePlayer +// +// Removes a player from the current game +// +void CL_RemovePlayer(INT32 playernum, kickreason_t reason) +{ + // Sanity check: exceptional cases (i.e. c-fails) can cause multiple + // kick commands to be issued for the same player. + if (!playeringame[playernum]) + return; + + if (server) + UnlinkPlayerFromNode(playernum); + + if (gametyperules & GTR_TEAMFLAGS) + P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you! + + RedistributeSpecialStageSpheres(playernum); + + LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting + + // don't look through someone's view who isn't there + if (playernum == displayplayer) + { + // Call ViewpointSwitch hooks here. + // The viewpoint was forcibly changed. + LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true); + displayplayer = consoleplayer; + } + + // Reset player data + CL_ClearPlayer(playernum); + + // remove avatar of player + playeringame[playernum] = false; + while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1) + doomcom->numslots--; + + // Reset the name + sprintf(player_names[playernum], "Player %d", playernum+1); + + player_name_changes[playernum] = 0; + + if (IsPlayerAdmin(playernum)) + { + RemoveAdminPlayer(playernum); // don't stay admin after you're gone + } + + LUA_InvalidatePlayer(&players[playernum]); + + if (G_TagGametype()) //Check if you still have a game. Location flexible. =P + P_CheckSurvivors(); + else if (gametyperules & GTR_RACE) + P_CheckRacers(); +} + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other players +// +void D_QuitNetGame(void) +{ + mousegrabbedbylua = true; + I_UpdateMouseGrab(); + + if (!netgame || !netbuffer) + return; + + DEBFILE("===========================================================================\n" + " Quitting Game, closing connection\n" + "===========================================================================\n"); + + // abort send/receive of files + CloseNetFile(); + RemoveAllLuaFileTransfers(); + waitingforluafiletransfer = false; + waitingforluafilecommand = false; + + if (server) + { + netbuffer->packettype = PT_SERVERSHUTDOWN; + for (INT32 i = 0; i < MAXNETNODES; i++) + if (netnodes[i].ingame) + HSendPacket(i, true, 0, 0); +#ifdef MASTERSERVER + if (serverrunning && ms_RoomId > 0) + UnregisterServer(); +#endif + } + else if (servernode > 0 && servernode < MAXNETNODES && netnodes[(UINT8)servernode].ingame) + { + netbuffer->packettype = PT_CLIENTQUIT; + HSendPacket(servernode, true, 0, 0); + } + + D_CloseConnection(); + ClearAdminPlayers(); + + DEBFILE("===========================================================================\n" + " Log finish\n" + "===========================================================================\n"); +#ifdef DEBUGFILE + if (debugfile) + { + fclose(debugfile); + debugfile = NULL; + } +#endif +} + +void CL_HandleTimeout(void) +{ + LUA_HookBool(false, HOOK(GameQuit)); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING); +} + +void CL_AddSplitscreenPlayer(void) +{ + if (cl_mode == CL_CONNECTED) + CL_SendJoin(); +} + +void CL_RemoveSplitscreenPlayer(void) +{ + if (cl_mode != CL_CONNECTED) + return; + + SendKick(secondarydisplayplayer, KICK_MSG_PLAYER_QUIT); +} + +void SV_ResetServer(void) +{ + // +1 because this command will be executed in com_executebuffer in + // tryruntic so gametic will be incremented, anyway maketic > gametic + // is not an issue + + maketic = gametic + 1; + neededtic = maketic; + tictoclear = maketic; + + joindelay = 0; + + for (INT32 i = 0; i < MAXNETNODES; i++) + ResetNode(i); + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + LUA_InvalidatePlayer(&players[i]); + playeringame[i] = false; + playernode[i] = UINT8_MAX; + memset(playeraddress[i], 0, sizeof(*playeraddress)); + sprintf(player_names[i], "Player %d", i + 1); + adminplayers[i] = -1; // Populate the entire adminplayers array with -1. + } + + memset(player_name_changes, 0, sizeof player_name_changes); + + mynode = 0; + cl_packetmissed = false; + cl_redownloadinggamestate = false; + + if (dedicated) + { + netnodes[0].ingame = true; + serverplayer = 0; + } + else + serverplayer = consoleplayer; + + if (server) + servernode = 0; + + doomcom->numslots = 0; + + // clear server_context + memset(server_context, '-', 8); + + CV_RevertNetVars(); + + // Ensure synched when creating a new server + M_CopyGameData(serverGamedata, clientGamedata); + + DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n"); +} + +static inline void SV_GenContext(void) +{ + // generate server_context, as exactly 8 bytes of randomly mixed A-Z and a-z + // (hopefully M_Random is initialized!! if not this will be awfully silly!) + for (UINT8 i = 0; i < 8; i++) + { + const char a = M_RandomKey(26*2); + if (a < 26) // uppercase + server_context[i] = 'A'+a; + else // lowercase + server_context[i] = 'a'+(a-26); + } +} + +void SV_SpawnServer(void) +{ + if (demoplayback) + G_StopDemo(); // reset engine parameter + if (metalplayback) + G_StopMetalDemo(); + + if (!serverrunning) + { + CONS_Printf(M_GetText("Starting Server....\n")); + serverrunning = true; + SV_ResetServer(); + SV_GenContext(); + if (netgame && I_NetOpenSocket) + { + I_NetOpenSocket(); +#ifdef MASTERSERVER + if (ms_RoomId > 0) + RegisterServer(); +#endif + } + + // non dedicated server just connect to itself + if (!dedicated) + CL_ConnectToServer(); + else doomcom->numslots = 1; + } +} + +// called at singleplayer start and stopdemo +void SV_StartSinglePlayerServer(void) +{ + server = true; + netgame = false; + multiplayer = false; + G_SetGametype(GT_COOP); + + // no more tic the game with this settings! + SV_StopServer(); + + if (splitscreen) + multiplayer = true; +} + +void SV_StopServer(void) +{ + if (gamestate == GS_INTERMISSION) + Y_EndIntermission(); + gamestate = wipegamestate = GS_NULL; + + localtextcmd[0] = 0; + localtextcmd2[0] = 0; + + for (tic_t i = firstticstosend; i < firstticstosend + BACKUPTICS; i++) + D_Clearticcmd(i); + + consoleplayer = 0; + cl_mode = CL_SEARCHING; + maketic = gametic+1; + neededtic = maketic; + serverrunning = false; +} + +/** Called when a PT_SERVERSHUTDOWN packet is received + * + * \param node The packet sender (should be the server) + * + */ +static void PT_ServerShutdown(SINT8 node) +{ + if (node != servernode || server || cl_mode == CL_SEARCHING) + return; + + (void)node; + LUA_HookBool(false, HOOK(GameQuit)); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText("Server has shutdown\n\nPress Esc\n"), NULL, MM_NOTHING); +} + +static void PT_Login(SINT8 node, INT32 netconsole) +{ + (void)node; + + if (client) + return; + +#ifndef NOMD5 + UINT8 finalmd5[16];/* Well, it's the cool thing to do? */ + + if (doomcom->datalength < 16)/* ignore partial sends */ + return; + + if (!adminpasswordset) + { + CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]); + return; + } + + // Do the final pass to compare with the sent md5 + D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5); + + if (!memcmp(netbuffer->u.md5sum, finalmd5, 16)) + { + CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]); + COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately + } + else + CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]); +#else + (void)netconsole; +#endif +} + +static void PT_AskLuaFile(SINT8 node) +{ + if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED) + AddLuaFileToSendQueue(node, luafiletransfers->realfilename); +} + +static void PT_HasLuaFile(SINT8 node) +{ + if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING) + SV_HandleLuaFileSent(node); +} + +static void PT_SendingLuaFile(SINT8 node) +{ + (void)node; + + if (client) + CL_PrepareDownloadLuaFile(); +} + +/* +Ping Update except better: +We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out. +If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave. +*/ + +static inline void PingUpdate(void) +{ + boolean laggers[MAXPLAYERS]; + UINT8 numlaggers = 0; + memset(laggers, 0, sizeof(boolean) * MAXPLAYERS); + + netbuffer->packettype = PT_PING; + + //check for ping limit breakage. + if (cv_maxping.value) + { + for (INT32 i = 1; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].quittime + && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value)) + { + if (players[i].jointime > 30 * TICRATE) + laggers[i] = true; + numlaggers++; + } + else + pingtimeout[i] = 0; + } + + //kick lagging players... unless everyone but the server's ping sucks. + //in that case, it is probably the server's fault. + if (numlaggers < D_NumPlayers() - 1) + { + for (INT32 i = 1; i < MAXPLAYERS; i++) + { + if (playeringame[i] && laggers[i]) + { + pingtimeout[i]++; + // ok your net has been bad for too long, you deserve to die. + if (pingtimeout[i] > cv_pingtimeout.value) + { + pingtimeout[i] = 0; + SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY); + } + } + /* + you aren't lagging, + but you aren't free yet. + In case you'll keep spiking, + we just make the timer go back down. (Very unstable net must still get kicked). + */ + else + pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1); + } + } + } + + //make the ping packet and clear server data for next one + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount; + //server takes a snapshot of the real ping for display. + //otherwise, pings fluctuate a lot and would be odd to look at. + playerpingtable[i] = realpingtable[i] / pingmeasurecount; + realpingtable[i] = 0; //Reset each as we go. + } + + // send the server's maxping as last element of our ping table. This is useful to let us know when we're about to get kicked. + netbuffer->u.pingtable[MAXPLAYERS] = cv_maxping.value; + + //send out our ping packets + for (INT32 i = 0; i < MAXNETNODES; i++) + if (netnodes[i].ingame) + HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1)); + + pingmeasurecount = 1; //Reset count +} + +static void PT_Ping(SINT8 node, INT32 netconsole) +{ + // Only accept PT_PING from the server. + if (node != servernode) + { + CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node); + if (server) + SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + return; + } + + //Update client ping table from the server. + if (client) + { + for (INT32 i = 0; i < MAXPLAYERS; i++) + if (playeringame[i]) + playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i]; + + servermaxping = (tic_t)netbuffer->u.pingtable[MAXPLAYERS]; + } +} + +static void PT_BasicKeepAlive(SINT8 node, INT32 netconsole) +{ + if (client) + return; + + // This should probably still timeout though, as the node should always have a player 1 number + if (netconsole == -1) + return; + + // If a client sends this it should mean they are done receiving the savegame + netnodes[node].sendingsavegame = false; + + // As long as clients send keep alives, the server can keep running, so reset the timeout + /// \todo Use a separate cvar for that kind of timeout? + netnodes[node].freezetimeout = I_GetTime() + connectiontimeout; + return; +} + +// Confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_BASICKEEPALIVE +// Used during wipes to tell the server that a node is still connected +static void CL_SendClientKeepAlive(void) +{ + netbuffer->packettype = PT_BASICKEEPALIVE; + + HSendPacket(servernode, false, 0, 0); +} + +static void SV_SendServerKeepAlive(void) +{ + for (INT32 n = 1; n < MAXNETNODES; n++) + { + if (netnodes[n].ingame) + { + netbuffer->packettype = PT_BASICKEEPALIVE; + HSendPacket(n, false, 0, 0); + } + } +} + +/** Handles a packet received from a node that isn't in game + * + * \param node The packet sender + * \todo Choose a better name, as the packet can also come from the server apparently? + * \sa HandlePacketFromPlayer + * \sa GetPackets + * + */ +static void HandlePacketFromAwayNode(SINT8 node) +{ + if (node != servernode) + DEBFILE(va("Received packet from unknown host %d\n", node)); + + switch (netbuffer->packettype) + { + case PT_ASKINFOVIAMS : PT_AskInfoViaMS (node ); break; + case PT_SERVERINFO : PT_ServerInfo (node ); break; + case PT_TELLFILESNEEDED: PT_TellFilesNeeded(node ); break; + case PT_MOREFILESNEEDED: PT_MoreFilesNeeded(node ); break; + case PT_ASKINFO : PT_AskInfo (node ); break; + case PT_SERVERREFUSE : PT_ServerRefuse (node ); break; + case PT_SERVERCFG : PT_ServerCFG (node ); break; + case PT_FILEFRAGMENT : PT_FileFragment (node, -1); break; + case PT_FILEACK : PT_FileAck (node ); break; + case PT_FILERECEIVED : PT_FileReceived (node ); break; + case PT_REQUESTFILE : PT_RequestFile (node ); break; + case PT_CLIENTQUIT : PT_ClientQuit (node, -1); break; + case PT_SERVERTICS : PT_ServerTics (node, -1); break; + case PT_CLIENTJOIN : PT_ClientJoin (node ); break; + case PT_SERVERSHUTDOWN : PT_ServerShutdown (node ); break; + case PT_CLIENTCMD : break; // This is not an "unknown packet" + case PT_PLAYERINFO : break; // This is not an "unknown packet" + + default: + DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype)); + Net_CloseConnection(node); + } +} + +/** Handles a packet received from a node that is in game + * + * \param node The packet sender + * \todo Choose a better name + * \sa HandlePacketFromAwayNode + * \sa GetPackets + * + */ +static void HandlePacketFromPlayer(SINT8 node) +{ + INT32 netconsole; + + if (dedicated && node == 0) + netconsole = 0; + else + netconsole = netnodes[node].player; + +#ifdef PARANOIA + if (netconsole >= MAXPLAYERS) + I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole); +#endif + + switch (netbuffer->packettype) + { + // SERVER RECEIVE + case PT_CLIENTCMD: + case PT_CLIENT2CMD: + case PT_CLIENTMIS: + case PT_CLIENT2MIS: + case PT_NODEKEEPALIVE: + case PT_NODEKEEPALIVEMIS: + PT_ClientCmd(node, netconsole); + break; + case PT_BASICKEEPALIVE : PT_BasicKeepAlive (node, netconsole); break; + case PT_TEXTCMD : PT_TextCmd (node, netconsole); break; + case PT_TEXTCMD2 : PT_TextCmd (node, netconsole); break; + case PT_LOGIN : PT_Login (node, netconsole); break; + case PT_CLIENTQUIT : PT_ClientQuit (node, netconsole); break; + case PT_CANRECEIVEGAMESTATE: PT_CanReceiveGamestate(node ); break; + case PT_ASKLUAFILE : PT_AskLuaFile (node ); break; + case PT_HASLUAFILE : PT_HasLuaFile (node ); break; + case PT_RECEIVEDGAMESTATE : PT_ReceivedGamestate (node ); break; + case PT_SERVERINFO : PT_ServerInfo (node ); break; + + // CLIENT RECEIVE + case PT_SERVERTICS : PT_ServerTics (node, netconsole); break; + case PT_PING : PT_Ping (node, netconsole); break; + case PT_FILEFRAGMENT : PT_FileFragment (node, netconsole); break; + case PT_FILEACK : PT_FileAck (node ); break; + case PT_FILERECEIVED : PT_FileReceived (node ); break; + case PT_WILLRESENDGAMESTATE: PT_WillResendGamestate(node ); break; + case PT_SENDINGLUAFILE : PT_SendingLuaFile (node ); break; + case PT_SERVERSHUTDOWN : PT_ServerShutdown (node ); break; + case PT_SERVERCFG : break; + case PT_CLIENTJOIN : break; + + default: + DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n", + netbuffer->packettype, node)); + } +} + +/** Handles all received packets, if any + * + * \todo Add details to this description (lol) + * + */ +void GetPackets(void) +{ + while (HGetPacket()) + { + SINT8 node = doomcom->remotenode; + + // Packet received from someone already playing + if (netnodes[node].ingame) + HandlePacketFromPlayer(node); + // Packet received from someone not playing + else + HandlePacketFromAwayNode(node); + } +} + +boolean TryRunTics(tic_t realtics) +{ + // the machine has lagged but it is not so bad + if (realtics > TICRATE/7) + { + if (server) + realtics = 1; + else + realtics = TICRATE/7; + } + + if (singletics) + realtics = 1; + + if (realtics >= 1) + { + COM_BufTicker(); + if (mapchangepending) + D_MapChange(-1, 0, ultimatemode, false, 2, false, fromlevelselect); // finish the map change + } + + NetUpdate(); + + if (demoplayback) + { + neededtic = gametic + realtics; + // start a game after a demo + maketic += realtics; + firstticstosend = maketic; + tictoclear = firstticstosend; + } + + GetPackets(); + +#ifdef DEBUGFILE + if (debugfile && (realtics || neededtic > gametic)) + { + fprintf(debugfile, "------------ Tryruntic: REAL:%d NEED:%d GAME:%d LOAD: %d\n", + realtics, neededtic, gametic, debugload); + debugload = 100000; + } +#endif + + if (neededtic > gametic) + { + if (realtics) + hu_stopped = false; + + if (advancedemo) + { + if (timedemo_quit) + COM_ImmedExecute("quit"); + else + D_StartTitle(); + } + else + // run the count * tics + while (neededtic > gametic) + { + boolean update_stats = !(paused || P_AutoPause()); + + DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic)); + + if (update_stats) + PS_START_TIMING(ps_tictime); + + G_Ticker((gametic % NEWTICRATERATIO) == 0); + ExtraDataTicker(); + gametic++; + consistancy[gametic%BACKUPTICS] = Consistancy(); + + if (update_stats) + { + PS_STOP_TIMING(ps_tictime); + PS_UpdateTickStats(); + } + + // Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame. + if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value) + break; + } + + return true; + } + else + { + if (realtics) + hu_stopped = true; + + return false; + } +} + +static void UpdatePingTable(void) +{ + if (server) + { + if (netgame && !(gametime % 35)) // update once per second. + PingUpdate(); + // update node latency values so we can take an average later. + for (INT32 i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && playernode[i] != UINT8_MAX) + realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i])); + pingmeasurecount++; + } +} + +// Handle timeouts to prevent definitive freezes from happenning +static void HandleNodeTimeouts(void) +{ + if (server) + { + for (INT32 i = 1; i < MAXNETNODES; i++) + if (netnodes[i].ingame && netnodes[i].freezetimeout < I_GetTime()) + Net_ConnectionTimeout(i); + + // In case the cvar value was lowered + if (joindelay) + joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE); + } +} + +// Keep the network alive while not advancing tics! +void NetKeepAlive(void) +{ + tic_t nowtime; + INT32 realtics; + + nowtime = I_GetTime(); + realtics = nowtime - gametime; + + // return if there's no time passed since the last call + if (realtics <= 0) // nothing new to update + return; + + UpdatePingTable(); + + GetPackets(); + +#ifdef MASTERSERVER + MasterClient_Ticker(); +#endif + + if (client) + { + // send keep alive + CL_SendClientKeepAlive(); + // No need to check for resynch because we aren't running any tics + } + else + { + SV_SendServerKeepAlive(); + } + + // No else because no tics are being run and we can't resynch during this + + Net_AckTicker(); + HandleNodeTimeouts(); + FileSendTicker(); +} + +void NetUpdate(void) +{ + static tic_t resptime = 0; + tic_t nowtime; + INT32 realtics; + + nowtime = I_GetTime(); + realtics = nowtime - gametime; + + if (realtics <= 0) // nothing new to update + return; + + if (realtics > 5) + { + if (server) + realtics = 1; + else + realtics = 5; + } + + if (server && dedicated && gamestate == GS_LEVEL) + { + const tic_t dedicatedidletime = cv_dedicatedidletime.value * TICRATE; + static tic_t dedicatedidletimeprev = 0; + static tic_t dedicatedidle = 0; + + if (dedicatedidletime > 0) + { + INT32 i; + + for (i = 1; i < MAXNETNODES; ++i) + if (netnodes[i].ingame) + { + if (dedicatedidle >= dedicatedidletime) + { + CONS_Printf("DEDICATED: Awakening from idle (Node %d detected...)\n", i); + dedicatedidle = 0; + } + break; + } + + if (i == MAXNETNODES) + { + if (leveltime == 2) + { + // On next tick... + dedicatedidle = dedicatedidletime-1; + } + else if (dedicatedidle >= dedicatedidletime) + { + if (D_GetExistingTextcmd(gametic, 0) || D_GetExistingTextcmd(gametic+1, 0)) + { + CONS_Printf("DEDICATED: Awakening from idle (Netxcmd detected...)\n"); + dedicatedidle = 0; + } + else + { + realtics = 0; + } + } + else if ((dedicatedidle += realtics) >= dedicatedidletime) + { + const char *idlereason = "at round start"; + if (leveltime > 3) + idlereason = va("for %d seconds", dedicatedidle/TICRATE); + + CONS_Printf("DEDICATED: No nodes %s, idling...\n", idlereason); + realtics = 0; + dedicatedidle = dedicatedidletime; + } + } + } + else + { + if (dedicatedidletimeprev > 0 && dedicatedidle >= dedicatedidletimeprev) + { + CONS_Printf("DEDICATED: Awakening from idle (Idle disabled...)\n"); + } + dedicatedidle = 0; + } + + dedicatedidletimeprev = dedicatedidletime; + } + + gametime = nowtime; + + UpdatePingTable(); + + if (client) + maketic = neededtic; + + Local_Maketic(realtics); + + if (server) + CL_SendClientCmd(); // send it + + GetPackets(); // get packet from client or from server + + // The client sends the command after receiving from the server + // The server sends it before because this is better in single player + +#ifdef MASTERSERVER + MasterClient_Ticker(); // Acking the Master Server +#endif + + if (client) + { + // If the client just finished redownloading the game state, load it + if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND) + CL_ReloadReceivedSavegame(); + + CL_SendClientCmd(); // Send tic cmd + hu_redownloadinggamestate = cl_redownloadinggamestate; + } + else + { + if (!demoplayback && realtics > 0) + { + hu_redownloadinggamestate = false; + + firstticstosend = gametic; + for (INT32 i = 0; i < MAXNETNODES; i++) + if (netnodes[i].ingame) + { + if (netnodes[i].tic < firstticstosend) + firstticstosend = netnodes[i].tic; + + if (maketic + realtics >= netnodes[i].tic + BACKUPTICS - TICRATE) + Net_ConnectionTimeout(i); + } + + // Don't erase tics not acknowledged + INT32 counts = realtics; + if (maketic + counts >= firstticstosend + BACKUPTICS) + counts = firstticstosend+BACKUPTICS-maketic-1; + + for (INT32 i = 0; i < counts; i++) + SV_Maketic(); // Create missed tics and increment maketic + + for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged + D_Clearticcmd(tictoclear); // Clear the maketic the new tic + + SV_SendTics(); + + neededtic = maketic; // The server is a client too + } + } + + Net_AckTicker(); + HandleNodeTimeouts(); + + nowtime /= NEWTICRATERATIO; + + if (nowtime > resptime) + { + resptime = nowtime; +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + M_Ticker(); +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + CON_Ticker(); + } + + FileSendTicker(); +} + +// called one time at init +void D_ClientServerInit(void) +{ + DEBFILE(va("- - -== SRB2 v%d.%.2d.%d "VERSIONSTRING" debugfile ==- - -\n", + VERSION/100, VERSION%100, SUBVERSION)); + + COM_AddCommand("getplayernum", Command_GetPlayerNum, COM_LUA); + COM_AddCommand("kick", Command_Kick, COM_LUA); + COM_AddCommand("ban", Command_Ban, COM_LUA); + COM_AddCommand("banip", Command_BanIP, COM_LUA); + COM_AddCommand("clearbans", Command_ClearBans, COM_LUA); + COM_AddCommand("showbanlist", Command_ShowBan, COM_LUA); + COM_AddCommand("reloadbans", Command_ReloadBan, COM_LUA); + COM_AddCommand("connect", Command_connect, COM_LUA); + COM_AddCommand("nodes", Command_Nodes, COM_LUA); + COM_AddCommand("resendgamestate", Command_ResendGamestate, COM_LUA); +#ifdef PACKETDROP + COM_AddCommand("drop", Command_Drop, COM_LUA); + COM_AddCommand("droprate", Command_Droprate, COM_LUA); +#endif +#ifdef _DEBUG + COM_AddCommand("numnodes", Command_Numnodes, COM_LUA); +#endif + + RegisterNetXCmd(XD_KICK, Got_KickCmd); + RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer); +#ifdef DUMPCONSISTENCY + CV_RegisterVar(&cv_dumpconsistency); +#endif + Ban_Load_File(false); + + gametic = 0; + localgametic = 0; + + // do not send anything before the real begin + SV_StopServer(); + SV_ResetServer(); + if (dedicated) + SV_SpawnServer(); +} + +SINT8 nametonum(const char *name) +{ + INT32 playernum; + + if (!strcmp(name, "0")) + return 0; + + playernum = (SINT8)atoi(name); + + if (playernum < 0 || playernum >= MAXPLAYERS) + return -1; + + if (playernum) + { + if (playeringame[playernum]) + return (SINT8)playernum; + else + return -1; + } + + for (INT32 i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] && !stricmp(player_names[i], name)) + return (SINT8)i; + + CONS_Printf(M_GetText("There is no player named \"%s\"\n"), name); + + return -1; +} + +// Is there a game running? +boolean Playing(void) +{ + return (server && serverrunning) || (client && cl_mode == CL_CONNECTED); +} + +/** Returns the number of players playing. + * \return Number of players. Can be zero if we're running a ::dedicated + * server. + * \author Graue + */ +INT32 D_NumPlayers(void) +{ + INT32 num = 0; + for (INT32 ix = 0; ix < MAXPLAYERS; ix++) + if (playeringame[ix]) + num++; + return num; +} + +/** Similar to the above, but counts only bots. + * Purpose is to remove bots from both the player count and the + * max player count on the server view +*/ +INT32 D_NumBots(void) +{ + INT32 num = 0, ix; + for (ix = 0; ix < MAXPLAYERS; ix++) + if (playeringame[ix] && players[ix].bot) + num++; + return num; +} + + +// +// Consistancy +// +// Note: It is called consistAncy on purpose. +// +INT16 Consistancy(void) +{ + UINT32 ret = 0; +#ifdef MOBJCONSISTANCY + thinker_t *th; + mobj_t *mo; +#endif + + DEBFILE(va("TIC %u ", gametic)); + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + ret ^= 0xCCCC; + else if (!players[i].mo); + else + { + ret += players[i].mo->x; + ret -= players[i].mo->y; + ret += players[i].powers[pw_shield]; + ret *= i+1; + } + } + // I give up + // Coop desynching enemies is painful + if (!G_PlatformGametype()) + ret += P_GetRandSeed(); + +#ifdef MOBJCONSISTANCY + if (gamestate == GS_LEVEL) + { + for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo = (mobj_t *)th; + + if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY)) + { + ret -= mo->type; + ret += mo->x; + ret -= mo->y; + ret += mo->z; + ret -= mo->momx; + ret += mo->momy; + ret -= mo->momz; + ret += mo->angle; + ret -= mo->flags; + ret += mo->flags2; + ret -= mo->eflags; + if (mo->target) + { + ret += mo->target->type; + ret -= mo->target->x; + ret += mo->target->y; + ret -= mo->target->z; + ret += mo->target->momx; + ret -= mo->target->momy; + ret += mo->target->momz; + ret -= mo->target->angle; + ret += mo->target->flags; + ret -= mo->target->flags2; + ret += mo->target->eflags; + ret -= mo->target->state - states; + ret += mo->target->tics; + ret -= mo->target->sprite; + ret += mo->target->frame; + } + else + ret ^= 0x3333; + if (mo->tracer && mo->tracer->type != MT_OVERLAY) + { + ret += mo->tracer->type; + ret -= mo->tracer->x; + ret += mo->tracer->y; + ret -= mo->tracer->z; + ret += mo->tracer->momx; + ret -= mo->tracer->momy; + ret += mo->tracer->momz; + ret -= mo->tracer->angle; + ret += mo->tracer->flags; + ret -= mo->tracer->flags2; + ret += mo->tracer->eflags; + ret -= mo->tracer->state - states; + ret += mo->tracer->tics; + ret -= mo->tracer->sprite; + ret += mo->tracer->frame; + } + else + ret ^= 0xAAAA; + ret -= mo->state - states; + ret += mo->tics; + ret -= mo->sprite; + ret += mo->frame; + } + } + } +#endif + + DEBFILE(va("Consistancy = %u\n", (ret & 0xFFFF))); + + return (INT16)(ret & 0xFFFF); +} + +tic_t GetLag(INT32 node) +{ + return gametic - netnodes[node].tic; +} + +void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest) +{ +#ifdef NOMD5 + (void)buffer; + (void)len; + (void)salt; + memset(dest, 0, 16); +#else + char tmpbuf[256]; + const size_t sl = strlen(salt); + + if (len > 256-sl) + len = 256-sl; + + memcpy(tmpbuf, buffer, len); + memmove(&tmpbuf[len], salt, sl); + //strcpy(&tmpbuf[len], salt); + len += strlen(salt); + if (len < 256) + memset(&tmpbuf[len],0,256-len); + + // Yes, we intentionally md5 the ENTIRE buffer regardless of size... + md5_buffer(tmpbuf, 256, dest); +#endif +} diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h new file mode 100644 index 000000000..d87ead9ec --- /dev/null +++ b/src/netcode/d_clisrv.h @@ -0,0 +1,135 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 d_clisrv.h +/// \brief high level networking stuff + +#ifndef __D_CLISRV__ +#define __D_CLISRV__ + +#include "protocol.h" +#include "../d_ticcmd.h" +#include "d_net.h" +#include "d_netcmd.h" +#include "d_net.h" +#include "../tables.h" +#include "../d_player.h" +#include "mserv.h" + +#define CLIENTBACKUPTICS 32 + +#ifdef PACKETDROP +void Command_Drop(void); +void Command_Droprate(void); +#endif +#ifdef _DEBUG +void Command_Numnodes(void); +#endif + +extern INT32 mapchangepending; + +// Points inside doomcom +extern doomdata_t *netbuffer; + +#define BASEPACKETSIZE offsetof(doomdata_t, u) +#define BASESERVERTICSSIZE offsetof(doomdata_t, u.serverpak.cmds[0]) + +typedef enum +{ + KR_KICK = 1, //Kicked by server + KR_PINGLIMIT = 2, //Broke Ping Limit + KR_SYNCH = 3, //Synch Failure + KR_TIMEOUT = 4, //Connection Timeout + KR_BAN = 5, //Banned by server + KR_LEAVE = 6, //Quit the game + +} kickreason_t; + +/* the max number of name changes in some time period */ +#define MAXNAMECHANGES (5) +#define NAMECHANGERATE (60*TICRATE) + +extern boolean server; +extern boolean serverrunning; +#define client (!server) +extern boolean dedicated; // For dedicated server +extern UINT16 software_MAXPACKETLENGTH; +extern boolean acceptnewnode; +extern SINT8 servernode; +extern tic_t maketic; +extern tic_t neededtic; +extern INT16 consistancy[BACKUPTICS]; + +void Command_Ping_f(void); +extern tic_t connectiontimeout; +extern UINT16 pingmeasurecount; +extern UINT32 realpingtable[MAXPLAYERS]; +extern UINT32 playerpingtable[MAXPLAYERS]; +extern tic_t servermaxping; + +extern consvar_t cv_netticbuffer, cv_resynchattempts, cv_blamecfail, cv_playbackspeed, cv_dedicatedidletime; + +// Used in d_net, the only dependence +void D_ClientServerInit(void); + +// Create any new ticcmds and broadcast to other players. +void NetUpdate(void); + +// Maintain connections to nodes without timing them all out. +void NetKeepAlive(void); + +void GetPackets(void); +void ResetNode(INT32 node); +INT16 Consistancy(void); + +void SV_StartSinglePlayerServer(void); +void SV_SpawnServer(void); +void SV_StopServer(void); +void SV_ResetServer(void); +void CL_AddSplitscreenPlayer(void); +void CL_RemoveSplitscreenPlayer(void); +void CL_Reset(void); +void CL_ClearPlayer(INT32 playernum); +void CL_RemovePlayer(INT32 playernum, kickreason_t reason); +void CL_HandleTimeout(void); +// Is there a game running +boolean Playing(void); + +// Broadcasts special packets to other players +// to notify of game exit +void D_QuitNetGame(void); + +//? How many ticks to run? +boolean TryRunTics(tic_t realtic); + +// extra data for lmps +// these functions scare me. they contain magic. +/*boolean AddLmpExtradata(UINT8 **demo_p, INT32 playernum); +void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum);*/ + +// translate a playername in a player number return -1 if not found and +// print a error message in the console +SINT8 nametonum(const char *name); + +extern char motd[254], server_context[8]; +extern UINT8 playernode[MAXPLAYERS]; + +INT32 D_NumPlayers(void); +INT32 D_NumBots(void); + +tic_t GetLag(INT32 node); + +void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest); + +extern UINT8 adminpassmd5[16]; +extern boolean adminpasswordset; + +extern boolean hu_stopped; + +#endif diff --git a/src/d_net.c b/src/netcode/d_net.c similarity index 93% rename from src/d_net.c rename to src/netcode/d_net.c index 6d8c72942..cfb1963b9 100644 --- a/src/d_net.c +++ b/src/netcode/d_net.c @@ -16,19 +16,21 @@ /// 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 "doomdef.h" -#include "g_game.h" -#include "i_time.h" +#include "../doomdef.h" +#include "../g_game.h" +#include "../i_time.h" #include "i_net.h" -#include "i_system.h" -#include "m_argv.h" +#include "../i_system.h" +#include "../m_argv.h" #include "d_net.h" -#include "w_wad.h" +#include "../w_wad.h" #include "d_netfil.h" #include "d_clisrv.h" -#include "z_zone.h" +#include "tic_command.h" +#include "net_command.h" +#include "../z_zone.h" #include "i_tcp.h" -#include "d_main.h" // srb2home +#include "../d_main.h" // srb2home // // NETWORKING @@ -138,7 +140,6 @@ boolean Net_GetNetStat(void) #define URGENTFREESLOTNUM 10 #define ACKTOSENDTIMEOUT (TICRATE/11) -#ifndef NONET typedef struct { UINT8 acknum; @@ -152,7 +153,6 @@ typedef struct doomdata_t data; } pak; } ackpak_t; -#endif typedef enum { @@ -160,10 +160,8 @@ typedef enum NF_TIMEOUT = 2, // Flag is set when the node got a timeout } node_flags_t; -#ifndef NONET // Table of packets that were not acknowleged can be resent (the sender window) static ackpak_t ackpak[MAXACKPACKETS]; -#endif typedef struct { @@ -191,7 +189,6 @@ typedef struct static node_t nodes[MAXNETNODES]; #define NODETIMEOUT 14 -#ifndef NONET // return <0 if a < b (mod 256) // 0 if a = n (mod 256) // >0 if a > b (mod 256) @@ -214,7 +211,7 @@ FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b) static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer) { node_t *node = &nodes[doomcom->remotenode]; - INT32 i, numfreeslot = 0; + INT32 numfreeslot = 0; if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0) { @@ -222,7 +219,7 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer) return false; } - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (!ackpak[i].acknum) { // For low priority packets, make sure to let freeslots so urgent packets can be sent @@ -279,10 +276,10 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer) */ INT32 Net_GetFreeAcks(boolean urgent) { - INT32 i, numfreeslot = 0; + INT32 numfreeslot = 0; INT32 n = 0; // Number of free acks found - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (!ackpak[i].acknum) { // For low priority packets, make sure to let freeslots so urgent packets can be sent @@ -318,7 +315,6 @@ static void RemoveAck(INT32 i) // We have got a packet, proceed the ack request and ack return static boolean Processackpak(void) { - INT32 i; boolean goodpacket = true; node_t *node = &nodes[doomcom->remotenode]; @@ -327,7 +323,7 @@ static boolean Processackpak(void) { node->remotefirstack = netbuffer->ackreturn; // Search the ackbuffer and free it - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes && cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0) { @@ -349,7 +345,7 @@ static boolean Processackpak(void) else { // Check if it is not already in the queue - for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND) + for (INT32 i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND) if (node->acktosend[i] == ack) { DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack)); @@ -377,7 +373,7 @@ static boolean Processackpak(void) while (change) { change = false; - for (i = node->acktosend_tail; i != node->acktosend_head; + for (INT32 i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND) { if (cmpack(node->acktosend[i], nextfirstack) <= 0) @@ -426,28 +422,20 @@ static boolean Processackpak(void) } return goodpacket; } -#endif // send special packet with only ack on it void Net_SendAcks(INT32 node) { -#ifdef NONET - (void)node; -#else netbuffer->packettype = PT_NOTHING; M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND); HSendPacket(node, false, 0, MAXACKTOSEND); -#endif } -#ifndef NONET static void GotAcks(void) { - INT32 i, j; - - for (j = 0; j < MAXACKTOSEND; j++) + for (INT32 j = 0; j < MAXACKTOSEND; j++) if (netbuffer->u.textcmd[j]) - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode) { if (ackpak[i].acknum == netbuffer->u.textcmd[j]) @@ -463,7 +451,6 @@ static void GotAcks(void) } } } -#endif void Net_ConnectionTimeout(INT32 node) { @@ -472,14 +459,10 @@ void Net_ConnectionTimeout(INT32 node) return; nodes[node].flags |= NF_TIMEOUT; - // Send a very special packet to self (hack the reboundstore queue) - // Main code will handle it - reboundstore[rebound_head].packettype = PT_NODETIMEOUT; - reboundstore[rebound_head].ack = 0; - reboundstore[rebound_head].ackreturn = 0; - reboundstore[rebound_head].u.textcmd[0] = (UINT8)node; - reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1); - rebound_head = (rebound_head+1) % MAXREBOUND; + if (server) + SendKicksForNode(node, KICK_MSG_TIMEOUT | KICK_MSG_KEEP_BODY); + else + CL_HandleTimeout(); // Do not redo it quickly (if we do not close connection it is // for a good reason!) @@ -489,10 +472,8 @@ void Net_ConnectionTimeout(INT32 node) // Resend the data if needed void Net_AckTicker(void) { -#ifndef NONET - INT32 i; - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) { const INT32 nodei = ackpak[i].destinationnode; node_t *node = &nodes[nodei]; @@ -519,7 +500,7 @@ void Net_AckTicker(void) } } - for (i = 1; i < MAXNETNODES; i++) + for (INT32 i = 1; i < MAXNETNODES; i++) { // This is something like node open flag if (nodes[i].firstacktosend) @@ -536,16 +517,12 @@ void Net_AckTicker(void) } } } -#endif } // Remove last packet received ack before resending the ackreturn // (the higher layer doesn't have room, or something else ....) void Net_UnAcknowledgePacket(INT32 node) { -#ifdef NONET - (void)node; -#else INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND; DEBFILE(va("UnAcknowledge node %d\n", node)); if (!node) @@ -577,10 +554,8 @@ void Net_UnAcknowledgePacket(INT32 node) if (!nodes[node].firstacktosend) nodes[node].firstacktosend = 1; } -#endif } -#ifndef NONET /** Checks if all acks have been received * * \return True if all acks have been received @@ -588,15 +563,12 @@ void Net_UnAcknowledgePacket(INT32 node) */ static boolean Net_AllAcksReceived(void) { - INT32 i; - - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (ackpak[i].acknum) return false; return true; } -#endif /** Waits for all ackreturns * @@ -605,9 +577,6 @@ static boolean Net_AllAcksReceived(void) */ void Net_WaitAllAckReceived(UINT32 timeout) { -#ifdef NONET - (void)timeout; -#else tic_t tictac = I_GetTime(); timeout = tictac + timeout*NEWTICRATE; @@ -623,7 +592,6 @@ void Net_WaitAllAckReceived(UINT32 timeout) HGetPacket(); Net_AckTicker(); } -#endif } static void InitNode(node_t *node) @@ -637,14 +605,10 @@ static void InitNode(node_t *node) static void InitAck(void) { - INT32 i; - -#ifndef NONET - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) ackpak[i].acknum = 0; -#endif - for (i = 0; i < MAXNETNODES; i++) + for (INT32 i = 0; i < MAXNETNODES; i++) InitNode(&nodes[i]); } @@ -655,17 +619,12 @@ static void InitAck(void) */ void Net_AbortPacketType(UINT8 packettype) { -#ifdef NONET - (void)packettype; -#else - INT32 i; - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (ackpak[i].acknum && (ackpak[i].pak.data.packettype == packettype || packettype == UINT8_MAX)) { ackpak[i].acknum = 0; } -#endif } // ----------------------------------------------------------------- @@ -675,10 +634,6 @@ void Net_AbortPacketType(UINT8 packettype) // remove a node, clear all ack from this node and reset askret void Net_CloseConnection(INT32 node) { -#ifdef NONET - (void)node; -#else - INT32 i; boolean forceclose = (node & FORCECLOSE) != 0; if (node == -1) @@ -708,7 +663,7 @@ void Net_CloseConnection(INT32 node) } // check if we are waiting for an ack from this node - for (i = 0; i < MAXACKPACKETS; i++) + for (INT32 i = 0; i < MAXACKPACKETS; i++) if (ackpak[i].acknum && ackpak[i].destinationnode == node) { if (!forceclose) @@ -722,10 +677,8 @@ void Net_CloseConnection(INT32 node) if (server) SV_AbortLuaFileTransfer(node); I_NetFreeNodenum(node); -#endif } -#ifndef NONET // // Checksum // @@ -734,23 +687,20 @@ static UINT32 NetbufferChecksum(void) UINT32 c = 0x1234567; const INT32 l = doomcom->datalength - 4; const UINT8 *buf = (UINT8 *)netbuffer + 4; - INT32 i; - for (i = 0; i < l; i++, buf++) + for (INT32 i = 0; i < l; i++, buf++) c += (*buf) * (i+1); return LONG(c); } -#endif #ifdef DEBUGFILE static void fprintfstring(char *s, size_t len) { INT32 mode = 0; - size_t i; - for (i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) if (s[i] < 32) { if (!mode) @@ -810,6 +760,8 @@ static const char *packettypename[NUMPACKETTYPE] = "ASKLUAFILE", "HASLUAFILE", + "PT_BASICKEEPALIVE", + "FILEFRAGMENT", "FILEACK", "FILERECEIVED", @@ -817,7 +769,6 @@ static const char *packettypename[NUMPACKETTYPE] = "TEXTCMD", "TEXTCMD2", "CLIENTJOIN", - "NODETIMEOUT", "LOGIN", "TELLFILESNEEDED", "MOREFILESNEEDED", @@ -921,7 +872,6 @@ void Command_Drop(void) { INT32 packetquantity; const char *packetname; - size_t i; if (COM_Argc() < 2) { @@ -951,11 +901,11 @@ void Command_Drop(void) packetname = COM_Argv(1); if (!(stricmp(packetname, "all") && stricmp(packetname, "any"))) - for (i = 0; i < NUMPACKETTYPE; i++) + for (size_t i = 0; i < NUMPACKETTYPE; i++) packetdropquantity[i] = packetquantity; else { - for (i = 0; i < NUMPACKETTYPE; i++) + for (size_t i = 0; i < NUMPACKETTYPE; i++) if (!stricmp(packetname, packettypename[i])) { packetdropquantity[i] = packetquantity; @@ -986,14 +936,12 @@ void Command_Droprate(void) packetdroprate = droprate; } -#ifndef NONET static boolean ShouldDropPacket(void) { return (packetdropquantity[netbuffer->packettype]) || (packetdroprate != 0 && rand() < (RAND_MAX * (packetdroprate / 100.f))) || packetdroprate == 100; } #endif -#endif // // HSendPacket @@ -1028,11 +976,6 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen if (!netgame) I_Error("Tried to transmit to another node"); -#ifdef NONET - (void)node; - (void)reliable; - (void)acknum; -#else // do this before GetFreeAcknum because this function backups // the current packet doomcom->remotenode = (INT16)node; @@ -1093,8 +1036,6 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen } #endif -#endif // ndef NONET - return true; } @@ -1112,10 +1053,7 @@ boolean HGetPacket(void) { M_Memcpy(netbuffer, &reboundstore[rebound_tail], reboundsize[rebound_tail]); doomcom->datalength = reboundsize[rebound_tail]; - if (netbuffer->packettype == PT_NODETIMEOUT) - doomcom->remotenode = netbuffer->u.textcmd[0]; - else - doomcom->remotenode = 0; + doomcom->remotenode = 0; rebound_tail = (rebound_tail+1) % MAXREBOUND; #ifdef DEBUGFILE @@ -1128,8 +1066,6 @@ boolean HGetPacket(void) if (!netgame) return false; -#ifndef NONET - while(true) { //nodejustjoined = I_NetGet(); @@ -1189,7 +1125,6 @@ boolean HGetPacket(void) } break; } -#endif // ndef NONET return true; } @@ -1399,13 +1334,12 @@ void Command_Ping_f(void) int name_width = 0; int ms_width = 0; - int n; - INT32 i; - pingc = 0; - for (i = 1; i < MAXPLAYERS; ++i) + for (INT32 i = 1; i < MAXPLAYERS; ++i) if (playeringame[i]) { + int n; + n = strlen(player_names[i]); if (n > name_width) name_width = n; @@ -1425,7 +1359,7 @@ void Command_Ping_f(void) qsort(pingv, pingc, sizeof (struct pingcell), &pingcellcmp); - for (i = 0; i < pingc; ++i) + for (INT32 i = 0; i < pingc; ++i) { CONS_Printf("%02d : %-*s %*d ms\n", pingv[i].num, @@ -1441,15 +1375,13 @@ void Command_Ping_f(void) void D_CloseConnection(void) { - INT32 i; - if (netgame) { // wait the ackreturn with timout of 5 Sec Net_WaitAllAckReceived(5); // close all connection - for (i = 0; i < MAXNETNODES; i++) + for (INT32 i = 0; i < MAXNETNODES; i++) Net_CloseConnection(i|FORCECLOSE); InitAck(); diff --git a/src/d_net.h b/src/netcode/d_net.h similarity index 74% rename from src/d_net.h rename to src/netcode/d_net.h index ddedbef4a..549f2b93c 100644 --- a/src/d_net.h +++ b/src/netcode/d_net.h @@ -18,6 +18,8 @@ #ifndef __D_NET__ #define __D_NET__ +#include "../doomtype.h" + // Max computers in a game // 127 is probably as high as this can go, because // SINT8 is used for nodes sometimes >:( @@ -37,10 +39,24 @@ boolean Net_GetNetStat(void); extern INT32 getbytes; extern INT64 sendbytes; // Realtime updated -extern SINT8 nodetoplayer[MAXNETNODES]; -extern SINT8 nodetoplayer2[MAXNETNODES]; // Say the numplayer for this node if any (splitscreen) -extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen -extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game +typedef struct netnode_s +{ + boolean ingame; // set false as nodes leave game + tic_t freezetimeout; // Until when can this node freeze the server before getting a timeout? + + SINT8 player; + SINT8 player2; // say the numplayer for this node if any (splitscreen) + UINT8 numplayers; // used specialy for scplitscreen + + tic_t tic; // what tic the client have received + tic_t supposedtic; // nettics prevision for smaller packet + + boolean sendingsavegame; // Are we sending the savegame? + boolean resendingsavegame; // Are we resending the savegame? + tic_t savegameresendcooldown; // How long before we can resend again? +} netnode_t; + +extern netnode_t netnodes[MAXNETNODES]; extern boolean serverrunning; @@ -52,9 +68,6 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength); boolean HGetPacket(void); void D_SetDoomcom(void); -#ifndef NONET -void D_SaveBan(void); -#endif boolean D_CheckNetGame(void); void D_CloseConnection(void); void Net_UnAcknowledgePacket(INT32 node); diff --git a/src/d_netcmd.c b/src/netcode/d_netcmd.c similarity index 94% rename from src/d_netcmd.c rename to src/netcode/d_netcmd.c index 77a271825..5afa95561 100644 --- a/src/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -12,44 +12,46 @@ /// commands are executed through the command buffer /// like console commands, other miscellaneous commands (at the end) -#include "doomdef.h" +#include "../doomdef.h" -#include "console.h" -#include "command.h" -#include "i_time.h" -#include "i_system.h" -#include "g_game.h" -#include "hu_stuff.h" -#include "g_input.h" -#include "m_menu.h" -#include "r_local.h" -#include "r_skins.h" -#include "p_local.h" -#include "p_setup.h" -#include "s_sound.h" -#include "i_sound.h" -#include "m_misc.h" -#include "am_map.h" -#include "byteptr.h" +#include "../console.h" +#include "../command.h" +#include "../i_time.h" +#include "../i_system.h" +#include "../g_game.h" +#include "../hu_stuff.h" +#include "../g_input.h" +#include "../m_menu.h" +#include "../r_local.h" +#include "../r_skins.h" +#include "../p_local.h" +#include "../p_setup.h" +#include "../s_sound.h" +#include "../i_sound.h" +#include "../m_misc.h" +#include "../am_map.h" +#include "../byteptr.h" #include "d_netfil.h" -#include "p_spec.h" -#include "m_cheat.h" +#include "../p_spec.h" +#include "../m_cheat.h" #include "d_clisrv.h" +#include "server_connection.h" +#include "net_command.h" #include "d_net.h" -#include "v_video.h" -#include "d_main.h" -#include "m_random.h" -#include "f_finale.h" -#include "filesrch.h" +#include "../v_video.h" +#include "../d_main.h" +#include "../m_random.h" +#include "../f_finale.h" +#include "../filesrch.h" #include "mserv.h" -#include "z_zone.h" -#include "lua_script.h" -#include "lua_hook.h" -#include "m_cond.h" -#include "m_anigif.h" -#include "md5.h" -#include "m_perfstats.h" -#include "u_list.h" +#include "../z_zone.h" +#include "../lua_script.h" +#include "../lua_hook.h" +#include "../m_cond.h" +#include "../m_anigif.h" +#include "../md5.h" +#include "../m_perfstats.h" +#include "../u_list.h" #ifdef NETGAME_DEVMODE #define CV_RESTRICT CV_NETVAR @@ -225,6 +227,7 @@ consvar_t cv_seenames = CVAR_INIT ("seenames", "Ally/Foe", CV_SAVE|CV_ALLOWLUA, consvar_t cv_allowseenames = CVAR_INIT ("allowseenames", "Yes", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_YesNo, NULL); // names +static char *lastskinnames[2]; consvar_t cv_playername = CVAR_INIT ("name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange); consvar_t cv_playername2 = CVAR_INIT ("name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange); // player colors @@ -594,13 +597,10 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_maxsend); CV_RegisterVar(&cv_noticedownload); CV_RegisterVar(&cv_downloadspeed); -#ifndef NONET CV_RegisterVar(&cv_allownewplayer); - CV_RegisterVar(&cv_joinnextround); CV_RegisterVar(&cv_showjoinaddress); CV_RegisterVar(&cv_blamecfail); CV_RegisterVar(&cv_dedicatedidletime); -#endif COM_AddCommand("ping", Command_Ping_f, COM_LUA); CV_RegisterVar(&cv_nettimeout); @@ -776,6 +776,8 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_showfocuslost); CV_RegisterVar(&cv_pauseifunfocused); + CV_RegisterVar(&cv_instantretry); + // g_input.c CV_RegisterVar(&cv_sideaxis); CV_RegisterVar(&cv_sideaxis2); @@ -1203,27 +1205,38 @@ UINT8 CanChangeSkin(INT32 playernum) static void ForceAllSkins(INT32 forcedskin) { - INT32 i; - for (i = 0; i < MAXPLAYERS; ++i) + for (INT32 i = 0; i < MAXPLAYERS; ++i) { - if (!playeringame[i]) - continue; - - SetPlayerSkinByNum(i, forcedskin); - - // If it's me (or my brother), set appropriate skin value in cv_skin/cv_skin2 - if (!dedicated) // But don't do this for dedicated servers, of course. - { - if (i == consoleplayer) - CV_StealthSet(&cv_skin, skins[forcedskin].name); - else if (i == secondarydisplayplayer) - CV_StealthSet(&cv_skin2, skins[forcedskin].name); - } + if (playeringame[i]) + SetPlayerSkinByNum(i, forcedskin); } } static INT32 snacpending = 0, snac2pending = 0, chmappending = 0; +static void SetSkinLocal(INT32 playernum, INT32 skinnum) +{ + if (metalrecording && playernum == consoleplayer) + { + // Starring Metal Sonic as themselves, obviously. + SetPlayerSkinByNum(playernum, 5); + return; + } + + if (skinnum != -1 && R_SkinUsable(playernum, skinnum)) + SetPlayerSkinByNum(playernum, skinnum); + else + SetPlayerSkinByNum(playernum, GetPlayerDefaultSkin(playernum)); +} + +static void SetColorLocal(INT32 playernum, UINT16 color) +{ + players[playernum].skincolor = color; + + if (players[playernum].mo && !players[playernum].powers[pw_dye]) + players[playernum].mo->color = P_GetPlayerColor(&players[playernum]); +} + // name, color, or skin has changed // static void SendNameAndColor(void) @@ -1233,15 +1246,6 @@ static void SendNameAndColor(void) p = buf; - // normal player colors - if (G_GametypeHasTeams()) - { - if (players[consoleplayer].ctfteam == 1 && cv_playercolor.value != skincolor_redteam) - CV_StealthSetValue(&cv_playercolor, skincolor_redteam); - else if (players[consoleplayer].ctfteam == 2 && cv_playercolor.value != skincolor_blueteam) - CV_StealthSetValue(&cv_playercolor, skincolor_blueteam); - } - // don't allow inaccessible colors if (!skincolors[cv_playercolor.value].accessible) { @@ -1272,50 +1276,15 @@ static void SendNameAndColor(void) // If you're not in a netgame, merely update the skin, color, and name. if (!netgame) { - INT32 foundskin; - CleanupPlayerName(consoleplayer, cv_playername.zstring); strcpy(player_names[consoleplayer], cv_playername.zstring); - players[consoleplayer].skincolor = cv_playercolor.value; + SetColorLocal(consoleplayer, cv_playercolor.value); - if (players[consoleplayer].mo && !players[consoleplayer].powers[pw_dye]) - players[consoleplayer].mo->color = players[consoleplayer].skincolor; - - if (metalrecording) - { // Starring Metal Sonic as themselves, obviously. - SetPlayerSkinByNum(consoleplayer, 5); - CV_StealthSet(&cv_skin, skins[5].name); - } - else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin)) - { - //boolean notsame; - - cv_skin.value = foundskin; - - //notsame = (cv_skin.value != players[consoleplayer].skin); - - SetPlayerSkin(consoleplayer, cv_skin.string); - CV_StealthSet(&cv_skin, skins[cv_skin.value].name); - - /*if (notsame) - { - CV_StealthSetValue(&cv_playercolor, skins[cv_skin.value].prefcolor); - - players[consoleplayer].skincolor = cv_playercolor.value % numskincolors; - - if (players[consoleplayer].mo) - players[consoleplayer].mo->color = (UINT16)players[consoleplayer].skincolor; - }*/ - } + if (splitscreen) + SetSkinLocal(consoleplayer, R_SkinAvailable(cv_skin.string)); else - { - cv_skin.value = players[consoleplayer].skin; - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); - // will always be same as current - SetPlayerSkin(consoleplayer, cv_skin.string); - } - + SetSkinLocal(consoleplayer, pickedchar); return; } @@ -1332,10 +1301,6 @@ static void SendNameAndColor(void) else // Cleanup name if changing it CleanupPlayerName(consoleplayer, cv_playername.zstring); - // Don't change skin if the server doesn't want you to. - if (!CanChangeSkin(consoleplayer)) - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); - // check if player has the skin loaded (cv_skin may have // the name of a skin that was available in the previous game) cv_skin.value = R_SkinAvailable(cv_skin.string); @@ -1367,16 +1332,6 @@ static void SendNameAndColor2(void) else // HACK secondplaya = 1; - // normal player colors - if (G_GametypeHasTeams()) - { - if (players[secondplaya].ctfteam == 1 && cv_playercolor2.value != skincolor_redteam) - CV_StealthSetValue(&cv_playercolor2, skincolor_redteam); - else if (players[secondplaya].ctfteam == 2 && cv_playercolor2.value != skincolor_blueteam) - CV_StealthSetValue(&cv_playercolor2, skincolor_blueteam); - } - - // don't allow inaccessible colors if (!skincolors[cv_playercolor2.value].accessible) { if (players[secondplaya].skincolor && skincolors[players[secondplaya].skincolor].accessible) @@ -1398,63 +1353,24 @@ static void SendNameAndColor2(void) if (!Playing()) return; - // If you're not in a netgame, merely update the skin, color, and name. if (botingame) { - players[secondplaya].skincolor = botcolor; - if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye]) - players[secondplaya].mo->color = players[secondplaya].skincolor; - + SetColorLocal(secondplaya, botcolor); SetPlayerSkinByNum(secondplaya, botskin-1); return; } else if (!netgame) { - INT32 foundskin; - + // If you're not in a netgame, merely update the skin, color, and name. CleanupPlayerName(secondplaya, cv_playername2.zstring); strcpy(player_names[secondplaya], cv_playername2.zstring); - // don't use secondarydisplayplayer: the second player must be 1 - players[secondplaya].skincolor = cv_playercolor2.value; - if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye]) - players[secondplaya].mo->color = players[secondplaya].skincolor; + SetColorLocal(secondplaya, cv_playercolor2.value); - if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player - { - const INT32 forcedskin = cv_forceskin.value; - - SetPlayerSkinByNum(secondplaya, forcedskin); - CV_StealthSet(&cv_skin2, skins[forcedskin].name); - } - else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUsable(secondplaya, foundskin)) - { - //boolean notsame; - - cv_skin2.value = foundskin; - - //notsame = (cv_skin2.value != players[secondplaya].skin); - - SetPlayerSkin(secondplaya, cv_skin2.string); - CV_StealthSet(&cv_skin2, skins[cv_skin2.value].name); - - /*if (notsame) - { - CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor); - - players[secondplaya].skincolor = cv_playercolor2.value % numskincolors; - - if (players[secondplaya].mo) - players[secondplaya].mo->color = players[secondplaya].skincolor; - }*/ - } + if (cv_forceskin.value >= 0) + SetSkinLocal(secondplaya, cv_forceskin.value); else - { - cv_skin2.value = players[secondplaya].skin; - CV_StealthSet(&cv_skin2, skins[players[secondplaya].skin].name); - // will always be same as current - SetPlayerSkin(secondplaya, cv_skin2.string); - } + SetSkinLocal(secondplaya, R_SkinAvailable(cv_skin2.string)); return; } @@ -1498,7 +1414,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) // set color p->skincolor = color % numskincolors; if (p->mo) - p->mo->color = (UINT16)p->skincolor; + p->mo->color = P_GetPlayerColor(p); // normal player colors if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer])) @@ -1507,15 +1423,6 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) UINT32 unlockShift = 0; UINT32 i; - // team colors - if (G_GametypeHasTeams()) - { - if (p->ctfteam == 1 && p->skincolor != skincolor_redteam) - kick = true; - else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) - kick = true; - } - // don't allow inaccessible colors if (skincolors[p->skincolor].accessible == false) kick = true; @@ -1556,16 +1463,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } // set skin - if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player - { - const INT32 forcedskin = cv_forceskin.value; + INT32 forcedskin = R_GetForcedSkin(playernum); + if (forcedskin != -1 && (netgame || multiplayer)) // Server wants everyone to use the same player (or the level is forcing one.) SetPlayerSkinByNum(playernum, forcedskin); - - if (playernum == consoleplayer) - CV_StealthSet(&cv_skin, skins[forcedskin].name); - else if (playernum == secondarydisplayplayer) - CV_StealthSet(&cv_skin2, skins[forcedskin].name); - } else SetPlayerSkinByNum(playernum, skin); } @@ -1841,8 +1741,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese // reset players if there is a new one if (!IsPlayerAdmin(consoleplayer)) { - if (SV_SpawnServer()) - buf[0] &= ~(1<<1); + SV_SpawnServer(); if (!Playing()) // you failed to start a server somehow, so cancel the map change return; } @@ -1902,8 +1801,8 @@ static void Command_Map_f(void) size_t option_gametype; const char *gametypename; boolean newresetplayers; - - boolean wouldSetCheats; + boolean prevent_cheat; + boolean set_cheated; INT32 newmapnum; @@ -1924,21 +1823,34 @@ static void Command_Map_f(void) option_gametype = COM_CheckPartialParm("-g"); newresetplayers = ! COM_CheckParm("-noresetplayers"); - wouldSetCheats = - !( netgame || multiplayer ) && - !( usedCheats ); + prevent_cheat = !( usedCheats ) && !( option_force || cv_debug ); + set_cheated = false; - if (wouldSetCheats && !option_force) + if (!( netgame || multiplayer )) { - /* May want to be more descriptive? */ - CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); - return; + if (prevent_cheat) + { + /* May want to be more descriptive? */ + CONS_Printf(M_GetText("Cheats must be enabled to level change in single player.\n")); + return; + } + else + { + set_cheated = true; + } } - if (!newresetplayers && !cv_debug) + if (!newresetplayers) { - CONS_Printf(M_GetText("DEVMODE must be enabled.\n")); - return; + if (prevent_cheat) + { + CONS_Printf(M_GetText("Cheats must be enabled to use -noresetplayers.\n")); + return; + } + else + { + set_cheated = true; + } } if (option_gametype) @@ -1946,7 +1858,7 @@ static void Command_Map_f(void) if (!multiplayer) { CONS_Printf(M_GetText( - "You can't switch gametypes in single player!\n")); + "You can't switch gametypes in single player!\n")); return; } else if (COM_Argc() < option_gametype + 2)/* no argument after? */ @@ -1959,7 +1871,9 @@ static void Command_Map_f(void) } if (!( first_option = COM_FirstOption() )) + { first_option = COM_Argc(); + } if (first_option < 2) { @@ -1982,11 +1896,6 @@ static void Command_Map_f(void) return; } - if (wouldSetCheats && option_force) - { - G_SetUsedCheats(false); - } - // new gametype value // use current one by default if (option_gametype) @@ -2028,15 +1937,13 @@ static void Command_Map_f(void) } // don't use a gametype the map doesn't support - if (cv_debug || option_force || cv_skipmapcheck.value) - fromlevelselect = false; // The player wants us to trek on anyway. Do so. // G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer - else + if (!( + mapheaderinfo[newmapnum-1] && + mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) + )) { - if (!( - mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) - )) + if (prevent_cheat && !cv_skipmapcheck.value) { CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); @@ -2046,23 +1953,33 @@ static void Command_Map_f(void) } else { - fromlevelselect = - ( netgame || multiplayer ) && - newgametype == gametype && - gametypedefaultrules[newgametype] & GTR_CAMPAIGN; + // The player wants us to trek on anyway. Do so. + fromlevelselect = false; + set_cheated = ((gametypedefaultrules[newgametype] & GTR_CAMPAIGN) == GTR_CAMPAIGN); } } + else + { + fromlevelselect = + ( netgame || multiplayer ) && + newgametype == gametype && + (gametypedefaultrules[newgametype] & GTR_CAMPAIGN); + } // Prevent warping to locked levels - // ... unless you're in a dedicated server. Yes, technically this means you can view any level by - // running a dedicated server and joining it yourself, but that's better than making dedicated server's - // lives hell. - if (!dedicated && M_MapLocked(newmapnum, serverGamedata)) + if (M_CampaignWarpIsCheat(newgametype, newmapnum, serverGamedata)) { - CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n")); - Z_Free(realmapname); - Z_Free(mapname); - return; + if (prevent_cheat) + { + CONS_Alert(CONS_NOTICE, M_GetText("Cheats must be enabled to warp to a locked level!\n")); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + else + { + set_cheated = true; + } } // Ultimate Mode only in SP via menu @@ -2079,6 +1996,11 @@ static void Command_Map_f(void) } tutorialmode = false; // warping takes us out of tutorial mode + if (set_cheated && !usedCheats) + { + G_SetUsedCheats(false); + } + D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect); Z_Free(realmapname); @@ -2120,11 +2042,13 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) lastgametype = gametype; gametype = READUINT8(*cp); - G_SetGametype(gametype); // I fear putting that macro as an argument if (gametype < 0 || gametype >= gametypecount) gametype = lastgametype; - else if (gametype != lastgametype) + else + G_SetGametype(gametype); + + if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype skipprecutscene = ((flags & (1<<2)) != 0); @@ -2150,7 +2074,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) { SetPlayerSkinByNum(0, cv_chooseskin.value-1); players[0].skincolor = skins[players[0].skin].prefcolor; - CV_StealthSetValue(&cv_playercolor, players[0].skincolor); } mapnumber = M_MapNumber(mapname[3], mapname[4]); @@ -2904,17 +2827,6 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) displayplayer = consoleplayer; } - if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam) - { - if (playernum == consoleplayer) //CTF and Team Match colors. - CV_SetValue(&cv_playercolor, NetPacket.packet.newteam + 5); - else if (playernum == secondarydisplayplayer) - CV_SetValue(&cv_playercolor2, NetPacket.packet.newteam + 5); - } - } - // In tag, check to see if you still have a game. if (G_TagGametype()) P_CheckSurvivors(); @@ -4252,9 +4164,6 @@ void D_GameTypeChanged(INT32 lastgametype) else if (!multiplayer && !netgame) { G_SetGametype(GT_COOP); - // These shouldn't matter anymore - //CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); - //CV_SetValue(&cv_itemrespawn, 0); } // reset timelimit and pointlimit in race/coop, prevent stupid cheats @@ -4555,25 +4464,37 @@ static void Command_Mapmd5_f(void) CONS_Printf(M_GetText("You must be in a level to use this.\n")); } +void D_SendExitLevel(boolean cheat) +{ + UINT8 buf[8]; + UINT8 *buf_p = buf; + + WRITEUINT8(buf_p, cheat); + + SendNetXCmd(XD_EXITLEVEL, &buf, buf_p - buf); +} + static void Command_ExitLevel_f(void) { - if (!(netgame || (multiplayer && gametype != GT_COOP)) && !cv_debug) - CONS_Printf(M_GetText("This only works in a netgame.\n")); - else if (!(server || (IsPlayerAdmin(consoleplayer)))) + if (!(server || (IsPlayerAdmin(consoleplayer)))) CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback) CONS_Printf(M_GetText("You must be in a level to use this.\n")); else - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(true); } static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) { - (void)cp; + boolean cheat = false; + + cheat = (boolean)READUINT8(*cp); // Ignore duplicate XD_EXITLEVEL commands. if (gameaction == ga_completed) + { return; + } if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -4583,6 +4504,11 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) return; } + if (G_CoopGametype() && cheat) + { + G_SetUsedCheats(false); + } + G_ExitLevel(); } @@ -4779,11 +4705,16 @@ static void ForceSkin_OnChange(void) return; if (cv_forceskin.value < 0) + { CONS_Printf("The server has lifted the forced skin restrictions.\n"); + if (Playing()) + D_SendPlayerConfig(); + } else { CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name); - ForceAllSkins(cv_forceskin.value); + if (Playing()) + ForceAllSkins(cv_forceskin.value); } } @@ -4797,7 +4728,6 @@ static void Name_OnChange(void) } else SendNameAndColor(); - } static void Name2_OnChange(void) @@ -4820,19 +4750,33 @@ static void Skin_OnChange(void) if (!Playing()) return; // do whatever you want - if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player. - && (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y + if (lastskinnames[0] == NULL) + lastskinnames[0] = Z_StrDup(cv_skin.string); + + if (!(multiplayer || netgame)) // In single player. { - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + if (!(cv_debug || devparm) + && (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y + { + CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + return; + } + + // Just do it here if devmode is enabled + SetSkinLocal(consoleplayer, R_SkinAvailable(cv_skin.string)); return; } if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer)) + { SendNameAndColor(); + Z_Free(lastskinnames[0]); + lastskinnames[0] = Z_StrDup(cv_skin.string); + } else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + CV_StealthSet(&cv_skin, lastskinnames[0]); } } @@ -4846,12 +4790,19 @@ static void Skin2_OnChange(void) if (!Playing() || !splitscreen) return; // do whatever you want + if (lastskinnames[1] == NULL) + lastskinnames[1] = Z_StrDup(cv_skin2.string); + if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer)) + { SendNameAndColor2(); + Z_Free(lastskinnames[1]); + lastskinnames[1] = Z_StrDup(cv_skin.string); + } else { CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name); + CV_StealthSet(&cv_skin2, lastskinnames[1]); } } @@ -4861,15 +4812,18 @@ static void Skin2_OnChange(void) */ static void Color_OnChange(void) { - if (!Playing()) { + if (!Playing()) + { if (!cv_playercolor.value || !skincolors[cv_playercolor.value].accessible) CV_StealthSetValue(&cv_playercolor, lastgoodcolor); } else { - if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player. + if (!(multiplayer || netgame)) // In single player. { - CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name); + // Just do it here if devmode is enabled + if (cv_debug || devparm) + SetColorLocal(consoleplayer, cv_playercolor.value); return; } diff --git a/src/d_netcmd.h b/src/netcode/d_netcmd.h similarity index 99% rename from src/d_netcmd.h rename to src/netcode/d_netcmd.h index 26bf4d5c6..4849079d0 100644 --- a/src/d_netcmd.h +++ b/src/netcode/d_netcmd.h @@ -15,7 +15,7 @@ #ifndef __D_NETCMD__ #define __D_NETCMD__ -#include "command.h" +#include "../command.h" // console vars extern consvar_t cv_playername; @@ -201,6 +201,7 @@ void D_SendPlayerConfig(void); void Command_ExitGame_f(void); void Command_Retry_f(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore +void D_SendExitLevel(boolean cheat); void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); boolean IsPlayerAdmin(INT32 playernum); void SetAdminPlayer(INT32 playernum); diff --git a/src/d_netfil.c b/src/netcode/d_netfil.c similarity index 93% rename from src/d_netfil.c rename to src/netcode/d_netfil.c index 3fef75681..c5ddef7b6 100644 --- a/src/d_netfil.c +++ b/src/netcode/d_netfil.c @@ -31,24 +31,25 @@ #include #endif -#include "doomdef.h" -#include "doomstat.h" -#include "d_main.h" -#include "g_game.h" -#include "i_time.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../d_main.h" +#include "../g_game.h" +#include "../i_time.h" #include "i_net.h" -#include "i_system.h" -#include "m_argv.h" +#include "../i_system.h" +#include "../m_argv.h" #include "d_net.h" -#include "w_wad.h" +#include "../w_wad.h" #include "d_netfil.h" -#include "z_zone.h" -#include "byteptr.h" -#include "p_setup.h" -#include "m_misc.h" -#include "m_menu.h" -#include "md5.h" -#include "filesrch.h" +#include "net_command.h" +#include "../z_zone.h" +#include "../byteptr.h" +#include "../p_setup.h" +#include "../m_misc.h" +#include "../m_menu.h" +#include "../md5.h" +#include "../filesrch.h" #include @@ -103,26 +104,31 @@ typedef struct } pauseddownload_t; static pauseddownload_t *pauseddownload = NULL; -#ifndef NONET // for cl loading screen INT32 lastfilenum = -1; INT32 downloadcompletednum = 0; UINT32 downloadcompletedsize = 0; INT32 totalfilesrequestednum = 0; UINT32 totalfilesrequestedsize = 0; -#endif luafiletransfer_t *luafiletransfers = NULL; boolean waitingforluafiletransfer = false; boolean waitingforluafilecommand = false; char luafiledir[256 + 16] = "luafiles"; +// max file size to send to a player (in kilobytes) +static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {204800, "MAX"}, {0, NULL}}; +consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL); + +consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); + +// Speed of file downloading (in packets per tic) +static CV_PossibleValue_t downloadspeed_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; +consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL); static UINT16 GetWadNumFromFileNeededId(UINT8 id) { - UINT16 wadnum; - - for (wadnum = mainwads; wadnum < numwadfiles; wadnum++) + for (UINT16 wadnum = mainwads; wadnum < numwadfiles; wadnum++) { if (!wadfiles[wadnum]->important) continue; @@ -142,14 +148,13 @@ static UINT16 GetWadNumFromFileNeededId(UINT8 id) */ UINT8 *PutFileNeeded(UINT16 firstfile) { - size_t i; UINT8 count = 0; UINT8 *p_start = netbuffer->packettype == PT_MOREFILESNEEDED ? netbuffer->u.filesneededcfg.files : netbuffer->u.serverinfo.fileneeded; UINT8 *p = p_start; char wadfilename[MAX_WADPATH] = ""; UINT8 filestatus, folder; - for (i = mainwads; i < numwadfiles; i++) //mainwads, otherwise we start on the first mainwad + for (size_t i = mainwads; i < numwadfiles; i++) //mainwads, otherwise we start on the first mainwad { // If it has only music/sound lumps, don't put it in the list if (!wadfiles[i]->important) @@ -224,7 +229,6 @@ void FreeFileNeeded(void) */ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 firstfile) { - INT32 i; UINT8 *p; UINT8 filestatus; @@ -233,7 +237,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi AllocFileNeeded(fileneedednum); - for (i = firstfile; i < fileneedednum; i++) + for (INT32 i = firstfile; i < fileneedednum; i++) { fileneeded[i].type = FILENEEDED_WAD; fileneeded[i].status = FS_NOTCHECKED; // We haven't even started looking for the file yet @@ -250,9 +254,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi void CL_PrepareDownloadSaveGame(const char *tmpsave) { -#ifndef NONET lastfilenum = -1; -#endif FreeFileNeeded(); AllocFileNeeded(1); @@ -275,9 +277,9 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave) */ boolean CL_CheckDownloadable(void) { - UINT8 i,dlstatus = 0; + UINT8 dlstatus = 0; - for (i = 0; i < fileneedednum; i++) + for (UINT8 i = 0; i < fileneedednum; i++) if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { if (fileneeded[i].willsend == 1) @@ -298,7 +300,7 @@ boolean CL_CheckDownloadable(void) // not downloadable, put reason in console CONS_Alert(CONS_NOTICE, M_GetText("You need additional files to connect to this server:\n")); - for (i = 0; i < fileneedednum; i++) + for (UINT8 i = 0; i < fileneedednum; i++) if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { CONS_Printf(" * \"%s\" (%dK)", fileneeded[i].filename, fileneeded[i].totalsize >> 10); @@ -368,14 +370,13 @@ void CL_AbortDownloadResume(void) boolean CL_SendFileRequest(void) { char *p; - INT32 i; INT64 totalfreespaceneeded = 0, availablefreespace; #ifdef PARANOIA if (M_CheckParm("-nodownload")) I_Error("Attempted to download files in -nodownload mode"); - for (i = 0; i < fileneedednum; i++) + for (INT32 i = 0; i < fileneedednum; i++) if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) { @@ -385,7 +386,7 @@ boolean CL_SendFileRequest(void) netbuffer->packettype = PT_REQUESTFILE; p = (char *)netbuffer->u.textcmd; - for (i = 0; i < fileneedednum; i++) + for (INT32 i = 0; i < fileneedednum; i++) if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)) { totalfreespaceneeded += fileneeded[i].totalsize; @@ -413,26 +414,31 @@ boolean CL_SendFileRequest(void) } // get request filepak and put it on the send queue -// returns false if a requested file was not found or cannot be sent -boolean PT_RequestFile(INT32 node) +void PT_RequestFile(SINT8 node) { UINT8 *p = netbuffer->u.textcmd; - UINT8 id; + + if (client || !cv_downloading.value) + { + Net_CloseConnection(node); // close connection if you are not the server or disabled downloading + return; + } while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow { - id = READUINT8(p); + UINT8 id = READUINT8(p); if (id == 0xFF) break; if (!AddFileToSendQueue(node, id)) { SV_AbortSendFiles(node); - return false; // don't read the rest of the files + Net_CloseConnection(node); // close connection if one of the requested files could not be sent + return; // don't read the rest of the files } } - return true; // no problems with any files + return; // no problems with any files } /** Checks if the files needed aren't already loaded or on the disk @@ -531,9 +537,7 @@ INT32 CL_CheckFiles(void) // Load it now boolean CL_LoadServerFiles(void) { - INT32 i; - - for (i = 0; i < fileneedednum; i++) + for (INT32 i = 0; i < fileneedednum; i++) { if (fileneeded[i].status == FS_OPEN) continue; // Already loaded @@ -629,11 +633,10 @@ void AddLuaFileTransfer(const char *filename, const char *mode) static void SV_PrepareSendLuaFileToNextNode(void) { - INT32 i; UINT8 success = 1; // Find a client to send the file to - for (i = 1; i < MAXNETNODES; i++) + for (INT32 i = 1; i < MAXNETNODES; i++) if (luafiletransfers->nodestatus[i] == LFTNS_WAITING) // Node waiting { // Tell the client we're about to send them the file @@ -655,13 +658,12 @@ static void SV_PrepareSendLuaFileToNextNode(void) void SV_PrepareSendLuaFile(void) { char *binfilename; - INT32 i; luafiletransfers->ongoing = true; // Set status to "waiting" for everyone - for (i = 0; i < MAXNETNODES; i++) - luafiletransfers->nodestatus[i] = (nodeingame[i] ? LFTNS_WAITING : LFTNS_NONE); + for (INT32 i = 0; i < MAXNETNODES; i++) + luafiletransfers->nodestatus[i] = (netnodes[i].ingame ? LFTNS_WAITING : LFTNS_NONE); if (FIL_ReadFileOK(luafiletransfers->realfilename)) { @@ -1137,12 +1139,13 @@ void FileSendTicker(void) } } -void PT_FileAck(void) +void PT_FileAck(SINT8 node) { fileack_pak *packet = &netbuffer->u.fileack; - INT32 node = doomcom->remotenode; filetran_t *trans = &transfer[node]; - INT32 i, j; + + if (client) + return; // Wrong file id? Ignore it, it's probably a late packet if (!(trans->txlist && packet->fileid == trans->txlist->fileid)) @@ -1161,11 +1164,11 @@ void PT_FileAck(void) trans->dontsenduntil = 0; } - for (i = 0; i < packet->numsegments; i++) + for (INT32 i = 0; i < packet->numsegments; i++) { fileacksegment_t *segment = &packet->segments[i]; - for (j = 0; j < 32; j++) + for (INT32 j = 0; j < 32; j++) if (LONG(segment->acks) & (1 << j)) { if (LONG(segment->start) * FILEFRAGMENTSIZE >= trans->txlist->size) @@ -1190,24 +1193,23 @@ void PT_FileAck(void) } } -void PT_FileReceived(void) +void PT_FileReceived(SINT8 node) { - filetx_t *trans = transfer[doomcom->remotenode].txlist; + filetx_t *trans = transfer[node].txlist; - if (trans && netbuffer->u.filereceived == trans->fileid) - SV_EndFileSend(doomcom->remotenode); + if (server && trans && netbuffer->u.filereceived == trans->fileid) + SV_EndFileSend(node); } static void SendAckPacket(fileack_pak *packet, UINT8 fileid) { size_t packetsize; - INT32 i; packetsize = sizeof(*packet) + packet->numsegments * sizeof(*packet->segments); // Finalise the packet packet->fileid = fileid; - for (i = 0; i < packet->numsegments; i++) + for (INT32 i = 0; i < packet->numsegments; i++) { packet->segments[i].start = LONG(packet->segments[i].start); packet->segments[i].acks = LONG(packet->segments[i].acks); @@ -1247,9 +1249,7 @@ static void AddFragmentToAckPacket(fileack_pak *packet, UINT8 iteration, UINT32 void FileReceiveTicker(void) { - INT32 i; - - for (i = 0; i < fileneedednum; i++) + for (INT32 i = 0; i < fileneedednum; i++) { fileneeded_t *file = &fileneeded[i]; @@ -1263,8 +1263,7 @@ void FileReceiveTicker(void) if (file->ackresendposition != UINT32_MAX && file->status == FS_DOWNLOADING) { // Acknowledge ~70 MB/s, whichs means the client sends ~18 KB/s - INT32 j; - for (j = 0; j < 2048; j++) + for (INT32 j = 0; j < 2048; j++) { if (file->receivedfragments[file->ackresendposition]) AddFragmentToAckPacket(file->ackpacket, file->iteration, file->ackresendposition, i); @@ -1281,8 +1280,27 @@ void FileReceiveTicker(void) } } -void PT_FileFragment(void) +void PT_FileFragment(SINT8 node, INT32 netconsole) { + if (netnodes[node].ingame) + { + // Only accept PT_FILEFRAGMENT from the server. + if (node != servernode) + { + CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node); + if (server) + SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + return; + } + if (server) + return; + } + else if (server || node != servernode) + { + Net_CloseConnection(node); + return; + } + INT32 filenum = netbuffer->u.filetxpak.fileid; fileneeded_t *file = &fileneeded[filenum]; UINT32 fragmentpos = LONG(netbuffer->u.filetxpak.position); @@ -1439,9 +1457,7 @@ void PT_FileFragment(void) I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s); } -#ifndef NONET lastfilenum = filenum; -#endif } /** \brief Checks if a node is downloading a file @@ -1469,15 +1485,14 @@ void SV_AbortSendFiles(INT32 node) void CloseNetFile(void) { - INT32 i; // Is sending? - for (i = 0; i < MAXNETNODES; i++) + for (INT32 i = 0; i < MAXNETNODES; i++) SV_AbortSendFiles(i); // Receiving a file? if (fileneeded) { - for (i = 0; i < fileneedednum; i++) + for (INT32 i = 0; i < fileneedednum; i++) if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file) { fclose(fileneeded[i].file); @@ -1510,9 +1525,7 @@ void CloseNetFile(void) void Command_Downloads_f(void) { - INT32 node; - - for (node = 0; node < MAXNETNODES; node++) + for (INT32 node = 0; node < MAXNETNODES; node++) if (transfer[node].txlist && transfer[node].txlist->ram == SF_FILE) // Node is downloading a file? { @@ -1546,14 +1559,11 @@ void Command_Downloads_f(void) void nameonly(char *s) { - size_t j, len; - void *ns; - - for (j = strlen(s); j != (size_t)-1; j--) + for (size_t j = strlen(s); j != (size_t)-1; j--) if ((s[j] == '\\') || (s[j] == ':') || (s[j] == '/')) { - ns = &(s[j+1]); - len = strlen(ns); + void *ns = &(s[j+1]); + size_t len = strlen(ns); #if 0 M_Memcpy(s, ns, len+1); #else @@ -1566,9 +1576,9 @@ void nameonly(char *s) // Returns the length in characters of the last element of a path. size_t nameonlylength(const char *s) { - size_t j, len = strlen(s); + size_t len = strlen(s); - for (j = len; j != (size_t)-1; j--) + for (size_t j = len; j != (size_t)-1; j--) if ((s[j] == '\\') || (s[j] == ':') || (s[j] == '/')) return len - j - 1; diff --git a/src/d_netfil.h b/src/netcode/d_netfil.h similarity index 94% rename from src/d_netfil.h rename to src/netcode/d_netfil.h index ecec976be..fdbec8c53 100644 --- a/src/d_netfil.h +++ b/src/netcode/d_netfil.h @@ -15,7 +15,7 @@ #include "d_net.h" #include "d_clisrv.h" -#include "w_wad.h" +#include "../w_wad.h" typedef enum { @@ -70,13 +70,13 @@ extern INT32 fileneedednum; extern fileneeded_t *fileneeded; extern char downloaddir[512]; -#ifndef NONET extern INT32 lastfilenum; extern INT32 downloadcompletednum; extern UINT32 downloadcompletedsize; extern INT32 totalfilesrequestednum; extern UINT32 totalfilesrequestedsize; -#endif + +extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; void AllocFileNeeded(INT32 size); void FreeFileNeeded(void); @@ -90,16 +90,16 @@ void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemet UINT8 fileid); void FileSendTicker(void); -void PT_FileAck(void); -void PT_FileReceived(void); +void PT_FileAck(SINT8 node); +void PT_FileReceived(SINT8 node); boolean SendingFile(INT32 node); void FileReceiveTicker(void); -void PT_FileFragment(void); +void PT_FileFragment(SINT8 node, INT32 netconsole); boolean CL_CheckDownloadable(void); boolean CL_SendFileRequest(void); -boolean PT_RequestFile(INT32 node); +void PT_RequestFile(SINT8 node); typedef enum { diff --git a/src/netcode/gamestate.c b/src/netcode/gamestate.c new file mode 100644 index 000000000..f36347c6d --- /dev/null +++ b/src/netcode/gamestate.c @@ -0,0 +1,336 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 gamestate.c +/// \brief Gamestate (re)sending + +#include "d_clisrv.h" +#include "d_netfil.h" +#include "gamestate.h" +#include "i_net.h" +#include "protocol.h" +#include "server_connection.h" +#include "../am_map.h" +#include "../byteptr.h" +#include "../console.h" +#include "../d_main.h" +#include "../doomstat.h" +#include "../doomtype.h" +#include "../f_finale.h" +#include "../g_demo.h" +#include "../g_game.h" +#include "../i_time.h" +#include "../lua_script.h" +#include "../lzf.h" +#include "../m_misc.h" +#include "../p_local.h" +#include "../p_saveg.h" +#include "../r_main.h" +#include "../tables.h" +#include "../z_zone.h" +#if defined (__GNUC__) || defined (__unix__) +#include +#endif + +#define SAVEGAMESIZE (768*1024) + +UINT8 hu_redownloadinggamestate = 0; +boolean cl_redownloadinggamestate = false; + +boolean SV_ResendingSavegameToAnyone(void) +{ + for (INT32 i = 0; i < MAXNETNODES; i++) + if (netnodes[i].resendingsavegame) + return true; + return false; +} + +void SV_SendSaveGame(INT32 node, boolean resending) +{ + size_t length, compressedlen; + UINT8 *savebuffer; + UINT8 *compressedsave; + UINT8 *buffertosend; + + // first save it in a malloced buffer + savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); + if (!savebuffer) + { + CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); + return; + } + + // Leave room for the uncompressed length. + save_p = savebuffer + sizeof(UINT32); + + P_SaveNetGame(resending); + + length = save_p - savebuffer; + if (length > SAVEGAMESIZE) + { + free(savebuffer); + save_p = NULL; + I_Error("Savegame buffer overrun"); + } + + // Allocate space for compressed save: one byte fewer than for the + // uncompressed data to ensure that the compression is worthwhile. + compressedsave = malloc(length - 1); + if (!compressedsave) + { + CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); + return; + } + + // Attempt to compress it. + if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) + { + // Compressing succeeded; send compressed data + + free(savebuffer); + + // State that we're compressed. + buffertosend = compressedsave; + WRITEUINT32(compressedsave, length - sizeof(UINT32)); + length = compressedlen + sizeof(UINT32); + } + else + { + // Compression failed to make it smaller; send original + + free(compressedsave); + + // State that we're not compressed + buffertosend = savebuffer; + WRITEUINT32(savebuffer, 0); + } + + AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); + save_p = NULL; + + // Remember when we started sending the savegame so we can handle timeouts + netnodes[node].sendingsavegame = true; + netnodes[node].freezetimeout = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte +} + +#ifdef DUMPCONSISTENCY +#define TMPSAVENAME "badmath.sav" +static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); + +void SV_SavedGame(void) +{ + size_t length; + UINT8 *savebuffer; + char tmpsave[256]; + + if (!cv_dumpconsistency.value) + return; + + sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + + // first save it in a malloced buffer + save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); + if (!save_p) + { + CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); + return; + } + + P_SaveNetGame(false); + + length = save_p - savebuffer; + if (length > SAVEGAMESIZE) + { + free(savebuffer); + save_p = NULL; + I_Error("Savegame buffer overrun"); + } + + // then save it! + if (!FIL_WriteFile(tmpsave, savebuffer, length)) + CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); + + free(savebuffer); + save_p = NULL; +} + +#undef TMPSAVENAME +#endif +#define TMPSAVENAME "$$$.sav" + + +void CL_LoadReceivedSavegame(boolean reloading) +{ + UINT8 *savebuffer = NULL; + size_t length, decompressedlen; + char tmpsave[256]; + + FreeFileNeeded(); + + sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + + length = FIL_ReadFile(tmpsave, &savebuffer); + + CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); + if (!length) + { + I_Error("Can't read savegame sent"); + return; + } + + save_p = savebuffer; + + // Decompress saved game if necessary. + decompressedlen = READUINT32(save_p); + if(decompressedlen > 0) + { + UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); + lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); + Z_Free(savebuffer); + save_p = savebuffer = decompressedbuffer; + } + + paused = false; + demoplayback = false; + titlemapinaction = TITLEMAP_OFF; + titledemo = false; + automapactive = false; + + // load a base level + if (P_LoadNetGame(reloading)) + { + const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum; + CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap)); + if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, "")) + { + CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl); + if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) + CONS_Printf(M_GetText(" Zone")); + if (actnum > 0) + CONS_Printf(" %2d", actnum); + } + CONS_Printf("\"\n"); + } + + // done + Z_Free(savebuffer); + save_p = NULL; + if (unlink(tmpsave) == -1) + CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); + consistancy[gametic%BACKUPTICS] = Consistancy(); + CON_ToggleOff(); + + // Tell the server we have received and reloaded the gamestate + // so they know they can resume the game + netbuffer->packettype = PT_RECEIVEDGAMESTATE; + HSendPacket(servernode, true, 0, 0); +} + +void CL_ReloadReceivedSavegame(void) +{ + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + LUA_InvalidatePlayer(&players[i]); + sprintf(player_names[i], "Player %d", i + 1); + } + + CL_LoadReceivedSavegame(true); + + neededtic = max(neededtic, gametic); + maketic = neededtic; + + ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn; + P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16)); + if (splitscreen) + { + ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn; + P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16)); + } + + camera.subsector = R_PointInSubsector(camera.x, camera.y); + camera2.subsector = R_PointInSubsector(camera2.x, camera2.y); + + cl_redownloadinggamestate = false; + + CONS_Printf(M_GetText("Game state reloaded\n")); +} + +void Command_ResendGamestate(void) +{ + SINT8 playernum; + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("resendgamestate : resend the game state to 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 || playernum == 0) + return; + + // Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on + netbuffer->packettype = PT_WILLRESENDGAMESTATE; + if (!HSendPacket(playernode[playernum], true, 0, 0)) + { + CONS_Alert(CONS_ERROR, M_GetText("A problem occurred, please try again.\n")); + return; + } +} + +void PT_CanReceiveGamestate(SINT8 node) +{ + if (client || netnodes[node].sendingsavegame) + return; + + CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[netnodes[node].player]); + + SV_SendSaveGame(node, true); // Resend a complete game state + netnodes[node].resendingsavegame = true; +} + +void PT_ReceivedGamestate(SINT8 node) +{ + netnodes[node].sendingsavegame = false; + netnodes[node].resendingsavegame = false; + netnodes[node].savegameresendcooldown = I_GetTime() + 5 * TICRATE; +} + +void PT_WillResendGamestate(SINT8 node) +{ + (void)node; + + char tmpsave[256]; + + if (server || cl_redownloadinggamestate) + return; + + // Send back a PT_CANRECEIVEGAMESTATE packet to the server + // so they know they can start sending the game state + netbuffer->packettype = PT_CANRECEIVEGAMESTATE; + if (!HSendPacket(servernode, true, 0, 0)) + return; + + CONS_Printf(M_GetText("Reloading game state...\n")); + + sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + + // Don't get a corrupt savegame error because tmpsave already exists + if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1) + I_Error("Can't delete %s\n", tmpsave); + + CL_PrepareDownloadSaveGame(tmpsave); + + cl_redownloadinggamestate = true; +} diff --git a/src/netcode/gamestate.h b/src/netcode/gamestate.h new file mode 100644 index 000000000..a2fae1f14 --- /dev/null +++ b/src/netcode/gamestate.h @@ -0,0 +1,31 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 gamestate.h +/// \brief Gamestate (re)sending + +#ifndef __GAMESTATE__ +#define __GAMESTATE__ + +#include "../doomtype.h" + +extern UINT8 hu_redownloadinggamestate; +extern boolean cl_redownloadinggamestate; + +boolean SV_ResendingSavegameToAnyone(void); +void SV_SendSaveGame(INT32 node, boolean resending); +void SV_SavedGame(void); +void CL_LoadReceivedSavegame(boolean reloading); +void CL_ReloadReceivedSavegame(void); +void Command_ResendGamestate(void); +void PT_CanReceiveGamestate(SINT8 node); +void PT_ReceivedGamestate(SINT8 node); +void PT_WillResendGamestate(SINT8 node); + +#endif diff --git a/src/http-mserv.c b/src/netcode/http-mserv.c similarity index 98% rename from src/http-mserv.c rename to src/netcode/http-mserv.c index df9a71a5c..f8bd5da21 100644 --- a/src/http-mserv.c +++ b/src/netcode/http-mserv.c @@ -18,14 +18,15 @@ Documentation available here. #include #endif -#include "doomdef.h" +#include "../doomdef.h" #include "d_clisrv.h" -#include "command.h" -#include "m_argv.h" -#include "m_menu.h" +#include "client_connection.h" +#include "../command.h" +#include "../m_argv.h" +#include "../m_menu.h" #include "mserv.h" #include "i_tcp.h"/* for current_port */ -#include "i_threads.h" +#include "../i_threads.h" /* reasonable default I guess?? */ #define DEFAULT_BUFFER_SIZE (4096) @@ -95,7 +96,7 @@ init_user_agent_once(void) { if (hms_useragent[0] != '\0') return; - + get_user_agent(hms_useragent, 512); } diff --git a/src/i_addrinfo.c b/src/netcode/i_addrinfo.c similarity index 100% rename from src/i_addrinfo.c rename to src/netcode/i_addrinfo.c diff --git a/src/i_addrinfo.h b/src/netcode/i_addrinfo.h similarity index 100% rename from src/i_addrinfo.h rename to src/netcode/i_addrinfo.h diff --git a/src/i_net.h b/src/netcode/i_net.h similarity index 98% rename from src/i_net.h rename to src/netcode/i_net.h index 12a07f183..09b842296 100644 --- a/src/i_net.h +++ b/src/netcode/i_net.h @@ -18,8 +18,8 @@ #pragma interface #endif -#include "doomdef.h" -#include "command.h" +#include "../doomdef.h" +#include "../command.h" /// \brief program net id #define DOOMCOM_ID (INT32)0x12345678l diff --git a/src/i_tcp.c b/src/netcode/i_tcp.c similarity index 88% rename from src/i_tcp.c rename to src/netcode/i_tcp.c index d95b381f4..698234579 100644 --- a/src/i_tcp.c +++ b/src/netcode/i_tcp.c @@ -36,109 +36,100 @@ #include #endif -#include "doomdef.h" +#include "../doomdef.h" -#if defined (NOMD5) && !defined (NONET) - //#define NONET +#ifdef USE_WINSOCK1 + #include +#else + #ifndef USE_WINSOCK + #include + #ifdef __APPLE_CC__ + #ifndef _BSD_SOCKLEN_T_ + #define _BSD_SOCKLEN_T_ + #endif //_BSD_SOCKLEN_T_ + #endif //__APPLE_CC__ + #include + #include + #include + #include + #endif //normal BSD API + + #include + #include + + #if defined (__unix__) || defined (__APPLE__) || defined (UNIXCOMMON) + #include + #endif // UNIXCOMMON #endif -#ifdef NONET - #undef HAVE_MINIUPNPC -#else - #ifdef USE_WINSOCK1 - #include - #else - #ifndef USE_WINSOCK - #include - #ifdef __APPLE_CC__ - #ifndef _BSD_SOCKLEN_T_ - #define _BSD_SOCKLEN_T_ - #endif //_BSD_SOCKLEN_T_ - #endif //__APPLE_CC__ - #include - #include - #include - #include - #endif //normal BSD API - - #include - #include - - #if defined (__unix__) || defined (__APPLE__) || defined (UNIXCOMMON) - #include - #endif // UNIXCOMMON +#ifdef USE_WINSOCK + // some undefined under win32 + #undef errno + //#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right? + #define errno h_errno // some very strange things happen when not using h_error?!? + #ifdef EWOULDBLOCK + #undef EWOULDBLOCK #endif - - #ifdef USE_WINSOCK - // some undefined under win32 - #undef errno - //#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right? - #define errno h_errno // some very strange things happen when not using h_error?!? - #ifdef EWOULDBLOCK - #undef EWOULDBLOCK - #endif - #define EWOULDBLOCK WSAEWOULDBLOCK - #ifdef EMSGSIZE - #undef EMSGSIZE - #endif - #define EMSGSIZE WSAEMSGSIZE - #ifdef ECONNREFUSED - #undef ECONNREFUSED - #endif - #define ECONNREFUSED WSAECONNREFUSED - #ifdef ETIMEDOUT - #undef ETIMEDOUT - #endif - #define ETIMEDOUT WSAETIMEDOUT - #ifndef IOC_VENDOR - #define IOC_VENDOR 0x18000000 - #endif - #ifndef _WSAIOW - #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) - #endif - #ifndef SIO_UDP_CONNRESET - #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) - #endif - #ifndef AI_ADDRCONFIG - #define AI_ADDRCONFIG 0x00000400 - #endif - #ifndef STATUS_INVALID_PARAMETER - #define STATUS_INVALID_PARAMETER 0xC000000D - #endif - #endif // USE_WINSOCK - - typedef union - { - struct sockaddr any; - struct sockaddr_in ip4; - #ifdef HAVE_IPV6 - struct sockaddr_in6 ip6; + #define EWOULDBLOCK WSAEWOULDBLOCK + #ifdef EMSGSIZE + #undef EMSGSIZE #endif - } mysockaddr_t; + #define EMSGSIZE WSAEMSGSIZE + #ifdef ECONNREFUSED + #undef ECONNREFUSED + #endif + #define ECONNREFUSED WSAECONNREFUSED + #ifdef ETIMEDOUT + #undef ETIMEDOUT + #endif + #define ETIMEDOUT WSAETIMEDOUT + #ifndef IOC_VENDOR + #define IOC_VENDOR 0x18000000 + #endif + #ifndef _WSAIOW + #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) + #endif + #ifndef SIO_UDP_CONNRESET + #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) + #endif + #ifndef AI_ADDRCONFIG + #define AI_ADDRCONFIG 0x00000400 + #endif + #ifndef STATUS_INVALID_PARAMETER + #define STATUS_INVALID_PARAMETER 0xC000000D + #endif +#endif // USE_WINSOCK - #ifdef HAVE_MINIUPNPC - #ifdef STATIC_MINIUPNPC - #define STATICLIB - #endif - #include "miniupnpc/miniwget.h" - #include "miniupnpc/miniupnpc.h" - #include "miniupnpc/upnpcommands.h" - #undef STATICLIB - static UINT8 UPNP_support = TRUE; - #endif // HAVE_MINIUPNC +typedef union +{ + struct sockaddr any; + struct sockaddr_in ip4; +#ifdef HAVE_IPV6 + struct sockaddr_in6 ip6; +#endif +} mysockaddr_t; -#endif // !NONET +#ifdef HAVE_MINIUPNPC + #ifdef STATIC_MINIUPNPC + #define STATICLIB + #endif + #include "miniupnpc/miniwget.h" + #include "miniupnpc/miniupnpc.h" + #include "miniupnpc/upnpcommands.h" + #undef STATICLIB + static UINT8 UPNP_support = TRUE; +#endif // HAVE_MINIUPNC #define MAXBANS 100 -#include "i_system.h" +#include "../i_system.h" #include "i_net.h" #include "d_net.h" #include "d_netfil.h" #include "i_tcp.h" -#include "m_argv.h" +#include "../m_argv.h" -#include "doomstat.h" +#include "../doomstat.h" // win32 #ifdef USE_WINSOCK @@ -151,7 +142,7 @@ #define SELECTTEST #define DEFAULTPORT "5029" -#if defined (USE_WINSOCK) && !defined (NONET) +#ifdef USE_WINSOCK typedef SOCKET SOCKET_TYPE; #define ERRSOCKET (SOCKET_ERROR) #else @@ -163,22 +154,20 @@ #define ERRSOCKET (-1) #endif -#ifndef NONET - // define socklen_t in DOS/Windows if it is not already defined - #ifdef USE_WINSOCK1 - typedef int socklen_t; - #endif - static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET}; - static size_t mysocketses = 0; - static int myfamily[MAXNETNODES+1] = {0}; - static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET}; - static mysockaddr_t clientaddress[MAXNETNODES+1]; - static mysockaddr_t broadcastaddress[MAXNETNODES+1]; - static size_t broadcastaddresses = 0; - static boolean nodeconnected[MAXNETNODES+1]; - static mysockaddr_t banned[MAXBANS]; - static UINT8 bannedmask[MAXBANS]; +// define socklen_t in DOS/Windows if it is not already defined +#ifdef USE_WINSOCK1 + typedef int socklen_t; #endif +static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET}; +static size_t mysocketses = 0; +static int myfamily[MAXNETNODES+1] = {0}; +static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET}; +static mysockaddr_t clientaddress[MAXNETNODES+1]; +static mysockaddr_t broadcastaddress[MAXNETNODES+1]; +static size_t broadcastaddresses = 0; +static boolean nodeconnected[MAXNETNODES+1]; +static mysockaddr_t banned[MAXBANS]; +static UINT8 bannedmask[MAXBANS]; static size_t numbans = 0; static boolean SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1? @@ -187,7 +176,6 @@ static boolean init_tcp_driver = false; static const char *serverport_name = DEFAULTPORT; static const char *clientport_name;/* any port */ -#ifndef NONET #ifdef USE_WINSOCK // stupid microsoft makes things complicated static char *get_WSAErrorStr(int e) @@ -387,47 +375,33 @@ static const char *SOCK_AddrToStr(mysockaddr_t *sk) #endif return s; } -#endif static const char *SOCK_GetNodeAddress(INT32 node) { if (node == 0) return "self"; -#ifdef NONET - return NULL; -#else if (!nodeconnected[node]) return NULL; return SOCK_AddrToStr(&clientaddress[node]); -#endif } static const char *SOCK_GetBanAddress(size_t ban) { if (ban >= numbans) return NULL; -#ifdef NONET - return NULL; -#else return SOCK_AddrToStr(&banned[ban]); -#endif } static const char *SOCK_GetBanMask(size_t ban) { -#ifdef NONET - (void)ban; -#else static char s[16]; //255.255.255.255 netmask? no, just CDIR for only if (ban >= numbans) return NULL; if (sprintf(s,"%d",bannedmask[ban]) > 0) return s; -#endif return NULL; } -#ifndef NONET static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) { UINT32 bitmask = INADDR_NONE; @@ -455,24 +429,20 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask) */ static void cleanupnodes(void) { - SINT8 j; - if (!Playing()) return; // Why can't I start at zero? - for (j = 1; j < MAXNETNODES; j++) - if (!(nodeingame[j] || SendingFile(j))) + for (SINT8 j = 1; j < MAXNETNODES; j++) + if (!(netnodes[j].ingame || SendingFile(j))) nodeconnected[j] = false; } static SINT8 getfreenode(void) { - SINT8 j; - cleanupnodes(); - for (j = 0; j < MAXNETNODES; j++) + for (SINT8 j = 0; j < MAXNETNODES; j++) if (!nodeconnected[j]) { nodeconnected[j] = true; @@ -485,8 +455,8 @@ static SINT8 getfreenode(void) * downloading a needed wad, but it's better than not letting anyone join... */ /*I_Error("No more free nodes!!1!11!11!!1111\n"); - for (j = 1; j < MAXNETNODES; j++) - if (!nodeingame[j]) + for (SINT8 j = 1; j < MAXNETNODES; j++) + if (!netnodes[j].ingame) return j;*/ return -1; @@ -497,28 +467,27 @@ void Command_Numnodes(void) { INT32 connected = 0; INT32 ingame = 0; - INT32 i; - for (i = 1; i < MAXNETNODES; i++) + for (INT32 i = 1; i < MAXNETNODES; i++) { - if (!(nodeconnected[i] || nodeingame[i])) + if (!(nodeconnected[i] || netnodes[i].ingame)) continue; if (nodeconnected[i]) connected++; - if (nodeingame[i]) + if (netnodes[i].ingame) ingame++; CONS_Printf("%2d - ", i); - if (nodetoplayer[i] != -1) - CONS_Printf("player %.2d", nodetoplayer[i]); + if (netnodes[i].player != -1) + CONS_Printf("player %.2d", netnodes[i].player); else CONS_Printf(" "); if (nodeconnected[i]) CONS_Printf(" - connected"); else CONS_Printf(" - "); - if (nodeingame[i]) + if (netnodes[i].ingame) CONS_Printf(" - ingame"); else CONS_Printf(" - "); @@ -531,19 +500,17 @@ void Command_Numnodes(void) connected, ingame); } #endif -#endif -#ifndef NONET // Returns true if a packet was received from a new node, false in all other cases static boolean SOCK_Get(void) { - size_t i, n; + size_t i; int j; ssize_t c; mysockaddr_t fromaddress; socklen_t fromlen; - for (n = 0; n < mysocketses; n++) + for (size_t n = 0; n < mysocketses; n++) { fromlen = (socklen_t)sizeof(fromaddress); c = recvfrom(mysockets[n], (char *)&doomcom->data, MAXPACKETLENGTH, 0, @@ -596,20 +563,17 @@ static boolean SOCK_Get(void) doomcom->remotenode = -1; // no packet return false; } -#endif // check if we can send (do not go over the buffer) -#ifndef NONET static fd_set masterset; #ifdef SELECTTEST static boolean FD_CPY(fd_set *src, fd_set *dst, SOCKET_TYPE *fd, size_t len) { - size_t i; boolean testset = false; FD_ZERO(dst); - for (i = 0; i < len;i++) + for (size_t i = 0; i < len;i++) { if(fd[i] != (SOCKET_TYPE)ERRSOCKET && FD_ISSET(fd[i], src) && !FD_ISSET(fd[i], dst)) // no checking for dups @@ -649,9 +613,7 @@ static boolean SOCK_CanGet(void) return false; } #endif -#endif -#ifndef NONET static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr) { socklen_t d4 = (socklen_t)sizeof(struct sockaddr_in); @@ -675,16 +637,15 @@ static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr static void SOCK_Send(void) { ssize_t c = ERRSOCKET; - size_t i, j; if (!nodeconnected[doomcom->remotenode]) return; if (doomcom->remotenode == BROADCASTADDR) { - for (i = 0; i < mysocketses; i++) + for (size_t i = 0; i < mysocketses; i++) { - for (j = 0; j < broadcastaddresses; j++) + for (size_t j = 0; j < broadcastaddresses; j++) { if (myfamily[i] == broadcastaddress[j].any.sa_family) SOCK_SendToAddr(mysockets[i], &broadcastaddress[j]); @@ -694,7 +655,7 @@ static void SOCK_Send(void) } else if (nodesocket[doomcom->remotenode] == (SOCKET_TYPE)ERRSOCKET) { - for (i = 0; i < mysocketses; i++) + for (size_t i = 0; i < mysocketses; i++) { if (myfamily[i] == clientaddress[doomcom->remotenode].any.sa_family) SOCK_SendToAddr(mysockets[i], &clientaddress[doomcom->remotenode]); @@ -714,9 +675,7 @@ static void SOCK_Send(void) SOCK_GetNodeAddress(doomcom->remotenode), e, strerror(e)); } } -#endif -#ifndef NONET static void SOCK_FreeNodenum(INT32 numnode) { // can't disconnect from self :) @@ -731,12 +690,10 @@ static void SOCK_FreeNodenum(INT32 numnode) // put invalid address memset(&clientaddress[numnode], 0, sizeof (clientaddress[numnode])); } -#endif // // UDPsocket // -#ifndef NONET // allocate a socket static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen) @@ -1061,12 +1018,10 @@ static boolean UDP_Socket(void) return true; } -#endif boolean I_InitTcpDriver(void) { boolean tcp_was_up = init_tcp_driver; -#ifndef NONET if (!init_tcp_driver) { #ifdef USE_WINSOCK @@ -1121,7 +1076,7 @@ boolean I_InitTcpDriver(void) #endif init_tcp_driver = true; } -#endif + if (!tcp_was_up && init_tcp_driver) { I_AddExitFunc(I_ShutdownTcpDriver); @@ -1135,11 +1090,9 @@ boolean I_InitTcpDriver(void) return init_tcp_driver; } -#ifndef NONET static void SOCK_CloseSocket(void) { - size_t i; - for (i=0; i < MAXNETNODES+1; i++) + for (size_t i=0; i < MAXNETNODES+1; i++) { if (mysockets[i] != (SOCKET_TYPE)ERRSOCKET && FD_ISSET(mysockets[i], &masterset)) @@ -1150,11 +1103,9 @@ static void SOCK_CloseSocket(void) mysockets[i] = ERRSOCKET; } } -#endif void I_ShutdownTcpDriver(void) { -#ifndef NONET SOCK_CloseSocket(); CONS_Printf("I_ShutdownTcpDriver: "); @@ -1164,10 +1115,8 @@ void I_ShutdownTcpDriver(void) #endif CONS_Printf("shut down\n"); init_tcp_driver = false; -#endif } -#ifndef NONET static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port) { SINT8 newnode = -1; @@ -1223,17 +1172,13 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port) I_freeaddrinfo(ai); return newnode; } -#endif static boolean SOCK_OpenSocket(void) { -#ifndef NONET - size_t i; - memset(clientaddress, 0, sizeof (clientaddress)); nodeconnected[0] = true; // always connected to self - for (i = 1; i < MAXNETNODES; i++) + for (size_t i = 1; i < MAXNETNODES; i++) nodeconnected[i] = false; nodeconnected[BROADCASTADDR] = true; I_NetSend = SOCK_Send; @@ -1251,18 +1196,12 @@ static boolean SOCK_OpenSocket(void) // build the socket but close it first SOCK_CloseSocket(); return UDP_Socket(); -#else - return false; -#endif } static boolean SOCK_Ban(INT32 node) { if (node > MAXNETNODES) return false; -#ifdef NONET - return false; -#else if (numbans == MAXBANS) return false; @@ -1281,16 +1220,10 @@ static boolean SOCK_Ban(INT32 node) #endif numbans++; return true; -#endif } static boolean SOCK_SetBanAddress(const char *address, const char *mask) { -#ifdef NONET - (void)address; - (void)mask; - return false; -#else struct my_addrinfo *ai, *runp, hints; int gaie; @@ -1335,7 +1268,6 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask) I_freeaddrinfo(ai); return true; -#endif } static void SOCK_ClearBans(void) diff --git a/src/i_tcp.h b/src/netcode/i_tcp.h similarity index 100% rename from src/i_tcp.h rename to src/netcode/i_tcp.h diff --git a/src/mserv.c b/src/netcode/mserv.c similarity index 98% rename from src/mserv.c rename to src/netcode/mserv.c index 62cda96e4..1c7f3e08d 100644 --- a/src/mserv.c +++ b/src/netcode/mserv.c @@ -15,13 +15,14 @@ #include #endif -#include "doomstat.h" -#include "doomdef.h" -#include "command.h" -#include "i_threads.h" +#include "../doomstat.h" +#include "../doomdef.h" +#include "../command.h" +#include "../i_threads.h" #include "mserv.h" -#include "m_menu.h" -#include "z_zone.h" +#include "client_connection.h" +#include "../m_menu.h" +#include "../z_zone.h" #ifdef MASTERSERVER @@ -45,9 +46,7 @@ static I_cond MSCond; # define Unlock_state() #endif/*HAVE_THREADS*/ -#ifndef NONET static void Command_Listserv_f(void); -#endif #endif/*MASTERSERVER*/ @@ -89,7 +88,6 @@ msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test */ void AddMServCommands(void) { -#ifndef NONET CV_RegisterVar(&cv_masterserver); CV_RegisterVar(&cv_masterserver_update_rate); CV_RegisterVar(&cv_masterserver_timeout); @@ -100,7 +98,6 @@ void AddMServCommands(void) COM_AddCommand("listserv", Command_Listserv_f, 0); COM_AddCommand("masterserver_update", Update_parameters, COM_LUA); // allows people to updates manually in case you were delisted by accident #endif -#endif } #ifdef MASTERSERVER @@ -189,7 +186,6 @@ void GetMODVersion_Console(void) } #endif -#ifndef NONET /** Gets a list of game servers. Called from console. */ static void Command_Listserv_f(void) @@ -200,7 +196,6 @@ static void Command_Listserv_f(void) HMS_list_servers(); } } -#endif static void Finish_registration (void) diff --git a/src/mserv.h b/src/netcode/mserv.h similarity index 99% rename from src/mserv.h rename to src/netcode/mserv.h index 07253da85..0bc8c8e6b 100644 --- a/src/mserv.h +++ b/src/netcode/mserv.h @@ -14,7 +14,7 @@ #ifndef _MSERV_H_ #define _MSERV_H_ -#include "i_threads.h" +#include "../i_threads.h" // lowered from 32 due to menu changes #define NUM_LIST_ROOMS 16 diff --git a/src/netcode/net_command.c b/src/netcode/net_command.c new file mode 100644 index 000000000..2b3abfd02 --- /dev/null +++ b/src/netcode/net_command.c @@ -0,0 +1,382 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2022 by Sonic Team Junior. +// +// 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 net_command.c +/// \brief Net command handling + +#include "net_command.h" +#include "tic_command.h" +#include "gamestate.h" +#include "server_connection.h" +#include "d_clisrv.h" +#include "i_net.h" +#include "../byteptr.h" +#include "../g_game.h" +#include "../z_zone.h" +#include "../doomtype.h" + +textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL}; +UINT8 localtextcmd[MAXTEXTCMD]; +UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen +static void (*listnetxcmd[MAXNETXCMD])(UINT8 **p, INT32 playernum); + +void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum)) +{ +#ifdef PARANOIA + if (id >= MAXNETXCMD) + I_Error("Command id %d too big", id); + if (listnetxcmd[id] != 0) + I_Error("Command id %d already used", id); +#endif + listnetxcmd[id] = cmd_f; +} + +void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam) +{ + if (localtextcmd[0]+2+nparam > MAXTEXTCMD) + { + // for future reference: if (cv_debug) != debug disabled. + CONS_Alert(CONS_ERROR, M_GetText("NetXCmd buffer full, cannot add netcmd %d! (size: %d, needed: %s)\n"), id, localtextcmd[0], sizeu1(nparam)); + return; + } + localtextcmd[0]++; + localtextcmd[localtextcmd[0]] = (UINT8)id; + if (param && nparam) + { + M_Memcpy(&localtextcmd[localtextcmd[0]+1], param, nparam); + localtextcmd[0] = (UINT8)(localtextcmd[0] + (UINT8)nparam); + } +} + +// splitscreen player +void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam) +{ + if (localtextcmd2[0]+2+nparam > MAXTEXTCMD) + { + I_Error("No more place in the buffer for netcmd %d\n",id); + return; + } + localtextcmd2[0]++; + localtextcmd2[localtextcmd2[0]] = (UINT8)id; + if (param && nparam) + { + M_Memcpy(&localtextcmd2[localtextcmd2[0]+1], param, nparam); + localtextcmd2[0] = (UINT8)(localtextcmd2[0] + (UINT8)nparam); + } +} + +UINT8 GetFreeXCmdSize(void) +{ + // -1 for the size and another -1 for the ID. + return (UINT8)(localtextcmd[0] - 2); +} + +// Frees all textcmd memory for the specified tic +void D_FreeTextcmd(tic_t tic) +{ + textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; + textcmdtic_t *textcmdtic = *tctprev; + + while (textcmdtic && textcmdtic->tic != tic) + { + tctprev = &textcmdtic->next; + textcmdtic = textcmdtic->next; + } + + if (textcmdtic) + { + // Remove this tic from the list. + *tctprev = textcmdtic->next; + + // Free all players. + for (INT32 i = 0; i < TEXTCMD_HASH_SIZE; i++) + { + textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[i]; + + while (textcmdplayer) + { + textcmdplayer_t *tcpnext = textcmdplayer->next; + Z_Free(textcmdplayer); + textcmdplayer = tcpnext; + } + } + + // Free this tic's own memory. + Z_Free(textcmdtic); + } +} + +// Gets the buffer for the specified ticcmd, or NULL if there isn't one +UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum) +{ + textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; + while (textcmdtic && textcmdtic->tic != tic) textcmdtic = textcmdtic->next; + + // Do we have an entry for the tic? If so, look for player. + if (textcmdtic) + { + textcmdplayer_t *textcmdplayer = textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)]; + while (textcmdplayer && textcmdplayer->playernum != playernum) textcmdplayer = textcmdplayer->next; + + if (textcmdplayer) return textcmdplayer->cmd; + } + + return NULL; +} + +// Gets the buffer for the specified ticcmd, creating one if necessary +UINT8* D_GetTextcmd(tic_t tic, INT32 playernum) +{ + textcmdtic_t *textcmdtic = textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; + textcmdtic_t **tctprev = &textcmds[tic & (TEXTCMD_HASH_SIZE - 1)]; + textcmdplayer_t *textcmdplayer, **tcpprev; + + // Look for the tic. + while (textcmdtic && textcmdtic->tic != tic) + { + tctprev = &textcmdtic->next; + textcmdtic = textcmdtic->next; + } + + // If we don't have an entry for the tic, make it. + if (!textcmdtic) + { + textcmdtic = *tctprev = Z_Calloc(sizeof (textcmdtic_t), PU_STATIC, NULL); + textcmdtic->tic = tic; + } + + tcpprev = &textcmdtic->playercmds[playernum & (TEXTCMD_HASH_SIZE - 1)]; + textcmdplayer = *tcpprev; + + // Look for the player. + while (textcmdplayer && textcmdplayer->playernum != playernum) + { + tcpprev = &textcmdplayer->next; + textcmdplayer = textcmdplayer->next; + } + + // If we don't have an entry for the player, make it. + if (!textcmdplayer) + { + textcmdplayer = *tcpprev = Z_Calloc(sizeof (textcmdplayer_t), PU_STATIC, NULL); + textcmdplayer->playernum = playernum; + } + + return textcmdplayer->cmd; +} + +void ExtraDataTicker(void) +{ + for (INT32 i = 0; i < MAXPLAYERS; i++) + if (playeringame[i] || i == 0) + { + UINT8 *bufferstart = D_GetExistingTextcmd(gametic, i); + + if (bufferstart) + { + UINT8 *curpos = bufferstart; + UINT8 *bufferend = &curpos[curpos[0]+1]; + + curpos++; + while (curpos < bufferend) + { + if (*curpos < MAXNETXCMD && listnetxcmd[*curpos]) + { + const UINT8 id = *curpos; + curpos++; + DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i)); + (listnetxcmd[id])(&curpos, i); + DEBFILE("done\n"); + } + else + { + if (server) + { + SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic)); + } + CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]); + break; + } + } + } + } + + // If you are a client, you can safely forget the net commands for this tic + // If you are the server, you need to remember them until every client has been acknowledged, + // because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it + if (client) + D_FreeTextcmd(gametic); +} + +// used at txtcmds received to check packetsize bound +size_t TotalTextCmdPerTic(tic_t tic) +{ + size_t total = 1; // num of textcmds in the tic (ntextcmd byte) + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + UINT8 *textcmd = D_GetExistingTextcmd(tic, i); + if ((!i || playeringame[i]) && textcmd) + total += 2 + textcmd[0]; // "+2" for size and playernum + } + + return total; +} + +void PT_TextCmd(SINT8 node, INT32 netconsole) +{ + if (client) + return; + + // splitscreen special + if (netbuffer->packettype == PT_TEXTCMD2) + netconsole = netnodes[node].player2; + + if (netconsole < 0 || netconsole >= MAXPLAYERS) + Net_UnAcknowledgePacket(node); + else + { + size_t j; + tic_t tic = maketic; + UINT8 *textcmd; + + // ignore if the textcmd has a reported size of zero + // this shouldn't be sent at all + if (!netbuffer->u.textcmd[0]) + { + DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n", + node, netconsole)); + Net_UnAcknowledgePacket(node); + return; + } + + // ignore if the textcmd size var is actually larger than it should be + // BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength + if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1) + { + DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n", + netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1), + node, netconsole)); + Net_UnAcknowledgePacket(node); + return; + } + + // check if tic that we are making isn't too large else we cannot send it :( + // doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time + j = software_MAXPACKETLENGTH + - (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE + + (doomcom->numslots+1)*sizeof(ticcmd_t)); + + // search a tic that have enougth space in the ticcmd + while ((textcmd = D_GetExistingTextcmd(tic, netconsole)), + (TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD) + && tic < firstticstosend + BACKUPTICS) + tic++; + + if (tic >= firstticstosend + BACKUPTICS) + { + DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, " + "tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)), + maketic, firstticstosend, node, netconsole)); + Net_UnAcknowledgePacket(node); + return; + } + + // Make sure we have a buffer + if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole); + + DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n", + tic, textcmd[0]+1, netconsole, firstticstosend, maketic)); + + M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]); + textcmd[0] += (UINT8)netbuffer->u.textcmd[0]; + } +} + +void SV_WriteNetCommandsForTic(tic_t tic, UINT8 **buf) +{ + UINT8 *numcmds; + + numcmds = (*buf)++; + *numcmds = 0; + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + UINT8 *cmd = D_GetExistingTextcmd(tic, i); + INT32 size = cmd ? cmd[0] : 0; + + if ((!i || playeringame[i]) && size) + { + (*numcmds)++; + WRITEUINT8(*buf, i); + M_Memcpy(*buf, cmd, size + 1); + *buf += size + 1; + } + } +} + +void CL_CopyNetCommandsFromServerPacket(tic_t tic, UINT8 **buf) +{ + UINT8 numcmds = *(*buf)++; + + for (UINT32 i = 0; i < numcmds; i++) + { + INT32 playernum = *(*buf)++; // playernum + size_t size = (*buf)[0]+1; + + if (tic >= gametic) // Don't copy old net commands + M_Memcpy(D_GetTextcmd(tic, playernum), *buf, size); + *buf += size; + } +} + +void CL_SendNetCommands(void) +{ + // Send extra data if needed + if (localtextcmd[0]) + { + netbuffer->packettype = PT_TEXTCMD; + M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1); + // All extra data have been sent + if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail... + localtextcmd[0] = 0; + } + + // Send extra data if needed for player 2 (splitscreen) + if (localtextcmd2[0]) + { + netbuffer->packettype = PT_TEXTCMD2; + M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1); + // All extra data have been sent + if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail... + localtextcmd2[0] = 0; + } +} + +void SendKick(UINT8 playernum, UINT8 msg) +{ + UINT8 buf[2]; + + if (!(server && cv_rejointimeout.value)) + msg &= ~KICK_MSG_KEEP_BODY; + + buf[0] = playernum; + buf[1] = msg; + SendNetXCmd(XD_KICK, &buf, 2); +} + +void SendKicksForNode(SINT8 node, UINT8 msg) +{ + if (!netnodes[node].ingame) + return; + + for (INT32 playernum = netnodes[node].player; playernum != -1; playernum = netnodes[node].player2) + if (playernum != -1 && playeringame[playernum]) + SendKick(playernum, msg); +} diff --git a/src/netcode/net_command.h b/src/netcode/net_command.h new file mode 100644 index 000000000..a0c46f3a2 --- /dev/null +++ b/src/netcode/net_command.h @@ -0,0 +1,66 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2022 by Sonic Team Junior. +// +// 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 net_command.h +/// \brief Net command handling + +#ifndef __D_NET_COMMAND__ +#define __D_NET_COMMAND__ + +#include "d_clisrv.h" +#include "../doomtype.h" + +// Must be a power of two +#define TEXTCMD_HASH_SIZE 4 + +typedef struct textcmdplayer_s +{ + INT32 playernum; + UINT8 cmd[MAXTEXTCMD]; + struct textcmdplayer_s *next; +} textcmdplayer_t; + +typedef struct textcmdtic_s +{ + tic_t tic; + textcmdplayer_t *playercmds[TEXTCMD_HASH_SIZE]; + struct textcmdtic_s *next; +} textcmdtic_t; + +extern textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE]; + +extern UINT8 localtextcmd[MAXTEXTCMD]; +extern UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen + +void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum)); +void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam); +void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player + +UINT8 GetFreeXCmdSize(void); +void D_FreeTextcmd(tic_t tic); + +// Gets the buffer for the specified ticcmd, or NULL if there isn't one +UINT8* D_GetExistingTextcmd(tic_t tic, INT32 playernum); + +// Gets the buffer for the specified ticcmd, creating one if necessary +UINT8* D_GetTextcmd(tic_t tic, INT32 playernum); + +void ExtraDataTicker(void); + +// used at txtcmds received to check packetsize bound +size_t TotalTextCmdPerTic(tic_t tic); + +void PT_TextCmd(SINT8 node, INT32 netconsole); +void SV_WriteNetCommandsForTic(tic_t tic, UINT8 **buf); +void CL_CopyNetCommandsFromServerPacket(tic_t tic, UINT8 **buf); +void CL_SendNetCommands(void); +void SendKick(UINT8 playernum, UINT8 msg); +void SendKicksForNode(SINT8 node, UINT8 msg); + +#endif diff --git a/src/d_clisrv.h b/src/netcode/protocol.h similarity index 59% rename from src/d_clisrv.h rename to src/netcode/protocol.h index 49fb5fc1d..a992e3b69 100644 --- a/src/d_clisrv.h +++ b/src/netcode/protocol.h @@ -7,19 +7,15 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file d_clisrv.h -/// \brief high level networking stuff +/// \file protocol.h +/// \brief Data exchanged through the network -#ifndef __D_CLISRV__ -#define __D_CLISRV__ +#ifndef __PROTOCOL__ +#define __PROTOCOL__ -#include "d_ticcmd.h" #include "d_net.h" -#include "d_netcmd.h" -#include "d_net.h" -#include "tables.h" -#include "d_player.h" -#include "mserv.h" +#include "../d_ticcmd.h" +#include "../doomdef.h" /* The 'packet version' is used to distinguish packet @@ -38,10 +34,9 @@ therein, increment this number. // one that defines the actual packets to // be transmitted. -// Networking and tick handling related. #define BACKUPTICS 1024 -#define CLIENTBACKUPTICS 32 #define MAXTEXTCMD 256 + // // Packet structure // @@ -77,7 +72,7 @@ typedef enum PT_ASKLUAFILE, // Client telling the server they don't have the file PT_HASLUAFILE, // Client telling the server they have the file - PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called + PT_BASICKEEPALIVE, // Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called // Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility. @@ -92,7 +87,6 @@ typedef enum PT_TEXTCMD, // Extra text commands from the client. PT_TEXTCMD2, // Splitscreen text commands. PT_CLIENTJOIN, // Client wants to join; used in start game. - PT_NODETIMEOUT, // Packet sent to self if the connection times out. PT_LOGIN, // Login attempt from the client. @@ -103,14 +97,6 @@ typedef enum NUMPACKETTYPE } packettype_t; -#ifdef PACKETDROP -void Command_Drop(void); -void Command_Droprate(void); -#endif -#ifdef _DEBUG -void Command_Numnodes(void); -#endif - #if defined(_MSC_VER) #pragma pack(1) #endif @@ -139,13 +125,12 @@ typedef struct #endif // Server to client packet -// this packet is too large typedef struct { tic_t starttic; UINT8 numtics; UINT8 numslots; // "Slots filled": Highest player number in use plus one. - ticcmd_t cmds[45]; // Normally [BACKUPTIC][MAXPLAYERS] but too large + ticcmd_t cmds[45]; } ATTRPACK servertics_pak; typedef struct @@ -215,6 +200,7 @@ enum { #define MAXSERVERNAME 32 #define MAXFILENEEDED 915 + // This packet is too large typedef struct { @@ -275,7 +261,7 @@ typedef struct UINT8 data; // Color is first four bits, hasflag, isit and issuper have one bit each, the last is unused. UINT32 score; UINT16 timeinserver; // In seconds. -} ATTRPACK plrinfo; +} ATTRPACK plrinfo_pak; // Shortest player information for join during intermission. typedef struct @@ -286,7 +272,7 @@ typedef struct UINT32 pflags; UINT32 score; UINT8 ctfteam; -} ATTRPACK plrconfig; +} ATTRPACK plrconfig_pak; typedef struct { @@ -309,25 +295,25 @@ typedef struct UINT8 reserved; // Padding union { - clientcmd_pak clientpak; // 144 bytes - client2cmd_pak client2pak; // 200 bytes - servertics_pak serverpak; // 132495 bytes (more around 360, no?) - serverconfig_pak servercfg; // 773 bytes - UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...) - filetx_pak filetxpak; // 139 bytes + clientcmd_pak clientpak; + client2cmd_pak client2pak; + servertics_pak serverpak; + serverconfig_pak servercfg; + UINT8 textcmd[MAXTEXTCMD+1]; + filetx_pak filetxpak; fileack_pak fileack; UINT8 filereceived; - clientconfig_pak clientcfg; // 136 bytes + clientconfig_pak clientcfg; UINT8 md5sum[16]; - serverinfo_pak serverinfo; // 1024 bytes - serverrefuse_pak serverrefuse; // 65025 bytes (somehow I feel like those values are garbage...) - askinfo_pak askinfo; // 61 bytes - msaskinfo_pak msaskinfo; // 22 bytes - plrinfo playerinfo[MAXPLAYERS]; // 576 bytes(?) - plrconfig playerconfig[MAXPLAYERS]; // (up to) 528 bytes(?) - INT32 filesneedednum; // 4 bytes - filesneededconfig_pak filesneededcfg; // ??? bytes - UINT32 pingtable[MAXPLAYERS+1]; // 68 bytes + serverinfo_pak serverinfo; + serverrefuse_pak serverrefuse; + askinfo_pak askinfo; + msaskinfo_pak msaskinfo; + plrinfo_pak playerinfo[MAXPLAYERS]; + plrconfig_pak playerconfig[MAXPLAYERS]; + INT32 filesneedednum; + filesneededconfig_pak filesneededcfg; + UINT32 pingtable[MAXPLAYERS+1]; } u; // This is needed to pack diff packet types data together } ATTRPACK doomdata_t; @@ -335,26 +321,7 @@ typedef struct #pragma pack() #endif -#define MAXSERVERLIST (MAXNETNODES-1) -typedef struct -{ - SINT8 node; - serverinfo_pak info; -} serverelem_t; - -extern serverelem_t serverlist[MAXSERVERLIST]; -extern UINT32 serverlistcount; -extern INT32 mapchangepending; - -// Points inside doomcom -extern doomdata_t *netbuffer; - -extern consvar_t cv_showjoinaddress; -extern consvar_t cv_playbackspeed; - -#define BASEPACKETSIZE offsetof(doomdata_t, u) #define FILETXHEADER offsetof(filetx_pak, data) -#define BASESERVERTICSSIZE offsetof(doomdata_t, u.serverpak.cmds[0]) #define KICK_MSG_GO_AWAY 1 #define KICK_MSG_CON_FAIL 2 @@ -366,107 +333,4 @@ extern consvar_t cv_playbackspeed; #define KICK_MSG_CUSTOM_BAN 8 #define KICK_MSG_KEEP_BODY 0x80 -typedef enum -{ - KR_KICK = 1, //Kicked by server - KR_PINGLIMIT = 2, //Broke Ping Limit - KR_SYNCH = 3, //Synch Failure - KR_TIMEOUT = 4, //Connection Timeout - KR_BAN = 5, //Banned by server - KR_LEAVE = 6, //Quit the game - -} kickreason_t; - -/* the max number of name changes in some time period */ -#define MAXNAMECHANGES (5) -#define NAMECHANGERATE (60*TICRATE) - -extern boolean server; -extern boolean serverrunning; -#define client (!server) -extern boolean dedicated; // For dedicated server -extern UINT16 software_MAXPACKETLENGTH; -extern boolean acceptnewnode; -extern SINT8 servernode; - -void Command_Ping_f(void); -extern tic_t connectiontimeout; -extern tic_t jointimeout; -extern UINT16 pingmeasurecount; -extern UINT32 realpingtable[MAXPLAYERS]; -extern UINT32 playerpingtable[MAXPLAYERS]; -extern tic_t servermaxping; - -extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout; -extern consvar_t cv_resynchattempts, cv_blamecfail; -extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; -extern consvar_t cv_dedicatedidletime; - -// Used in d_net, the only dependence -tic_t ExpandTics(INT32 low, INT32 node); -void D_ClientServerInit(void); - -// Initialise the other field -void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum)); -void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam); -void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player -void SendKick(UINT8 playernum, UINT8 msg); - -// Create any new ticcmds and broadcast to other players. -void NetUpdate(void); - -// Maintain connections to nodes without timing them all out. -void NetKeepAlive(void); - -void SV_StartSinglePlayerServer(void); -boolean SV_SpawnServer(void); -void SV_StopServer(void); -void SV_ResetServer(void); -void CL_AddSplitscreenPlayer(void); -void CL_RemoveSplitscreenPlayer(void); -void CL_Reset(void); -void CL_ClearPlayer(INT32 playernum); -void CL_QueryServerList(msg_server_t *list); -void CL_UpdateServerList(boolean internetsearch, INT32 room); -void CL_RemovePlayer(INT32 playernum, kickreason_t reason); -// Is there a game running -boolean Playing(void); - -// Broadcasts special packets to other players -// to notify of game exit -void D_QuitNetGame(void); - -//? How many ticks to run? -boolean TryRunTics(tic_t realtic); - -// extra data for lmps -// these functions scare me. they contain magic. -/*boolean AddLmpExtradata(UINT8 **demo_p, INT32 playernum); -void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum);*/ - -#ifndef NONET -// translate a playername in a player number return -1 if not found and -// print a error message in the console -SINT8 nametonum(const char *name); -#endif - -extern char motd[254], server_context[8]; -extern UINT8 playernode[MAXPLAYERS]; - -INT32 D_NumPlayers(void); -INT32 D_NumBots(void); -void D_ResetTiccmds(void); - -tic_t GetLag(INT32 node); -UINT8 GetFreeXCmdSize(void); - -void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest); - -extern UINT8 hu_redownloadinggamestate; - -extern UINT8 adminpassmd5[16]; -extern boolean adminpasswordset; - -extern boolean hu_stopped; - #endif diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c new file mode 100644 index 000000000..2164f411a --- /dev/null +++ b/src/netcode/server_connection.c @@ -0,0 +1,507 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 server_connection.c +/// \brief Server-side part of connection handling + +#include "server_connection.h" +#include "i_net.h" +#include "d_clisrv.h" +#include "d_netfil.h" +#include "mserv.h" +#include "net_command.h" +#include "gamestate.h" +#include "../byteptr.h" +#include "../g_game.h" +#include "../g_state.h" +#include "../p_setup.h" +#include "../p_tick.h" +#include "../command.h" +#include "../doomstat.h" + +// Minimum timeout for sending the savegame +// The actual timeout will be longer depending on the savegame length +tic_t jointimeout = (10*TICRATE); + +// Incremented by cv_joindelay when a client joins, decremented each tic. +// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled. +tic_t joindelay = 0; + +// Minimum timeout for sending the savegame +// The actual timeout will be longer depending on the savegame length +char playeraddress[MAXPLAYERS][64]; + +consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); + +consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_OnOff, NULL); + +static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}}; +consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, maxplayers_cons_t, NULL); + +static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}}; +consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL); + +static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}}; +consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL); + +static INT32 FindRejoinerNum(SINT8 node) +{ + char addressbuffer[64]; + const char *nodeaddress; + const char *strippednodeaddress; + + // Make sure there is no dead dress before proceeding to the stripping + if (!I_GetNodeAddress) + return -1; + nodeaddress = I_GetNodeAddress(node); + if (!nodeaddress) + return -1; + + // Strip the address of its port + strcpy(addressbuffer, nodeaddress); + strippednodeaddress = I_NetSplitAddress(addressbuffer, NULL); + + // Check if any player matches the stripped address + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX + && !strcmp(playeraddress[i], strippednodeaddress)) + return i; + } + + return -1; +} + +static UINT8 +GetRefuseReason (INT32 node) +{ + if (!node || FindRejoinerNum(node) != -1) + return 0; + else if (bannednode && bannednode[node]) + return REFUSE_BANNED; + else if (!cv_allownewplayer.value) + return REFUSE_JOINS_DISABLED; + else if (D_NumPlayers() >= cv_maxplayers.value) + return REFUSE_SLOTS_FULL; + else + return 0; +} + +static void SV_SendServerInfo(INT32 node, tic_t servertime) +{ + UINT8 *p; + + netbuffer->packettype = PT_SERVERINFO; + netbuffer->u.serverinfo._255 = 255; + netbuffer->u.serverinfo.packetversion = PACKETVERSION; + netbuffer->u.serverinfo.version = VERSION; + netbuffer->u.serverinfo.subversion = SUBVERSION; + strncpy(netbuffer->u.serverinfo.application, SRB2APPLICATION, + sizeof netbuffer->u.serverinfo.application); + // return back the time value so client can compute their ping + netbuffer->u.serverinfo.time = (tic_t)LONG(servertime); + netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime); + + // Exclude bots from both counts + netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumPlayers() - D_NumBots()); + netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots()); + + netbuffer->u.serverinfo.refusereason = GetRefuseReason(node); + + strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], + sizeof netbuffer->u.serverinfo.gametypename); + netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; + netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); + netbuffer->u.serverinfo.flags = (dedicated ? SV_DEDICATED : 0); + strncpy(netbuffer->u.serverinfo.servername, cv_servername.string, + MAXSERVERNAME); + strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7); + + M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16); + + memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle); + + if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl) + { + char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle; + while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0') + { + if (!(*read & 0x80)) + { + *writ = toupper(*read); + writ++; + } + read++; + } + *writ = '\0'; + //strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33); + } + else + strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32); + + if (mapheaderinfo[gamemap-1] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) + netbuffer->u.serverinfo.iszone = 1; + else + netbuffer->u.serverinfo.iszone = 0; + + if (mapheaderinfo[gamemap-1]) + netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum; + + p = PutFileNeeded(0); + + HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u)); +} + +static void SV_SendPlayerInfo(INT32 node) +{ + netbuffer->packettype = PT_PLAYERINFO; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + { + netbuffer->u.playerinfo[i].num = 255; // This slot is empty. + continue; + } + + netbuffer->u.playerinfo[i].num = i; + strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1); + netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0'; + + //fetch IP address + //No, don't do that, you fuckface. + memset(netbuffer->u.playerinfo[i].address, 0, 4); + + if (G_GametypeHasTeams()) + { + if (!players[i].ctfteam) + netbuffer->u.playerinfo[i].team = 255; + else + netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam; + } + else + { + if (players[i].spectator) + netbuffer->u.playerinfo[i].team = 255; + 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 + ); + + // Extra data + netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor; + + if (players[i].pflags & PF_TAGIT) + netbuffer->u.playerinfo[i].data |= 0x20; + + if (players[i].gotflag) + netbuffer->u.playerinfo[i].data |= 0x40; + + if (players[i].powers[pw_super]) + netbuffer->u.playerinfo[i].data |= 0x80; + } + + HSendPacket(node, false, 0, sizeof(plrinfo_pak) * MAXPLAYERS); +} + +/** Sends a PT_SERVERCFG packet + * + * \param node The destination + * \return True if the packet was successfully sent + * + */ +static boolean SV_SendServerConfig(INT32 node) +{ + boolean waspacketsent; + + netbuffer->packettype = PT_SERVERCFG; + + netbuffer->u.servercfg.serverplayer = (UINT8)serverplayer; + netbuffer->u.servercfg.totalslotnum = (UINT8)(doomcom->numslots); + netbuffer->u.servercfg.gametic = (tic_t)LONG(gametic); + netbuffer->u.servercfg.clientnode = (UINT8)node; + netbuffer->u.servercfg.gamestate = (UINT8)gamestate; + netbuffer->u.servercfg.gametype = (UINT8)gametype; + netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame; + netbuffer->u.servercfg.usedCheats = (UINT8)usedCheats; + + memcpy(netbuffer->u.servercfg.server_context, server_context, 8); + + { + const size_t len = sizeof (serverconfig_pak); + +#ifdef DEBUGFILE + if (debugfile) + { + fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n", + sizeu1(len), node); + } +#endif + + waspacketsent = HSendPacket(node, true, 0, len); + } + +#ifdef DEBUGFILE + if (debugfile) + { + if (waspacketsent) + { + fprintf(debugfile, "ServerConfig Packet was sent\n"); + } + else + { + fprintf(debugfile, "ServerConfig Packet could not be sent right now\n"); + } + } +#endif + + return waspacketsent; +} + +// Adds a node to the game (player will follow at map change or at savegame....) +static inline void SV_AddNode(INT32 node) +{ + netnodes[node].tic = gametic; + netnodes[node].supposedtic = gametic; + // little hack because the server connects to itself and puts + // nodeingame when connected not here + if (node) + netnodes[node].ingame = true; +} + +static void SV_AddPlayer(SINT8 node, const char *name) +{ + INT32 n; + UINT8 buf[2 + MAXPLAYERNAME]; + UINT8 *p; + INT32 newplayernum; + + newplayernum = FindRejoinerNum(node); + if (newplayernum == -1) + { + // search for a free playernum + // we can't use playeringame since it is not updated here + for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++) + { + if (playeringame[newplayernum]) + continue; + for (n = 0; n < MAXNETNODES; n++) + if (netnodes[n].player == newplayernum || netnodes[n].player2 == newplayernum) + break; + if (n == MAXNETNODES) + break; + } + } + + // should never happen since we check the playernum + // before accepting the join + I_Assert(newplayernum < MAXPLAYERS); + + playernode[newplayernum] = (UINT8)node; + + p = buf + 2; + buf[0] = (UINT8)node; + buf[1] = newplayernum; + if (netnodes[node].numplayers < 1) + { + netnodes[node].player = newplayernum; + } + else + { + netnodes[node].player2 = newplayernum; + buf[1] |= 0x80; + } + WRITESTRINGN(p, name, MAXPLAYERNAME); + netnodes[node].numplayers++; + + SendNetXCmd(XD_ADDPLAYER, &buf, p - buf); + + DEBFILE(va("Server added player %d node %d\n", newplayernum, node)); +} + +static void SV_SendRefuse(INT32 node, const char *reason) +{ + strcpy(netbuffer->u.serverrefuse.reason, reason); + + netbuffer->packettype = PT_SERVERREFUSE; + HSendPacket(node, true, 0, strlen(netbuffer->u.serverrefuse.reason) + 1); + Net_CloseConnection(node); +} + +static const char * +GetRefuseMessage (SINT8 node, INT32 rejoinernum) +{ + clientconfig_pak *cc = &netbuffer->u.clientcfg; + + boolean rejoining = (rejoinernum != -1); + + if (!node)/* server connecting to itself */ + return NULL; + + if ( + cc->modversion != MODVERSION || + strncmp(cc->application, SRB2APPLICATION, + sizeof cc->application) + ){ + return/* this is probably client's fault */ + "Incompatible."; + } + else if (bannednode && bannednode[node]) + { + return + "You have been banned\n" + "from the server."; + } + else if (cc->localplayers != 1) + { + return + "Wrong player count."; + } + + if (!rejoining) + { + if (!cv_allownewplayer.value) + { + return + "The server is not accepting\n" + "joins for the moment."; + } + else if (D_NumPlayers() >= cv_maxplayers.value) + { + return va( + "Maximum players reached: %d", + cv_maxplayers.value); + } + } + + if (luafiletransfers) + { + return + "The serveris broadcasting a file\n" + "requested by a Lua script.\n" + "Please wait a bit and then\n" + "try rejoining."; + } + + if (netgame) + { + const tic_t th = 2 * cv_joindelay.value * TICRATE; + + if (joindelay > th) + { + return va( + "Too many people are connecting.\n" + "Please wait %d seconds and then\n" + "try rejoining.", + (joindelay - th) / TICRATE); + } + } + + return NULL; +} + +/** Called when a PT_CLIENTJOIN packet is received + * + * \param node The packet sender + * + */ +void PT_ClientJoin(SINT8 node) +{ + char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1]; + INT32 numplayers = netbuffer->u.clientcfg.localplayers; + INT32 rejoinernum; + + // Ignore duplicate packets + if (client || netnodes[node].ingame) + return; + + rejoinernum = FindRejoinerNum(node); + + const char *refuse = GetRefuseMessage(node, rejoinernum); + if (refuse) + { + SV_SendRefuse(node, refuse); + return; + } + + for (INT32 i = 0; i < numplayers; i++) + { + strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1); + if (!EnsurePlayerNameIsGood(names[i], rejoinernum)) + { + SV_SendRefuse(node, "Bad player name"); + return; + } + } + + SV_AddNode(node); + + if (!SV_SendServerConfig(node)) + { + /// \note Shouldn't SV_SendRefuse be called before ResetNode? + ResetNode(node); + SV_SendRefuse(node, M_GetText("Server couldn't send info, please try again")); + /// \todo fix this !!! + return; + } + DEBFILE("new node joined\n"); + + if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) + { + SV_SendSaveGame(node, false); // send a complete game state + DEBFILE("send savegame\n"); + } + + // Splitscreen can allow 2 players in one node + for (INT32 i = 0; i < numplayers; i++) + SV_AddPlayer(node, names[i]); + + joindelay += cv_joindelay.value * TICRATE; +} + +void PT_AskInfoViaMS(SINT8 node) +{ + Net_CloseConnection(node); +} + +void PT_TellFilesNeeded(SINT8 node) +{ + if (server && serverrunning) + { + UINT8 *p; + INT32 firstfile = netbuffer->u.filesneedednum; + + netbuffer->packettype = PT_MOREFILESNEEDED; + netbuffer->u.filesneededcfg.first = firstfile; + netbuffer->u.filesneededcfg.more = 0; + + p = PutFileNeeded(firstfile); + + HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u)); + } + else // Shouldn't get this if you aren't the server...? + Net_CloseConnection(node); +} + +void PT_AskInfo(SINT8 node) +{ + if (server && serverrunning) + { + SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time)); + SV_SendPlayerInfo(node); // Send extra info + } + Net_CloseConnection(node); +} diff --git a/src/netcode/server_connection.h b/src/netcode/server_connection.h new file mode 100644 index 000000000..14ac5913c --- /dev/null +++ b/src/netcode/server_connection.h @@ -0,0 +1,30 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 server_connection.h +/// \brief Server-side part of connection handling + +#ifndef __D_SERVER_CONNECTION__ +#define __D_SERVER_CONNECTION__ + +#include "../command.h" +#include "../doomdef.h" +#include "../doomtype.h" + +void PT_ClientJoin(SINT8 node); +void PT_AskInfoViaMS(SINT8 node); +void PT_TellFilesNeeded(SINT8 node); +void PT_AskInfo(SINT8 node); + +extern tic_t jointimeout; +extern tic_t joindelay; +extern char playeraddress[MAXPLAYERS][64]; +extern consvar_t cv_showjoinaddress, cv_allownewplayer, cv_maxplayers, cv_joindelay, cv_rejointimeout; + +#endif diff --git a/src/netcode/tic_command.c b/src/netcode/tic_command.c new file mode 100644 index 000000000..7721bc3f1 --- /dev/null +++ b/src/netcode/tic_command.c @@ -0,0 +1,471 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 tic_command.c +/// \brief Tic command handling + +#include "tic_command.h" +#include "d_clisrv.h" +#include "net_command.h" +#include "client_connection.h" +#include "gamestate.h" +#include "i_net.h" +#include "../d_main.h" +#include "../g_game.h" +#include "../i_system.h" +#include "../i_time.h" +#include "../byteptr.h" +#include "../doomstat.h" +#include "../doomtype.h" + +tic_t firstticstosend; // Smallest netnode.tic +tic_t tictoclear = 0; // Optimize D_ClearTiccmd +ticcmd_t localcmds; +ticcmd_t localcmds2; +boolean cl_packetmissed; +ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS]; + +static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n) +{ + const size_t d = n / sizeof(ticcmd_t); + const size_t r = n % sizeof(ticcmd_t); + UINT8 *ret = dest; + + if (r) + M_Memcpy(dest, src, n); + else if (d) + G_MoveTiccmd(dest, src, d); + return ret+n; +} + +static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n) +{ + const size_t d = n / sizeof(ticcmd_t); + const size_t r = n % sizeof(ticcmd_t); + UINT8 *ret = src; + + if (r) + M_Memcpy(dest, src, n); + else if (d) + G_MoveTiccmd(dest, src, d); + return ret+n; +} + +/** Guesses the full value of a tic from its lowest byte, for a specific node + * + * \param low The lowest byte of the tic value + * \param node The node to deduce the tic for + * \return The full tic value + * + */ +tic_t ExpandTics(INT32 low, INT32 node) +{ + INT32 delta; + + delta = low - (netnodes[node].tic & UINT8_MAX); + + if (delta >= -64 && delta <= 64) + return (netnodes[node].tic & ~UINT8_MAX) + low; + else if (delta > 64) + return (netnodes[node].tic & ~UINT8_MAX) - 256 + low; + else //if (delta < -64) + return (netnodes[node].tic & ~UINT8_MAX) + 256 + low; +} + +void D_Clearticcmd(tic_t tic) +{ + D_FreeTextcmd(tic); + + for (INT32 i = 0; i < MAXPLAYERS; i++) + netcmds[tic%BACKUPTICS][i].angleturn = 0; + + DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS)); +} + +void D_ResetTiccmds(void) +{ + memset(&localcmds, 0, sizeof(ticcmd_t)); + memset(&localcmds2, 0, sizeof(ticcmd_t)); + + // Reset the net command list + for (INT32 i = 0; i < TEXTCMD_HASH_SIZE; i++) + while (textcmds[i]) + D_Clearticcmd(textcmds[i]->tic); +} + +// Check ticcmd for "speed hacks" +static void CheckTiccmdHacks(INT32 playernum, tic_t tic) +{ + ticcmd_t *cmd = &netcmds[tic%BACKUPTICS][playernum]; + if (cmd->forwardmove > MAXPLMOVE || cmd->forwardmove < -MAXPLMOVE + || cmd->sidemove > MAXPLMOVE || cmd->sidemove < -MAXPLMOVE) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), playernum); + SendKick(playernum, KICK_MSG_CON_FAIL); + } +} + +// Check player consistancy during the level +static void CheckConsistancy(SINT8 nodenum, tic_t tic) +{ + netnode_t *node = &netnodes[nodenum]; + INT16 neededconsistancy = consistancy[tic%BACKUPTICS]; + INT16 clientconsistancy = SHORT(netbuffer->u.clientpak.consistancy); + + if (tic > gametic || tic + BACKUPTICS - 1 <= gametic || gamestate != GS_LEVEL + || neededconsistancy == clientconsistancy || SV_ResendingSavegameToAnyone() + || node->resendingsavegame || node->savegameresendcooldown > I_GetTime()) + return; + + if (cv_resynchattempts.value) + { + // Tell the client we are about to resend them the gamestate + netbuffer->packettype = PT_WILLRESENDGAMESTATE; + HSendPacket(nodenum, true, 0, 0); + + node->resendingsavegame = true; + + if (cv_blamecfail.value) + CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"), + node->player+1, player_names[node->player], + neededconsistancy, clientconsistancy); + + DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n", + node->player, tic, neededconsistancy, clientconsistancy)); + } + else + { + SendKick(node->player, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + + DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n", + node->player, tic, neededconsistancy, clientconsistancy)); + } +} + +void PT_ClientCmd(SINT8 nodenum, INT32 netconsole) +{ + netnode_t *node = &netnodes[nodenum]; + tic_t realend, realstart; + + if (client) + return; + + // To save bytes, only the low byte of tic numbers are sent + // Use ExpandTics to figure out what the rest of the bytes are + realstart = ExpandTics(netbuffer->u.clientpak.client_tic, nodenum); + realend = ExpandTics(netbuffer->u.clientpak.resendfrom, nodenum); + + if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS + || netbuffer->packettype == PT_NODEKEEPALIVEMIS + || node->supposedtic < realend) + { + node->supposedtic = realend; + } + // Discard out of order packet + if (node->tic > realend) + { + DEBFILE(va("out of order ticcmd discarded nettics = %u\n", node->tic)); + return; + } + + // Update the nettics + node->tic = realend; + + // This should probably still timeout though, as the node should always have a player 1 number + if (netconsole == -1) + return; + + // As long as clients send valid ticcmds, the server can keep running, so reset the timeout + /// \todo Use a separate cvar for that kind of timeout? + node->freezetimeout = I_GetTime() + connectiontimeout; + + // Don't do anything for packets of type NODEKEEPALIVE? + // Sryder 2018/07/01: Update the freezetimeout still! + if (netbuffer->packettype == PT_NODEKEEPALIVE + || netbuffer->packettype == PT_NODEKEEPALIVEMIS) + return; + + // If we've alredy received a ticcmd for this tic, just submit it for the next one. + tic_t faketic = maketic; + if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED)) + && (maketic - firstticstosend < BACKUPTICS - 1)) + faketic++; + + // Copy ticcmd + G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1); + + // Splitscreen cmd + if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS) + && node->player2 >= 0) + G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][(UINT8)node->player2], + &netbuffer->u.client2pak.cmd2, 1); + + CheckTiccmdHacks(netconsole, faketic); + CheckConsistancy(nodenum, realstart); +} + +void PT_ServerTics(SINT8 node, INT32 netconsole) +{ + tic_t realend, realstart; + servertics_pak *packet = &netbuffer->u.serverpak; + + if (!netnodes[node].ingame) + { + // Do not remove my own server (we have just get a out of order packet) + if (node != servernode) + { + DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype)); + Net_CloseConnection(node); + } + return; + } + + // Only accept PT_SERVERTICS from the server. + if (node != servernode) + { + CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node); + if (server) + SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + return; + } + + realstart = packet->starttic; + realend = realstart + packet->numtics; + + realend = min(realend, gametic + CLIENTBACKUPTICS); + cl_packetmissed = realstart > neededtic; + + if (realstart <= neededtic && realend > neededtic) + { + UINT8 *pak = (UINT8 *)&packet->cmds; + UINT8 *txtpak = (UINT8 *)&packet->cmds[packet->numslots * packet->numtics]; + + for (tic_t i = realstart; i < realend; i++) + { + // clear first + D_Clearticcmd(i); + + // copy the tics + pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak, + packet->numslots*sizeof (ticcmd_t)); + + CL_CopyNetCommandsFromServerPacket(i, &txtpak); + } + + neededtic = realend; + } + else + { + DEBFILE(va("frame not in bound: %u\n", neededtic)); + } +} + +// send the client packet to the server +void CL_SendClientCmd(void) +{ + size_t packetsize = 0; + boolean mis = false; + + netbuffer->packettype = PT_CLIENTCMD; + + if (cl_packetmissed) + { + netbuffer->packettype = PT_CLIENTMIS; + mis = true; + } + + netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX); + netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX); + + if (gamestate == GS_WAITINGPLAYERS) + { + // Send PT_NODEKEEPALIVE packet + netbuffer->packettype = (mis ? PT_NODEKEEPALIVEMIS : PT_NODEKEEPALIVE); + packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16); + HSendPacket(servernode, false, 0, packetsize); + } + else if (gamestate != GS_NULL && (addedtogame || dedicated)) + { + packetsize = sizeof (clientcmd_pak); + G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1); + netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]); + + // Send a special packet with 2 cmd for splitscreen + if (splitscreen || botingame) + { + netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD); + packetsize = sizeof (client2cmd_pak); + G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1); + } + + HSendPacket(servernode, false, 0, packetsize); + } + + if (cl_mode == CL_CONNECTED || dedicated) + CL_SendNetCommands(); +} + +// PT_SERVERTICS packets can grow too large for a single UDP packet, +// So this checks how many tics worth of data can be sent in one packet. +// The rest can be sent later, usually the next tic. +static tic_t SV_CalculateNumTicsForPacket(SINT8 nodenum, tic_t firsttic, tic_t lasttic) +{ + size_t size = BASESERVERTICSSIZE; + + for (tic_t tic = firsttic; tic < lasttic; tic++) + { + size += sizeof (ticcmd_t) * doomcom->numslots; + size += TotalTextCmdPerTic(tic); + + if (size > software_MAXPACKETLENGTH) + { + DEBFILE(va("packet too large (%s) at tic %d (should be from %d to %d)\n", + sizeu1(size), tic, firsttic, lasttic)); + lasttic = tic; + + // Too bad: too many players have sent extra data + // and there is too much data for a single tic. + // To avoid that, keep the data for the next tic (see PT_TEXTCMD). + if (lasttic == firsttic) + { + if (size > MAXPACKETLENGTH) + I_Error("Too many players: can't send %s data for %d players to node %d\n" + "Well sorry nobody is perfect....\n", + sizeu1(size), doomcom->numslots, nodenum); + else + { + lasttic++; // send it anyway! + DEBFILE("sending it anyway\n"); + } + } + break; + } + } + + return lasttic - firsttic; +} + +// Sends the server packet +// Sends tic/net commands from firstticstosend to maketic-1 +void SV_SendTics(void) +{ + tic_t realfirsttic, lasttictosend; + + // Send to all clients except yourself + // For each node, create a packet with x tics and send it + // x is computed using node.supposedtic, max packet size and maketic + for (INT32 n = 1; n < MAXNETNODES; n++) + if (netnodes[n].ingame) + { + netnode_t *node = &netnodes[n]; + + // assert node->supposedtic>=node->tic + realfirsttic = node->supposedtic; + lasttictosend = min(maketic, node->tic + CLIENTBACKUPTICS); + + if (realfirsttic >= lasttictosend) + { + // Well, we have sent all the tics, so we will use extra bandwidth + // to resend packets that are supposed lost. + // This is necessary since lost packet detection + // works when we receive a packet with firsttic > neededtic (PT_SERVERTICS) + DEBFILE(va("Nothing to send node %u mak=%u sup=%u net=%u \n", + n, maketic, node->supposedtic, node->tic)); + + realfirsttic = node->tic; + + if (realfirsttic >= lasttictosend || (I_GetTime() + n)&3) + // All tics are Ok + continue; + + DEBFILE(va("Sent %d anyway\n", realfirsttic)); + } + realfirsttic = max(realfirsttic, firstticstosend); + + lasttictosend = realfirsttic + SV_CalculateNumTicsForPacket(n, realfirsttic, lasttictosend); + + // Prepare the packet header + netbuffer->packettype = PT_SERVERTICS; + netbuffer->u.serverpak.starttic = realfirsttic; + netbuffer->u.serverpak.numtics = (UINT8)(lasttictosend - realfirsttic); + netbuffer->u.serverpak.numslots = (UINT8)SHORT(doomcom->numslots); + + // Fill and send the packet + UINT8 *bufpos = (UINT8 *)&netbuffer->u.serverpak.cmds; + for (tic_t i = realfirsttic; i < lasttictosend; i++) + bufpos = G_DcpyTiccmd(bufpos, netcmds[i%BACKUPTICS], doomcom->numslots * sizeof (ticcmd_t)); + for (tic_t i = realfirsttic; i < lasttictosend; i++) + SV_WriteNetCommandsForTic(i, &bufpos); + size_t packsize = bufpos - (UINT8 *)&(netbuffer->u); + HSendPacket(n, false, 0, packsize); + + // When tics are too large, only one tic is sent so don't go backwards! + if (lasttictosend-doomcom->extratics > realfirsttic) + node->supposedtic = lasttictosend-doomcom->extratics; + else + node->supposedtic = lasttictosend; + node->supposedtic = max(node->supposedtic, node->tic); + } + + // node 0 is me! + netnodes[0].supposedtic = maketic; +} + +void Local_Maketic(INT32 realtics) +{ + I_OsPolling(); // I_Getevent + D_ProcessEvents(); // menu responder, cons responder, + // game responder calls HU_Responder, AM_Responder, + // and G_MapEventsToControls + if (!dedicated) + rendergametic = gametic; + // translate inputs (keyboard/mouse/joystick) into game controls + G_BuildTiccmd(&localcmds, realtics, 1); + if (splitscreen || botingame) + G_BuildTiccmd(&localcmds2, realtics, 2); + + localcmds.angleturn |= TICCMD_RECEIVED; + localcmds2.angleturn |= TICCMD_RECEIVED; +} + +// create missed tic +void SV_Maketic(void) +{ + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + // We didn't receive this tic + if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0) + { + ticcmd_t * ticcmd = &netcmds[(maketic ) % BACKUPTICS][i]; + ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i]; + + if (players[i].quittime) + { + // Copy the angle/aiming from the previous tic + // and empty the other inputs + memset(ticcmd, 0, sizeof(netcmds[0][0])); + ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED; + ticcmd->aiming = prevticcmd->aiming; + } + else + { + DEBFILE(va("MISS tic%4d for player %d\n", maketic, i)); + // Copy the input from the previous tic + *ticcmd = *prevticcmd; + ticcmd->angleturn &= ~TICCMD_RECEIVED; + } + } + } + + // All tics have been processed, make the next + maketic++; +} diff --git a/src/netcode/tic_command.h b/src/netcode/tic_command.h new file mode 100644 index 000000000..725037216 --- /dev/null +++ b/src/netcode/tic_command.h @@ -0,0 +1,44 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 1999-2023 by Sonic Team Junior. +// +// 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 tic_command.h +/// \brief Tic command handling + +#ifndef __D_TIC_COMMAND__ +#define __D_TIC_COMMAND__ + +#include "d_clisrv.h" +#include "../d_ticcmd.h" +#include "../doomdef.h" +#include "../doomtype.h" + +extern tic_t firstticstosend; // min of the nettics +extern tic_t tictoclear; // optimize d_clearticcmd + +extern ticcmd_t localcmds; +extern ticcmd_t localcmds2; +extern boolean cl_packetmissed; + +extern ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS]; + +tic_t ExpandTics(INT32 low, INT32 node); +void D_Clearticcmd(tic_t tic); +void D_ResetTiccmds(void); + +void PT_ClientCmd(SINT8 nodenum, INT32 netconsole); +void PT_ServerTics(SINT8 node, INT32 netconsole); + +// send the client packet to the server +void CL_SendClientCmd(void); + +void SV_SendTics(void); +void Local_Maketic(INT32 realtics); +void SV_Maketic(void); + +#endif diff --git a/src/p_ceilng.c b/src/p_ceilng.c index 98e931362..61430d73f 100644 --- a/src/p_ceilng.c +++ b/src/p_ceilng.c @@ -17,7 +17,7 @@ #include "r_main.h" #include "s_sound.h" #include "z_zone.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" // ========================================================================== // CEILINGS diff --git a/src/p_enemy.c b/src/p_enemy.c index eebb65f3c..93c828fbe 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -5248,7 +5248,7 @@ void A_SignPlayer(mobj_t *actor) return; skin = &skins[actor->target->player->skin]; - facecolor = actor->target->player->skincolor; + facecolor = P_GetPlayerColor(actor->target->player); if (signcolor) ; @@ -9059,7 +9059,7 @@ void A_Dye(mobj_t *actor) if (!color) { target->colorized = false; - target->color = target->player ? target->player->skincolor : SKINCOLOR_NONE; + target->color = target->player ? P_GetPlayerColor(target->player) : SKINCOLOR_NONE; } else if (!(target->player)) { diff --git a/src/p_inter.c b/src/p_inter.c index 4d22ba343..c3811cbe4 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -28,6 +28,7 @@ #include "m_misc.h" #include "v_video.h" // video flags for CEchos #include "f_finale.h" +#include "netcode/net_command.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -1144,7 +1145,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!(mo2->type == MT_RING || mo2->type == MT_COIN || mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE || mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR - || ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL)))) + || ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL) && P_CanPickupEmblem(player, mo2->health - 1) && !P_EmblemWasCollected(mo2->health - 1)))) continue; // Yay! The thing's in reach! Pull it in! @@ -2235,7 +2236,7 @@ void P_CheckTimeLimit(void) } if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } //Optional tie-breaker for Match/CTF @@ -2298,11 +2299,11 @@ void P_CheckTimeLimit(void) } } if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } /** Checks if a player's score is over the pointlimit and the round should end. @@ -2331,7 +2332,7 @@ void P_CheckPointLimit(void) if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore) { if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } } else @@ -2344,7 +2345,7 @@ void P_CheckPointLimit(void) if ((UINT32)cv_pointlimit.value <= players[i].score) { if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); return; } } @@ -2388,7 +2389,7 @@ void P_CheckSurvivors(void) { CONS_Printf(M_GetText("The IT player has left the game.\n")); if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); return; } @@ -2408,7 +2409,7 @@ void P_CheckSurvivors(void) { CONS_Printf(M_GetText("All players have been tagged!\n")); if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } return; @@ -2420,7 +2421,7 @@ void P_CheckSurvivors(void) { CONS_Printf(M_GetText("There are no players able to become IT.\n")); if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } return; @@ -2432,7 +2433,7 @@ void P_CheckSurvivors(void) { CONS_Printf(M_GetText("All players have been tagged!\n")); if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } } @@ -2630,7 +2631,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } } - target->color = target->player->skincolor; + target->color = P_GetPlayerColor(target->player); target->colorized = false; G_GhostAddColor(GHC_NORMAL); @@ -3323,7 +3324,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage) // Get rid of shield player->powers[pw_shield] = SH_NONE; - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); // Get rid of emeralds player->powers[pw_emeralds] = 0; @@ -3440,7 +3441,7 @@ void P_RemoveShield(player_t *player) { // Second layer shields if (((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER) && !(player->powers[pw_super] || (mariomode && player->powers[pw_invulnerability]))) { - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); G_GhostAddColor(GHC_NORMAL); } player->powers[pw_shield] = SH_NONE; diff --git a/src/p_lights.c b/src/p_lights.c index 4b6a3673b..971165e88 100644 --- a/src/p_lights.c +++ b/src/p_lights.c @@ -17,7 +17,7 @@ #include "r_state.h" #include "z_zone.h" #include "m_random.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" /** Removes any active lighting effects in a sector. * diff --git a/src/p_local.h b/src/p_local.h index 7110d3494..714a8d94e 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -146,6 +146,7 @@ void P_ForceLocalAngle(player_t *player, angle_t angle); boolean P_PlayerFullbright(player_t *player); boolean P_PlayerCanEnterSpinGaps(player_t *player); boolean P_PlayerShouldUseSpinHeight(player_t *player); +UINT16 P_GetPlayerColor(player_t *player); boolean P_IsObjectInGoop(mobj_t *mo); boolean P_IsObjectOnGround(mobj_t *mo); diff --git a/src/p_map.c b/src/p_map.c index 9ffe238e9..2170d2910 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -2524,7 +2524,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam) boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam) { subsector_t *s = R_PointInSubsector(x, y); - boolean retval = true; boolean itsatwodlevel = false; floatok = false; @@ -2539,8 +2538,8 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam) fixed_t tryx = thiscam->x; fixed_t tryy = thiscam->y; - if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP)) - || (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP))) + if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP || players[displayplayer].powers[pw_carry] == CR_NIGHTSMODE)) + || (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP || players[secondarydisplayplayer].powers[pw_carry] == CR_NIGHTSMODE))) { // Noclipping player camera noclips too!! floatok = true; thiscam->floorz = thiscam->z; @@ -2608,7 +2607,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam) thiscam->y = y; thiscam->subsector = s; - return retval; + return true; } // @@ -3731,7 +3730,9 @@ void P_SlideMove(mobj_t *mo) boolean papercol = false; vertex_t v1, v2; // fake vertexes - line_t junk; // fake linedef + static line_t junk; // fake linedef + + memset(&junk, 0x00, sizeof(junk)); if (tmhitthing && mo->z + mo->height > tmhitthing->z && mo->z < tmhitthing->z + tmhitthing->height) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 8c0fa17ad..a81845918 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -36,6 +36,7 @@ #include "p_slopes.h" #include "f_finale.h" #include "m_cond.h" +#include "netcode/net_command.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); @@ -3688,7 +3689,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled dummy.y = thiscam->y; dummy.z = thiscam->z; dummy.height = thiscam->height; - if (!resetcalled && !(player->pflags & PF_NOCLIP) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead. + if (!resetcalled && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead. P_ResetCamera(player, thiscam); else { @@ -3719,7 +3720,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled // adjust height thiscam->z += thiscam->momz + player->mo->pmomz; - if (!itsatwodlevel && !(player->pflags & PF_NOCLIP)) + if (!itsatwodlevel && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE)) { // clip movement if (thiscam->z <= thiscam->floorz) // hit the floor @@ -6882,7 +6883,6 @@ void P_RunOverlays(void) mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP); mo->scale = mo->destscale = mo->target->scale; mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir; - P_SetTarget(&mo->dontdrawforviewmobj, mo->target->dontdrawforviewmobj); // Hide the overlay from the view that its target is hidden from - But don't copy drawonlyforplayer! if (!(mo->state->frame & FF_ANIMATE)) zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale); @@ -9228,7 +9228,7 @@ static void P_DragonbomberThink(mobj_t *mobj) else { fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale); - fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height); + fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->eflags & MFE_VERTICALFLIP ? -128*mobj->scale : (128*mobj->scale + mobj->target->height)); angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle; if (diff > ANGLE_180) mobj->angle -= DRAGONTURNSPEED; @@ -10564,6 +10564,29 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing) case MT_REDFLAG: case MT_BLUEFLAG: + case MT_BOUNCERING: + case MT_AUTOMATICRING: + case MT_INFINITYRING: + case MT_RAILRING: + case MT_EXPLOSIONRING: + case MT_SCATTERRING: + case MT_GRENADERING: + + case MT_BOUNCEPICKUP: + case MT_RAILPICKUP: + case MT_AUTOPICKUP: + case MT_EXPLODEPICKUP: + case MT_SCATTERPICKUP: + case MT_GRENADEPICKUP: + + case MT_REDRING: + case MT_THROWNBOUNCE: + case MT_THROWNINFINITY: + case MT_THROWNAUTOMATIC: + case MT_THROWNSCATTER: + case MT_THROWNEXPLOSION: + case MT_THROWNGRENADE: + case MT_EMBLEM: case MT_TOKEN: @@ -11564,8 +11587,6 @@ void P_SpawnPlayer(INT32 playernum) // Spawn as a spectator, // yes even in splitscreen mode p->spectator = true; - if (playernum&1) p->skincolor = skincolor_redteam; - else p->skincolor = skincolor_blueteam; // but immediately send a team change packet. NetPacket.packet.playernum = playernum; @@ -11585,13 +11606,6 @@ void P_SpawnPlayer(INT32 playernum) // Fix stupid non spectator spectators. if (!p->spectator && !p->ctfteam) p->spectator = true; - - // 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 ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS)) @@ -11603,7 +11617,7 @@ void P_SpawnPlayer(INT32 playernum) mobj->angle = 0; // set color translations for player sprites - mobj->color = p->skincolor; + mobj->color = P_GetPlayerColor(p); // set 'spritedef' override in mobj for player skins.. (see ProjectSprite) // (usefulness: when body mobj is detached from player (who respawns), @@ -11841,7 +11855,7 @@ void P_MovePlayerToStarpost(INT32 playernum) mapthing_t *huntemeralds[MAXHUNTEMERALDS]; INT32 numhuntemeralds; -fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale) +fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale, const boolean absolutez) { const subsector_t *ss = R_PointInSubsector(x, y); @@ -11851,9 +11865,9 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f // Establish height. if (flip) - return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height); + return (absolutez ? dz : P_GetSectorCeilingZAt(ss->sector, x, y) - dz) - FixedMul(scale, offset + mobjinfo[mobjtype].height); else - return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset); + return (absolutez ? dz : P_GetSectorFloorZAt(ss->sector, x, y) + dz) + FixedMul(scale, offset); } fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y) @@ -11861,6 +11875,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt fixed_t dz = mthing->z << FRACBITS; // Base offset from the floor. fixed_t offset = 0; // Specific scaling object offset. boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP)); + boolean absolutez = !!(mthing->options & MTF_ABSOLUTEZ); switch (mobjtype) { @@ -11916,7 +11931,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt offset += mthing->args[0] ? 0 : 24*FRACUNIT; } - if (!(dz + offset)) // Snap to the surfaces when there's no offset set. + if (!(dz + offset) && !absolutez) // Snap to the surfaces when there's no offset set. { if (flip) return ONCEILINGZ; @@ -11924,7 +11939,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt return ONFLOORZ; } - return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale); + return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale, absolutez); } static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing) @@ -12593,7 +12608,7 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj) dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0); if (flip && mobj->height != oldheight) - P_MoveOrigin(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight)); + P_SetOrigin(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight)); if (!flip) { @@ -13332,6 +13347,9 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); mobj->destscale = FixedMul(mobj->destscale, mthing->scale); + mobj->spritexscale = mthing->spritexscale; + mobj->spriteyscale = mthing->spriteyscale; + if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) return mobj; @@ -13418,7 +13436,7 @@ void P_SpawnHoop(mapthing_t *mthing) vector4_t v, res; fixed_t x = mthing->x << FRACBITS; fixed_t y = mthing->y << FRACBITS; - fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale); + fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ); hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER); hoopcenter->spawnpoint = mthing; @@ -13545,7 +13563,7 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi itemtypes[r] = P_GetMobjtypeSubstitute(&dummything, itemtypes[r]); } } - z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale); + z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale, mthing->options & MTF_ABSOLUTEZ); for (r = 0; r < numitems; r++) { @@ -13604,7 +13622,7 @@ static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 n itemtypes[i] = P_GetMobjtypeSubstitute(&dummything, itemtypes[i]); } } - z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale); + z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ); for (i = 0; i < numitems; i++) { diff --git a/src/p_mobj.h b/src/p_mobj.h index 9c598f59e..a980691be 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -491,7 +491,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing); void P_MovePlayerToStarpost(INT32 playernum); void P_AfterPlayerSpawn(INT32 playernum); -fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale); +fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale, const boolean absolutez); 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); diff --git a/src/p_setup.c b/src/p_setup.c index aac02417d..0390761b6 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -50,6 +50,7 @@ #include "m_random.h" #include "dehacked.h" // for map headers +#include "deh_tables.h" // FREE_SKINCOLORS #include "r_main.h" #include "m_cond.h" // for emblems @@ -87,6 +88,8 @@ #include "taglist.h" +#include "netcode/net_command.h" + // // Map MD5, calculated on level load. // Sent to clients in PT_SERVERINFO. @@ -1266,12 +1269,19 @@ static void P_WriteDuplicateText(const char *text, char **target) static void P_WriteSkincolor(INT32 constant, char **target) { + const char *color_name; + if (constant <= SKINCOLOR_NONE || constant >= (INT32)numskincolors) return; + if (constant >= SKINCOLOR_FIRSTFREESLOT) + color_name = FREE_SKINCOLORS[constant - SKINCOLOR_FIRSTFREESLOT]; + else + color_name = COLOR_ENUMS[constant]; + P_WriteDuplicateText( - va("SKINCOLOR_%s", skincolors[constant].name), + va("SKINCOLOR_%s", color_name), target ); } @@ -1510,6 +1520,7 @@ static void P_LoadThings(UINT8 *data) mt->extrainfo = (UINT8)(mt->type >> 12); Tag_FSet(&mt->tags, 0); mt->scale = FRACUNIT; + mt->spritexscale = mt->spriteyscale = FRACUNIT; memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); mt->pitch = mt->roll = 0; @@ -2013,11 +2024,19 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * mapthings[i].roll = atol(val); else if (fastcmp(param, "type")) mapthings[i].type = atol(val); - else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley")) + else if (fastcmp(param, "scale")) + mapthings[i].spritexscale = mapthings[i].spriteyscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scalex")) + mapthings[i].spritexscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "scaley")) + mapthings[i].spriteyscale = FLOAT_TO_FIXED(atof(val)); + else if (fastcmp(param, "mobjscale")) mapthings[i].scale = FLOAT_TO_FIXED(atof(val)); // Flags else if (fastcmp(param, "flip") && fastcmp("true", val)) mapthings[i].options |= MTF_OBJECTFLIP; + else if (fastcmp(param, "absolutez") && fastcmp("true", val)) + mapthings[i].options |= MTF_ABSOLUTEZ; else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9) { @@ -2431,8 +2450,12 @@ static void P_WriteTextmap(void) fprintf(f, "roll = %d;\n", wmapthings[i].roll); if (wmapthings[i].type != 0) fprintf(f, "type = %d;\n", wmapthings[i].type); + if (wmapthings[i].spritexscale != FRACUNIT) + fprintf(f, "scalex = %f;\n", FIXED_TO_FLOAT(wmapthings[i].spritexscale)); + if (wmapthings[i].spriteyscale != FRACUNIT) + fprintf(f, "scaley = %f;\n", FIXED_TO_FLOAT(wmapthings[i].spriteyscale)); if (wmapthings[i].scale != FRACUNIT) - fprintf(f, "scale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale)); + fprintf(f, "mobjscale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale)); if (wmapthings[i].options & MTF_OBJECTFLIP) fprintf(f, "flip = true;\n"); for (j = 0; j < NUMMAPTHINGARGS; j++) @@ -2978,6 +3001,7 @@ static void P_LoadTextmap(void) mt->extrainfo = 0; Tag_FSet(&mt->tags, 0); mt->scale = FRACUNIT; + mt->spritexscale = mt->spriteyscale = FRACUNIT; memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args)); memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs)); mt->mobj = NULL; @@ -5455,7 +5479,7 @@ static void P_ConvertBinaryLinedefTypes(void) break; case 442: //Change object type state lines[i].args[0] = tag; - lines[i].args[3] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0; + lines[i].args[1] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0; break; case 443: //Call Lua function if (lines[i].stringargs[0] == NULL) @@ -6798,6 +6822,9 @@ static void P_ConvertBinaryThingTypes(void) default: break; } + + // Clear binary thing height hacks, to prevent interfering with UDMF-only flags + mapthings[i].options &= 0xF; } } @@ -7149,37 +7176,23 @@ static void P_RunLevelScript(const char *scriptname) static void P_ForceCharacter(const char *forcecharskin) { - if (netgame) - { - char skincmd[33]; - if (splitscreen) - { - sprintf(skincmd, "skin2 %s\n", forcecharskin); - CV_Set(&cv_skin2, forcecharskin); - } + // Don't do anything if the server is forcing a skin already. + if (netgame && cv_forceskin.value >= 0) + return; - sprintf(skincmd, "skin %s\n", forcecharskin); - COM_BufAddText(skincmd); - } - else + for (unsigned i = 0; i < MAXPLAYERS; i++) { - if (splitscreen) - { - SetPlayerSkin(secondarydisplayplayer, forcecharskin); - if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor) - { - CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor); - players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor; - } - } + if (!playeringame[i]) + continue; - SetPlayerSkin(consoleplayer, forcecharskin); - // normal player colors in single player - if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor) - { - CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor); - players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor; - } + INT32 skinnum = R_SkinAvailable(forcecharskin); + if (skinnum == -1 || !R_SkinUsable(i, skinnum)) + continue; + + SetPlayerSkinByNum(i, skinnum); + + if (!netgame) + players[i].skincolor = skins[skinnum].prefcolor; } } @@ -8226,7 +8239,7 @@ static boolean P_LoadAddon(UINT16 numlumps) { CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap); if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } return true; diff --git a/src/p_slopes.c b/src/p_slopes.c index 6aca116a5..1c0ee81a7 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -566,6 +566,7 @@ static void line_SpawnViaMapthingVertexes(const int linenum, const boolean spawn case TMSP_BACKCEILING: slopetoset = &line->backsector->c_slope; side = &sides[line->sidenum[1]]; + break; default: return; } diff --git a/src/p_spec.c b/src/p_spec.c index 169f129b9..28ecc60f4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -3286,19 +3286,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) 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 (!(line->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, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic false, // do not handle FOF_EXISTS @@ -3372,19 +3371,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) 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 1 for relative alpha calculation if (!(line->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, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic !(line->args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS @@ -6565,10 +6563,10 @@ void P_SpawnSpecials(boolean fromnetsave) //Cutting options if (ffloorflags & FOF_RENDERALL) { - //If inside is visible, cut inner walls - if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFA_SPLAT) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE)) + //If inside is visible from the outside, cut inner walls + if (lines[i].args[1] < 255 || (lines[i].args[3] & TMFA_SPLAT)) ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA; - else + else if (!(lines[i].args[4] & TMFT_VISIBLEFROMINSIDE)) ffloorflags |= FOF_CUTLEVEL; } @@ -6624,20 +6622,19 @@ void P_SpawnSpecials(boolean fromnetsave) if (lines[i].args[4] & TMFC_SPLAT) ffloorflags |= FOF_SPLAT; - //If inside is visible, cut inner walls - if (lines[i].args[1] < 0xff || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE) || (lines[i].args[4] & TMFC_SPLAT)) + //If inside is visible from the outside, cut inner walls + if (lines[i].args[1] < 255 || (lines[i].args[4] & TMFC_SPLAT)) ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA; - else - ffloorflags |= FOF_CUTLEVEL; - - //If player can enter it, render insides - if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE) + //If player can view it from the inside, render insides + else if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE) { if (ffloorflags & FOF_RENDERPLANES) ffloorflags |= FOF_BOTHPLANES; if (ffloorflags & FOF_RENDERSIDES) ffloorflags |= FOF_ALLSIDES; } + else + ffloorflags |= FOF_CUTLEVEL; P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers); if (lines[i].args[4] & TMFC_AIRBOB) @@ -6688,10 +6685,10 @@ void P_SpawnSpecials(boolean fromnetsave) //Cutting options if (ffloorflags & FOF_RENDERALL) { - //If inside is visible, cut inner walls - if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFA_SPLAT) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE)) + //If inside is visible from the outside, cut inner walls + if (lines[i].args[1] < 255 || (lines[i].args[3] & TMFA_SPLAT)) ffloorflags |= FOF_CUTEXTRA|FOF_EXTRA; - else + else if (!(lines[i].args[4] & TMFT_VISIBLEFROMINSIDE)) ffloorflags |= FOF_CUTLEVEL; } @@ -7756,15 +7753,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 1 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; @@ -7850,7 +7846,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; @@ -7862,7 +7858,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)) @@ -7962,11 +7958,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) @@ -7982,7 +7978,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; } } @@ -8012,17 +8008,16 @@ 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 1 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); @@ -8033,7 +8028,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) { diff --git a/src/p_tick.c b/src/p_tick.c index ec5d8a2da..444b68d2f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -26,6 +26,8 @@ #include "r_main.h" #include "r_fps.h" #include "i_video.h" // rendermode +#include "netcode/net_command.h" +#include "netcode/server_connection.h" // Object place #include "m_cheat.h" diff --git a/src/p_user.c b/src/p_user.c index cf4818298..5730d476d 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -17,7 +17,8 @@ #include "doomdef.h" #include "i_system.h" #include "d_event.h" -#include "d_net.h" +#include "netcode/d_net.h" +#include "netcode/net_command.h" #include "g_game.h" #include "p_local.h" #include "r_fps.h" @@ -686,7 +687,7 @@ static void P_DeNightserizePlayer(player_t *player) player->mo->skin = &skins[player->skin]; player->followitem = skins[player->skin].followitem; - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); G_GhostAddColor(GHC_RETURNSKIN); // Restore aiming angle @@ -1855,6 +1856,7 @@ void P_SpawnShieldOrb(player_t *player) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); + P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person P_SetMobjState(ov, shieldobj->info->seestate); P_SetTarget(&shieldobj->tracer, ov); } @@ -1862,12 +1864,14 @@ void P_SpawnShieldOrb(player_t *player) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); + P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person P_SetMobjState(ov, shieldobj->info->meleestate); } if (shieldobj->info->missilestate) { ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY); P_SetTarget(&ov->target, shieldobj); + P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person P_SetMobjState(ov, shieldobj->info->missilestate); } if (player->powers[pw_shield] & SH_FORCE) @@ -2005,14 +2009,14 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->standingslope = mobj->standingslope; if (mobj->flags2 & MF2_OBJECTFLIP) - ghost->flags |= MF2_OBJECTFLIP; + ghost->flags2 |= MF2_OBJECTFLIP; if (mobj->player && mobj->player->followmobj) { mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj); P_SetTarget(&ghost2->tracer, ghost); P_SetTarget(&ghost->tracer, ghost2); - P_SetTarget(&ghost2->dontdrawforviewmobj, mobj); // Hide the follow-ghost for the non-follow object + P_SetTarget(&ghost2->dontdrawforviewmobj, mobj); // Hide the follow-ghost for the non-follow target ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW); } @@ -3029,7 +3033,7 @@ static void P_CheckInvincibilityTimer(player_t *player) } else { - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); G_GhostAddColor(GHC_NORMAL); } } @@ -4302,7 +4306,7 @@ static void P_DoSuperStuff(player_t *player) } else { - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); G_GhostAddColor(GHC_NORMAL); } @@ -4352,7 +4356,7 @@ static void P_DoSuperStuff(player_t *player) } else { - player->mo->color = player->skincolor; + player->mo->color = P_GetPlayerColor(player); G_GhostAddColor(GHC_NORMAL); } @@ -8717,7 +8721,10 @@ void P_MovePlayer(player_t *player) player->mo->height = P_GetPlayerHeight(player); if (player->mo->eflags & MFE_VERTICALFLIP && player->mo->height != oldheight) // adjust z height for reverse gravity, similar to how it's done for scaling - player->mo->z -= player->mo->height - oldheight; + { + player->mo->z -= player->mo->height - oldheight; + player->mo->old_z -= player->mo->height - oldheight; // Snap the Z adjustment, while keeping the Z interpolation + } // Crush test... if ((player->mo->ceilingz - player->mo->floorz < player->mo->height) @@ -11153,7 +11160,7 @@ static void P_MinecartThink(player_t *player) detright->drawonlyforplayer = player; } else - P_RemoveMobj(detleft); + P_RemoveMobj(detright); } } else @@ -11507,7 +11514,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume) } fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it - fume->eflags = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity! + fume->flags2 = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity! fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity! // Finally, set its position @@ -11758,7 +11765,7 @@ void P_PlayerThink(player_t *player) if (!total || ((4*exiting)/total) >= numneeded) { if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } else player->exiting = 3; @@ -11766,7 +11773,7 @@ void P_PlayerThink(player_t *player) else { if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + D_SendExitLevel(false); } } @@ -11864,7 +11871,7 @@ void P_PlayerThink(player_t *player) mo2 = (mobj_t *)th; if (!(mo2->type == MT_RING || mo2->type == MT_COIN - || mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE + || mo2->type == MT_BLUESPHERE // || mo2->type == MT_BOMBSPHERE || mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR)) continue; @@ -13113,3 +13120,16 @@ boolean P_PlayerShouldUseSpinHeight(player_t *player) && player->dashmode >= DASHMODE_THRESHOLD && player->mo->state-states == S_PLAY_DASH) || JUMPCURLED(player)); } + +UINT16 P_GetPlayerColor(player_t *player) +{ + if (G_GametypeHasTeams() && player->ctfteam) + { + if (player->ctfteam == 1) + return skincolor_redteam; + else if (player->ctfteam == 2) + return skincolor_blueteam; + } + + return player->skincolor; +} diff --git a/src/r_bbox.c b/src/r_bbox.c index 4302d1b9a..cf417ec37 100644 --- a/src/r_bbox.c +++ b/src/r_bbox.c @@ -34,7 +34,7 @@ static CV_PossibleValue_t renderhitbox_cons_t[] = { {RENDERHITBOX_RINGS, "Rings"}, {0}}; -consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", CV_CHEAT, renderhitbox_cons_t, NULL); +consvar_t cv_renderhitbox = CVAR_INIT ("renderhitbox", "Off", CV_CHEAT|CV_NOTINNET, renderhitbox_cons_t, NULL); consvar_t cv_renderhitboxinterpolation = CVAR_INIT ("renderhitbox_interpolation", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_renderhitboxgldepth = CVAR_INIT ("renderhitbox_gldepth", "Off", CV_SAVE, CV_OnOff, NULL); @@ -268,6 +268,9 @@ boolean R_ThingBoundingBoxVisible(mobj_t *thing) { INT32 cvmode = cv_renderhitbox.value; + if (multiplayer) // No hitboxes in multiplayer to avoid cheating + return false; + // Do not render bbox for these switch (thing->type) { diff --git a/src/r_defs.h b/src/r_defs.h index 1edbba473..0275b4222 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -760,12 +760,12 @@ typedef struct drawseg_s // Pointers to lists for sprite clipping, all three adjusted so [x1] is first value. INT16 *sprtopclip; INT16 *sprbottomclip; - INT16 *maskedtexturecol; + fixed_t *maskedtexturecol; struct visplane_s *ffloorplanes[MAXFFLOORS]; INT32 numffloorplanes; struct ffloor_s *thicksides[MAXFFLOORS]; - INT16 *thicksidecol; + fixed_t *thicksidecol; INT32 numthicksides; fixed_t frontscale[MAXVIDWIDTH]; diff --git a/src/r_fps.c b/src/r_fps.c index c6eb59482..de450aaa7 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -292,6 +292,8 @@ void R_InterpolateMobjState(mobj_t *mobj, fixed_t frac, interpmobjstate_t *out) out->y = mobj->y; out->z = mobj->z; out->scale = mobj->scale; + out->radius = mobj->radius; + out->height = mobj->height; out->subsector = mobj->subsector; out->angle = mobj->player ? mobj->player->drawangle : mobj->angle; out->pitch = mobj->pitch; @@ -307,10 +309,22 @@ void R_InterpolateMobjState(mobj_t *mobj, fixed_t frac, interpmobjstate_t *out) out->x = R_LerpFixed(mobj->old_x, mobj->x, frac); out->y = R_LerpFixed(mobj->old_y, mobj->y, frac); out->z = R_LerpFixed(mobj->old_z, mobj->z, frac); - out->scale = mobj->resetinterp ? mobj->scale : R_LerpFixed(mobj->old_scale, mobj->scale, frac); out->spritexscale = mobj->resetinterp ? mobj->spritexscale : R_LerpFixed(mobj->old_spritexscale, mobj->spritexscale, frac); out->spriteyscale = mobj->resetinterp ? mobj->spriteyscale : R_LerpFixed(mobj->old_spriteyscale, mobj->spriteyscale, frac); + if (mobj->scale == mobj->old_scale) // Tiny optimisation - scale is usually unchanging, so let's skip a lerp, two FixedMuls, and two FixedDivs + { + out->scale = mobj->scale; + out->radius = mobj->radius; + out->height = mobj->height; + } + else + { + out->scale = R_LerpFixed(mobj->old_scale, mobj->scale, frac); + out->radius = FixedMul(mobj->radius, FixedDiv(out->scale, mobj->scale)); + out->height = FixedMul(mobj->height, FixedDiv(out->scale, mobj->scale)); + } + // Sprite offsets are not interpolated until we have a way to interpolate them explicitly in Lua. // It seems existing mods visually break more often than not if it is interpolated. out->spritexoffset = mobj->spritexoffset; @@ -340,6 +354,8 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst out->y = mobj->y; out->z = mobj->z; out->scale = FRACUNIT; + out->radius = mobj->radius; + out->height = mobj->height; out->subsector = mobj->subsector; out->angle = mobj->angle; out->pitch = mobj->angle; @@ -356,6 +372,8 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst out->y = R_LerpFixed(mobj->old_y, mobj->y, frac); out->z = R_LerpFixed(mobj->old_z, mobj->z, frac); out->scale = FRACUNIT; + out->radius = mobj->radius; + out->height = mobj->height; out->spritexscale = R_LerpFixed(mobj->old_spritexscale, mobj->spritexscale, frac); out->spriteyscale = R_LerpFixed(mobj->old_spriteyscale, mobj->spriteyscale, frac); out->spritexoffset = R_LerpFixed(mobj->old_spritexoffset, mobj->spritexoffset, frac); diff --git a/src/r_fps.h b/src/r_fps.h index 9a8bfa38a..f43d29f30 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -63,6 +63,8 @@ typedef struct { angle_t roll; angle_t spriteroll; fixed_t scale; + fixed_t radius; + fixed_t height; fixed_t spritexscale; fixed_t spriteyscale; fixed_t spritexoffset; diff --git a/src/r_main.c b/src/r_main.c index 55bb9c4ff..0655bd06f 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -41,16 +41,6 @@ #include "hardware/hw_main.h" #endif -//profile stuff --------------------------------------------------------- -//#define TIMING -#ifdef TIMING -#include "p5prof.h" -INT64 mycount; -INT64 mytotal = 0; -//unsigned long nombre = 100000; -#endif -//profile stuff --------------------------------------------------------- - // Fineangles in the SCREENWIDTH wide window. #define FIELDOFVIEW 2048 @@ -157,7 +147,8 @@ consvar_t cv_flipcam2 = CVAR_INIT ("flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, consvar_t cv_shadow = CVAR_INIT ("shadow", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_skybox = CVAR_INIT ("skybox", "On", CV_SAVE, CV_OnOff, NULL); -consvar_t cv_ffloorclip = CVAR_INIT ("ffloorclip", "On", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_ffloorclip = CVAR_INIT ("r_ffloorclip", "On", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_spriteclip = CVAR_INIT ("r_spriteclip", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_allowmlook = CVAR_INIT ("allowmlook", "Yes", CV_NETVAR|CV_ALLOWLUA, CV_YesNo, NULL); consvar_t cv_showhud = CVAR_INIT ("showhud", "Yes", CV_CALL|CV_ALLOWLUA, CV_YesNo, R_SetViewSize); consvar_t cv_translucenthud = CVAR_INIT ("translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL); @@ -258,7 +249,7 @@ static void FlipCam2_OnChange(void) // // killough 5/2/98: reformatted // -INT32 R_PointOnSide(fixed_t x, fixed_t y, node_t *node) +INT32 R_PointOnSide(fixed_t x, fixed_t y, node_t *restrict node) { if (!node->dx) return x <= node->x ? node->dy > 0 : node->dy < 0; @@ -270,9 +261,10 @@ INT32 R_PointOnSide(fixed_t x, fixed_t y, node_t *node) fixed_t dy = (y >> 1) - (node->y >> 1); // Try to quickly decide by looking at sign bits. - if ((node->dy ^ node->dx ^ dx ^ dy) < 0) - return (node->dy ^ dx) < 0; // (left is negative) - return FixedMul(dy, node->dx>>FRACBITS) >= FixedMul(node->dy>>FRACBITS, dx); + // also use a mask to avoid branch prediction + INT32 mask = (node->dy ^ node->dx ^ dx ^ dy) >> 31; + return (mask & ((node->dy ^ dx) < 0)) | // (left is negative) + (~mask & (FixedMul(dy, node->dx>>FRACBITS) >= FixedMul(node->dy>>FRACBITS, dx))); } // killough 5/2/98: reformatted @@ -1479,6 +1471,7 @@ void R_RenderPlayerView(player_t *player) R_ClearClipSegs(); } R_ClearDrawSegs(); + R_ClearSegTables(); R_ClearSprites(); Portal_InitList(); @@ -1489,29 +1482,17 @@ void R_RenderPlayerView(player_t *player) Mask_Pre(&masks[nummasks - 1]); curdrawsegs = ds_p; -//profile stuff --------------------------------------------------------- -#ifdef TIMING - mytotal = 0; - ProfZeroTimer(); -#endif ps_numbspcalls.value.i = ps_numpolyobjects.value.i = ps_numdrawnodes.value.i = 0; PS_START_TIMING(ps_bsptime); R_RenderBSPNode((INT32)numnodes - 1); PS_STOP_TIMING(ps_bsptime); - ps_numsprites.value.i = visspritecount; -#ifdef TIMING - RDMSR(0x10, &mycount); - mytotal += mycount; // 64bit add - - CONS_Debug(DBG_RENDER, "RenderBSPNode: 0x%d %d\n", *((INT32 *)&mytotal + 1), (INT32)mytotal); -#endif -//profile stuff --------------------------------------------------------- Mask_Post(&masks[nummasks - 1]); PS_START_TIMING(ps_sw_spritecliptime); R_ClipSprites(drawsegs, NULL); PS_STOP_TIMING(ps_sw_spritecliptime); + ps_numsprites.value.i = numvisiblesprites; // Add skybox portals caused by sky visplanes. if (cv_skybox.value && skyboxmo[0]) @@ -1602,6 +1583,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_shadow); CV_RegisterVar(&cv_skybox); CV_RegisterVar(&cv_ffloorclip); + CV_RegisterVar(&cv_spriteclip); CV_RegisterVar(&cv_cam_dist); CV_RegisterVar(&cv_cam_still); diff --git a/src/r_main.h b/src/r_main.h index f08070d0f..a6fb42ba2 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -114,7 +114,7 @@ extern consvar_t cv_chasecam, cv_chasecam2; extern consvar_t cv_flipcam, cv_flipcam2; extern consvar_t cv_shadow; -extern consvar_t cv_ffloorclip; +extern consvar_t cv_ffloorclip, cv_spriteclip; extern consvar_t cv_translucency; extern consvar_t cv_drawdist, cv_drawdist_nights, cv_drawdist_precip; extern consvar_t cv_fov; diff --git a/src/r_plane.c b/src/r_plane.c index c568484b6..29ce26b29 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -53,10 +53,6 @@ INT32 numffloors; #define visplane_hash(picnum,lightlevel,height) \ ((unsigned)((picnum)*3+(lightlevel)+(height)*7) & VISPLANEHASHMASK) -//SoM: 3/23/2000: Use boom opening limit removal -size_t maxopenings; -INT16 *openings, *lastopening; /// \todo free leak - // // Clip values are the solid pixel bounding the range. // floorclip starts out SCREENHEIGHT @@ -366,8 +362,6 @@ void R_ClearPlanes(void) freehead = &(*freehead)->next; } - lastopening = openings; - // texture calculation memset(cachedheight, 0, sizeof (cachedheight)); } diff --git a/src/r_plane.h b/src/r_plane.h index 9870a43e2..917e8b041 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -60,9 +60,6 @@ extern visplane_t *floorplane; extern visplane_t *ceilingplane; // Visplane related. -extern INT16 *lastopening, *openings; -extern size_t maxopenings; - extern INT16 floorclip[MAXVIDWIDTH], ceilingclip[MAXVIDWIDTH]; extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16]; extern fixed_t cachedheight[MAXVIDHEIGHT]; diff --git a/src/r_segs.c b/src/r_segs.c index facab62ab..019a0d5c6 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -20,7 +20,7 @@ #include "w_wad.h" #include "z_zone.h" -#include "d_netcmd.h" +#include "netcode/d_netcmd.h" #include "m_misc.h" #include "p_local.h" // Camera... #include "p_slopes.h" @@ -71,9 +71,22 @@ static fixed_t topfrac, topstep; static fixed_t bottomfrac, bottomstep; static lighttable_t **walllights; -static INT16 *maskedtexturecol; +static fixed_t *maskedtexturecol; static fixed_t *maskedtextureheight = NULL; +//SoM: 3/23/2000: Use boom opening limit removal +static size_t numopenings; +static INT16 *openings, *lastopening; + +static size_t texturecolumntablesize; +static fixed_t *texturecolumntable, *curtexturecolumntable; + +void R_ClearSegTables(void) +{ + lastopening = openings; + curtexturecolumntable = texturecolumntable; +} + // ========================================================================== // R_RenderMaskedSegRange // ========================================================================== @@ -350,170 +363,115 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) dc_texturemid += (textureheight[texnum])*times + textureheight[texnum]; else dc_texturemid -= (textureheight[texnum])*times; - // calculate lighting - if (maskedtexturecol[dc_x] != INT16_MAX) + + // Check for overflows first + overflow_test = (INT64)centeryfrac - (((INT64)dc_texturemid*spryscale)>>FRACBITS); + if (overflow_test < 0) overflow_test = -overflow_test; + if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) { - // Check for overflows first - overflow_test = (INT64)centeryfrac - (((INT64)dc_texturemid*spryscale)>>FRACBITS); - if (overflow_test < 0) overflow_test = -overflow_test; - if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) + // Eh, no, go away, don't waste our time + if (dc_numlights) { - // Eh, no, go away, don't waste our time - if (dc_numlights) + for (i = 0; i < dc_numlights; i++) { - for (i = 0; i < dc_numlights; i++) + rlight = &dc_lightlist[i]; + rlight->height += rlight->heightstep; + } + } + spryscale += rw_scalestep; + continue; + } + + // calculate lighting + if (dc_numlights) + { + lighttable_t **xwalllights; + + sprbotscreen = INT32_MAX; + sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale)); + + realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen; + dc_iscale = 0xffffffffu / (unsigned)spryscale; + + // draw the texture + col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); + + for (i = 0; i < dc_numlights; i++) + { + rlight = &dc_lightlist[i]; + + if ((rlight->flags & FOF_NOSHADE)) + continue; + + if (rlight->lightnum < 0) + xwalllights = scalelight[0]; + else if (rlight->lightnum >= LIGHTLEVELS) + xwalllights = scalelight[LIGHTLEVELS-1]; + else + xwalllights = scalelight[rlight->lightnum]; + + pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + + if (pindex >= MAXLIGHTSCALE) + pindex = MAXLIGHTSCALE - 1; + + if (rlight->extra_colormap) + rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps); + else + rlight->rcolormap = xwalllights[pindex]; + + height = rlight->height; + rlight->height += rlight->heightstep; + + if (height <= windowtop) + { + dc_colormap = rlight->rcolormap; + continue; + } + + windowbottom = height; + if (windowbottom >= realbot) + { + windowbottom = realbot; + colfunc_2s(col); + for (i++; i < dc_numlights; i++) { rlight = &dc_lightlist[i]; rlight->height += rlight->heightstep; } + + continue; } - spryscale += rw_scalestep; - continue; - } - - if (dc_numlights) - { - lighttable_t **xwalllights; - - sprbotscreen = INT32_MAX; - sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale)); - - realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen; - dc_iscale = 0xffffffffu / (unsigned)spryscale; - - // draw the texture - col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3); - - for (i = 0; i < dc_numlights; i++) - { - rlight = &dc_lightlist[i]; - - if ((rlight->flags & FOF_NOSHADE)) - continue; - - if (rlight->lightnum < 0) - xwalllights = scalelight[0]; - else if (rlight->lightnum >= LIGHTLEVELS) - xwalllights = scalelight[LIGHTLEVELS-1]; - else - xwalllights = scalelight[rlight->lightnum]; - - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; - - if (pindex >= MAXLIGHTSCALE) - pindex = MAXLIGHTSCALE - 1; - - if (rlight->extra_colormap) - rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps); - else - rlight->rcolormap = xwalllights[pindex]; - - height = rlight->height; - rlight->height += rlight->heightstep; - - if (height <= windowtop) - { - dc_colormap = rlight->rcolormap; - continue; - } - - windowbottom = height; - if (windowbottom >= realbot) - { - windowbottom = realbot; - colfunc_2s(col); - for (i++; i < dc_numlights; i++) - { - rlight = &dc_lightlist[i]; - rlight->height += rlight->heightstep; - } - - continue; - } - colfunc_2s(col); - windowtop = windowbottom + 1; - dc_colormap = rlight->rcolormap; - } - windowbottom = realbot; - if (windowtop < windowbottom) - colfunc_2s(col); - - spryscale += rw_scalestep; - continue; - } - - // calculate lighting - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; - - if (pindex >= MAXLIGHTSCALE) - pindex = MAXLIGHTSCALE - 1; - - dc_colormap = walllights[pindex]; - - if (frontsector->extra_colormap) - dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); - - sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); - dc_iscale = 0xffffffffu / (unsigned)spryscale; - - // draw the texture - col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3); - -#if 0 // Disabling this allows inside edges to render below the planes, for until the clipping is fixed to work right when POs are near the camera. -Red - if (curline->dontrenderme && curline->polyseg && (curline->polyseg->flags & POF_RENDERPLANES)) - { - fixed_t my_topscreen; - fixed_t my_bottomscreen; - fixed_t my_yl, my_yh; - - my_topscreen = sprtopscreen + spryscale*col->topdelta; - my_bottomscreen = sprbotscreen == INT32_MAX ? my_topscreen + spryscale*col->length - : sprbotscreen + spryscale*col->length; - - my_yl = (my_topscreen+FRACUNIT-1)>>FRACBITS; - my_yh = (my_bottomscreen-1)>>FRACBITS; - // CONS_Debug(DBG_RENDER, "my_topscreen: %d\nmy_bottomscreen: %d\nmy_yl: %d\nmy_yh: %d\n", my_topscreen, my_bottomscreen, my_yl, my_yh); - - if (numffloors) - { - INT32 top = my_yl; - INT32 bottom = my_yh; - - for (i = 0; i < numffloors; i++) - { - if (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg) - continue; - - if (ffloor[i].height < viewz) - { - INT32 top_w = ffloor[i].plane->top[dc_x]; - - // CONS_Debug(DBG_RENDER, "Leveltime : %d\n", leveltime); - // CONS_Debug(DBG_RENDER, "Top is %d, top_w is %d\n", top, top_w); - if (top_w < top) - { - ffloor[i].plane->top[dc_x] = (INT16)top; - ffloor[i].plane->picnum = 0; - } - // CONS_Debug(DBG_RENDER, "top_w is now %d\n", ffloor[i].plane->top[dc_x]); - } - else if (ffloor[i].height > viewz) - { - INT32 bottom_w = ffloor[i].plane->bottom[dc_x]; - - if (bottom_w > bottom) - { - ffloor[i].plane->bottom[dc_x] = (INT16)bottom; - ffloor[i].plane->picnum = 0; - } - } - } - } - } - else -#endif colfunc_2s(col); + windowtop = windowbottom + 1; + dc_colormap = rlight->rcolormap; + } + windowbottom = realbot; + if (windowtop < windowbottom) + colfunc_2s(col); + + spryscale += rw_scalestep; + continue; } + + // calculate lighting + pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + + if (pindex >= MAXLIGHTSCALE) + pindex = MAXLIGHTSCALE - 1; + + dc_colormap = walllights[pindex]; + + if (frontsector->extra_colormap) + dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); + + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + dc_iscale = 0xffffffffu / (unsigned)spryscale; + + // draw the texture + col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); + colfunc_2s(col); + spryscale += rw_scalestep; } } @@ -857,183 +815,182 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) // draw the columns for (dc_x = x1; dc_x <= x2; dc_x++) { - if (maskedtexturecol[dc_x] != INT16_MAX) + // skew FOF walls + if (ffloortextureslide) { - if (ffloortextureslide) { // skew FOF walls - if (oldx != -1) - dc_texturemid += FixedMul(ffloortextureslide, (maskedtexturecol[oldx]-maskedtexturecol[dc_x])< (INT64)CLAMPMAX) sprtopscreen = windowtop = CLAMPMAX; - else if (top_frac > (INT64)CLAMPMIN) sprtopscreen = windowtop = (fixed_t)top_frac; - else sprtopscreen = windowtop = CLAMPMIN; - if (bottom_frac > (INT64)CLAMPMAX) sprbotscreen = windowbottom = CLAMPMAX; - else if (bottom_frac > (INT64)CLAMPMIN) sprbotscreen = windowbottom = (fixed_t)bottom_frac; - else sprbotscreen = windowbottom = CLAMPMIN; + // Calculate bounds + // clamp the values if necessary to avoid overflows and rendering glitches caused by them + if (top_frac > (INT64)CLAMPMAX) sprtopscreen = windowtop = CLAMPMAX; + else if (top_frac > (INT64)CLAMPMIN) sprtopscreen = windowtop = (fixed_t)top_frac; + else sprtopscreen = windowtop = CLAMPMIN; + if (bottom_frac > (INT64)CLAMPMAX) sprbotscreen = windowbottom = CLAMPMAX; + else if (bottom_frac > (INT64)CLAMPMIN) sprbotscreen = windowbottom = (fixed_t)bottom_frac; + else sprbotscreen = windowbottom = CLAMPMIN; - top_frac += top_step; - bottom_frac += bottom_step; + top_frac += top_step; + bottom_frac += bottom_step; - // SoM: If column is out of range, why bother with it?? - if (windowbottom < topbounds || windowtop > bottombounds) + // SoM: If column is out of range, why bother with it?? + if (windowbottom < topbounds || windowtop > bottombounds) + { + if (dc_numlights) { - if (dc_numlights) + for (i = 0; i < dc_numlights; i++) { - for (i = 0; i < dc_numlights; i++) + rlight = &dc_lightlist[i]; + rlight->height += rlight->heightstep; + if (rlight->flags & FOF_CUTLEVEL) + rlight->botheight += rlight->botheightstep; + } + } + spryscale += rw_scalestep; + continue; + } + + dc_iscale = 0xffffffffu / (unsigned)spryscale; + + // Get data for the column + col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3); + + // SoM: New code does not rely on R_DrawColumnShadowed_8 which + // will (hopefully) put less strain on the stack. + if (dc_numlights) + { + lighttable_t **xwalllights; + fixed_t height; + fixed_t bheight = 0; + INT32 solid = 0; + INT32 lighteffect = 0; + + for (i = 0; i < dc_numlights; i++) + { + // Check if the current light effects the colormap/lightlevel + rlight = &dc_lightlist[i]; + lighteffect = !(dc_lightlist[i].flags & FOF_NOSHADE); + if (lighteffect) + { + lightnum = rlight->lightnum; + + if (lightnum < 0) + xwalllights = scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + xwalllights = scalelight[LIGHTLEVELS-1]; + else + xwalllights = scalelight[lightnum]; + + pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + + if (pindex >= MAXLIGHTSCALE) + pindex = MAXLIGHTSCALE-1; + + if (pfloor->fofflags & FOF_FOG) + { + if (pfloor->master->frontsector->extra_colormap) + rlight->rcolormap = pfloor->master->frontsector->extra_colormap->colormap + (xwalllights[pindex] - colormaps); + else + rlight->rcolormap = xwalllights[pindex]; + } + else + { + if (rlight->extra_colormap) + rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps); + else + rlight->rcolormap = xwalllights[pindex]; + } + } + + solid = 0; // don't carry over solid-cutting flag from the previous light + + // Check if the current light can cut the current 3D floor. + if (rlight->flags & FOF_CUTSOLIDS && !(pfloor->fofflags & FOF_EXTRA)) + solid = 1; + else if (rlight->flags & FOF_CUTEXTRA && pfloor->fofflags & FOF_EXTRA) + { + if (rlight->flags & FOF_EXTRA) + { + // The light is from an extra 3D floor... Check the flags so + // there are no undesired cuts. + if ((rlight->flags & (FOF_FOG|FOF_SWIMMABLE)) == (pfloor->fofflags & (FOF_FOG|FOF_SWIMMABLE))) + solid = 1; + } + else + solid = 1; + } + else + solid = 0; + + height = rlight->height; + rlight->height += rlight->heightstep; + + if (solid) + { + bheight = rlight->botheight - (FRACUNIT >> 1); + rlight->botheight += rlight->botheightstep; + } + + if (height <= windowtop) + { + if (lighteffect) + dc_colormap = rlight->rcolormap; + if (solid && windowtop < bheight) + windowtop = bheight; + continue; + } + + windowbottom = height; + if (windowbottom >= sprbotscreen) + { + windowbottom = sprbotscreen; + // draw the texture + colfunc_2s (col); + for (i++; i < dc_numlights; i++) { rlight = &dc_lightlist[i]; rlight->height += rlight->heightstep; if (rlight->flags & FOF_CUTLEVEL) rlight->botheight += rlight->botheightstep; } + continue; } - spryscale += rw_scalestep; - continue; + // draw the texture + colfunc_2s (col); + if (solid) + windowtop = bheight; + else + windowtop = windowbottom + 1; + if (lighteffect) + dc_colormap = rlight->rcolormap; } + windowbottom = sprbotscreen; + // draw the texture, if there is any space left + if (windowtop < windowbottom) + colfunc_2s (col); - dc_iscale = 0xffffffffu / (unsigned)spryscale; - - // Get data for the column - col = (column_t *)((UINT8 *)R_GetColumn(texnum,maskedtexturecol[dc_x]) - 3); - - // SoM: New code does not rely on R_DrawColumnShadowed_8 which - // will (hopefully) put less strain on the stack. - if (dc_numlights) - { - lighttable_t **xwalllights; - fixed_t height; - fixed_t bheight = 0; - INT32 solid = 0; - INT32 lighteffect = 0; - - for (i = 0; i < dc_numlights; i++) - { - // Check if the current light effects the colormap/lightlevel - rlight = &dc_lightlist[i]; - lighteffect = !(dc_lightlist[i].flags & FOF_NOSHADE); - if (lighteffect) - { - lightnum = rlight->lightnum; - - if (lightnum < 0) - xwalllights = scalelight[0]; - else if (lightnum >= LIGHTLEVELS) - xwalllights = scalelight[LIGHTLEVELS-1]; - else - xwalllights = scalelight[lightnum]; - - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; - - if (pindex >= MAXLIGHTSCALE) - pindex = MAXLIGHTSCALE-1; - - if (pfloor->fofflags & FOF_FOG) - { - if (pfloor->master->frontsector->extra_colormap) - rlight->rcolormap = pfloor->master->frontsector->extra_colormap->colormap + (xwalllights[pindex] - colormaps); - else - rlight->rcolormap = xwalllights[pindex]; - } - else - { - if (rlight->extra_colormap) - rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps); - else - rlight->rcolormap = xwalllights[pindex]; - } - } - - solid = 0; // don't carry over solid-cutting flag from the previous light - - // Check if the current light can cut the current 3D floor. - if (rlight->flags & FOF_CUTSOLIDS && !(pfloor->fofflags & FOF_EXTRA)) - solid = 1; - else if (rlight->flags & FOF_CUTEXTRA && pfloor->fofflags & FOF_EXTRA) - { - if (rlight->flags & FOF_EXTRA) - { - // The light is from an extra 3D floor... Check the flags so - // there are no undesired cuts. - if ((rlight->flags & (FOF_FOG|FOF_SWIMMABLE)) == (pfloor->fofflags & (FOF_FOG|FOF_SWIMMABLE))) - solid = 1; - } - else - solid = 1; - } - else - solid = 0; - - height = rlight->height; - rlight->height += rlight->heightstep; - - if (solid) - { - bheight = rlight->botheight - (FRACUNIT >> 1); - rlight->botheight += rlight->botheightstep; - } - - if (height <= windowtop) - { - if (lighteffect) - dc_colormap = rlight->rcolormap; - if (solid && windowtop < bheight) - windowtop = bheight; - continue; - } - - windowbottom = height; - if (windowbottom >= sprbotscreen) - { - windowbottom = sprbotscreen; - // draw the texture - colfunc_2s (col); - for (i++; i < dc_numlights; i++) - { - rlight = &dc_lightlist[i]; - rlight->height += rlight->heightstep; - if (rlight->flags & FOF_CUTLEVEL) - rlight->botheight += rlight->botheightstep; - } - continue; - } - // draw the texture - colfunc_2s (col); - if (solid) - windowtop = bheight; - else - windowtop = windowbottom + 1; - if (lighteffect) - dc_colormap = rlight->rcolormap; - } - windowbottom = sprbotscreen; - // draw the texture, if there is any space left - if (windowtop < windowbottom) - colfunc_2s (col); - - spryscale += rw_scalestep; - continue; - } - - // calculate lighting - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; - - if (pindex >= MAXLIGHTSCALE) - pindex = MAXLIGHTSCALE - 1; - - dc_colormap = walllights[pindex]; - - if (pfloor->fofflags & FOF_FOG && pfloor->master->frontsector->extra_colormap) - dc_colormap = pfloor->master->frontsector->extra_colormap->colormap + (dc_colormap - colormaps); - else if (frontsector->extra_colormap) - dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); - - // draw the texture - colfunc_2s (col); spryscale += rw_scalestep; + continue; } + + // calculate lighting + pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + + if (pindex >= MAXLIGHTSCALE) + pindex = MAXLIGHTSCALE - 1; + + dc_colormap = walllights[pindex]; + + if (pfloor->fofflags & FOF_FOG && pfloor->master->frontsector->extra_colormap) + dc_colormap = pfloor->master->frontsector->extra_colormap->colormap + (dc_colormap - colormaps); + else if (frontsector->extra_colormap) + dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); + + // draw the texture + colfunc_2s (col); + spryscale += rw_scalestep; } colfunc = colfuncs[BASEDRAWFUNC]; @@ -1270,7 +1227,7 @@ static void R_RenderSegLoop (void) } oldtexturecolumn = texturecolumn; - texturecolumn >>= FRACBITS; + INT32 itexturecolumn = texturecolumn >> FRACBITS; // texturecolumn and lighting are independent of wall tiers if (segtextured) @@ -1336,7 +1293,7 @@ static void R_RenderSegLoop (void) dc_yl = yl; dc_yh = yh; dc_texturemid = rw_midtexturemid; - dc_source = R_GetColumn(midtexture,texturecolumn + (rw_offset_mid>>FRACBITS)); + dc_source = R_GetColumn(midtexture, itexturecolumn + (rw_offset_mid>>FRACBITS)); dc_texheight = textureheight[midtexture]>>FRACBITS; //profile stuff --------------------------------------------------------- @@ -1397,7 +1354,7 @@ static void R_RenderSegLoop (void) dc_yl = yl; dc_yh = mid; dc_texturemid = rw_toptexturemid; - dc_source = R_GetColumn(toptexture,texturecolumn + (rw_offset_top>>FRACBITS)); + dc_source = R_GetColumn(toptexture, itexturecolumn + (rw_offset_top>>FRACBITS)); dc_texheight = textureheight[toptexture]>>FRACBITS; colfunc(); ceilingclip[rw_x] = (INT16)mid; @@ -1433,8 +1390,7 @@ static void R_RenderSegLoop (void) dc_yl = mid; dc_yh = yh; dc_texturemid = rw_bottomtexturemid; - dc_source = R_GetColumn(bottomtexture, - texturecolumn + (rw_offset_bot>>FRACBITS)); + dc_source = R_GetColumn(bottomtexture, itexturecolumn + (rw_offset_bot>>FRACBITS)); dc_texheight = textureheight[bottomtexture]>>FRACBITS; colfunc(); floorclip[rw_x] = (INT16)mid; @@ -1453,7 +1409,7 @@ static void R_RenderSegLoop (void) { // save texturecol // for backdrawing of masked mid texture - maskedtexturecol[rw_x] = (INT16)(texturecolumn + (rw_offset_mid>>FRACBITS)); + maskedtexturecol[rw_x] = texturecolumn + rw_offset_mid; if (maskedtextureheight != NULL) { maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ? @@ -1512,6 +1468,73 @@ static INT64 R_CalcSegDist(seg_t* seg, INT64 x2, INT64 y2) } } +//SoM: Code to remove limits on openings. +static void R_AllocClippingTables(size_t range) +{ + size_t pos = lastopening - openings; + size_t need = range * 2; // for both sprtopclip and sprbottomclip + + if (pos + need < numopenings) + return; + + INT16 *oldopenings = openings; + INT16 *oldlast = lastopening; + + if (numopenings == 0) + numopenings = 16384; + + numopenings += need; + openings = Z_Realloc(openings, numopenings * sizeof (*openings), PU_STATIC, NULL); + lastopening = openings + pos; + + if (oldopenings == NULL) + return; + + // borrowed fix from *cough* zdoom *cough* + // [RH] We also need to adjust the openings pointers that + // were already stored in drawsegs. + for (drawseg_t *ds = drawsegs; ds < ds_p; ds++) + { + // Check if it's in range of the openings + if (ds->sprtopclip + ds->x1 >= oldopenings && ds->sprtopclip + ds->x1 <= oldlast) + ds->sprtopclip = (ds->sprtopclip - oldopenings) + openings; + if (ds->sprbottomclip + ds->x1 >= oldopenings && ds->sprbottomclip + ds->x1 <= oldlast) + ds->sprbottomclip = (ds->sprbottomclip - oldopenings) + openings; + } +} + +static void R_AllocTextureColumnTables(size_t range) +{ + size_t pos = curtexturecolumntable - texturecolumntable; + + // For both tables, we reserve exactly an amount of memory that's equivalent to + // how many columns the seg will take on the entire screen (think about it) + if (pos + range < texturecolumntablesize) + return; + + fixed_t *oldtable = texturecolumntable; + fixed_t *oldlast = curtexturecolumntable; + + if (texturecolumntablesize == 0) + texturecolumntablesize = 16384; + + texturecolumntablesize += range; + texturecolumntable = Z_Realloc(texturecolumntable, texturecolumntablesize * sizeof (*texturecolumntable), PU_STATIC, NULL); + curtexturecolumntable = texturecolumntable + pos; + + if (oldtable == NULL) + return; + + for (drawseg_t *ds = drawsegs; ds < ds_p; ds++) + { + // Check if it's in range of the tables + if (ds->maskedtexturecol + ds->x1 >= oldtable && ds->maskedtexturecol + ds->x1 <= oldlast) + ds->maskedtexturecol = (ds->maskedtexturecol - oldtable) + texturecolumntable; + if (ds->thicksidecol + ds->x1 >= oldtable && ds->thicksidecol + ds->x1 <= oldlast) + ds->thicksidecol = (ds->thicksidecol - oldtable) + texturecolumntable; + } +} + // // R_StoreWallRange // A wall segment will be drawn @@ -1580,37 +1603,6 @@ void R_StoreWallRange(INT32 start, INT32 stop) ds_p->curline = curline; rw_stopx = stop+1; - //SoM: Code to remove limits on openings. - { - size_t pos = lastopening - openings; - size_t need = (rw_stopx - start)*4 + pos; - if (need > maxopenings) - { - drawseg_t *ds; //needed for fix from *cough* zdoom *cough* - INT16 *oldopenings = openings; - INT16 *oldlast = lastopening; - - do - maxopenings = maxopenings ? maxopenings*2 : 16384; - while (need > maxopenings); - openings = Z_Realloc(openings, maxopenings * sizeof (*openings), PU_STATIC, NULL); - lastopening = openings + pos; - - // borrowed fix from *cough* zdoom *cough* - // [RH] We also need to adjust the openings pointers that - // were already stored in drawsegs. - for (ds = drawsegs; ds < ds_p; ds++) - { -#define ADJUST(p) if (ds->p + ds->x1 >= oldopenings && ds->p + ds->x1 <= oldlast) ds->p = ds->p - oldopenings + openings; - ADJUST(maskedtexturecol); - ADJUST(sprtopclip); - ADJUST(sprbottomclip); - ADJUST(thicksidecol); -#undef ADJUST - } - } - } // end of code to remove limits on openings - // calculate scale at both ends and step ds_p->scale1 = rw_scale = R_ScaleFromGlobalAngle(viewangle + xtoviewangle[start]); @@ -2026,6 +2018,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) rw_toptexturemid += sidedef->rowoffset + sidedef->offsety_top; rw_bottomtexturemid += sidedef->rowoffset + sidedef->offsety_bot; + R_AllocTextureColumnTables(rw_stopx - start); + // allocate space for masked texture tables if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors)) { @@ -2040,8 +2034,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) //markceiling = markfloor = true; maskedtexture = true; - ds_p->thicksidecol = maskedtexturecol = lastopening - rw_x; - lastopening += rw_stopx - rw_x; + ds_p->thicksidecol = maskedtexturecol = curtexturecolumntable - rw_x; + curtexturecolumntable += rw_stopx - rw_x; lowcut = max(worldbottom, worldlow) + viewz; highcut = min(worldtop, worldhigh) + viewz; @@ -2224,8 +2218,8 @@ void R_StoreWallRange(INT32 start, INT32 stop) // masked midtexture if (!ds_p->thicksidecol) { - ds_p->maskedtexturecol = maskedtexturecol = lastopening - rw_x; - lastopening += rw_stopx - rw_x; + ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x; + curtexturecolumntable += rw_stopx - rw_x; } else ds_p->maskedtexturecol = ds_p->thicksidecol; @@ -2737,29 +2731,34 @@ void R_StoreWallRange(INT32 start, INT32 stop) ds_p->portalpass = 0; // save sprite clipping info - if (((ds_p->silhouette & SIL_TOP) || maskedtexture) && !ds_p->sprtopclip) + if (maskedtexture || (ds_p->silhouette & (SIL_TOP | SIL_BOTTOM))) { - M_Memcpy(lastopening, ceilingclip+start, 2*(rw_stopx - start)); - ds_p->sprtopclip = lastopening - start; - lastopening += rw_stopx - start; - } + R_AllocClippingTables(rw_stopx - start); - if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) && !ds_p->sprbottomclip) - { - M_Memcpy(lastopening, floorclip + start, 2*(rw_stopx-start)); - ds_p->sprbottomclip = lastopening - start; - lastopening += rw_stopx - start; + if (((ds_p->silhouette & SIL_TOP) || maskedtexture) && !ds_p->sprtopclip) + { + M_Memcpy(lastopening, ceilingclip + start, 2*(rw_stopx - start)); + ds_p->sprtopclip = lastopening - start; + lastopening += rw_stopx - start; + } + + if (((ds_p->silhouette & SIL_BOTTOM) || maskedtexture) && !ds_p->sprbottomclip) + { + M_Memcpy(lastopening, floorclip + start, 2*(rw_stopx - start)); + ds_p->sprbottomclip = lastopening - start; + lastopening += rw_stopx - start; + } } if (maskedtexture && !(ds_p->silhouette & SIL_TOP)) { ds_p->silhouette |= SIL_TOP; - ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MIN: INT32_MAX; + ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MIN : INT32_MAX; } if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM)) { ds_p->silhouette |= SIL_BOTTOM; - ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MAX: INT32_MIN; + ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MAX : INT32_MIN; } ds_p++; } diff --git a/src/r_segs.h b/src/r_segs.h index 09c68b27e..cad014674 100644 --- a/src/r_segs.h +++ b/src/r_segs.h @@ -22,5 +22,6 @@ transnum_t R_GetLinedefTransTable(fixed_t alpha); void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2); void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pffloor); void R_StoreWallRange(INT32 start, INT32 stop); +void R_ClearSegTables(void); #endif diff --git a/src/r_skins.c b/src/r_skins.c index 9443ad49b..308cee8d6 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -113,6 +113,7 @@ static void Sk_SetDefaultValue(skin_t *skin) strcpy(skin->realname, "Someone"); strcpy(skin->hudname, "???"); + strcpy(skin->supername, "Someone super"); skin->starttranscolor = 96; skin->prefcolor = SKINCOLOR_GREEN; @@ -305,6 +306,21 @@ INT32 R_SkinAvailable(const char *name) return -1; } +INT32 R_GetForcedSkin(INT32 playernum) +{ + if (netgame && cv_forceskin.value >= 0 && R_SkinUsable(playernum, cv_forceskin.value)) + return cv_forceskin.value; + + if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0') + { + INT32 skinnum = R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter); + if (skinnum != -1 && R_SkinUsable(playernum, skinnum)) + return skinnum; + } + + return -1; +} + // Auxillary function that actually sets the skin static void SetSkin(player_t *player, INT32 skinnum) { @@ -346,10 +362,6 @@ static void SetSkin(player_t *player, INT32 skinnum) if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback)) { - if (player == &players[consoleplayer]) - CV_StealthSetValue(&cv_playercolor, skin->prefcolor); - else if (player == &players[secondarydisplayplayer]) - CV_StealthSetValue(&cv_playercolor2, skin->prefcolor); player->skincolor = newcolor = skin->prefcolor; if (player->bot && botingame) { @@ -682,7 +694,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) char *value; size_t size; skin_t *skin; - boolean hudname, realname; + boolean hudname, realname, supername; // // search for all skin markers in pwad @@ -712,7 +724,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) skin = &skins[numskins]; Sk_SetDefaultValue(skin); skin->wadnum = wadnum; - hudname = realname = false; + hudname = realname = supername = false; // parse stoken = strtok (buf2, "\r\n= "); while (stoken) @@ -755,7 +767,7 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) Z_Free(value2); } - // copy to hudname and fullname as a default. + // copy to hudname, realname, and supername as a default. if (!realname) { STRBUFCPY(skin->realname, skin->name); @@ -771,6 +783,19 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) strupr(skin->hudname); SYMBOLCONVERT(skin->hudname) } + if (!supername) + { + char superstring[SKINNAMESIZE+7]; + strcpy(superstring, "Super "); + strlcat(superstring, skin->name, sizeof(superstring)); + STRBUFCPY(skin->supername, superstring); + } + } + else if (!stricmp(stoken, "supername")) + { // Super name (eg. "Super Knuckles") + supername = true; + STRBUFCPY(skin->supername, value); + SYMBOLCONVERT(skin->supername) } else if (!stricmp(stoken, "realname")) { // Display name (eg. "Knuckles") @@ -779,6 +804,13 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile) SYMBOLCONVERT(skin->realname) if (!hudname) HUDNAMEWRITE(skin->realname); + if (!supername) //copy over default to capitalise the name + { + char superstring[SKINNAMESIZE+7]; + strcpy(superstring, "Super "); + strlcat(superstring, skin->realname, sizeof(superstring)); + STRBUFCPY(skin->supername, superstring); + } } else if (!stricmp(stoken, "hudname")) { // Life icon name (eg. "K.T.E") @@ -831,7 +863,7 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile) char *value; size_t size; skin_t *skin; - boolean noskincomplain, realname, hudname; + boolean noskincomplain, realname, hudname, supername; // // search for all skin patch markers in pwad @@ -855,7 +887,7 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile) buf2[size] = '\0'; skin = NULL; - noskincomplain = realname = hudname = false; + noskincomplain = realname = hudname = supername = false; /* Parse. Has more phases than the parser in R_AddSkins because it needs to have the patching name first (no default skin name is acceptible for patching, unlike skin creation) @@ -894,13 +926,26 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile) else // Get the properties! { // Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines. - if (!stricmp(stoken, "realname")) + if (!stricmp(stoken, "supername")) + { // Super name (eg. "Super Knuckles") + supername = true; + STRBUFCPY(skin->supername, value); + SYMBOLCONVERT(skin->supername) + } + else if (!stricmp(stoken, "realname")) { // Display name (eg. "Knuckles") realname = true; STRBUFCPY(skin->realname, value); SYMBOLCONVERT(skin->realname) if (!hudname) HUDNAMEWRITE(skin->realname); + if (!supername) //copy over default to capitalise the name + { + char superstring[SKINNAMESIZE+7]; + strcpy(superstring, "Super "); + strlcat(superstring, skin->realname, sizeof(superstring)); + STRBUFCPY(skin->supername, superstring); + } } else if (!stricmp(stoken, "hudname")) { // Life icon name (eg. "K.T.E") diff --git a/src/r_skins.h b/src/r_skins.h index 082b690dd..bf2275a49 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -35,8 +35,9 @@ typedef struct UINT16 wadnum; skinflags_t flags; - char realname[SKINNAMESIZE+1]; // Display name for level completion. + char realname[SKINNAMESIZE+1]; // Display name for level completion char hudname[SKINNAMESIZE+1]; // HUD name to display (officially exactly 5 characters long) + char supername[SKINNAMESIZE+7]; // Super name to display when collecting all emeralds UINT8 ability; // ability definition UINT8 ability2; // secondary ability definition @@ -95,6 +96,7 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 boolean R_SkinUsable(INT32 playernum, INT32 skinnum); UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); +INT32 R_GetForcedSkin(INT32 playernum); void R_AddSkins(UINT16 wadnum, boolean mainfile); void R_PatchSkins(UINT16 wadnum, boolean mainfile); diff --git a/src/r_splats.c b/src/r_splats.c index 737b6d110..0b482d798 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -314,9 +314,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr fixed_t planeheight = 0; fixed_t step; - int spanfunctype = SPANDRAWFUNC_SPRITE; - - prepare_rastertab(); + int spanfunctype; #define RASTERPARAMS(vnum1, vnum2, tv1, tv2, tc, dir) \ x1 = verts[vnum1].x; \ @@ -367,21 +365,15 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr if (ry1 > maxy) \ maxy = ry1; - // do segment a -> top of texture - RASTERPARAMS(3,2,0,pSplat->width-1,0,0); - // do segment b -> right side of texture - RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0); - // do segment c -> bottom of texture - RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0); - // do segment d -> left side of texture - RASTERPARAMS(0,3,pSplat->width-1,0,0,1); - ds_source = (UINT8 *)pSplat->pic; ds_flatwidth = pSplat->width; ds_flatheight = pSplat->height; - ds_powersoftwo = false; - if (R_CheckPowersOfTwo()) + ds_powersoftwo = ds_solidcolor = false; + + if (R_CheckSolidColorFlat()) + ds_solidcolor = true; + else if (R_CheckPowersOfTwo()) { R_SetFlatVars(ds_flatwidth * ds_flatheight); ds_powersoftwo = true; @@ -392,9 +384,8 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr R_SetTiltedSpan(0); R_SetScaledSlopePlane(pSplat->slope, vis->viewpoint.x, vis->viewpoint.y, vis->viewpoint.z, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewpoint.angle, pSplat->angle); R_CalculateSlopeVectors(); - spanfunctype = SPANDRAWFUNC_TILTEDSPRITE; } - else + else if (!ds_solidcolor) { planeheight = abs(pSplat->z - vis->viewpoint.z); @@ -429,23 +420,70 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr ds_colormap = &vis->extra_colormap->colormap[ds_colormap - colormaps]; } - if (vis->transmap) - { - ds_transmap = vis->transmap; + ds_transmap = vis->transmap; + // Determine which R_DrawWhatever to use + + // Solid color + if (ds_solidcolor) + { + UINT16 px = *(UINT16 *)ds_source; + + // Uh, it's not visible. + if (!(px & 0xFF00)) + return; + + // Pixel color is contained in the lower 8 bits (upper 8 are the opacity), so advance the pointer + ds_source++; + + if (pSplat->slope) + { + if (ds_transmap) + spanfunctype = SPANDRAWFUNC_TILTEDTRANSSOLID; + else + spanfunctype = SPANDRAWFUNC_TILTEDSOLID; + } + else + { + if (ds_transmap) + spanfunctype = SPANDRAWFUNC_TRANSSOLID; + else + spanfunctype = SPANDRAWFUNC_SOLID; + } + } + // Transparent + else if (ds_transmap) + { if (pSplat->slope) spanfunctype = SPANDRAWFUNC_TILTEDTRANSSPRITE; else spanfunctype = SPANDRAWFUNC_TRANSSPRITE; } + // Opaque else - ds_transmap = NULL; + { + if (pSplat->slope) + spanfunctype = SPANDRAWFUNC_TILTEDSPRITE; + else + spanfunctype = SPANDRAWFUNC_SPRITE; + } - if (ds_powersoftwo) + if (ds_powersoftwo || ds_solidcolor) spanfunc = spanfuncs[spanfunctype]; else spanfunc = spanfuncs_npo2[spanfunctype]; + prepare_rastertab(); + + // do segment a -> top of texture + RASTERPARAMS(3,2,0,pSplat->width-1,0,0); + // do segment b -> right side of texture + RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0); + // do segment c -> bottom of texture + RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0); + // do segment d -> left side of texture + RASTERPARAMS(0,3,pSplat->width-1,0,0,1); + if (maxy >= vid.height) maxy = vid.height-1; @@ -500,7 +538,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr if (x2 < x1) continue; - if (!pSplat->slope) + if (!ds_solidcolor && !pSplat->slope) { fixed_t xstep, ystep; fixed_t distance, span; @@ -549,7 +587,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr rastertab[y].maxx = INT32_MIN; } - if (pSplat->angle && !pSplat->slope) + if (!ds_solidcolor && pSplat->angle && !pSplat->slope) memset(cachedheight, 0, sizeof(cachedheight)); } diff --git a/src/r_things.c b/src/r_things.c index a0b49072e..3c7d70ee2 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -34,7 +34,7 @@ #include "p_tick.h" #include "p_local.h" #include "p_slopes.h" -#include "d_netfil.h" // blargh. for nameonly(). +#include "netcode/d_netfil.h" // blargh. for nameonly(). #include "m_cheat.h" // objectplace #ifdef HWRENDER #include "hardware/hw_md2.h" @@ -524,7 +524,8 @@ void R_AddSpriteDefs(UINT16 wadnum) // // GAME FUNCTIONS // -UINT32 visspritecount; +UINT32 visspritecount, numvisiblesprites; + static UINT32 clippedvissprites; static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL}; @@ -598,7 +599,7 @@ void R_InitSprites(void) // void R_ClearSprites(void) { - visspritecount = clippedvissprites = 0; + visspritecount = numvisiblesprites = clippedvissprites = 0; } // @@ -844,6 +845,15 @@ static void R_DrawVisSprite(vissprite_t *vis) if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) return; // ditto } + // TODO This check should not be necessary. But Papersprites near to the camera will sometimes create invalid values + // for the vissprite's startfrac. This happens because they are not depth culled like other sprites. + // Someone who is more familiar with papersprites pls check and try to fix <3 + if (vis->startfrac < 0 || vis->startfrac > (patch->width << FRACBITS)) + { + // never draw vissprites with startfrac out of patch range + return; + } + colfunc = colfuncs[BASEDRAWFUNC]; // hack: this isn't resetting properly somewhere. dc_colormap = vis->colormap; dc_translation = R_GetSpriteTranslation(vis); @@ -887,7 +897,7 @@ static void R_DrawVisSprite(vissprite_t *vis) frac = vis->startfrac; windowtop = windowbottom = sprbotscreen = INT32_MAX; - if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES) + if (vis->cut & SC_SHADOW && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES) this_scale = FixedMul(this_scale, ((skin_t *)vis->mobj->skin)->highresscale); if (this_scale <= 0) this_scale = 1; @@ -897,10 +907,10 @@ static void R_DrawVisSprite(vissprite_t *vis) { vis->scale = FixedMul(vis->scale, this_scale); vis->scalestep = FixedMul(vis->scalestep, this_scale); - vis->xiscale = FixedDiv(vis->xiscale,this_scale); + vis->xiscale = FixedDiv(vis->xiscale, this_scale); vis->cut |= SC_ISSCALED; } - dc_texturemid = FixedDiv(dc_texturemid,this_scale); + dc_texturemid = FixedDiv(dc_texturemid, this_scale); } spryscale = vis->scale; @@ -1184,7 +1194,7 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) R_InterpolateMobjState(thing, FRACUNIT, &interp); } - halfHeight = interp.z + (thing->height >> 1); + halfHeight = interp.z + (interp.height >> 1); floorz = P_GetFloorZ(thing, interp.subsector->sector, interp.x, interp.y, NULL); ceilingz = P_GetCeilingZ(thing, interp.subsector->sector, interp.x, interp.y, NULL); @@ -1248,8 +1258,8 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) } } - if (isflipped ? (ceilingz < groundz - (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2))) - : (floorz > groundz + (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2)))) + if (isflipped ? (ceilingz < groundz - (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), interp.radius*3/2))) + : (floorz > groundz + (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), interp.radius*3/2)))) { groundz = isflipped ? ceilingz : floorz; groundslope = NULL; @@ -1292,9 +1302,9 @@ static void R_SkewShadowSprite( //CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope); if (viewz < groundz) - *shadowyscale += FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope); + *shadowyscale += FixedMul(FixedMul(interp.radius*2 / spriteheight, scalemul), zslope); else - *shadowyscale -= FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope); + *shadowyscale -= FixedMul(FixedMul(interp.radius*2 / spriteheight, scalemul), zslope); *shadowyscale = abs((*shadowyscale)); *shadowskew = xslope; @@ -1345,20 +1355,18 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, return; } - floordiff = abs((isflipped ? thing->height : 0) + interp.z - groundz); + floordiff = abs((isflipped ? interp.height : 0) + interp.z - groundz); trans = floordiff / (100*FRACUNIT) + 3; if (trans >= 9) return; scalemul = FixedMul(FRACUNIT - floordiff/640, scale); - if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs - scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale)); patch = W_CachePatchName("DSHADOW", PU_SPRITE); xscale = FixedDiv(projection, tz); yscale = FixedDiv(projectiony, tz); - shadowxscale = FixedMul(thing->radius*2, scalemul); - shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz)); + shadowxscale = FixedMul(interp.radius*2, scalemul); + shadowyscale = FixedMul(FixedMul(interp.radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz)); shadowyscale = min(shadowyscale, shadowxscale) / patch->height; shadowxscale /= patch->width; shadowskew = 0; @@ -1464,8 +1472,8 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) // 0--2 // start in the (0) corner - gx = interp.x - thing->radius - viewx; - gy = interp.y - thing->radius - viewy; + gx = interp.x - interp.radius - viewx; + gy = interp.y - interp.radius - viewy; tz = FixedMul(gx, viewcos) + FixedMul(gy, viewsin); @@ -1487,14 +1495,14 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) box = R_NewVisSprite(); box->mobj = thing; box->mobjflags = thing->flags; - box->thingheight = thing->height; + box->thingheight = interp.height; box->cut = SC_BBOX; box->gx = tx; box->gy = tz; - box->scale = 2 * FixedMul(thing->radius, viewsin); - box->xscale = 2 * FixedMul(thing->radius, viewcos); + box->scale = 2 * FixedMul(interp.radius, viewsin); + box->xscale = 2 * FixedMul(interp.radius, viewcos); box->pz = interp.z; box->pzt = box->pz + box->thingheight; @@ -1543,6 +1551,7 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t tr_x, tr_y; fixed_t tx, tz; fixed_t xscale, yscale; //added : 02-02-98 : aaargll..if I were a math-guy!!! + fixed_t radius, height; // For drop shadows fixed_t sortscale, sortsplat = 0; fixed_t linkscale = 0; fixed_t sort_x = 0, sort_y = 0, sort_z; @@ -1618,6 +1627,8 @@ static void R_ProjectSprite(mobj_t *thing) } this_scale = interp.scale; + radius = interp.radius; // For drop shadows + height = interp.height; // Ditto // transform the origin point tr_x = interp.x - viewx; @@ -1738,9 +1749,6 @@ static void R_ProjectSprite(mobj_t *thing) I_Assert(lump < max_spritelumps); - if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES) - this_scale = FixedMul(this_scale, ((skin_t *)thing->skin)->highresscale); - spr_width = spritecachedinfo[lump].width; spr_height = spritecachedinfo[lump].height; spr_offset = spritecachedinfo[lump].offset; @@ -1790,6 +1798,14 @@ static void R_ProjectSprite(mobj_t *thing) // calculate edges of the shape spritexscale = interp.spritexscale; spriteyscale = interp.spriteyscale; + + if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES) + { + fixed_t highresscale = ((skin_t *)thing->skin)->highresscale; + spritexscale = FixedMul(spritexscale, highresscale); + spriteyscale = FixedMul(spriteyscale, highresscale); + } + if (spritexscale < 1 || spriteyscale < 1) return; @@ -1957,6 +1973,8 @@ static void R_ProjectSprite(mobj_t *thing) { R_InterpolateMobjState(thing, FRACUNIT, &tracer_interp); } + radius = tracer_interp.radius; // For drop shadows + height = tracer_interp.height; // Ditto tr_x = (tracer_interp.x + sort_x) - viewx; tr_y = (tracer_interp.y + sort_y) - viewy; @@ -2048,7 +2066,7 @@ static void R_ProjectSprite(mobj_t *thing) if (abs(groundz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes - floordiff = abs((isflipped ? caster->height : 0) + casterinterp.z - groundz); + floordiff = abs((isflipped ? casterinterp.height : 0) + casterinterp.z - groundz); trans += ((floordiff / (100*FRACUNIT)) + 3); shadowscale = FixedMul(FRACUNIT - floordiff/640, casterinterp.scale); } @@ -2063,8 +2081,8 @@ static void R_ProjectSprite(mobj_t *thing) if (shadowdraw) { - spritexscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spritexscale)); - spriteyscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spriteyscale)); + spritexscale = FixedMul(radius * 2, FixedMul(shadowscale, spritexscale)); + spriteyscale = FixedMul(radius * 2, FixedMul(shadowscale, spriteyscale)); spriteyscale = FixedMul(spriteyscale, FixedDiv(abs(groundz - viewz), tz)); spriteyscale = min(spriteyscale, spritexscale) / patch->height; spritexscale /= patch->width; @@ -2079,7 +2097,7 @@ static void R_ProjectSprite(mobj_t *thing) { R_SkewShadowSprite(thing, thing->standingslope, groundz, patch->height, shadowscale, &spriteyscale, &sheartan); - gzt = (isflipped ? (interp.z + thing->height) : interp.z) + patch->height * spriteyscale / 2; + gzt = (isflipped ? (interp.z + height) : interp.z) + patch->height * spriteyscale / 2; gz = gzt - patch->height * spriteyscale; cut |= SC_SHEAR; @@ -2094,7 +2112,7 @@ static void R_ProjectSprite(mobj_t *thing) // When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned. // sprite height - sprite topoffset is the proper inverse of the vertical offset, of course. // remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes! - gz = interp.z + oldthing->height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale)); + gz = interp.z + interp.height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale)); gzt = gz + FixedMul(spr_height, FixedMul(spriteyscale, this_scale)); } else @@ -2161,7 +2179,7 @@ static void R_ProjectSprite(mobj_t *thing) vis->gy = interp.y; vis->gz = gz; vis->gzt = gzt; - vis->thingheight = thing->height; + vis->thingheight = height; vis->pz = interp.z; vis->pzt = vis->pz + vis->thingheight; vis->texturemid = FixedDiv(gzt - viewz, spriteyscale); @@ -2601,6 +2619,14 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e // bundle linkdraw for (ds = unsorted.prev; ds != &unsorted; ds = ds->prev) { + // Remove this sprite if it was determined to not be visible + if (ds->cut & SC_NOTVISIBLE) + { + ds->next->prev = ds->prev; + ds->prev->next = ds->next; + continue; + } + if (!(ds->cut & SC_LINKDRAW)) continue; @@ -2627,21 +2653,27 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e continue; // don't connect if the tracer's top is cut off, but lower than the link's top - if ((dsfirst->cut & SC_TOP) - && dsfirst->szt > ds->szt) + if ((dsfirst->cut & SC_TOP) && dsfirst->szt > ds->szt) continue; // don't connect if the tracer's bottom is cut off, but higher than the link's bottom - if ((dsfirst->cut & SC_BOTTOM) - && dsfirst->sz < ds->sz) + if ((dsfirst->cut & SC_BOTTOM) && dsfirst->sz < ds->sz) continue; + // If the object isn't visible, then the bounding box isn't either + if (ds->cut & SC_BBOX && dsfirst->cut & SC_NOTVISIBLE) + ds->cut |= SC_NOTVISIBLE; + break; } // remove from chain ds->next->prev = ds->prev; ds->prev->next = ds->next; + + if (ds->cut & SC_NOTVISIBLE) + continue; + linkedvissprites++; if (dsfirst != &unsorted) @@ -2693,12 +2725,15 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e best = ds; } } - best->next->prev = best->prev; - best->prev->next = best->next; - best->next = vsprsortedhead; - best->prev = vsprsortedhead->prev; - vsprsortedhead->prev->next = best; - vsprsortedhead->prev = best; + if (best) + { + best->next->prev = best->prev; + best->prev->next = best->next; + best->next = vsprsortedhead; + best->prev = vsprsortedhead->prev; + vsprsortedhead->prev->next = best; + vsprsortedhead->prev = best; + } } } @@ -3129,6 +3164,44 @@ static void R_HeightSecClip(vissprite_t *spr, INT32 x1, INT32 x2) } } +static boolean R_CheckSpriteVisible(vissprite_t *spr, INT32 x1, INT32 x2) +{ + INT16 sz = spr->sz; + INT16 szt = spr->szt; + + fixed_t texturemid, yscale, scalestep = spr->scalestep; + INT32 height; + + if (scalestep) + { + height = spr->patch->height; + yscale = spr->scale; + scalestep = FixedMul(scalestep, spr->spriteyscale); + + if (spr->thingscale != FRACUNIT) + texturemid = FixedDiv(spr->texturemid, max(spr->thingscale, 1)); + else + texturemid = spr->texturemid; + } + + for (INT32 x = x1; x <= x2; x++) + { + if (scalestep) + { + fixed_t top = centeryfrac - FixedMul(texturemid, yscale); + fixed_t bottom = top + (height * yscale); + szt = (INT16)(top >> FRACBITS); + sz = (INT16)(bottom >> FRACBITS); + yscale += scalestep; + } + + if (spr->cliptop[x] < spr->clipbot[x] && sz > spr->cliptop[x] && szt < spr->clipbot[x]) + return true; + } + + return false; +} + // R_ClipVisSprite // Clips vissprites without drawing, so that portals can work. -Red static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* portal) @@ -3272,8 +3345,7 @@ static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* port spr->clipbot[x] = (INT16)viewheight; if (spr->cliptop[x] == -2) - //Fab : 26-04-98: was -1, now clips against console bottom - spr->cliptop[x] = (INT16)con_clipviewtop; + spr->cliptop[x] = -1; } if (portal) @@ -3298,6 +3370,14 @@ static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* port spr->cliptop[x] = -1; } } + + // Check if it'll be visible + // Not done for floorsprites. + if (cv_spriteclip.value && (spr->cut & SC_SPLAT) == 0) + { + if (!R_CheckSpriteVisible(spr, x1, x2)) + spr->cut |= SC_NOTVISIBLE; + } } void R_ClipSprites(drawseg_t* dsstart, portal_t* portal) @@ -3366,8 +3446,19 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal) { vissprite_t *spr = R_GetVisSprite(clippedvissprites); - if (spr->cut & SC_BBOX) + if (cv_spriteclip.value + && (spr->szt > vid.height || spr->sz < 0) + && !((spr->cut & SC_SPLAT) || spr->scalestep)) + { + spr->cut |= SC_NOTVISIBLE; continue; + } + + if (spr->cut & SC_BBOX) + { + numvisiblesprites++; + continue; + } INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1; INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2; @@ -3389,6 +3480,9 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal) } R_ClipVisSprite(spr, x1, x2, portal); + + if ((spr->cut & SC_NOTVISIBLE) == 0) + numvisiblesprites++; } } diff --git a/src/r_things.h b/src/r_things.h index e11005363..318234886 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -123,21 +123,22 @@ typedef enum SC_NONE = 0, SC_TOP = 1, SC_BOTTOM = 1<<1, + SC_NOTVISIBLE = 1<<2, // other flags - SC_PRECIP = 1<<2, - SC_LINKDRAW = 1<<3, - SC_FULLBRIGHT = 1<<4, - SC_SEMIBRIGHT = 1<<5, - SC_FULLDARK = 1<<6, - SC_VFLIP = 1<<7, - SC_ISSCALED = 1<<8, - SC_ISROTATED = 1<<9, - SC_SHADOW = 1<<10, - SC_SHEAR = 1<<11, - SC_SPLAT = 1<<12, - SC_BBOX = 1<<13, + SC_PRECIP = 1<<3, + SC_LINKDRAW = 1<<4, + SC_FULLBRIGHT = 1<<5, + SC_SEMIBRIGHT = 1<<6, + SC_FULLDARK = 1<<7, + SC_VFLIP = 1<<8, + SC_ISSCALED = 1<<9, + SC_ISROTATED = 1<<10, + SC_SHADOW = 1<<11, + SC_SHEAR = 1<<12, + SC_SPLAT = 1<<13, + SC_BBOX = 1<<14, // masks - SC_CUTMASK = SC_TOP|SC_BOTTOM, + SC_CUTMASK = SC_TOP|SC_BOTTOM|SC_NOTVISIBLE, SC_FLAGMASK = ~SC_CUTMASK } spritecut_e; @@ -219,7 +220,7 @@ typedef struct vissprite_s INT32 dispoffset; // copy of mobj->dispoffset, affects ordering but not drawing } vissprite_t; -extern UINT32 visspritecount; +extern UINT32 visspritecount, numvisiblesprites; void R_ClipSprites(drawseg_t* dsstart, portal_t* portal); diff --git a/src/screen.c b/src/screen.c index 417e793bd..3f249f8d3 100644 --- a/src/screen.c +++ b/src/screen.c @@ -27,7 +27,7 @@ #include "hu_stuff.h" #include "z_zone.h" #include "d_main.h" -#include "d_clisrv.h" +#include "netcode/d_clisrv.h" #include "f_finale.h" #include "y_inter.h" // usebuffer #include "i_sound.h" // closed captions diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj index 0b95cd0b2..9b51cfb80 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -262,7 +262,6 @@ - @@ -297,6 +296,21 @@ + + + + + + + + + + + + + + + @@ -329,6 +343,7 @@ + @@ -400,11 +415,7 @@ - - - - @@ -427,10 +438,6 @@ - - true - - @@ -451,8 +458,6 @@ - - @@ -466,6 +471,22 @@ + + + + + + + + + + true + + + + + + @@ -510,6 +531,7 @@ + diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters index eb37ce9da..96501b216 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj.filters +++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters @@ -156,24 +156,12 @@ D_Doom - - D_Doom - D_Doom D_Doom - - D_Doom - - - D_Doom - - - D_Doom - D_Doom @@ -279,9 +267,6 @@ I_Interface - - I_Interface - I_Interface @@ -306,12 +291,6 @@ I_Interface - - I_Interface - - - I_Interface - LUA @@ -372,6 +351,54 @@ M_Misc + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + I_Interface + + + I_Interface + + + I_Interface + + + I_Interface + + + I_Interface + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + O_Other @@ -465,6 +492,9 @@ R_Rend + + M_Misc + S_Sounds @@ -639,21 +669,9 @@ D_Doom - - D_Doom - D_Doom - - D_Doom - - - D_Doom - - - D_Doom - D_Doom @@ -732,18 +750,9 @@ I_Interface - - I_Interface - I_Interface - - I_Interface - - - I_Interface - LUA @@ -831,6 +840,48 @@ M_Misc + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + D_Doom + + + I_Interface + + + I_Interface + + + I_Interface + + + I_Interface + + + D_Doom + + + D_Doom + + + D_Doom + M_Misc @@ -954,6 +1005,9 @@ R_Rend + + M_Misc + S_Sounds diff --git a/src/sdl/i_net.c b/src/sdl/i_net.c index ee4a34c13..515a85568 100644 --- a/src/sdl/i_net.c +++ b/src/sdl/i_net.c @@ -21,16 +21,16 @@ #include "../i_system.h" #include "../d_event.h" -#include "../d_net.h" +#include "../netcode/d_net.h" #include "../m_argv.h" #include "../doomstat.h" -#include "../i_net.h" +#include "../netcode/i_net.h" #include "../z_zone.h" -#include "../i_tcp.h" +#include "../netcode/i_tcp.h" #ifdef HAVE_SDL diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 075199a19..b05f40ee3 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -90,7 +90,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #include #endif #include -#include +#include #endif #endif @@ -187,7 +187,8 @@ static char returnWadPath[256]; #include "../i_system.h" #include "../i_threads.h" #include "../screen.h" //vid.WndParent -#include "../d_net.h" +#include "../netcode/d_net.h" +#include "../netcode/commands.h" #include "../g_game.h" #include "../filesrch.h" #include "endtxt.h" @@ -208,7 +209,7 @@ static char returnWadPath[256]; #if !defined(NOMUMBLE) && defined(HAVE_MUMBLE) // Mumble context string -#include "../d_clisrv.h" +#include "../netcode/d_clisrv.h" #include "../byteptr.h" #endif @@ -259,10 +260,10 @@ UINT8 keyboard_started = false; #ifdef UNIXBACKTRACE #define STDERR_WRITE(string) if (fd != -1) I_OutputMsg("%s", string) -#define CRASHLOG_WRITE(string) if (fd != -1) write(fd, string, strlen(string)) +#define CRASHLOG_WRITE(string) if (fd != -1) junk = write(fd, string, strlen(string)) #define CRASHLOG_STDERR_WRITE(string) \ if (fd != -1)\ - write(fd, string, strlen(string));\ + junk = write(fd, string, strlen(string));\ I_OutputMsg("%s", string) static void write_backtrace(INT32 signal) @@ -271,6 +272,7 @@ static void write_backtrace(INT32 signal) size_t size; time_t rawtime; struct tm timeinfo; + ssize_t junk; enum { BT_SIZE = 1024, STR_SIZE = 32 }; void *array[BT_SIZE]; @@ -314,7 +316,7 @@ static void write_backtrace(INT32 signal) backtrace_symbols_fd(array, size, STDERR_FILENO); CRASHLOG_WRITE("\n"); // Write another newline to the log so it looks nice :) - + (void)junk; close(fd); } #undef STDERR_WRITE @@ -325,8 +327,10 @@ static void write_backtrace(INT32 signal) static void I_ReportSignal(int num, int coredumped) { //static char msg[] = "oh no! back to reality!\r\n"; - const char *sigmsg, *sigttl; + const char *sigmsg, *signame; char ttl[128]; + char sigttl[512] = "Process killed by signal: "; + const char *reportmsg = "\n\nTo help us figure out the cause, you can visit our official Discord server\nwhere you will find more instructions on how to submit a crash report.\n\nSorry for the inconvenience!"; switch (num) { @@ -335,16 +339,16 @@ static void I_ReportSignal(int num, int coredumped) // sigmsg = "SRB2 was interrupted prematurely by the user."; // break; case SIGILL: - sigmsg = "SRB2 has attempted to execute an illegal instruction and needs to close. %s"; - sigttl = "SIGILL"; // illegal instruction - invalid function image + sigmsg = "SRB2 has attempted to execute an illegal instruction and needs to close."; + signame = "SIGILL"; // illegal instruction - invalid function image break; case SIGFPE: - sigmsg = "SRB2 has encountered a mathematical exception and needs to close. %s"; - sigttl = "SIGFPE"; // mathematical exception + sigmsg = "SRB2 has encountered a mathematical exception and needs to close."; + signame = "SIGFPE"; // mathematical exception break; case SIGSEGV: - sigmsg = "SRB2 has attempted to access a memory location that it shouldn't and needs to close. %s"; - sigttl = "SIGSEGV"; // segment violation + sigmsg = "SRB2 has attempted to access a memory location that it shouldn't and needs to close."; + signame = "SIGSEGV"; // segment violation break; // case SIGTERM: // sigmsg = "SRB2 was terminated by a kill signal."; @@ -355,34 +359,31 @@ static void I_ReportSignal(int num, int coredumped) // sigttl = "SIGBREAK" // Ctrl-Break sequence // break; case SIGABRT: - sigmsg = "SRB2 was terminated by an abort signal. %s"; - sigttl = "SIGABRT"; // abnormal termination triggered by abort call + sigmsg = "SRB2 was terminated by an abort signal."; + signame = "SIGABRT"; // abnormal termination triggered by abort call break; default: - sigmsg = "SRB2 was terminated by an unknown signal. %s"; + sigmsg = "SRB2 was terminated by an unknown signal."; sprintf(ttl, "number %d", num); if (coredumped) - sigttl = 0; + signame = 0; else - sigttl = ttl; + signame = ttl; } if (coredumped) { - if (sigttl) - sprintf(ttl, "%s (core dumped)", sigttl); + if (signame) + sprintf(ttl, "%s (core dumped)", signame); else strcat(ttl, " (core dumped)"); - sigttl = ttl; + signame = ttl; } - sprintf(ttl, "Process killed by signal: %s", sigttl); - - sigttl = ttl; - - I_OutputMsg("\n%s\n\n", sigttl); + strcat(sigttl, signame); + I_OutputMsg("%s\n", sigttl); if (M_CheckParm("-dedicated")) return; @@ -396,8 +397,7 @@ static void I_ReportSignal(int num, int coredumped) SDL_MESSAGEBOX_ERROR, /* .flags */ NULL, /* .window */ sigttl, /* .title */ - va(sigmsg, - "\n\nTo help us figure out the cause, you can visit our official Discord server\nwhere you will find more instructions on how to submit a crash report.\n\nSorry for the inconvenience!"), /* .message */ + va("%s %s", sigmsg, reportmsg), /* .message */ SDL_arraysize(buttons), /* .numbuttons */ buttons, /* .buttons */ NULL /* .colorScheme */ @@ -407,8 +407,10 @@ static void I_ReportSignal(int num, int coredumped) SDL_ShowMessageBox(&messageboxdata, &buttonid); +#if SDL_VERSION_ATLEAST(2,0,14) if (buttonid == 1) SDL_OpenURL("https://www.srb2.org/discord"); +#endif } #ifndef NEWSIGNALHANDLER @@ -2264,7 +2266,7 @@ void I_Sleep(UINT32 ms) } #ifdef NEWSIGNALHANDLER -static void newsignalhandler_Warn(const char *pr) +ATTRNORETURN static FUNCNORETURN void newsignalhandler_Warn(const char *pr) { char text[128]; @@ -2386,9 +2388,7 @@ void I_Quit(void) SDLforceUngrabMouse(); quiting = SDL_FALSE; M_SaveConfig(NULL); //save game config, cvars.. -#ifndef NONET D_SaveBan(); // save the ban list -#endif G_SaveGameData(clientGamedata); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -2503,9 +2503,7 @@ void I_Error(const char *error, ...) // --- M_SaveConfig(NULL); // save game config, cvars.. -#ifndef NONET D_SaveBan(); // save the ban list -#endif G_SaveGameData(clientGamedata); // Tails 12-08-2002 // Shutdown. Here might be other errors. @@ -3035,40 +3033,17 @@ static long get_entry(const char* name, const char* buf) size_t I_GetFreeMem(size_t *total) { #ifdef FREEBSD - struct vmmeter sum; - kvm_t *kd; - struct nlist namelist[] = - { -#define X_SUM 0 - {"_cnt"}, - {NULL} - }; - if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL) - { - if (total) - *total = 0L; - return 0; - } - if (kvm_nlist(kd, namelist) != 0) - { - kvm_close (kd); - if (total) - *total = 0L; - return 0; - } - if (kvm_read(kd, namelist[X_SUM].n_value, &sum, - sizeof (sum)) != sizeof (sum)) - { - kvm_close(kd); - if (total) - *total = 0L; - return 0; - } - kvm_close(kd); + u_int v_free_count, v_page_size, v_page_count; + size_t size = sizeof(v_free_count); + sysctlbyname("vm.stats.vm.v_free_count", &v_free_count, &size, NULL, 0); + size = sizeof(v_page_size); + sysctlbyname("vm.stats.vm.v_page_size", &v_page_size, &size, NULL, 0); + size = sizeof(v_page_count); + sysctlbyname("vm.stats.vm.v_page_count", &v_page_count, &size, NULL, 0); if (total) - *total = sum.v_page_count * sum.v_page_size; - return sum.v_free_count * sum.v_page_size; + *total = v_page_count * v_page_size; + return v_free_count * v_page_size; #elif defined (SOLARIS) /* Just guess */ if (total) diff --git a/src/sdl/i_ttf.c b/src/sdl/i_ttf.c index f2cd497ee..1f838e9b4 100644 --- a/src/sdl/i_ttf.c +++ b/src/sdl/i_ttf.c @@ -21,7 +21,7 @@ #include "SDL_ttf.h" #include "../doomdef.h" #include "../doomstat.h" -#include "../d_netfil.h" +#include "../netcode/d_netfil.h" #include "../filesrch.h" #include "i_ttf.h" diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index b2215f634..590d7d142 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -1593,7 +1593,6 @@ boolean VID_CheckRenderer(void) else if (vid.glstate == VID_GL_LIBRARY_ERROR) rendererchanged = false; } - else #endif if (!contextcreated) diff --git a/src/snake.c b/src/snake.c new file mode 100644 index 000000000..2349d5fdb --- /dev/null +++ b/src/snake.c @@ -0,0 +1,593 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023-2023 by Louis-Antoine de Moulins de Rochefort. +// +// 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 snake.c +/// \brief Snake minigame for the download screen. + +#include "snake.h" +#include "g_input.h" +#include "g_game.h" +#include "i_joy.h" +#include "m_random.h" +#include "s_sound.h" +#include "screen.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" + +#define SPEED 5 + +#define NUM_BLOCKS_X 20 +#define NUM_BLOCKS_Y 10 +#define BLOCK_SIZE 12 +#define BORDER_SIZE 12 + +#define MAP_WIDTH (NUM_BLOCKS_X * BLOCK_SIZE) +#define MAP_HEIGHT (NUM_BLOCKS_Y * BLOCK_SIZE) + +#define LEFT_X ((BASEVIDWIDTH - MAP_WIDTH) / 2 - BORDER_SIZE) +#define RIGHT_X (LEFT_X + MAP_WIDTH + BORDER_SIZE * 2 - 1) +#define BOTTOM_Y (BASEVIDHEIGHT - 48) +#define TOP_Y (BOTTOM_Y - MAP_HEIGHT - BORDER_SIZE * 2 + 1) + +enum bonustype_s { + BONUS_NONE = 0, + BONUS_SLOW, + BONUS_FAST, + BONUS_GHOST, + BONUS_NUKE, + BONUS_SCISSORS, + BONUS_REVERSE, + BONUS_EGGMAN, + NUM_BONUSES, +}; + +typedef struct snake_s +{ + boolean paused; + boolean pausepressed; + tic_t time; + tic_t nextupdate; + boolean gameover; + UINT8 background; + + UINT16 snakelength; + enum bonustype_s snakebonus; + tic_t snakebonustime; + UINT8 snakex[NUM_BLOCKS_X * NUM_BLOCKS_Y]; + UINT8 snakey[NUM_BLOCKS_X * NUM_BLOCKS_Y]; + UINT8 snakedir[NUM_BLOCKS_X * NUM_BLOCKS_Y]; + + UINT8 applex; + UINT8 appley; + + enum bonustype_s bonustype; + UINT8 bonusx; + UINT8 bonusy; + + event_t *joyevents[MAXEVENTS]; + UINT16 joyeventcount; +} snake_t; + +static const char *bonuspatches[] = { + NULL, + "DL_SLOW", + "TVSSC0", + "TVIVC0", + "TVARC0", + "DL_SCISSORS", + "TVRCC0", + "TVEGC0", +}; + +static const char *backgrounds[] = { + "RVPUMICF", + "FRSTRCKF", + "TAR", + "MMFLRB4", + "RVDARKF1", + "RVZWALF1", + "RVZWALF4", + "RVZWALF5", + "RVZGRS02", + "RVZGRS04", +}; + +static void Initialise(snake_t *snake) +{ + snake->paused = false; + snake->pausepressed = false; + snake->time = 0; + snake->nextupdate = SPEED; + snake->gameover = false; + snake->background = M_RandomKey(sizeof(backgrounds) / sizeof(*backgrounds)); + + snake->snakelength = 1; + snake->snakebonus = BONUS_NONE; + snake->snakex[0] = M_RandomKey(NUM_BLOCKS_X); + snake->snakey[0] = M_RandomKey(NUM_BLOCKS_Y); + snake->snakedir[0] = 0; + snake->snakedir[1] = 0; + + snake->applex = M_RandomKey(NUM_BLOCKS_X); + snake->appley = M_RandomKey(NUM_BLOCKS_Y); + + snake->bonustype = BONUS_NONE; + + snake->joyeventcount = 0; +} + +static UINT8 GetOppositeDir(UINT8 dir) +{ + if (dir == 1 || dir == 3) + return dir + 1; + else if (dir == 2 || dir == 4) + return dir - 1; + else + return 12 + 5 - dir; +} + +static void FindFreeSlot(snake_t *snake, UINT8 *freex, UINT8 *freey, UINT8 headx, UINT8 heady) +{ + UINT8 x, y; + UINT16 i; + + do + { + x = M_RandomKey(NUM_BLOCKS_X); + y = M_RandomKey(NUM_BLOCKS_Y); + + for (i = 0; i < snake->snakelength; i++) + if (x == snake->snakex[i] && y == snake->snakey[i]) + break; + } while (i < snake->snakelength || (x == headx && y == heady) + || (x == snake->applex && y == snake->appley) + || (snake->bonustype != BONUS_NONE && x == snake->bonusx && y == snake->bonusy)); + + *freex = x; + *freey = y; +} + +void Snake_Allocate(void **opaque) +{ + if (*opaque) + Snake_Free(opaque); + *opaque = malloc(sizeof(snake_t)); + Initialise(*opaque); +} + +void Snake_Update(void *opaque) +{ + UINT8 x, y; + UINT8 oldx, oldy; + UINT16 i; + UINT16 joystate = 0; + static INT32 pjoyx = 0, pjoyy = 0; + + snake_t *snake = opaque; + + // Handle retry + if (snake->gameover && (PLAYER1INPUTDOWN(GC_JUMP) || gamekeydown[KEY_ENTER])) + { + Initialise(snake); + snake->pausepressed = true; // Avoid accidental pause on respawn + } + + // Handle pause + if (PLAYER1INPUTDOWN(GC_PAUSE) || gamekeydown[KEY_ENTER]) + { + if (!snake->pausepressed) + snake->paused = !snake->paused; + snake->pausepressed = true; + } + else + snake->pausepressed = false; + + if (snake->paused) + return; + + snake->time++; + + x = snake->snakex[0]; + y = snake->snakey[0]; + oldx = snake->snakex[1]; + oldy = snake->snakey[1]; + + // Process the input events in here dear lord + for (UINT16 j = 0; j < snake->joyeventcount; j++) + { + event_t *ev = snake->joyevents[j]; + const INT32 jdeadzone = (JOYAXISRANGE * cv_digitaldeadzone.value) / FRACUNIT; + if (ev->y != INT32_MAX) + { + if (Joystick.bGamepadStyle || abs(ev->y) > jdeadzone) + { + if (ev->y < 0 && pjoyy >= 0) + joystate = 1; + else if (ev->y > 0 && pjoyy <= 0) + joystate = 2; + pjoyy = ev->y; + } + else + pjoyy = 0; + } + + if (ev->x != INT32_MAX) + { + if (Joystick.bGamepadStyle || abs(ev->x) > jdeadzone) + { + if (ev->x < 0 && pjoyx >= 0) + joystate = 3; + else if (ev->x > 0 && pjoyx <= 0) + joystate = 4; + pjoyx = ev->x; + } + else + pjoyx = 0; + } + } + snake->joyeventcount = 0; + + // Update direction + if (PLAYER1INPUTDOWN(GC_STRAFELEFT) || gamekeydown[KEY_LEFTARROW] || joystate == 3) + { + if (snake->snakelength < 2 || x <= oldx) + snake->snakedir[0] = 1; + } + else if (PLAYER1INPUTDOWN(GC_STRAFERIGHT) || gamekeydown[KEY_RIGHTARROW] || joystate == 4) + { + if (snake->snakelength < 2 || x >= oldx) + snake->snakedir[0] = 2; + } + else if (PLAYER1INPUTDOWN(GC_FORWARD) || gamekeydown[KEY_UPARROW] || joystate == 1) + { + if (snake->snakelength < 2 || y <= oldy) + snake->snakedir[0] = 3; + } + else if (PLAYER1INPUTDOWN(GC_BACKWARD) || gamekeydown[KEY_DOWNARROW] || joystate == 2) + { + if (snake->snakelength < 2 || y >= oldy) + snake->snakedir[0] = 4; + } + + if (snake->snakebonustime) + { + snake->snakebonustime--; + if (!snake->snakebonustime) + snake->snakebonus = BONUS_NONE; + } + + snake->nextupdate--; + if (snake->nextupdate) + return; + if (snake->snakebonus == BONUS_SLOW) + snake->nextupdate = SPEED * 2; + else if (snake->snakebonus == BONUS_FAST) + snake->nextupdate = SPEED * 2 / 3; + else + snake->nextupdate = SPEED; + + if (snake->gameover) + return; + + // Find new position + switch (snake->snakedir[0]) + { + case 1: + if (x > 0) + x--; + else + snake->gameover = true; + break; + case 2: + if (x < NUM_BLOCKS_X - 1) + x++; + else + snake->gameover = true; + break; + case 3: + if (y > 0) + y--; + else + snake->gameover = true; + break; + case 4: + if (y < NUM_BLOCKS_Y - 1) + y++; + else + snake->gameover = true; + break; + } + + // Check collision with snake + if (snake->snakebonus != BONUS_GHOST) + for (i = 1; i < snake->snakelength - 1; i++) + if (x == snake->snakex[i] && y == snake->snakey[i]) + { + if (snake->snakebonus == BONUS_SCISSORS) + { + snake->snakebonus = BONUS_NONE; + snake->snakelength = i; + S_StartSound(NULL, sfx_adderr); + } + else + snake->gameover = true; + } + + if (snake->gameover) + { + S_StartSound(NULL, sfx_lose); + return; + } + + // Check collision with apple + if (x == snake->applex && y == snake->appley) + { + if (snake->snakelength + 3 < NUM_BLOCKS_X * NUM_BLOCKS_Y) + { + snake->snakelength++; + snake->snakex [snake->snakelength - 1] = snake->snakex [snake->snakelength - 2]; + snake->snakey [snake->snakelength - 1] = snake->snakey [snake->snakelength - 2]; + snake->snakedir[snake->snakelength - 1] = snake->snakedir[snake->snakelength - 2]; + } + + // Spawn new apple + FindFreeSlot(snake, &snake->applex, &snake->appley, x, y); + + // Spawn new bonus + if (!(snake->snakelength % 5)) + { + do + { + snake->bonustype = M_RandomKey(NUM_BONUSES - 1) + 1; + } while (snake->snakelength > NUM_BLOCKS_X * NUM_BLOCKS_Y * 3 / 4 + && (snake->bonustype == BONUS_EGGMAN || snake->bonustype == BONUS_FAST || snake->bonustype == BONUS_REVERSE)); + + FindFreeSlot(snake, &snake->bonusx, &snake->bonusy, x, y); + } + + S_StartSound(NULL, sfx_s3k6b); + } + + if (snake->snakelength > 1 && snake->snakedir[0]) + { + UINT8 dir = snake->snakedir[0]; + + oldx = snake->snakex[1]; + oldy = snake->snakey[1]; + + // Move + for (i = snake->snakelength - 1; i > 0; i--) + { + snake->snakex[i] = snake->snakex[i - 1]; + snake->snakey[i] = snake->snakey[i - 1]; + snake->snakedir[i] = snake->snakedir[i - 1]; + } + + // Handle corners + if (x < oldx && dir == 3) + dir = 5; + else if (x > oldx && dir == 3) + dir = 6; + else if (x < oldx && dir == 4) + dir = 7; + else if (x > oldx && dir == 4) + dir = 8; + else if (y < oldy && dir == 1) + dir = 9; + else if (y < oldy && dir == 2) + dir = 10; + else if (y > oldy && dir == 1) + dir = 11; + else if (y > oldy && dir == 2) + dir = 12; + snake->snakedir[1] = dir; + } + + snake->snakex[0] = x; + snake->snakey[0] = y; + + // Check collision with bonus + if (snake->bonustype != BONUS_NONE && x == snake->bonusx && y == snake->bonusy) + { + S_StartSound(NULL, sfx_ncchip); + + switch (snake->bonustype) + { + case BONUS_SLOW: + snake->snakebonus = BONUS_SLOW; + snake->snakebonustime = 20 * TICRATE; + break; + case BONUS_FAST: + snake->snakebonus = BONUS_FAST; + snake->snakebonustime = 20 * TICRATE; + break; + case BONUS_GHOST: + snake->snakebonus = BONUS_GHOST; + snake->snakebonustime = 10 * TICRATE; + break; + case BONUS_NUKE: + for (i = 0; i < snake->snakelength; i++) + { + snake->snakex [i] = snake->snakex [0]; + snake->snakey [i] = snake->snakey [0]; + snake->snakedir[i] = snake->snakedir[0]; + } + + S_StartSound(NULL, sfx_bkpoof); + break; + case BONUS_SCISSORS: + snake->snakebonus = BONUS_SCISSORS; + snake->snakebonustime = 60 * TICRATE; + break; + case BONUS_REVERSE: + for (i = 0; i < (snake->snakelength + 1) / 2; i++) + { + UINT16 i2 = snake->snakelength - 1 - i; + UINT8 tmpx = snake->snakex [i]; + UINT8 tmpy = snake->snakey [i]; + UINT8 tmpdir = snake->snakedir[i]; + + // Swap first segment with last segment + snake->snakex [i] = snake->snakex [i2]; + snake->snakey [i] = snake->snakey [i2]; + snake->snakedir[i] = GetOppositeDir(snake->snakedir[i2]); + snake->snakex [i2] = tmpx; + snake->snakey [i2] = tmpy; + snake->snakedir[i2] = GetOppositeDir(tmpdir); + } + + snake->snakedir[0] = 0; + + S_StartSound(NULL, sfx_gravch); + break; + default: + if (snake->snakebonus != BONUS_GHOST) + { + snake->gameover = true; + S_StartSound(NULL, sfx_lose); + } + } + + snake->bonustype = BONUS_NONE; + } +} + +void Snake_Draw(void *opaque) +{ + INT16 i; + + snake_t *snake = opaque; + + // Background + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + + V_DrawFlatFill( + LEFT_X + BORDER_SIZE, + TOP_Y + BORDER_SIZE, + MAP_WIDTH, + MAP_HEIGHT, + W_GetNumForName(backgrounds[snake->background]) + ); + + // Borders + V_DrawFill(LEFT_X, TOP_Y, BORDER_SIZE + MAP_WIDTH, BORDER_SIZE, 242); // Top + V_DrawFill(LEFT_X + BORDER_SIZE + MAP_WIDTH, TOP_Y, BORDER_SIZE, BORDER_SIZE + MAP_HEIGHT, 242); // Right + V_DrawFill(LEFT_X + BORDER_SIZE, TOP_Y + BORDER_SIZE + MAP_HEIGHT, BORDER_SIZE + MAP_WIDTH, BORDER_SIZE, 242); // Bottom + V_DrawFill(LEFT_X, TOP_Y + BORDER_SIZE, BORDER_SIZE, BORDER_SIZE + MAP_HEIGHT, 242); // Left + + // Apple + V_DrawFixedPatch( + (LEFT_X + BORDER_SIZE + snake->applex * BLOCK_SIZE + BLOCK_SIZE / 2) * FRACUNIT, + (TOP_Y + BORDER_SIZE + snake->appley * BLOCK_SIZE + BLOCK_SIZE / 2) * FRACUNIT, + FRACUNIT / 4, + 0, + W_CachePatchLongName("DL_APPLE", PU_HUDGFX), + NULL + ); + + // Bonus + if (snake->bonustype != BONUS_NONE) + V_DrawFixedPatch( + (LEFT_X + BORDER_SIZE + snake->bonusx * BLOCK_SIZE + BLOCK_SIZE / 2 ) * FRACUNIT, + (TOP_Y + BORDER_SIZE + snake->bonusy * BLOCK_SIZE + BLOCK_SIZE / 2 + 4) * FRACUNIT, + FRACUNIT / 2, + 0, + W_CachePatchLongName(bonuspatches[snake->bonustype], PU_HUDGFX), + NULL + ); + + // Snake + if (!snake->gameover || snake->time % 8 < 8 / 2) // Blink if game over + { + for (i = snake->snakelength - 1; i >= 0; i--) + { + const char *patchname; + UINT8 dir = snake->snakedir[i]; + + if (i == 0) // Head + { + switch (dir) + { + case 1: patchname = "DL_SNAKEHEAD_L"; break; + case 2: patchname = "DL_SNAKEHEAD_R"; break; + case 3: patchname = "DL_SNAKEHEAD_T"; break; + case 4: patchname = "DL_SNAKEHEAD_B"; break; + default: patchname = "DL_SNAKEHEAD_M"; + } + } + else // Body + { + switch (dir) + { + case 1: patchname = "DL_SNAKEBODY_L"; break; + case 2: patchname = "DL_SNAKEBODY_R"; break; + case 3: patchname = "DL_SNAKEBODY_T"; break; + case 4: patchname = "DL_SNAKEBODY_B"; break; + case 5: patchname = "DL_SNAKEBODY_LT"; break; + case 6: patchname = "DL_SNAKEBODY_RT"; break; + case 7: patchname = "DL_SNAKEBODY_LB"; break; + case 8: patchname = "DL_SNAKEBODY_RB"; break; + case 9: patchname = "DL_SNAKEBODY_TL"; break; + case 10: patchname = "DL_SNAKEBODY_TR"; break; + case 11: patchname = "DL_SNAKEBODY_BL"; break; + case 12: patchname = "DL_SNAKEBODY_BR"; break; + default: patchname = "DL_SNAKEBODY_B"; + } + } + + V_DrawFixedPatch( + (LEFT_X + BORDER_SIZE + snake->snakex[i] * BLOCK_SIZE + BLOCK_SIZE / 2) * FRACUNIT, + (TOP_Y + BORDER_SIZE + snake->snakey[i] * BLOCK_SIZE + BLOCK_SIZE / 2) * FRACUNIT, + i == 0 && dir == 0 ? FRACUNIT / 5 : FRACUNIT / 2, + snake->snakebonus == BONUS_GHOST ? V_TRANSLUCENT : 0, + W_CachePatchLongName(patchname, PU_HUDGFX), + NULL + ); + } + } + + // Length + V_DrawString(RIGHT_X + 4, TOP_Y, V_MONOSPACE, va("%u", snake->snakelength)); + + // Bonus + if (snake->snakebonus != BONUS_NONE + && (snake->snakebonustime >= 3 * TICRATE || snake->time % 4 < 4 / 2)) + V_DrawFixedPatch( + (RIGHT_X + 10) * FRACUNIT, + (TOP_Y + 24) * FRACUNIT, + FRACUNIT / 2, + 0, + W_CachePatchLongName(bonuspatches[snake->snakebonus], PU_HUDGFX), + NULL + ); +} + +void Snake_Free(void **opaque) +{ + if (*opaque) + { + free(*opaque); + *opaque = NULL; + } +} + +// I'm screaming the hack is clean - ashi +boolean Snake_JoyGrabber(void *opaque, event_t *ev) +{ + snake_t *snake = opaque; + + if (ev->type == ev_joystick && ev->key == 0) + { + snake->joyevents[snake->joyeventcount] = ev; + snake->joyeventcount++; + return true; + } + else + return false; +} diff --git a/src/snake.h b/src/snake.h new file mode 100644 index 000000000..6bca338e9 --- /dev/null +++ b/src/snake.h @@ -0,0 +1,23 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023-2023 by Louis-Antoine de Moulins de Rochefort. +// +// 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 snake.h +/// \brief Snake minigame for the download screen. + +#ifndef __SNAKE__ +#define __SNAKE__ + +#include "d_event.h" + +void Snake_Allocate(void **opaque); +void Snake_Update(void *opaque); +void Snake_Draw(void *opaque); +void Snake_Free(void **opaque); +boolean Snake_JoyGrabber(void *opaque, event_t *ev); + +#endif diff --git a/src/st_stuff.c b/src/st_stuff.c index c6e6befc6..de19c468f 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -174,7 +174,7 @@ static huddrawlist_h luahuddrawlist_titlecard; skincolornum_t linkColor[3][NUMLINKCOLORS] = { {SKINCOLOR_SHAMROCK, SKINCOLOR_AQUA, SKINCOLOR_SKY, SKINCOLOR_BLUE, SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA, SKINCOLOR_ROSY, SKINCOLOR_RED, SKINCOLOR_ORANGE, SKINCOLOR_GOLD, SKINCOLOR_YELLOW, SKINCOLOR_PERIDOT}, -{SKINCOLOR_EMERALD, SKINCOLOR_AQUAMARINE, SKINCOLOR_WAVE, SKINCOLOR_SAPPHIRE, SKINCOLOR_GALAXY, SKINCOLOR_CRYSTAL, +{SKINCOLOR_EMERALD, SKINCOLOR_OCEAN, SKINCOLOR_AQUAMARINE, SKINCOLOR_SAPPHIRE, SKINCOLOR_GALAXY, SKINCOLOR_SIBERITE, SKINCOLOR_TAFFY, SKINCOLOR_RUBY, SKINCOLOR_GARNET, SKINCOLOR_TOPAZ, SKINCOLOR_LEMON, SKINCOLOR_LIME}, {SKINCOLOR_ISLAND, SKINCOLOR_TURQUOISE, SKINCOLOR_DREAM, SKINCOLOR_DAYBREAK, SKINCOLOR_VAPOR, SKINCOLOR_FUCHSIA, SKINCOLOR_VIOLET, SKINCOLOR_EVENTIDE, SKINCOLOR_KETCHUP, SKINCOLOR_FOUNDATION, SKINCOLOR_HEADLIGHT, SKINCOLOR_CHARTREUSE}}; @@ -827,6 +827,8 @@ static void ST_drawLivesArea(void) V_DrawSmallScaledPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y, hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, livesback); + UINT16 facecolor = P_GetPlayerColor(stplyr); + // face if (stplyr->spectator) { @@ -856,10 +858,10 @@ static void ST_drawLivesArea(void) } } } - else if (stplyr->skincolor) + else if (facecolor) { // skincolor face - UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); + UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, facecolor, GTC_CACHE); V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y, hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, faceprefix[stplyr->skin], colormap); } @@ -1030,7 +1032,8 @@ static void ST_drawLivesArea(void) static void ST_drawInput(void) { - const INT32 accent = V_SNAPTOLEFT|V_SNAPTOBOTTOM|(stplyr->skincolor ? skincolors[stplyr->skincolor].ramp[4] : 0); + UINT16 color = P_GetPlayerColor(stplyr); + const INT32 accent = V_SNAPTOLEFT|V_SNAPTOBOTTOM|(color ? skincolors[color].ramp[4] : 0); INT32 col; UINT8 offs; @@ -2512,6 +2515,8 @@ num: static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset) { INT32 interval, i; + if (stplyr->mo == NULL) + return 0; // player just joined after spectating, can happen on custom gamemodes. UINT32 dist = ((UINT32)P_AproxDistance(P_AproxDistance(stplyr->mo->x - hunt->x, stplyr->mo->y - hunt->y), stplyr->mo->z - hunt->z))>>FRACBITS; if (dist < 128) diff --git a/src/string.c b/src/string.c index dd3080a97..2f16fa4c6 100644 --- a/src/string.c +++ b/src/string.c @@ -15,7 +15,7 @@ #include #include "doomdef.h" -#if !defined (__APPLE__) +#ifndef SRB2_HAVE_STRLCPY // Like the OpenBSD version, but it doesn't check for src not being a valid // C string. diff --git a/src/version.h b/src/version.h index 083c53134..3242cad67 100644 --- a/src/version.h +++ b/src/version.h @@ -1,4 +1,4 @@ -#define SRB2VERSION "2.2.11"/* this must be the first line, for cmake !! */ +#define SRB2VERSION "2.2.13"/* this must be the first line, for cmake !! */ // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/members/?key=ms_admin ). // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server. @@ -9,7 +9,7 @@ // it's only for detection of the version the player is using so the MS can alert them of an update. // Only set it higher, not lower, obviously. // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1". -#define MODVERSION 52 +#define MODVERSION 54 // Define this as a prerelease version suffix (pre#, RC#) //#define BETAVERSION "pre1" diff --git a/src/w_wad.c b/src/w_wad.c index c8880f693..d012182f1 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -51,8 +51,8 @@ #include "filesrch.h" #include "d_main.h" -#include "d_netfil.h" -#include "d_clisrv.h" +#include "netcode/d_netfil.h" +#include "netcode/d_clisrv.h" #include "dehacked.h" #include "r_defs.h" #include "r_data.h" diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc index 869c0e7d3..b69900746 100644 --- a/src/win32/Srb2win.rc +++ b/src/win32/Srb2win.rc @@ -77,8 +77,8 @@ END #include "../doomdef.h" // Needed for version string VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,2,11,0 - PRODUCTVERSION 2,2,11,0 + FILEVERSION 2,2,13,0 + PRODUCTVERSION 2,2,13,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L diff --git a/src/y_inter.c b/src/y_inter.c index 1b1f49e0b..69dc931ba 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -15,7 +15,7 @@ #include "f_finale.h" #include "g_game.h" #include "hu_stuff.h" -#include "i_net.h" +#include "netcode/i_net.h" #include "i_video.h" #include "p_tick.h" #include "r_defs.h" @@ -998,8 +998,7 @@ void Y_Ticker(void) if (paused || P_AutoPause()) return; - LUA_HookBool(intertype == int_spec && stagefailed, - HOOK(IntermissionThinker)); + LUA_HookBool(stagefailed, HOOK(IntermissionThinker)); intertic++; @@ -1477,10 +1476,10 @@ void Y_StartIntermission(void) if (players[consoleplayer].charflags & SF_SUPER) { strcpy(data.spec.passed3, "can now become"); - snprintf(data.spec.passed4, - sizeof data.spec.passed4, "Super %s", - skins[players[consoleplayer].skin].realname); - data.spec.passed4[sizeof data.spec.passed4 - 1] = '\0'; + if (strlen(skins[players[consoleplayer].skin].supername) > 20) //too long, use generic + strcpy(data.spec.passed4, "their super form"); + else + strcpy(data.spec.passed4, skins[players[consoleplayer].skin].supername); } } else @@ -2044,7 +2043,7 @@ static void Y_AwardCoopBonuses(void) y_bonus_t localbonuses[4]; // set score/total first - data.coop.total = 0; + data.coop.total = players[consoleplayer].recordscore; data.coop.score = players[consoleplayer].score; data.coop.gotperfbonus = -1; memset(data.coop.bonuses, 0, sizeof(data.coop.bonuses));