diff --git a/.circleci/config.yml b/.circleci/config.yml
index 711be39d76fdfb80c4680bbae19c8dd135af0afb..b3b97363eeb3f31e41ddf01f01e6f01f52c86204 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 nasm 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 7751149ac07713a953529c5e8ba579109d76356d..c2e507352e6419be54e8866cc5bb40f5570a0fc7 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 cd828dc116957ceda70d6c5b8a2b929b158f1f80..268e3632906a9b840747e6c015b32848ba45b50f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,5 @@ Win32_LIB_ASM_Release
 /make
 /bin
 /build
-/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 0000000000000000000000000000000000000000..b292bfc06c21f5ef02d6bb155ae4ab5e4ee6533c
--- /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/CMakeLists.txt b/CMakeLists.txt
index 915912af5e8689b782e62b30c8eafbee66804b97..80a3bdcd6798a48d10ea928d60ca3c4fb77bf353 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -53,11 +53,15 @@ else()
 	set(SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT OFF)
 endif()
 
+# Clang tidy options will be ignored if CMAKE_<LANG>_CLANG_TIDY are set.
+option(SRB2_CONFIG_ENABLE_CLANG_TIDY_C "Enable default clang-tidy check configuration for C" OFF)
+option(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX "Enable default clang-tidy check configuration for C++" OFF)
 option(
 	SRB2_CONFIG_SYSTEM_LIBRARIES
 	"Link dependencies using CMake's find_package and do not use internal builds"
 	${SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT}
 )
+option(SRB2_CONFIG_ENABLE_TESTS "Build the test suite" ON)
 # This option isn't recommended for distribution builds and probably won't work (yet).
 cmake_dependent_option(
 	SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES
@@ -76,6 +80,25 @@ option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF)
 option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF)
 set(SRB2_CONFIG_ASSET_DIRECTORY "" CACHE PATH "Path to directory that contains all asset files for the installer. If set, assets will be part of installation and cpack.")
 
+if(SRB2_CONFIG_ENABLE_TESTS)
+	# https://github.com/catchorg/Catch2
+	CPMAddPackage(
+		NAME Catch2
+		VERSION 3.4.0
+		GITHUB_REPOSITORY catchorg/Catch2
+		OPTIONS
+			"CATCH_INSTALL_DOCS OFF"
+	)
+	list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/extras")
+	include(CTest)
+	include(Catch)
+	add_executable(srb2tests)
+	# To add tests, use target_sources to add individual test files to the target in subdirs.
+	target_link_libraries(srb2tests PRIVATE Catch2::Catch2 Catch2::Catch2WithMain)
+	target_compile_features(srb2tests PRIVATE c_std_11 cxx_std_17)
+	catch_discover_tests(srb2tests)
+endif()
+
 # Enable CCache
 # (Set USE_CCACHE=ON to use, CCACHE_OPTIONS for options)
 if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL Windows)
@@ -108,7 +131,11 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}")
 	find_package(SDL2_mixer REQUIRED)
 	find_package(CURL REQUIRED)
 	find_package(OPENMPT REQUIRED)
-	find_package(GME REQUIRED)
+
+	# libgme defaults to "Nuked" YM2612 emulator, which is
+	# very SLOW. The system library probably uses the
+	# default so just always build it.
+	#find_package(GME REQUIRED)
 endif()
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
@@ -119,13 +146,6 @@ if ((${SRB2_USE_CCACHE}) AND (${CMAKE_C_COMPILER} MATCHES "clang"))
 	message(WARNING "Using clang and CCache: You may want to set environment variable CCACHE_CPP2=yes to prevent include errors during compile.")
 endif()
 
-# Add sources from Sourcefile
-function(target_sourcefile type)
-	file(STRINGS Sourcefile list
-		REGEX "[-0-9A-Za-z_]+\.${type}")
-	target_sources(SRB2SDL2 PRIVATE ${list})
-endfunction()
-
 # bitness check
 set(SRB2_SYSTEM_BITS 0)
 if(CMAKE_SIZEOF_VOID_P EQUAL 8)
@@ -144,7 +164,8 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
 set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
 
 # Set EXE names so the assets CMakeLists can refer to its target
-set(SRB2_SDL2_EXE_NAME srb2 CACHE STRING "Executable binary output name")
+set(SRB2_SDL2_EXE_NAME "" CACHE STRING "Override executable binary output name")
+set(SRB2_SDL2_EXE_SUFFIX "" CACHE STRING "Optional executable suffix, separated by an underscore")
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR}/src)
 
@@ -152,11 +173,37 @@ add_subdirectory(src)
 add_subdirectory(assets)
 
 
-## config.h generation
 set(GIT_EXECUTABLE "git" CACHE FILEPATH "Path to git binary")
 include(GitUtilities)
-git_latest_commit(SRB2_COMP_COMMIT "${CMAKE_SOURCE_DIR}")
-git_current_branch(SRB2_GIT_BRANCH "${CMAKE_SOURCE_DIR}")
-set(SRB2_COMP_BRANCH "${SRB2_GIT_BRANCH}")
-set(SRB2_COMP_REVISION "${SRB2_COMP_COMMIT}")
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/config.h)
+
+if("${SRB2_SDL2_EXE_NAME}" STREQUAL "")
+	# cause a reconfigure if the branch changes
+	get_git_dir(SRB2_GIT_DIR)
+	configure_file("${SRB2_GIT_DIR}/HEAD" HEAD COPYONLY)
+
+	git_current_branch(SRB2_GIT_REVISION)
+
+	if("${SRB2_GIT_REVISION}" STREQUAL "")
+		# use abbreviated commit hash if on detached HEAD
+		git_latest_commit(SRB2_GIT_REVISION)
+	endif()
+
+	if("${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
+		list(APPEND EXE_NAME_PARTS "srb2win")
+	elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
+		list(APPEND EXE_NAME_PARTS "lsdlsrb2")
+	else()
+		list(APPEND EXE_NAME_PARTS "srb2")
+	endif()
+
+	if(NOT "${SRB2_GIT_REVISION}" STREQUAL "master")
+		list(APPEND EXE_NAME_PARTS ${SRB2_GIT_REVISION})
+	endif()
+else()
+	list(APPEND EXE_NAME_PARTS ${SRB2_SDL2_EXE_NAME})
+endif()
+
+list(APPEND EXE_NAME_PARTS ${SRB2_SDL2_EXE_SUFFIX})
+
+list(JOIN EXE_NAME_PARTS "_" EXE_NAME)
+set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME ${EXE_NAME})
diff --git a/CMakePresets.json b/CMakePresets.json
new file mode 100644
index 0000000000000000000000000000000000000000..7713bb38516877d5e8e50cc46914a7f58c9c6d73
--- /dev/null
+++ b/CMakePresets.json
@@ -0,0 +1,29 @@
+{
+	"version": 3,
+	"configurePresets": [
+		{
+			"name": "default",
+			"description": "Build using default generator",
+			"binaryDir": "build",
+			"cacheVariables": {
+				"CMAKE_C_FLAGS": "-fdiagnostics-color",
+				"CMAKE_CXX_FLAGS": "-fdiagnostics-color",
+				"CMAKE_BUILD_TYPE": "RelWithDebInfo"
+			}
+		},
+		{
+			"name": "debug",
+			"description": "Build for development (no optimizations)",
+			"inherits": "default",
+			"cacheVariables": {
+				"CMAKE_BUILD_TYPE": "Debug"
+			}
+		}
+	],
+	"buildPresets": [
+		{
+			"name": "default",
+			"configurePreset": "default"
+		}
+	]
+}
diff --git a/README.md b/README.md
index 49a3cc36d167169467a2d65bec7527610691694d..56ff2d02d24e39fe6f8038a0770e7b1b265c7ae7 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,6 @@
 [Sonic Robo Blast 2](https://srb2.org/) is a 3D Sonic the Hedgehog fangame based on a modified version of [Doom Legacy](http://doomlegacy.sourceforge.net/).
 
 ## Dependencies
-- NASM (x86 builds only)
 - SDL2 (Linux/OS X only)
 - SDL2-Mixer (Linux/OS X only)
 - libupnp (Linux/OS X only)
diff --git a/SRB2.cbp b/SRB2.cbp
index 2a1eb87b8565b3d2d9c13216c23d39dceecd6f92..9e887bf859c05ab821b3936f29cb3089daef2b2d 100644
--- a/SRB2.cbp
+++ b/SRB2.cbp
@@ -1992,24 +1992,6 @@ HW3SOUND for 3D hardware sound  support
 			<Option compilerVar="CC" />
 		</Unit>
 		<Unit filename="src/v_video.h" />
-		<Unit filename="src/vid_copy.s">
-			<Option compilerVar="CC" />
-			<Option compiler="avrgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gnu_gcc_compiler_for_mingw32" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gnu_gcc_compiler_for_mingw64" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="armelfgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="tricoregcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="ppcgcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option compiler="gcc" use="1" buildCommand="$compiler $options -x assembler-with-cpp -c $file -o $object" />
-			<Option target="Debug Native/SDL" />
-			<Option target="Release Native/SDL" />
-			<Option target="Debug Linux/SDL" />
-			<Option target="Release Linux/SDL" />
-			<Option target="Debug Mingw/SDL" />
-			<Option target="Release Mingw/SDL" />
-			<Option target="Debug Mingw/DirectX" />
-			<Option target="Release Mingw/DirectX" />
-		</Unit>
 		<Unit filename="src/w_wad.c">
 			<Option compilerVar="CC" />
 		</Unit>
diff --git a/SRB2_common.props b/SRB2_common.props
index 0f80ceb174874e682f0205de06733cb25e2b247a..6a0d53484f10106bc254851b1e1056dc7b24a86a 100644
--- a/SRB2_common.props
+++ b/SRB2_common.props
@@ -25,9 +25,6 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(PlatformTarget)'=='x86'">
-    <ClCompile>
-      <PreprocessorDefinitions>USEASM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-    </ClCompile>
     <Link>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
     </Link>
diff --git a/Srb2.dev b/Srb2.dev
index 21683e7c3c5e055393d91778fba3405bfae240de..8bd36cf490cc84dae69d25399113d346392e4fc8 100644
--- a/Srb2.dev
+++ b/Srb2.dev
@@ -5,7 +5,7 @@ Ver=3
 IsCpp=0
 Type=0
 UnitCount=279
-Folders=A_Asm,B_Bot,BLUA,D_Doom,F_Frame,G_Game,H_Hud,Hw_Hardware,Hw_Hardware/r_opengl,I_Interface,I_Interface/Dummy,I_Interface/SDL,I_Interface/Win32,LUA,M_Misc,P_Play,R_Rend,S_Sounds,W_Wad
+Folders=B_Bot,BLUA,D_Doom,F_Frame,G_Game,H_Hud,Hw_Hardware,Hw_Hardware/r_opengl,I_Interface,I_Interface/Dummy,I_Interface/SDL,I_Interface/Win32,LUA,M_Misc,P_Play,R_Rend,S_Sounds,W_Wad
 CommandLine=
 CompilerSettings=00000000000100000111e1
 PchHead=-1
@@ -1473,36 +1473,6 @@ Priority=1000
 OverrideBuildCmd=0
 BuildCmd=
 
-[Unit149]
-FileName=src\tmap.nas
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=nasm.exe -g -o $@ -f win32 src/tmap.nas
-
-[Unit150]
-FileName=src\asm_defs.inc
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=0
-BuildCmd=
-
-[Unit151]
-FileName=src\vid_copy.s
-Folder=A_Asm
-Compile=1
-CompileCpp=0
-Link=1
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=$(CC) $(CFLAGS) -x assembler-with-cpp -c src/vid_copy.s -o $@
-
 [Unit152]
 FileName=src\y_inter.h
 Folder=H_Hud
@@ -1543,26 +1513,6 @@ Priority=1000
 OverrideBuildCmd=0
 BuildCmd=
 
-[Unit156]
-FileName=src\p5prof.h
-Folder=A_Asm
-Compile=1
-CompileCpp=0
-Link=1
-Priority=1000
-OverrideBuildCmd=0
-BuildCmd=
-
-[Unit157]
-FileName=src\tmap_mmx.nas
-Folder=A_Asm
-Compile=0
-CompileCpp=0
-Link=0
-Priority=1000
-OverrideBuildCmd=1
-BuildCmd=nasm.exe -g -o $@ -f win32 src/tmap_mmx.nas
-
 [Unit159]
 FileName=src\lzf.h
 Folder=W_Wad
diff --git a/alias-bootstrap.sh b/alias-bootstrap.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f1a6ac4ed918db4391a39c9a3076dfcc38d2861b
--- /dev/null
+++ b/alias-bootstrap.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env sh
+
+# All these commands can be run from anywhere in the git
+# tree, not just the top level.
+
+# Usage: git cmake
+#
+# Same usage as standard CMake command.
+#
+git config 'alias.cmake' '!cmake'
+
+# Usage: git build <build preset> [options]
+# Usage: git build [options]
+#
+# In the second usage, when no preset is given, the
+# "default" build preset is used.
+#
+# Available options can be found by running:
+#
+#     git cmake --build
+#
+git config 'alias.build' '!p="${1##-*}"; [ "$p" ] && shift; git cmake --build --preset "${p:-default}"'
+
+# Usage: git crossmake
+#
+# Shortcut to i686-w64-mingw32-cmake (CMake cross
+# compiler)
+#
+git config 'alias.crossmake' '!i686-w64-mingw32-cmake'
diff --git a/appveyor.yml b/appveyor.yml
index 348b727b1d8a8f31dc202b6c822d1420325e7ddc..63d801b734719bf4ba47dfb42b1b7849c3a23189 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.10.{branch}-{build}
+version: 2.2.13.{branch}-{build}
 os: MinGW
 
 environment:
@@ -7,8 +7,6 @@ environment:
  # c:\mingw-w64 i686 has gcc 6.3.0, so use c:\msys64 7.3.0 instead
  MINGW_SDK: c:\msys64\mingw32
  CFLAGS: -Wno-implicit-fallthrough
- NASM_ZIP: nasm-2.12.01
- NASM_URL: http://www.nasm.us/pub/nasm/releasebuilds/2.12.01/win64/nasm-2.12.01-win64.zip
  UPX_ZIP: upx391w
  UPX_URL: http://upx.sourceforge.net/download/upx391w.zip
  CCACHE_EXE: ccache.exe
@@ -40,17 +38,12 @@ environment:
  ASSET_CLEAN: 0
 
 cache:
-- nasm-2.12.01.zip
 - upx391w.zip
 - ccache.exe
 - C:\Users\appveyor\.ccache
 - C:\Users\appveyor\srb2_cache
 
 install:
-- if not exist "%NASM_ZIP%.zip" appveyor DownloadFile "%NASM_URL%" -FileName "%NASM_ZIP%.zip"
-- 7z x -y "%NASM_ZIP%.zip" -o%TMP% >null
-- robocopy /S /xx /ns /nc /nfl /ndl /np /njh /njs "%TMP%\%NASM_ZIP%" "%MINGW_SDK%\bin" nasm.exe || exit 0
-
 - if not exist "%UPX_ZIP%.zip" appveyor DownloadFile "%UPX_URL%" -FileName "%UPX_ZIP%.zip"
 - 7z x -y "%UPX_ZIP%.zip" -o%TMP% >null
 - robocopy /S /xx /ns /nc /nfl /ndl /np /njh /njs "%TMP%\%UPX_ZIP%" "%MINGW_SDK%\bin" upx.exe || exit 0
@@ -65,7 +58,6 @@ configuration:
 before_build:
 - set "Path=%MINGW_SDK%\bin;%Path%"
 - mingw32-make --version
-- nasm -v
 - if not [%NOUPX%] == [1] ( upx -V )
 - ccache -V
 - ccache -s
diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index de164b23f018e2dcab105bb6ccb6954a03a96358..dfb2f4180db7dbbae9a454a6f939aeda9e6f26a4 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -29,6 +29,7 @@ set(SRB2_ASSETS_GAME
 	"srb2.pk3"
 	"player.dta"
 	"zones.pk3"
+	"patch.pk3"
 	"music.dta"
 	"models.dat"
 )
diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..8388aed9ece077229a35d601ffd0679cb2ecd035
--- /dev/null
+++ b/cmake/Comptime.cmake
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+
+set(CMAKE_BINARY_DIR "${BINARY_DIR}")
+set(CMAKE_CURRENT_BINARY_DIR "${BINARY_DIR}")
+
+# Set up CMAKE path
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+
+include(GitUtilities)
+
+git_current_branch(SRB2_COMP_BRANCH)
+git_working_tree_dirty(SRB2_COMP_UNCOMMITTED)
+
+git_latest_commit(SRB2_COMP_REVISION)
+git_subject(subject)
+string(REGEX REPLACE "([\"\\])" "\\\\\\1" SRB2_COMP_NOTE "${subject}")
+
+if("${CMAKE_BUILD_TYPE}" STREQUAL "")
+	set(CMAKE_BUILD_TYPE None)
+endif()
+
+# These build types enable optimizations of some kind by default.
+set(optimized_build_types "MINSIZEREL;RELEASE;RELWITHDEBINFO")
+
+string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
+if("${build_type}" IN_LIST optimized_build_types)
+	set(SRB2_COMP_OPTIMIZED TRUE)
+else()
+	set(SRB2_COMP_OPTIMIZED FALSE)
+endif()
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h")
diff --git a/cmake/Modules/CMakeASM_YASMInformation.cmake b/cmake/Modules/CMakeASM_YASMInformation.cmake
deleted file mode 100644
index 1765180853bb2d23217a1eb785f97737411e68b1..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeASM_YASMInformation.cmake
+++ /dev/null
@@ -1,46 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# support for the yasm assembler
-
-set(CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS nasm yasm asm)
-
-if(NOT CMAKE_ASM_YASM_OBJECT_FORMAT)
-  if(WIN32)
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT win64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT win32)
-    endif()
-  elseif(APPLE)
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT macho64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT macho)
-    endif()
-  else()
-    if(CMAKE_C_SIZEOF_DATA_PTR EQUAL 8)
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT elf64)
-    else()
-      set(CMAKE_ASM_YASM_OBJECT_FORMAT elf)
-    endif()
-  endif()
-endif()
-
-set(CMAKE_ASM_YASM_COMPILE_OBJECT "<CMAKE_ASM_YASM_COMPILER> <FLAGS> -f ${CMAKE_ASM_YASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
-
-# Load the generic ASMInformation file:
-set(ASM_DIALECT "_YASM")
-include(CMakeASMInformation)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake b/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake
deleted file mode 100644
index a5e7c9e5801121f04411e5f1b1c6efa98736bcc1..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeDetermineASM_YASMCompiler.cmake
+++ /dev/null
@@ -1,27 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# Find the nasm assembler. yasm (http://www.tortall.net/projects/yasm/) is nasm compatible
-
-set(CMAKE_ASM_YASM_COMPILER_LIST nasm yasm)
-
-if(NOT CMAKE_ASM_YASM_COMPILER)
-  find_program(CMAKE_ASM_YASM_COMPILER yasm
-    "$ENV{ProgramFiles}/YASM")
-endif()
-
-# Load the generic DetermineASM compiler file with the DIALECT set properly:
-set(ASM_DIALECT "_YASM")
-include(CMakeDetermineASMCompiler)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/CMakeTestASM_YASMCompiler.cmake b/cmake/Modules/CMakeTestASM_YASMCompiler.cmake
deleted file mode 100644
index 745f7125c4a2f7a003c488b89d977b75a8eb3ebc..0000000000000000000000000000000000000000
--- a/cmake/Modules/CMakeTestASM_YASMCompiler.cmake
+++ /dev/null
@@ -1,23 +0,0 @@
-
-#=============================================================================
-# Copyright 2010 Kitware, Inc.
-#
-# Distributed under the OSI-approved BSD License (the "License");
-# see accompanying file Copyright.txt for details.
-#
-# This software is distributed WITHOUT ANY WARRANTY; without even the
-# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-# See the License for more information.
-#=============================================================================
-# (To distribute this file outside of CMake, substitute the full
-#  License text for the above reference.)
-
-# This file is used by EnableLanguage in cmGlobalGenerator to
-# determine that the selected ASM_NASM "compiler" works.
-# For assembler this can only check whether the compiler has been found,
-# because otherwise there would have to be a separate assembler source file
-# for each assembler on every architecture.
-
-set(ASM_DIALECT "_YASM")
-include(CMakeTestASMCompiler)
-set(ASM_DIALECT)
diff --git a/cmake/Modules/GitUtilities.cmake b/cmake/Modules/GitUtilities.cmake
index d29e6b509dd27015f429c9e8f4de12e20fcc5b12..586c7b433ff31476fe2a4cf0d5634d36d2c997be 100644
--- a/cmake/Modules/GitUtilities.cmake
+++ b/cmake/Modules/GitUtilities.cmake
@@ -6,38 +6,54 @@ endif()
 
 set(__GitUtilities ON)
 
-function(git_describe variable path)
-	execute_process(COMMAND "${GIT_EXECUTABLE}" "describe"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
+macro(_git_command)
+	execute_process(
+		COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+		WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
 		OUTPUT_VARIABLE output
 		ERROR_QUIET
 		OUTPUT_STRIP_TRAILING_WHITESPACE
 	)
+endmacro()
 
+macro(_git_easy_command)
+	_git_command(${ARGN})
 	set(${variable} "${output}" PARENT_SCOPE)
-endfunction()
+endmacro()
 
-function(git_current_branch variable path)
-	execute_process(COMMAND ${GIT_EXECUTABLE} "symbolic-ref" "--short" "HEAD"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
-		OUTPUT_VARIABLE output
-		ERROR_QUIET
-		OUTPUT_STRIP_TRAILING_WHITESPACE
-	)
+function(git_current_branch variable)
+	_git_command(symbolic-ref -q --short HEAD)
+
+	# If a detached head, a ref could still be resolved.
+	if("${output}" STREQUAL "")
+		_git_command(describe --all --exact-match)
+
+		# Get the ref, in the form heads/master or
+		# remotes/origin/master so isolate the final part.
+		string(REGEX REPLACE ".*/" "" output "${output}")
+	endif()
 
 	set(${variable} "${output}" PARENT_SCOPE)
 endfunction()
 
-function(git_latest_commit variable path)
-	execute_process(COMMAND ${GIT_EXECUTABLE} "rev-parse" "--short" "HEAD"
-		WORKING_DIRECTORY "${path}"
-		RESULT_VARIABLE result
-		OUTPUT_VARIABLE output
-		ERROR_QUIET
-		OUTPUT_STRIP_TRAILING_WHITESPACE
-	)
+function(git_latest_commit variable)
+	_git_easy_command(rev-parse --short HEAD)
+endfunction()
 
-	set(${variable} "${output}" PARENT_SCOPE)
-endfunction()
\ No newline at end of file
+function(git_working_tree_dirty variable)
+	_git_command(status --porcelain -uno)
+
+	if(output STREQUAL "")
+		set(${variable} FALSE PARENT_SCOPE)
+	else()
+		set(${variable} TRUE PARENT_SCOPE)
+	endif()
+endfunction()
+
+function(git_subject variable)
+	_git_easy_command(log -1 --format=%s)
+endfunction()
+
+function(get_git_dir variable)
+	_git_easy_command(rev-parse --git-dir)
+endfunction()
diff --git a/cmake/Modules/clang-tidy-default.cmake b/cmake/Modules/clang-tidy-default.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..2be3af10d961229bac835c7083a0d81f677f7144
--- /dev/null
+++ b/cmake/Modules/clang-tidy-default.cmake
@@ -0,0 +1,21 @@
+find_program(CLANG_TIDY clang-tidy)
+
+# Note: Apple Clang does not ship with clang tools. If you want clang-tidy on
+# macOS, it's best to install the Homebrew llvm bottle and set CLANG_TIDY
+# in your build directory. The llvm package is keg-only, so it will not
+# collide with Apple Clang.
+
+function(target_set_default_clang_tidy target lang checks)
+    if("${CLANG_TIDY}" STREQUAL "CLANG_TIDY-NOTFOUND")
+        return()
+    endif()
+
+    get_target_property(c_clang_tidy_prop SRB2SDL2 C_CLANG_TIDY)
+    if(NOT ("${c_clang_tidy_prop}" STREQUAL "c_clang_tidy_prop-NOTFOUND"))
+        return()
+    endif()
+
+    set_target_properties("${target}" PROPERTIES
+	    ${lang}_CLANG_TIDY "${CLANG_TIDY};-checks=${checks}"
+    )
+endfunction()
diff --git a/cpdebug.mk b/cpdebug.mk
index 6baedf2275db4bbb06606a646cce9a4cb2c1d35e..75f08c66f4af721939c6f6c904aefc62f0d75534 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 ae905b637b334286c58eb339f18b735838c5929d..41ad998891154f56fe850cdfe457a48189dd648f 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -41,6 +41,9 @@ linetagindicatesectors = true;
 
 // The format interface handles the map data format - DoomMapSetIO for SRB2DB2, SRB2MapSetIO for Zone Builder
 formatinterface = "SRB2MapSetIO";
+	
+//Maximum safe map size check (0 means skip check)
+safeboundary = 0;
 
 //Sky textures for vanilla maps
 defaultskytextures
@@ -77,7 +80,7 @@ defaultskytextures
 defaultlumpname = "MAP01";
 
 // Default testing parameters
-testparameters = "-file \"%AP\" \"%F\" -warp %L";
+testparameters = "-folder \"%AF\" -file \"%AA\" \"%F\" -warp %L";
 testshortpaths = true;
 
 // Default nodebuilder configurations
@@ -437,6 +440,8 @@ sectortypes
 	144 = "Egg Capsule";
 	160 = "Special Stage Time/Spheres Parameters <deprecated>";
 	176 = "Custom Global Gravity <deprecated>";
+	512 = "Wind/Current <deprecated>";
+	1024 = "Conveyor Belt <deprecated>";
 	1280 = "Speed Pad";
 	1536 = "Flip Gravity on Jump";
 	4096 = "Star Post Activator";
@@ -496,6 +501,8 @@ gen_sectortypes
 	third
 	{
 		0 = "Normal";
+		512 = "Wind/Current <deprecated>";
+		1024 = "Conveyor Belt <deprecated>";		 
 		1280 = "Speed Pad";
 		1536 = "Flip Gravity on Jump";
 	}
@@ -578,7 +585,7 @@ linedeftypes
 			title = "Per-Sector Gravity";
 			prefix = "(1)";
 			flags64text = "[6] Flip in reverse gravity";
-			flags8192text = "[13] Reverse while inside";
+			flags8192text = "[13] Cancel MF2_OBJECTFLIP";
 		}
 
 		5
@@ -641,35 +648,35 @@ linedeftypes
 
 		96
 		{
-			title = "Apply Tag to Tagged Sectors";
+			title = "Add Front Sector Tag to Tagged Sectors";
 			prefix = "(96)";
 			flags1024text = "[10] Offsets are target tags";
-			flags8192text = "[13] Use front side offsets";
-			flags32768text = "[15] Use back side offsets";
+			flags8192text = "[13] Add front side offsets";
+			flags32768text = "[15] Add back side offsets";
 		}
 
 		97
 		{
-			title = "Apply Tag to Front Sector";
+			title = "Add Tag to Front Sector";
 			prefix = "(97)";
-			flags8192text = "[13] Use front side offsets";
-			flags32768text = "[15] Use back side offsets";
+			flags8192text = "[13] Add front side offsets";
+			flags32768text = "[15] Add back side offsets";
 		}
 
 		98
 		{
-			title = "Apply Tag to Back Sector";
+			title = "Add Tag to Back Sector";
 			prefix = "(98)";
-			flags8192text = "[13] Use front side offsets";
-			flags32768text = "[15] Use back side offsets";
+			flags8192text = "[13] Add front side offsets";
+			flags32768text = "[15] Add back side offsets";
 		}
 
 		99
 		{
-			title = "Apply Tag to Front and Back Sectors";
+			title = "Add Tag to Front and Back Sectors";
 			prefix = "(99)";
-			flags8192text = "[13] Use front side offsets";
-			flags32768text = "[15] Use back side offsets";
+			flags8192text = "[13] Add front side offsets";
+			flags32768text = "[15] Add back side offsets";
 		}
 
 		540
@@ -990,6 +997,7 @@ linedeftypes
 			flags128text = "[7] Only block non-players";
 			3dfloor = true;
 			3dfloorflags = "47";
+			invisiblefof = true;
 		}
 
 		140
@@ -1227,6 +1235,7 @@ linedeftypes
 			prefix = "(223)";
 			3dfloor = true;
 			3dfloorflags = "41";
+			invisiblefof = true;
 		}
 	}
 
@@ -1524,6 +1533,7 @@ linedeftypes
 			prefix = "(200)";
 			3dfloor = true;
 			3dfloorflags = "20201";
+			invisiblefof = true;
 		}
 
 		201
@@ -1532,6 +1542,7 @@ linedeftypes
 			prefix = "(201)";
 			3dfloor = true;
 			3dfloorflags = "201";
+			invisiblefof = true;
 		}
 
 		202
@@ -1539,7 +1550,8 @@ linedeftypes
 			title = "Fog Block";
 			prefix = "(202)";
 			3dfloor = true;
-			3dfloorflags = "3EF19";
+			3dfloorflags = "3EF01";
+			invisiblefof = true;
 		}
 
 		250
@@ -2854,36 +2866,63 @@ linedeftypes
 		{
 			title = "Scroll Floor Texture";
 			prefix = "(510)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		511
 		{
 			title = "Scroll Floor Texture (Accelerative)";
 			prefix = "(511)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		512
 		{
 			title = "Scroll Floor Texture (Displacement)";
 			prefix = "(512)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		513
 		{
 			title = "Scroll Ceiling Texture";
 			prefix = "(513)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		514
 		{
 			title = "Scroll Ceiling Texture (Accelerative)";
 			prefix = "(514)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		515
 		{
 			title = "Scroll Ceiling Texture (Displacement)";
 			prefix = "(515)";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		516
+		{
+			title = "Scroll Floor and Ceiling Texture";
+			prefix = "(516)";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		517
+		{
+			title = "Scroll Floor and Ceiling Texture (Accelerative)";
+			prefix = "(517)";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		518
+		{
+			title = "Scroll Floor and Ceiling Texture (Displacement)";
+			prefix = "(518)";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		520
@@ -2891,6 +2930,7 @@ linedeftypes
 			title = "Carry Objects on Floor";
 			prefix = "(520)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		521
@@ -2898,6 +2938,7 @@ linedeftypes
 			title = "Carry Objects on Floor (Accelerative)";
 			prefix = "(521)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		522
@@ -2905,6 +2946,7 @@ linedeftypes
 			title = "Carry Objects on Floor (Displacement)";
 			prefix = "(522)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		523
@@ -2912,6 +2954,7 @@ linedeftypes
 			title = "Carry Objects on Ceiling";
 			prefix = "(523)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		524
@@ -2919,6 +2962,7 @@ linedeftypes
 			title = "Carry Objects on Ceiling (Accelerative)";
 			prefix = "(524)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		525
@@ -2926,6 +2970,31 @@ linedeftypes
 			title = "Carry Objects on Ceiling (Displacement)";
 			prefix = "(525)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		526
+		{
+			title = "Carry Objects on Floor and Ceiling";
+			prefix = "(526)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		527
+		{
+			title = "Carry Objects on Floor and Ceiling (Accelerative)";
+			prefix = "(527)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		528
+		{
+			title = "Carry Objects on Floor and Ceiling (Displacement)";
+			prefix = "(528)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		530
@@ -2933,6 +3002,7 @@ linedeftypes
 			title = "Scroll Floor Texture and Carry Objects";
 			prefix = "(530)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		531
@@ -2940,6 +3010,7 @@ linedeftypes
 			title = "Scroll Floor Texture and Carry Objects (Accelerative)";
 			prefix = "(531)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		532
@@ -2947,6 +3018,7 @@ linedeftypes
 			title = "Scroll Floor Texture and Carry Objects (Displacement)";
 			prefix = "(532)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		533
@@ -2954,6 +3026,7 @@ linedeftypes
 			title = "Scroll Ceiling Texture and Carry Objects";
 			prefix = "(533)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		534
@@ -2961,6 +3034,7 @@ linedeftypes
 			title = "Scroll Ceiling Texture and Carry Objects (Accelerative)";
 			prefix = "(534)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		535
@@ -2968,6 +3042,31 @@ linedeftypes
 			title = "Scroll Ceiling Texture and Carry Objects (Displacement)";
 			prefix = "(535)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		536
+		{
+			title = "Scroll Floor and Ceiling Texture and Carry Objects";
+			prefix = "(536)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		537
+		{
+			title = "Scroll Floor and Ceiling Texture and Carry Objects (Accelerative)";
+			prefix = "(537)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
+		}
+
+		538
+		{
+			title = "Scroll Floor and Ceiling Texture and Carry Objects (Displacement)";
+			prefix = "(538)";
+			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use angle and X offset";
 		}
 	}
 
@@ -2979,48 +3078,54 @@ linedeftypes
 		{
 			title = "Wind";
 			prefix = "(541)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		542
 		{
 			title = "Upwards Wind";
 			prefix = "(542)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use X offset";
 		}
 
 		543
 		{
 			title = "Downwards Wind";
 			prefix = "(543)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use X offset";
 		}
 
 		544
 		{
 			title = "Current";
 			prefix = "(544)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use angle and X offset";
 		}
 
 		545
 		{
 			title = "Upwards Current";
 			prefix = "(545)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use X offset";
 		}
 
 		546
 		{
 			title = "Downwards Current";
 			prefix = "(546)";
-			flags512text = "[9] Player slides";
 			flags64text = "[6] Exclusive";
+			flags512text = "[9] Player slides";
+			flags8192text = "[13] Use X offset";
 		}
 
 		547
@@ -3028,6 +3133,7 @@ linedeftypes
 			title = "Push/Pull";
 			prefix = "(547)";
 			flags64text = "[6] Exclusive";
+			flags8192text = "[13] Use X offset";
 		}
 	}
 
@@ -3624,7 +3730,7 @@ thingtypes
 
 		3328 = "3D Mode Start";
 	}
-
+	
 	starts
 	{
 		color = 1; // Blue
@@ -3814,7 +3920,7 @@ thingtypes
 
 	enemies
 	{
-		color = 9; // Light_Blue
+		color = 9; // Light Blue
 		arrow = 1;
 		title = "Enemies";
 
@@ -4124,7 +4230,7 @@ thingtypes
 
 	bosses
 	{
-		color = 8; // Dark_Gray
+		color = 4; // Dark Red
 		arrow = 1;
 		title = "Bosses";
 
@@ -4223,6 +4329,7 @@ thingtypes
 			sprite = "internal:capsule";
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		292
 		{
@@ -4260,7 +4367,6 @@ thingtypes
 	{
 		color = 14; // Yellow
 		title = "Rings and Weapon Panels";
-		width = 24;
 		height = 24;
 		flags8height = 24;
 		flags8text = "[8] Float";
@@ -4270,7 +4376,6 @@ thingtypes
 		{
 			title = "Ring";
 			sprite = "RINGA0";
-			width = 16;
 		}
 		301
 		{
@@ -4286,6 +4391,7 @@ thingtypes
 		{
 			title = "Infinity Ring";
 			sprite = "RNGIA0";
+			width = 24;
 		}
 		304
 		{
@@ -4310,53 +4416,63 @@ thingtypes
 		308
 		{
 			title = "CTF Team Ring (Red)";
-			sprite = "internal:TRNGA0r";
-			width = 16;
+			sprite = "internal:TRNGA0R";
 		}
 		309
 		{
 			title = "CTF Team Ring (Blue)";
-			sprite = "internal:TRNGA0b";
-			width = 16;
+			sprite = "internal:TRNGA0B";
 		}
 		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;
 		}
 	}
 
 	collectibles
 	{
-		color = 10; // Light_Green
+		color = 10; // Light Green
 		title = "Other Collectibles";
 		width = 16;
-		height = 32;
+		height = 24;
 		sort = 1;
 		sprite = "CEMGA0";
 
@@ -4422,6 +4538,7 @@ thingtypes
 		{
 			title = "Emerald Hunt Location";
 			sprite = "SHRDA0";
+			height = 32;
 			flags8height = 24;
 			flags8text = "[8] Float";
 		}
@@ -4457,6 +4574,7 @@ thingtypes
 		flags8text = "[8] Random (Weak)";
 		angletext = "Tag";
 		fixedrotation = 1;
+		tagthing = true;
 
 		400
 		{
@@ -4589,6 +4707,7 @@ thingtypes
 		flags1text = "[1] Run linedef executor on pop";
 		angletext = "Tag";
 		fixedrotation = 1;
+		tagthing = true;
 
 		431
 		{
@@ -4659,7 +4778,7 @@ thingtypes
 
 	generic
 	{
-		color = 11; // Light_Cyan
+		color = 11; // Light Cyan
 		title = "Generic Items & Hazards";
 
 		500
@@ -4765,7 +4884,7 @@ thingtypes
 
 	springs
 	{
-		color = 12; // Light_Red
+		color = 12; // Light Red
 		title = "Springs and Fans";
 		width = 20;
 		height = 16;
@@ -4927,13 +5046,13 @@ thingtypes
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Yellow Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalyellow";
 		}
 		601
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Red Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalred";
 			height = 1024;
 		}
 		602
@@ -4951,7 +5070,7 @@ thingtypes
 		604
 		{
 			title = "Circle of Rings";
-			sprite = "RINGA0";
+			sprite = "internal:circlering";
 			width = 96;
 			height = 192;
 			unflippable = true;
@@ -4960,7 +5079,7 @@ thingtypes
 		605
 		{
 			title = "Circle of Rings (Big)";
-			sprite = "RINGA0";
+			sprite = "internal:circlebigring";
 			width = 192;
 			unflippable = true;
 			centerHitbox = true;
@@ -4968,7 +5087,7 @@ thingtypes
 		606
 		{
 			title = "Circle of Blue Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circlesphere";
 			width = 96;
 			height = 192;
 			unflippable = true;
@@ -4977,7 +5096,7 @@ thingtypes
 		607
 		{
 			title = "Circle of Blue Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigsphere";
 			width = 192;
 			unflippable = true;
 			centerHitbox = true;
@@ -4985,7 +5104,7 @@ thingtypes
 		608
 		{
 			title = "Circle of Rings and Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circleringsphere";
 			width = 96;
 			height = 192;
 			unflippable = true;
@@ -4994,86 +5113,84 @@ thingtypes
 		609
 		{
 			title = "Circle of Rings and Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigringsphere";
 			width = 192;
 			unflippable = true;
 			centerHitbox = true;
 		}
 	}
 
-	invisible
+	ambience
 	{
-		color = 15; // White
-		title = "Misc. Invisible";
+		color = 8; // Dark Gray
+		title = "Ambience";
 		width = 8;
 		height = 16;
-		sprite = "UNKNA0";
+		sprite = "internal:ambiance";
 
 		700
 		{
 			title = "Water Ambience A (Large)";
-			sprite = "internal:ambiance";
 		}
 
 		701
 		{
 			title = "Water Ambience B (Large)";
-			sprite = "internal:ambiance";
 		}
 
 		702
 		{
 			title = "Water Ambience C (Medium)";
-			sprite = "internal:ambiance";
 		}
 
 		703
 		{
 			title = "Water Ambience D (Medium)";
-			sprite = "internal:ambiance";
 		}
 
 		704
 		{
 			title = "Water Ambience E (Small)";
-			sprite = "internal:ambiance";
 		}
 
 		705
 		{
 			title = "Water Ambience F (Small)";
-			sprite = "internal:ambiance";
 		}
 
 		706
 		{
 			title = "Water Ambience G (Extra Large)";
-			sprite = "internal:ambiance";
 		}
 
 		707
 		{
 			title = "Water Ambience H (Extra Large)";
-			sprite = "internal:ambiance";
 		}
 
 		708
 		{
 			title = "Disco Ambience";
-			sprite = "internal:ambiance";
 		}
 
 		709
 		{
 			title = "Volcano Ambience";
-			sprite = "internal:ambiance";
 		}
 
 		710
 		{
 			title = "Machine Ambience";
-			sprite = "internal:ambiance";
 		}
+	}
+
+	invisible
+	{
+		color = 15; // White
+		title = "Misc. Invisible";
+		width = 8;
+		height = 16;
+		sprite = "UNKNA0";
 
 		750
 		{
@@ -5083,6 +5200,7 @@ thingtypes
 			fixedrotation = 1;
 			parametertext = "Absolute?";
 			flagsvaluetext = "Absolute Z";
+			tagthing = true;
 		}
 
 		751
@@ -5128,20 +5246,22 @@ thingtypes
 		756
 		{
 			title = "Blast Linedef Executor";
-			sprite = "TOADA0";
+			sprite = "internal:blastexec";
 			width = 32;
 			height = 16;
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		757
 		{
 			title = "Fan Particle Generator";
-			sprite = "PRTLA0";
+			sprite = "internal:fanparticles";
 			width = 8;
 			height = 16;
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		758
 		{
@@ -5154,6 +5274,8 @@ thingtypes
 			sprite = "internal:polyanchor";
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
+			unflippable = true;
 		}
 
 		761
@@ -5162,6 +5284,8 @@ thingtypes
 			sprite = "internal:polycenter";
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
+			unflippable = true;
 		}
 
 		762
@@ -5170,6 +5294,8 @@ thingtypes
 			sprite = "internal:polycentercrush";
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
+			unflippable = true;
 		}
 		780
 		{
@@ -5183,7 +5309,7 @@ thingtypes
 
 	greenflower
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Greenflower";
 
 		800
@@ -5288,7 +5414,7 @@ thingtypes
 
 	technohill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Techno Hill";
 
 		900
@@ -5332,7 +5458,7 @@ thingtypes
 
 	deepsea
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Deep Sea";
 
 		1000
@@ -5467,7 +5593,7 @@ thingtypes
 
 	castleeggman
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Castle Eggman";
 
 		1100
@@ -5516,6 +5642,7 @@ thingtypes
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1105
 		{
@@ -5528,6 +5655,7 @@ thingtypes
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1106
 		{
@@ -5540,23 +5668,25 @@ thingtypes
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1107
 		{
 			title = "Chain Spawnpoint";
-			sprite = "BMCHA0";
+			sprite = "BMCHB0";
 			width = 17;
 			height = 34;
 			flags8text = "[8] Double size";
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1108
 		{
 			arrow = 1;
 			title = "Hidden Chain Spawnpoint";
-			sprite = "internal:chain3";
+			sprite = "SMCHA0";
 			width = 17;
 			height = 34;
 			flags8text = "[8] Double size";
@@ -5572,6 +5702,7 @@ thingtypes
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1110
 		{
@@ -5583,6 +5714,7 @@ thingtypes
 			angletext = "Tag";
 			parametertext = "Spokes";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1111
 		{
@@ -5736,7 +5868,7 @@ thingtypes
 
 	aridcanyon
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Arid Canyon";
 
 		1200
@@ -5764,6 +5896,7 @@ thingtypes
 			height = 16;
 			angletext = "Tag";
 			fixedrotation = 1;
+			tagthing = true;
 		}
 		1203
 		{
@@ -5955,7 +6088,7 @@ thingtypes
 
 	redvolcano
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Red Volcano";
 
 		1300
@@ -6055,7 +6188,7 @@ thingtypes
 
 	botanicserenity
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Botanic Serenity";
 		width = 16;
 		height = 32;
@@ -6298,7 +6431,7 @@ thingtypes
 
 	azuretemple
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Azure Temple";
 
 		1500
@@ -6374,7 +6507,7 @@ thingtypes
 
 	dreamhill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Dream Hill";
 
 		1600
@@ -6402,8 +6535,8 @@ thingtypes
 
 	nightstrk
 	{
-		color = 13; // Pink
-		title = "NiGHTS Track";
+		color = 16; // Light Pink
+		title = "NiGHTS Track & Basics";
 		width = 8;
 		height = 4096;
 		sprite = "UNKNA0";
@@ -6438,6 +6571,19 @@ thingtypes
 			flagsvaluetext = "Order";
 			parametertext = "Mare";
 		}
+		1703
+		{
+			title = "Ideya Drone";
+			sprite = "NDRNA1";
+			width = 16;
+			height = 56;
+			flags1text = "[1] Align player to middle";
+			flags4text = "[4] Align player to top";
+			flags8text = "[8] Die upon time up";
+			angletext = "Time limit";
+			fixedrotation = 1;
+			parametertext = "Height";
+		}
 		1710
 		{
 			title = "Ideya Capture";
@@ -6455,20 +6601,6 @@ thingtypes
 		title = "NiGHTS Items";
 		width = 16;
 		height = 32;
-
-		1703
-		{
-			title = "Ideya Drone";
-			sprite = "NDRNA1";
-			width = 16;
-			height = 56;
-			flags1text = "[1] Align player to middle";
-			flags4text = "[4] Align player to top";
-			flags8text = "[8] Die upon time up";
-			angletext = "Time limit";
-			fixedrotation = 1;
-			parametertext = "Height";
-		}
 		1704
 		{
 			arrow = 1;
@@ -6479,13 +6611,12 @@ thingtypes
 			unflippable = true;
 			flagsvaluetext = "Pitch";
 			angletext = "Yaw";
-			fixedrotation = 1;
 		}
 		1705
 		{
 			arrow = 1;
 			title = "Hoop (Generic)";
-			sprite = "HOOPA0";
+			sprite = "internal:nightshoop";
 			width = 80;
 			height = 160;
 			unflippable = true;
@@ -6502,7 +6633,6 @@ thingtypes
 			height = 24;
 			flags8height = 24;
 			flags8text = "[8] Float";
-			unflippable = true;
 		}
 		1707
 		{
@@ -6547,7 +6677,7 @@ thingtypes
 			flags2text = "[2] Radius +32";
 			flags4text = "[4] Radius +64";
 			flags8text = "[8] Radius +128";
-			sprite = "HOOPA0";
+			sprite = "internal:nightshoop";
 			width = 80;
 			height = 160;
 			unflippable = true;
@@ -6657,7 +6787,7 @@ thingtypes
 
 	christmasdisco
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Christmas & Disco";
 
 		1850
@@ -6760,7 +6890,7 @@ thingtypes
 
 	stalagmites
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Stalagmites";
 		width = 16;
 		height = 40;
@@ -6839,7 +6969,7 @@ thingtypes
 
 	hauntedheights
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Haunted Heights";
 
 		2000
@@ -6928,7 +7058,7 @@ thingtypes
 
 	frozenhillside
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Frozen Hillside";
 
 		2100
@@ -6979,7 +7109,7 @@ thingtypes
 
 	tutorial
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Tutorial";
 
 		799
@@ -6990,10 +7120,11 @@ thingtypes
 			height = 144;
 			parametertext = "Start frame";
 		}
+	}
 
 	flickies
 	{
-		color = 10; // Green
+		color = 3; // Teal
 		title = "Flickies";
 		width = 8;
 		height = 20;
diff --git a/extras/conf/udb/Includes/SRB222_common.cfg b/extras/conf/udb/Includes/SRB222_common.cfg
index 0ff044a6d382cc102c5b5dd98681e04ff3c81b31..8f37aabaa41de4072ede5b6910666fb654d7cfd0 100644
--- a/extras/conf/udb/Includes/SRB222_common.cfg
+++ b/extras/conf/udb/Includes/SRB222_common.cfg
@@ -15,7 +15,7 @@ common
 	ignoredextensions = "wad pk3 pk7 bak backup1 backup2 backup3 zip rar 7z";
 
 	// Default testing parameters
-	testparameters = "-file \"%AP\" \"%F\" -warp %L";
+	testparameters = "-folder \"%AF\" -file \"%AA\" \"%F\" -warp %L";
 	testshortpaths = true;
 
 	// Action special help
@@ -26,7 +26,7 @@ common
 	generalizedsectors = true;
 
 	// Maximum safe map size check (0 means skip check)
-	safeboundary = 1;
+	safeboundary = 0;
 
 	// Map boundaries. Map objects can only be placed within these boundaries
 	leftboundary = -32768;
@@ -40,6 +40,8 @@ common
 	defaultflatscale = 1.0f;
 	scaledtextureoffsets = true;
 
+	maxcolormapalpha = 25;
+
 	// Thing number for start position in 3D Mode
 	start3dmode = 3328;
 
@@ -68,137 +70,6 @@ common
 	}
 }
 
-mapformat_doom
-{
-	// The format interface handles the map data format
-	formatinterface = "DoomMapSetIO";
-
-	// Default nodebuilder configurations
-	defaultsavecompiler = "zennode_normal";
-	defaulttestcompiler = "zennode_fast";
-
-	/*
-	GAME DETECT PATTERN
-	Used to guess the game for which a WAD file is made.
-
-	1 = One of these lumps must exist
-	2 = None of these lumps must exist
-	3 = All of these lumps must exist
-	*/
-
-	gamedetect
-	{
-		EXTENDED = 2;
-
-
-		BEHAVIOR = 2;
-
-		E#M# = 2;
-
-		MAP?? = 1;
-	}
-
-	/*
-	MAP LUMP NAMES
-	Map lumps are loaded with the map as long as they are right after each other. When the editor
-	meets a lump which is not defined in this list it will ignore the map if not satisfied.
-	The order of items defines the order in which lumps will be written to WAD file on save.
-	To indicate the map header lump, use ~MAP
-
-	Legenda:
-	required = Lump is required to exist.
-	blindcopy = Lump will be copied along with the map blindly. (usefull for lumps Doom Builder doesn't use)
-	nodebuild = The nodebuilder generates this lump.
-	allowempty = The nodebuilder is allowed to leave this lump empty.
-	script = This lump is a text-based script. Specify the filename of the script configuration to use.
-	*/
-
-	maplumpnames
-	{
-		include("SRB222_misc.cfg", "doommaplumpnames");
-	}
-
-	// When this is set to true, sectors with the same tag will light up when a line is highlighted
-	linetagindicatesectors = true;
-
-	// Special linedefs
-	include("SRB222_misc.cfg", "speciallinedefs");
-
-	// Default flags for first new thing
-	defaultthingflags
-	{
-	}
-
-	// DEFAULT SECTOR BRIGHTNESS LEVELS
-	sectorbrightness
-	{
-		include("SRB222_misc.cfg", "sectorbrightness");
-	}
-
-	// SECTOR TYPES
-	sectortypes
-	{
-		include("SRB222_sectors.cfg", "sectortypes");
-	}
-
-	// GENERALISED SECTOR TYPES
-	gen_sectortypes
-	{
-		include("SRB222_sectors.cfg", "gen_sectortypes");
-	}
-
-	// LINEDEF FLAGS
-	linedefflags
-	{
-		include("SRB222_misc.cfg", "linedefflags");
-	}
-
-	// Linedef flags UDMF translation table
-	// This is needed for copy/paste and prefabs to work properly
-	// When the UDMF field name is prefixed with ! it is inverted
-	linedefflagstranslation
-	{
-		include("SRB222_misc.cfg", "linedefflagstranslation");
-	}
-
-	// LINEDEF ACTIVATIONS
-	linedefactivations
-	{
-	}
-
-	// LINEDEF TYPES
-	linedeftypes
-	{
-		include("SRB222_linedefs.cfg", "doom");
-	}
-
-	// THING FLAGS
-	thingflags
-	{
-		include("SRB222_misc.cfg", "thingflags");
-	}
-
-	// Thing flags UDMF translation table
-	// This is needed for copy/paste and prefabs to work properly
-	// When the UDMF field name is prefixed with ! it is inverted
-	thingflagstranslation
-	{
-		include("SRB222_misc.cfg", "thingflagstranslation");
-	}
-
-	// THING FLAGS ERROR MASK
-	// Mask for the thing flags which indicates the options
-	// that make the same thing appear in the same modes
-	thingflagsmask1 = 7;	// 1 + 2 + 4
-	thingflagsmask2 = 0;
-
-	// THING TYPES
-	thingtypes
-	{
-		include("SRB222_things.cfg", "doom");
-	}
-}
-
 mapformat_udmf
 {
 	// The format interface handles the map data format
@@ -222,9 +93,17 @@ mapformat_udmf
 	{
 		include("SRB222_misc.cfg", "universalfields");
 	}
+	
+	// Disable Doom-related modes that don't make sense for SRB2
+	soundsupport = false;
+	automapsupport = false;
 
 	// When this is set to true, sectors with the same tag will light up when a line is highlighted
 	linetagindicatesectors = false;
+	localsidedeftextureoffsets = true;
+	distinctfloorandceilingbrightness = true;
+	
+	planeequationsupport = true;
 
 	// Special linedefs
 	include("SRB222_misc.cfg", "speciallinedefs_udmf");
@@ -240,6 +119,11 @@ mapformat_udmf
 		include("SRB222_misc.cfg", "sectorflags");
 	}
 
+	sectorflagscategories
+	{
+		include("SRB222_misc.cfg", "sectorflagscategories");
+	}
+
 	// DEFAULT SECTOR BRIGHTNESS LEVELS
 	sectorbrightness
 	{
@@ -247,6 +131,7 @@ mapformat_udmf
 	}
 
 	damagetypes = "Generic Water Fire Lava Electric Spike DeathPitTilt DeathPitNoTilt Instakill SpecialStage";
+	triggerertypes = "Player AllPlayers Mobj";
 
 	// LINEDEF FLAGS
 	linedefflags
@@ -282,7 +167,6 @@ mapformat_udmf
 	// How to compare thing flags (for the stuck things error checker)
 	thingflagscompare
 	{
-		include("UDMF_misc.cfg", "thingflagscompare");
 	}
 
 	// THING TYPES
diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg
index 3a3bd80cc7f95774ef6380623dd7802c4f8183dd..68077862392a0ca2668d296f051203cd250e93ed 100644
--- a/extras/conf/udb/Includes/SRB222_linedefs.cfg
+++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg
@@ -1,1748 +1,3 @@
-doom
-{
-	misc
-	{
-		title = "Miscellaneous";
-
-		0
-		{
-			title = "None";
-			prefix = "(0)";
-		}
-		1
-		{
-			title = "Per-Sector Gravity";
-			prefix = "(1)";
-		}
-		5
-		{
-			title = "Camera Scanner <deprecated>";
-			prefix = "(5)";
-		}
-		7
-		{
-			title = "Sector Flat Alignment";
-			prefix = "(7)";
-		}
-		10
-		{
-			title = "Culling Plane";
-			prefix = "(10)";
-		}
-		13
-		{
-			title = "Heat Wave Effect";
-			prefix = "(13)";
-		}
-		40
-		{
-			title = "Visual Portal Between Tagged Linedefs";
-			prefix = "(40)";
-		}
-		41
-		{
-			title = "Horizon Effect";
-			prefix = "(41)";
-		}
-		50
-		{
-			title = "Instantly Lower Floor on Level Load";
-			prefix = "(50)";
-		}
-		51
-		{
-			title = "Instantly Raise Ceiling on Level Load";
-			prefix = "(51)";
-		}
-		63
-		{
-			title = "Fake Floor/Ceiling Planes";
-			prefix = "(63)";
-		}
-		540
-		{
-			title = "Floor Friction";
-			prefix = "(540)";
-		}
-	}
-
-	parameters
-	{
-		title = "Parameters";
-
-		2
-		{
-			title = "Custom Exit";
-			prefix = "(2)";
-		}
-		3
-		{
-			title = "Zoom Tube Parameters";
-			prefix = "(3)";
-		}
-		4
-		{
-			title = "Speed Pad Parameters";
-			prefix = "(4)";
-		}
-		8
-		{
-			title = "Special Sector Properties";
-			prefix = "(8)";
-		}
-		9
-		{
-			title = "Chain Parameters";
-			prefix = "(9)";
-		}
-		11
-		{
-			title = "Rope Hang Parameters";
-			prefix = "(11)";
-		}
-		12
-		{
-			title = "Rock Spawner Parameters";
-			prefix = "(12)";
-		}
-		14
-		{
-			title = "Bustable Block Parameters";
-			prefix = "(14)";
-		}
-		15
-		{
-			title = "Fan Particle Spawner Parameters";
-			prefix = "(15)";
-		}
-		16
-		{
-			title = "Minecart Parameters";
-			prefix = "(16)";
-		}
-		64
-		{
-			title = "Continuously Appearing/Disappearing FOF";
-			prefix = "(64)";
-		}
-		76
-		{
-			title = "Make FOF Bouncy";
-			prefix = "(76)";
-		}
-	}
-
-	polyobject
-	{
-		title = "PolyObject";
-
-		20
-		{
-			title = "First Line";
-			prefix = "(20)";
-		}
-		21
-		{
-			title = "Explicitly Include Line <disabled>";
-			prefix = "(21)";
-		}
-		22
-		{
-			title = "Parameters";
-			prefix = "(22)";
-		}
-		30
-		{
-			title = "Waving Flag";
-			prefix = "(30)";
-		}
-		31
-		{
-			title = "Displacement by Front Sector";
-			prefix = "(31)";
-		}
-		32
-		{
-			title = "Angular Displacement by Front Sector";
-			prefix = "(32)";
-		}
-	}
-
-	planemove
-	{
-		title = "Plane Movement";
-
-		52
-		{
-			title = "Continuously Falling Sector";
-			prefix = "(52)";
-		}
-		53
-		{
-			title = "Continuous Floor/Ceiling Mover";
-			prefix = "(53)";
-		}
-		54
-		{
-			title = "Continuous Floor Mover";
-			prefix = "(54)";
-		}
-		55
-		{
-			title = "Continuous Ceiling Mover";
-			prefix = "(55)";
-		}
-		56
-		{
-			title = "Continuous Two-Speed Floor/Ceiling Mover";
-			prefix = "(56)";
-		}
-		57
-		{
-			title = "Continuous Two-Speed Floor Mover";
-			prefix = "(57)";
-		}
-		58
-		{
-			title = "Continuous Two-Speed Ceiling Mover";
-			prefix = "(58)";
-		}
-		59
-		{
-			title = "Activate Moving Platform";
-			prefix = "(59)";
-		}
-		60
-		{
-			title = "Activate Moving Platform (Adjustable Speed)";
-			prefix = "(60)";
-		}
-		61
-		{
-			title = "Crusher (Ceiling to Floor)";
-			prefix = "(61)";
-		}
-		62
-		{
-			title = "Crusher (Floor to Ceiling)";
-			prefix = "(62)";
-		}
-		66
-		{
-			title = "Move Floor by Displacement";
-			prefix = "(66)";
-		}
-		67
-		{
-			title = "Move Ceiling by Displacement";
-			prefix = "(67)";
-		}
-		68
-		{
-			title = "Move Floor and Ceiling by Displacement";
-			prefix = "(68)";
-		}
-	}
-
-	fofsolid
-	{
-		title = "FOF (solid)";
-
-		100
-		{
-			title = "Solid, Opaque";
-			prefix = "(100)";
-		}
-		101
-		{
-			title = "Solid, Opaque, No Shadow";
-			prefix = "(101)";
-		}
-		102
-		{
-			title = "Solid, Translucent";
-			prefix = "(102)";
-		}
-		103
-		{
-			title = "Solid, Sides Only";
-			prefix = "(103)";
-		}
-		104
-		{
-			title = "Solid, No Sides";
-			prefix = "(104)";
-		}
-		105
-		{
-			title = "Solid, Invisible";
-			prefix = "(105)";
-		}
-		140
-		{
-			title = "Intangible from Bottom, Opaque";
-			prefix = "(140)";
-		}
-		141
-		{
-			title = "Intangible from Bottom, Translucent";
-			prefix = "(141)";
-		}
-		142
-		{
-			title = "Intangible from Bottom, Translucent, No Sides";
-			prefix = "(142)";
-		}
-		143
-		{
-			title = "Intangible from Top, Opaque";
-			prefix = "(143)";
-		}
-		144
-		{
-			title = "Intangible from Top, Translucent";
-			prefix = "(144)";
-		}
-		145
-		{
-			title = "Intangible from Top, Translucent, No Sides";
-			prefix = "(145)";
-		}
-		146
-		{
-			title = "Only Tangible from Sides";
-			prefix = "(146)";
-		}
-	}
-
-	fofintangible
-	{
-		title = "FOF (intangible)";
-
-		120
-		{
-			title = "Water, Opaque";
-			prefix = "(120)";
-		}
-		121
-		{
-			title = "Water, Translucent";
-			prefix = "(121)";
-		}
-		122
-		{
-			title = "Water, Opaque, No Sides";
-			prefix = "(122)";
-		}
-		123
-		{
-			title = "Water, Translucent, No Sides";
-			prefix = "(123)";
-		}
-		124
-		{
-			title = "Goo Water, Translucent";
-			prefix = "(124)";
-		}
-		125
-		{
-			title = "Goo Water, Translucent, No Sides";
-			prefix = "(125)";
-		}
-		220
-		{
-			title = "Intangible, Opaque";
-			prefix = "(220)";
-		}
-		221
-		{
-			title = "Intangible, Translucent";
-			prefix = "(221)";
-		}
-		222
-		{
-			title = "Intangible, Sides Only";
-			prefix = "(222)";
-		}
-		223
-		{
-			title = "Intangible, Invisible";
-			prefix = "(223)";
-		}
-	}
-
-	fofmoving
-	{
-		title = "FOF (moving)";
-
-		150
-		{
-			title = "Air Bobbing";
-			prefix = "(150)";
-		}
-		151
-		{
-			title = "Air Bobbing (Adjustable)";
-			prefix = "(151)";
-		}
-		152
-		{
-			title = "Reverse Air Bobbing (Adjustable)";
-			prefix = "(152)";
-		}
-		153
-		{
-			title = "Dynamically Sinking Platform";
-			prefix = "(153)";
-		}
-		160
-		{
-			title = "Water Bobbing";
-			prefix = "(160)";
-		}
-		190
-		{
-			title = "Rising Platform, Solid, Opaque";
-			prefix = "(190)";
-		}
-		191
-		{
-			title = "Rising Platform, Solid, Opaque, No Shadow";
-			prefix = "(191)";
-		}
-		192
-		{
-			title = "Rising Platform, Solid, Translucent";
-			prefix = "(192)";
-		}
-		193
-		{
-			title = "Rising Platform, Solid, Invisible";
-			prefix = "(193)";
-		}
-		194
-		{
-			title = "Rising Platform, Intangible from Bottom, Opaque";
-			prefix = "(194)";
-		}
-		195
-		{
-			title = "Rising Platform, Intangible from Bottom, Translucent";
-			prefix = "(195)";
-		}
-	}
-
-	fofcrumbling
-	{
-		title = "FOF (crumbling)";
-
-		170
-		{
-			title = "Crumbling, Respawn";
-			prefix = "(170)";
-		}
-		171
-		{
-			title = "Crumbling, No Respawn";
-			prefix = "(171)";
-		}
-		172
-		{
-			title = "Crumbling, Respawn, Intangible from Bottom";
-			prefix = "(172)";
-		}
-		173
-		{
-			title = "Crumbling, No Respawn, Intangible from Bottom";
-			prefix = "(173)";
-		}
-		174
-		{
-			title = "Crumbling, Respawn, Int. from Bottom, Translucent";
-			prefix = "(174)";
-		}
-		175
-		{
-			title = "Crumbling, No Respawn, Int. from Bottom, Translucent";
-			prefix = "(175)";
-		}
-		176
-		{
-			title = "Crumbling, Respawn, Floating, Bobbing";
-			prefix = "(176)";
-		}
-		177
-		{
-			title = "Crumbling, No Respawn, Floating, Bobbing";
-			prefix = "(177)";
-		}
-		178
-		{
-			title = "Crumbling, Respawn, Floating";
-			prefix = "(178)";
-		}
-		179
-		{
-			title = "Crumbling, No Respawn, Floating";
-			prefix = "(179)";
-		}
-		180
-		{
-			title = "Crumbling, Respawn, Air Bobbing";
-			prefix = "(180)";
-		}
-	}
-
-	fofspecial
-	{
-		title = "FOF (special)";
-
-		200
-		{
-			title = "Light Block";
-			prefix = "(200)";
-		}
-		201
-		{
-			title = "Half Light Block";
-			prefix = "(201)";
-		}
-		202
-		{
-			title = "Fog Block";
-			prefix = "(202)";
-		}
-		250
-		{
-			title = "Mario Block";
-			prefix = "(250)";
-		}
-		251
-		{
-			title = "Thwomp Block";
-			prefix = "(251)";
-		}
-		252
-		{
-			title = "Shatter Block";
-			prefix = "(252)";
-		}
-		253
-		{
-			title = "Shatter Block, Translucent";
-			prefix = "(253)";
-		}
-		254
-		{
-			title = "Bustable Block";
-			prefix = "(254)";
-		}
-		255
-		{
-			title = "Spin-Bustable Block";
-			prefix = "(255)";
-		}
-		256
-		{
-			title = "Spin-Bustable Block, Translucent";
-			prefix = "(256)";
-		}
-		257
-		{
-			title = "Quicksand";
-			prefix = "(257)";
-		}
-		258
-		{
-			title = "Laser";
-			prefix = "(258)";
-		}
-		259
-		{
-			title = "Custom FOF";
-			prefix = "(259)";
-		}
-	}
-
-	linedeftrigger
-	{
-		title = "Linedef Executor Trigger";
-
-		300
-		{
-			title = "Continuous";
-			prefix = "(300)";
-		}
-		301
-		{
-			title = "Each Time";
-			prefix = "(301)";
-		}
-		302
-		{
-			title = "Once";
-			prefix = "(302)";
-		}
-		303
-		{
-			title = "Ring Count - Continuous";
-			prefix = "(303)";
-		}
-		304
-		{
-			title = "Ring Count - Once";
-			prefix = "(304)";
-		}
-		305
-		{
-			title = "Character Ability - Continuous";
-			prefix = "(305)";
-		}
-		306
-		{
-			title = "Character Ability - Each Time";
-			prefix = "(306)";
-		}
-		307
-		{
-			title = "Character Ability - Once";
-			prefix = "(307)";
-		}
-		308
-		{
-			title = "Race Only - Once";
-			prefix = "(308)";
-		}
-		309
-		{
-			title = "CTF Red Team - Continuous";
-			prefix = "(309)";
-		}
-		310
-		{
-			title = "CTF Red Team - Each Time";
-			prefix = "(310)";
-		}
-		311
-		{
-			title = "CTF Blue Team - Continuous";
-			prefix = "(311)";
-		}
-		312
-		{
-			title = "CTF Blue Team - Each Time";
-			prefix = "(312)";
-		}
-		313
-		{
-			title = "No More Enemies - Once";
-			prefix = "(313)";
-		}
-		314
-		{
-			title = "Number of Pushables - Continuous";
-			prefix = "(314)";
-		}
-		315
-		{
-			title = "Number of Pushables - Once";
-			prefix = "(315)";
-		}
-		317
-		{
-			title = "Condition Set Trigger - Continuous";
-			prefix = "(317)";
-		}
-		318
-		{
-			title = "Condition Set Trigger - Once";
-			prefix = "(318)";
-		}
-		319
-		{
-			title = "Unlockable - Continuous";
-			prefix = "(319)";
-		}
-		320
-		{
-			title = "Unlockable - Once";
-			prefix = "(320)";
-		}
-		321
-		{
-			title = "Trigger After X Calls - Continuous";
-			prefix = "(321)";
-		}
-		322
-		{
-			title = "Trigger After X Calls - Each Time";
-			prefix = "(322)";
-		}
-		323
-		{
-			title = "NiGHTSerize - Each Time";
-			prefix = "(323)";
-		}
-		324
-		{
-			title = "NiGHTSerize - Once";
-			prefix = "(324)";
-		}
-		325
-		{
-			title = "De-NiGHTSerize - Each Time";
-			prefix = "(325)";
-		}
-		326
-		{
-			title = "De-NiGHTSerize - Once";
-			prefix = "(326)";
-		}
-		327
-		{
-			title = "NiGHTS Lap - Each Time";
-			prefix = "(327)";
-		}
-		328
-		{
-			title = "NiGHTS Lap - Once";
-			prefix = "(328)";
-		}
-		329
-		{
-			title = "Ideya Capture Touch - Each Time";
-			prefix = "(329)";
-		}
-		330
-		{
-			title = "Ideya Capture Touch - Once";
-			prefix = "(330)";
-		}
-		331
-		{
-			title = "Player Skin - Continuous";
-			flags64text = "[6] Disable for this skin";
-			prefix = "(331)";
-		}
-		332
-		{
-			title = "Player Skin - Each Time";
-			prefix = "(332)";
-		}
-		333
-		{
-			title = "Player Skin - Once";
-			prefix = "(333)";
-		}
-		334
-		{
-			title = "Object Dye - Continuous";
-			prefix = "(334)";
-		}
-		335
-		{
-			title = "Object Dye - Each Time";
-			prefix = "(335)";
-		}
-		336
-		{
-			title = "Object Dye - Once";
-			prefix = "(336)";
-		}
-		337
-		{
-			title = "Emerald Check - Continuous";
-			prefix = "(337)";
-		}
-		338
-		{
-			title = "Emerald Check - Each Time";
-			prefix = "(338)";
-		}
-		339
-		{
-			title = "Emerald Check - Once";
-			prefix = "(339)";
-		}
-		340
-		{
-			title = "NiGHTS Mare - Continuous";
-			prefix = "(340)";
-		}
-		341
-		{
-			title = "NiGHTS Mare - Each Time";
-			prefix = "(341)";
-		}
-		342
-		{
-			title = "NiGHTS Mare - Once";
-			prefix = "(342)";
-		}
-		343
-		{
-			title = "Gravity Check - Continuous";
-			prefix = "(343)";
-		}
-		344
-		{
-			title = "Gravity Check - Each Time";
-			prefix = "(344)";
-		}
-		345
-		{
-			title = "Gravity Check - Once";
-			prefix = "(345)";
-		}
-		399
-		{
-			title = "Level Load";
-			prefix = "(399)";
-		}
-	}
-
-	linedefexecsector
-	{
-		title = "Linedef Executor (sector)";
-
-		400
-		{
-			title = "Set Tagged Sector's Floor Height/Texture";
-			prefix = "(400)";
-		}
-		401
-		{
-			title = "Set Tagged Sector's Ceiling Height/Texture";
-			prefix = "(401)";
-		}
-		402
-		{
-			title = "Copy Light Level to Tagged Sectors";
-			prefix = "(402)";
-		}
-		408
-		{
-			title = "Set Tagged Sector's Flats";
-			prefix = "(408)";
-		}
-		409
-		{
-			title = "Change Tagged Sector's Tag";
-			prefix = "(409)";
-		}
-		410
-		{
-			title = "Change Front Sector's Tag";
-			prefix = "(410)";
-		}
-		416
-		{
-			title = "Start Adjustable Flickering Light";
-			prefix = "(416)";
-		}
-		417
-		{
-			title = "Start Adjustable Pulsating Light";
-			prefix = "(417)";
-		}
-		418
-		{
-			title = "Start Adjustable Blinking Light (unsynchronized)";
-			prefix = "(418)";
-		}
-		419
-		{
-			title = "Start Adjustable Blinking Light (synchronized)";
-			prefix = "(419)";
-		}
-		420
-		{
-			title = "Fade Light Level";
-			prefix = "(420)";
-		}
-		421
-		{
-			title = "Stop Lighting Effect";
-			prefix = "(421)";
-		}
-		435
-		{
-			title = "Change Plane Scroller Direction";
-			prefix = "(435)";
-		}
-		467
-		{
-			title = "Set Tagged Sector's Light Level";
-			prefix = "(467)";
-		}
-	}
-
-	linedefexecplane
-	{
-		title = "Linedef Executor (plane movement)";
-
-		403
-		{
-			title = "Move Tagged Sector's Floor";
-			prefix = "(403)";
-		}
-		404
-		{
-			title = "Move Tagged Sector's Ceiling";
-			prefix = "(404)";
-		}
-		405
-		{
-			title = "Move Floor According to Front Texture Offsets";
-			prefix = "(405)";
-		}
-		407
-		{
-			title = "Move Ceiling According to Front Texture Offsets";
-			prefix = "(407)";
-		}
-		411
-		{
-			title = "Stop Plane Movement";
-			prefix = "(411)";
-		}
-		428
-		{
-			title = "Start Platform Movement";
-			prefix = "(428)";
-		}
-		429
-		{
-			title = "Crush Ceiling Once";
-			prefix = "(429)";
-		}
-		430
-		{
-			title = "Crush Floor Once";
-			prefix = "(430)";
-		}
-		431
-		{
-			title = "Crush Floor and Ceiling Once";
-			prefix = "(431)";
-		}
-	}
-
-	linedefexecplayer
-	{
-		title = "Linedef Executor (player/object)";
-
-		412
-		{
-			title = "Teleporter";
-			prefix = "(412)";
-		}
-		425
-		{
-			title = "Change Object State";
-			prefix = "(425)";
-		}
-		426
-		{
-			title = "Stop Object";
-			prefix = "(426)";
-		}
-		427
-		{
-			title = "Award Score";
-			prefix = "(427)";
-		}
-		432
-		{
-			title = "Enable/Disable 2D Mode";
-			prefix = "(432)";
-		}
-		433
-		{
-			title = "Enable/Disable Gravity Flip";
-			prefix = "(433)";
-		}
-		434
-		{
-			title = "Award Power-Up";
-			prefix = "(434)";
-		}
-		437
-		{
-			title = "Disable Player Control";
-			prefix = "(437)";
-		}
-		438
-		{
-			title = "Change Object Size";
-			prefix = "(438)";
-		}
-		442
-		{
-			title = "Change Object Type State";
-			prefix = "(442)";
-		}
-		457
-		{
-			title = "Track Object's Angle";
-			prefix = "(457)";
-		}
-		458
-		{
-			title = "Stop Tracking Object's Angle";
-			prefix = "(458)";
-		}
-		460
-		{
-			title = "Award Rings";
-			prefix = "(460)";
-		}
-		461
-		{
-			title = "Spawn Object";
-			prefix = "(461)";
-		}
-		462
-		{
-			title = "Stop Timer/Exit Stage in Record Attack";
-			prefix = "(462)";
-		}
-		463
-		{
-			title = "Dye Object";
-			prefix = "(463)";
-		}
-		464
-		{
-			title = "Trigger Egg Capsule";
-			prefix = "(464)";
-		}
-		466
-		{
-			title = "Set Level Failure State";
-			prefix = "(466)";
-		}
-	}
-
-	linedefexecmisc
-	{
-		title = "Linedef Executor (misc.)";
-
-		413
-		{
-			title = "Change Music";
-			prefix = "(413)";
-		}
-		414
-		{
-			title = "Play Sound Effect";
-			prefix = "(414)";
-		}
-		415
-		{
-			title = "Run Script";
-			prefix = "(415)";
-		}
-		422
-		{
-			title = "Switch to Cut-Away View";
-			prefix = "(422)";
-		}
-		423
-		{
-			title = "Change Sky";
-			prefix = "(423)";
-		}
-		424
-		{
-			title = "Change Weather";
-			prefix = "(424)";
-		}
-		436
-		{
-			title = "Shatter FOF";
-			prefix = "(436)";
-		}
-		439
-		{
-			title = "Change Tagged Linedef's Textures";
-			prefix = "(439)";
-		}
-		440
-		{
-			title = "Start Metal Sonic Race";
-			prefix = "(440)";
-		}
-		441
-		{
-			title = "Condition Set Trigger";
-			prefix = "(441)";
-		}
-		443
-		{
-			title = "Call Lua Function";
-			prefix = "(443)";
-		}
-		444
-		{
-			title = "Earthquake";
-			prefix = "(444)";
-		}
-		445
-		{
-			title = "Make FOF Disappear/Reappear";
-			prefix = "(445)";
-		}
-		446
-		{
-			title = "Make FOF Crumble";
-			prefix = "(446)";
-		}
-		447
-		{
-			title = "Change Tagged Sector's Colormap";
-			prefix = "(447)";
-		}
-		448
-		{
-			title = "Change Skybox";
-			prefix = "(448)";
-		}
-		449
-		{
-			title = "Enable Bosses with Parameter";
-			prefix = "(449)";
-		}
-		450
-		{
-			title = "Execute Linedef Executor (specific tag)";
-			prefix = "(450)";
-		}
-		451
-		{
-			title = "Execute Linedef Executor (random tag in range)";
-			prefix = "(451)";
-		}
-		452
-		{
-			title = "Set FOF Translucency";
-			prefix = "(452)";
-		}
-		453
-		{
-			title = "Fade FOF";
-			prefix = "(453)";
-		}
-		454
-		{
-			title = "Stop Fading FOF";
-			prefix = "(454)";
-		}
-		455
-		{
-			title = "Fade Tagged Sector's Colormap";
-			prefix = "(455)";
-		}
-		456
-		{
-			title = "Stop Fading Tagged Sector's Colormap";
-			prefix = "(456)";
-		}
-		459
-		{
-			title = "Control Text Prompt";
-			prefix = "(459)";
-		}
-	}
-
-	linedefexecpoly
-	{
-		title = "Linedef Executor (polyobject)";
-
-		480
-		{
-			title = "Door Slide";
-			prefix = "(480)";
-		}
-		481
-		{
-			title = "Door Swing";
-			prefix = "(481)";
-		}
-		482
-		{
-			title = "Move";
-			prefix = "(482)";
-		}
-		483
-		{
-			title = "Move, Override";
-			prefix = "(483)";
-		}
-		484
-		{
-			title = "Rotate Right";
-			prefix = "(484)";
-		}
-		485
-		{
-			title = "Rotate Right, Override";
-			prefix = "(485)";
-		}
-		486
-		{
-			title = "Rotate Left";
-			prefix = "(486)";
-		}
-		487
-		{
-			title = "Rotate Left, Override";
-			prefix = "(487)";
-		}
-		488
-		{
-			title = "Move by Waypoints";
-			prefix = "(488)";
-		}
-		489
-		{
-			title = "Turn Invisible, Intangible";
-			prefix = "(489)";
-		}
-		490
-		{
-			title = "Turn Visible, Tangible";
-			prefix = "(490)";
-		}
-		491
-		{
-			title = "Set Translucency";
-			prefix = "(491)";
-		}
-		492
-		{
-			title = "Fade Translucency";
-			prefix = "(492)";
-		}
-	}
-
-	wallscroll
-	{
-		title = "Wall Scrolling";
-
-		500
-		{
-			title = "Scroll Wall Front Side Left";
-			prefix = "(500)";
-		}
-		501
-		{
-			title = "Scroll Wall Front Side Right";
-			prefix = "(501)";
-		}
-		502
-		{
-			title = "Scroll Wall According to Linedef";
-			prefix = "(502)";
-		}
-		503
-		{
-			title = "Scroll Wall According to Linedef (Accelerative)";
-			prefix = "(503)";
-		}
-		504
-		{
-			title = "Scroll Wall According to Linedef (Displacement)";
-			prefix = "(504)";
-		}
-		505
-		{
-			title = "Scroll Texture by Front Side Offsets";
-			prefix = "(505)";
-		}
-		506
-		{
-			title = "Scroll Texture by Back Side Offsets";
-			prefix = "(506)";
-		}
-	}
-
-	planescroll
-	{
-		title = "Plane Scrolling";
-
-		510
-		{
-			title = "Scroll Floor Texture";
-			prefix = "(510)";
-		}
-		511
-		{
-			title = "Scroll Floor Texture (Accelerative)";
-			prefix = "(511)";
-		}
-		512
-		{
-			title = "Scroll Floor Texture (Displacement)";
-			prefix = "(512)";
-		}
-		513
-		{
-			title = "Scroll Ceiling Texture";
-			prefix = "(513)";
-		}
-		514
-		{
-			title = "Scroll Ceiling Texture (Accelerative)";
-			prefix = "(514)";
-		}
-		515
-		{
-			title = "Scroll Ceiling Texture (Displacement)";
-			prefix = "(515)";
-		}
-		520
-		{
-			title = "Carry Objects on Floor";
-			prefix = "(520)";
-		}
-		521
-		{
-			title = "Carry Objects on Floor (Accelerative)";
-			prefix = "(521)";
-		}
-		522
-		{
-			title = "Carry Objects on Floor (Displacement)";
-			prefix = "(522)";
-		}
-		523
-		{
-			title = "Carry Objects on Ceiling";
-			prefix = "(523)";
-		}
-		524
-		{
-			title = "Carry Objects on Ceiling (Accelerative)";
-			prefix = "(524)";
-		}
-		525
-		{
-			title = "Carry Objects on Ceiling (Displacement)";
-			prefix = "(525)";
-		}
-		530
-		{
-			title = "Scroll Floor Texture and Carry Objects";
-			prefix = "(530)";
-		}
-		531
-		{
-			title = "Scroll Floor Texture and Carry Objects (Accelerative)";
-			prefix = "(531)";
-		}
-		532
-		{
-			title = "Scroll Floor Texture and Carry Objects (Displacement)";
-			prefix = "(532)";
-		}
-		533
-		{
-			title = "Scroll Ceiling Texture and Carry Objects";
-			prefix = "(533)";
-		}
-		534
-		{
-			title = "Scroll Ceiling Texture and Carry Objects (Accelerative)";
-			prefix = "(534)";
-		}
-		535
-		{
-			title = "Scroll Ceiling Texture and Carry Objects (Displacement)";
-			prefix = "(535)";
-		}
-	}
-
-	pusher
-	{
-		title = "Pusher";
-
-		541
-		{
-			title = "Wind";
-			prefix = "(541)";
-		}
-		542
-		{
-			title = "Upwards Wind";
-			prefix = "(542)";
-		}
-		543
-		{
-			title = "Downwards Wind";
-			prefix = "(543)";
-		}
-		544
-		{
-			title = "Current";
-			prefix = "(544)";
-		}
-		545
-		{
-			title = "Upwards Current";
-			prefix = "(545)";
-		}
-		546
-		{
-			title = "Downwards Current";
-			prefix = "(546)";
-		}
-		547
-		{
-			title = "Push/Pull";
-			prefix = "(547)";
-		}
-	}
-
-	light
-	{
-		title = "Lighting";
-
-		600
-		{
-			title = "Floor Lighting";
-			prefix = "(600)";
-		}
-		601
-		{
-			title = "Ceiling Lighting";
-			prefix = "(601)";
-		}
-		602
-		{
-			title = "Adjustable Pulsating Light";
-			prefix = "(602)";
-		}
-		603
-		{
-			title = "Adjustable Flickering Light";
-			prefix = "(603)";
-		}
-		604
-		{
-			title = "Adjustable Blinking Light (unsynchronized)";
-			prefix = "(604)";
-		}
-		605
-		{
-			title = "Adjustable Blinking Light (synchronized)";
-			prefix = "(605)";
-		}
-		606
-		{
-			title = "Colormap";
-			prefix = "(606)";
-		}
-	}
-
-	slope
-	{
-		title = "Slope";
-
-		700
-		{
-			title = "Slope Frontside Floor";
-			prefix = "(700)";
-		}
-		701
-		{
-			title = "Slope Frontside Ceiling";
-			prefix = "(701)";
-		}
-		702
-		{
-			title = "Slope Frontside Floor and Ceiling";
-			prefix = "(702)";
-		}
-		703
-		{
-			title = "Slope Frontside Floor and Backside Ceiling";
-			prefix = "(703)";
-´		}
-		704
-		{
-			title = "Slope Frontside Floor by 3 Tagged Vertex Things";
-			prefix = "(704)";
-		}
-		705
-		{
-			title = "Slope Frontside Ceiling by 3 Tagged Vertex Things";
-			prefix = "(705)";
-		}
-		710
-		{
-			title = "Slope Backside Floor";
-			prefix = "(710)";
-		}
-		711
-		{
-			title = "Slope Backside Ceiling";
-			prefix = "(711)";
-		}
-		712
-		{
-			title = "Slope Backside Floor and Ceiling";
-			prefix = "(712)";
-		}
-		713
-		{
-			title = "Slope Backside Floor and Frontside Ceiling";
-			prefix = "(713)";
-		}
-		714
-		{
-			title = "Slope Backside Floor by 3 Tagged Vertex Things";
-			prefix = "(714)";
-		}
-		715
-		{
-			title = "Slope Backside Ceiling by 3 Tagged Vertex Things";
-			prefix = "(715)";
-		}
-		720
-		{
-			title = "Copy Frontside Floor Slope from Line Tag";
-			prefix = "(720)";
-		}
-		721
-		{
-			title = "Copy Frontside Ceiling Slope from Line Tag";
-			prefix = "(721)";
-		}
-		722
-		{
-			title = "Copy Frontside Floor and Ceiling Slope from Line Tag";
-			prefix = "(722)";
-		}
-		799
-		{
-			title = "Set Tagged Dynamic Slope Vertex to Front Sector Height";
-			prefix = "(799)";
-		}
-	}
-
-	transwall
-	{
-		title = "Translucent Wall";
-
-		900
-		{
-			title = "90% Opaque";
-			prefix = "(900)";
-		}
-		901
-		{
-			title = "80% Opaque";
-			prefix = "(901)";
-		}
-		902
-		{
-			title = "70% Opaque";
-			prefix = "(902)";
-		}
-		903
-		{
-			title = "60% Opaque";
-			prefix = "(903)";
-		}
-		904
-		{
-			title = "50% Opaque";
-			prefix = "(904)";
-		}
-		905
-		{
-			title = "40% Opaque";
-			prefix = "(905)";
-		}
-		906
-		{
-			title = "30% Opaque";
-			prefix = "(906)";
-		}
-		907
-		{
-			title = "20% Opaque";
-			prefix = "(907)";
-		}
-		908
-		{
-			title = "10% Opaque";
-			prefix = "(908)";
-		}
-		909
-		{
-			title = "Fog Wall";
-			prefix = "(909)";
-		}
-		910
-		{
-			title = "100% Additive";
-			prefix = "(910)";
-		}
-		911
-		{
-			title = "90% Additive";
-			prefix = "(911)";
-		}
-		912
-		{
-			title = "80% Additive";
-			prefix = "(912)";
-		}
-		913
-		{
-			title = "70% Additive";
-			prefix = "(913)";
-		}
-		914
-		{
-			title = "60% Additive";
-			prefix = "(914)";
-		}
-		915
-		{
-			title = "50% Additive";
-			prefix = "(915)";
-		}
-		916
-		{
-			title = "40% Additive";
-			prefix = "(916)";
-		}
-		917
-		{
-			title = "30% Additive";
-			prefix = "(917)";
-		}
-		918
-		{
-			title = "20% Additive";
-			prefix = "(918)";
-		}
-		919
-		{
-			title = "10% Additive";
-			prefix = "(919)";
-		}
-		920
-		{
-			title = "100% Subtractive";
-			prefix = "(920)";
-		}
-		921
-		{
-			title = "90% Subtractive";
-			prefix = "(921)";
-		}
-		922
-		{
-			title = "80% Subtractive";
-			prefix = "(922)";
-		}
-		923
-		{
-			title = "70% Subtractive";
-			prefix = "(923)";
-		}
-		924
-		{
-			title = "60% Subtractive";
-			prefix = "(924)";
-		}
-		925
-		{
-			title = "50% Subtractive";
-			prefix = "(925)";
-		}
-		926
-		{
-			title = "40% Subtractive";
-			prefix = "(926)";
-		}
-		927
-		{
-			title = "30% Subtractive";
-			prefix = "(927)";
-		}
-		928
-		{
-			title = "20% Subtractive";
-			prefix = "(928)";
-		}
-		929
-		{
-			title = "10% Subtractive";
-			prefix = "(929)";
-		}
-		930
-		{
-			title = "100% Reverse Subtractive";
-			prefix = "(930)";
-		}
-		931
-		{
-			title = "90% Reverse Subtractive";
-			prefix = "(931)";
-		}
-		932
-		{
-			title = "80% Reverse Subtractive";
-			prefix = "(932)";
-		}
-		933
-		{
-			title = "70% Reverse Subtractive";
-			prefix = "(933)";
-		}
-		934
-		{
-			title = "60% Reverse Subtractive";
-			prefix = "(934)";
-		}
-		935
-		{
-			title = "50% Reverse Subtractive";
-			prefix = "(935)";
-		}
-		936
-		{
-			title = "40% Reverse Subtractive";
-			prefix = "(936)";
-		}
-		937
-		{
-			title = "30% Reverse Subtractive";
-			prefix = "(937)";
-		}
-		938
-		{
-			title = "20% Reverse Subtractive";
-			prefix = "(938)";
-		}
-		939
-		{
-			title = "10% Reverse Subtractive";
-			prefix = "(939)";
-		}
-		940
-		{
-			title = "Modulate";
-			prefix = "(940)";
-		}
-	}
-}
-
 udmf
 {
 	misc
@@ -2457,6 +712,7 @@ udmf
 		{
 			title = "Solid";
 			prefix = "(100)";
+			id = "srb2_fofsolid";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2499,6 +755,7 @@ udmf
 		{
 			title = "Water";
 			prefix = "(120)";
+			id = "srb2_fofwater";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2535,6 +792,7 @@ udmf
 		{
 			title = "Air Bobbing";
 			prefix = "(150)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2561,6 +819,7 @@ udmf
 		{
 			title = "Water Bobbing";
 			prefix = "(160)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2572,6 +831,7 @@ udmf
 		{
 			title = "Crumbling";
 			prefix = "(170)";
+			id = "srb2_fofcrumbling";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2608,11 +868,12 @@ udmf
 				}
 			}
 		}
-
+		
 		190
 		{
 			title = "Rising";
 			prefix = "(190)";
+			id = "srb2_fofsolid";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2669,6 +930,7 @@ udmf
 		{
 			title = "Light Block";
 			prefix = "(200)";
+			id = "srb2_foflight";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2686,6 +948,7 @@ udmf
 		{
 			title = "Fog Block";
 			prefix = "(202)";
+			id = "srb2_foffog";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2697,6 +960,7 @@ udmf
 		{
 			title = "Intangible";
 			prefix = "(220)";
+			id = "srb2_fofintangible";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2733,6 +997,7 @@ udmf
 		{
 			title = "Intangible, Invisible";
 			prefix = "(223)";
+			id = "srb2_fofintangibleinvisible";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2744,6 +1009,7 @@ udmf
 		{
 			title = "Mario Block";
 			prefix = "(250)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2765,6 +1031,7 @@ udmf
 		{
 			title = "Thwomp Block";
 			prefix = "(251)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2789,6 +1056,7 @@ udmf
 		{
 			title = "Bustable Block";
 			prefix = "(254)";
+			id = "srb2_fofbustable";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2840,6 +1108,7 @@ udmf
 		{
 			title = "Quicksand";
 			prefix = "(257)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2865,6 +1134,7 @@ udmf
 		{
 			title = "Laser";
 			prefix = "(258)";
+			id = "srb2_foflaser";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2897,6 +1167,7 @@ udmf
 		{
 			title = "Custom";
 			prefix = "(259)";
+			id = "srb2_fofcustom";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2950,52 +1221,6 @@ udmf
 				}
 			}
 		}
-		260
-		{
-			title = "Generalized 3D Floor";
-			prefix = "(260)";
-			id = "Sector_Set3dFloor";
-			requiresactivation = false;
-
-			arg0
-			{
-				title = "Target sector tag";
-				type = 13;
-			}
-			arg1
-			{
-				title = "Type";
-				type = 26;
-				default = 1;
-				enum
-				{
-					1 = "Solid";
-					2 = "Water";
-					3 = "Intangible";
-				}
-				flags
-				{
-					4 = "Render insides";
-					16 = "Only render insides";
-				}
-			}
-			arg2
-			{
-				title = "Flags";
-				type = 12;
-				enum
-				{
-					1 = "No shadow";
-					2 = "Double shadow";
-					4 = "Fog";
-				}
-			}
-			arg3
-			{
-				title = "Alpha";
-				default = 255;
-			}
-		}
 	}
 
 	linedeftrigger
@@ -5641,6 +3866,7 @@ udmf
 		{
 			title = "Create Vertex-Based Slope";
 			prefix = "(704)";
+			id = "srb2_vertexslope";
 			arg0
 			{
 				title = "Plane";
@@ -5684,6 +3910,7 @@ udmf
 		{
 			title = "Copy Slope";
 			prefix = "(720)";
+			id = "plane_copy";
 			arg0
 			{
 				title = "Front floor tag";
diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg
index ed0488a3ff872aa28e5262ee876ccd2b53ce1397..e274fece69ea37d4be43cd8b7560fde69e181ee5 100644
--- a/extras/conf/udb/Includes/SRB222_misc.cfg
+++ b/extras/conf/udb/Includes/SRB222_misc.cfg
@@ -1,24 +1,3 @@
-linedefflags
-{
-	1 = "[0] Impassable";
-	2 = "[1] Block Enemies";
-	4 = "[2] Double-Sided";
-	8 = "[3] Upper Unpegged";
-	16 = "[4] Lower Unpegged";
-	32 = "[5] Slope Skew (E1)";
-	64 = "[6] Not Climbable";
-	128 = "[7] No Midtexture Skew (E2)";
-	256 = "[8] Peg Midtexture (E3)";
-	512 = "[9] Solid Midtexture (E4)";
-	1024 = "[10] Repeat Midtexture (E5)";
-	2048 = "[11] Netgame Only";
-	4096 = "[12] No Netgame";
-	8192 = "[13] Effect 6";
-	16384 = "[14] Bouncy Wall";
-	32768 = "[15] Transfer Line";
-}
-
-
 // Linedef flags UDMF translation table
 // This is needed for copy/paste and prefabs to work properly
 // When the UDMF field name is prefixed with ! it is inverted
@@ -42,7 +21,6 @@ linedefflagstranslation
 	32768 = "transfer";
 }
 
-
 linedefflags_udmf
 {
 	blocking = "Impassable";
@@ -74,19 +52,13 @@ linedefrenderstyles
 
 sectorflags
 {
-	colormapfog = "Fog Planes in Colormap";
-	colormapfadesprites = "Fade Fullbright in Colormap";
-	colormapprotected = "Protected Colormap";
-	flipspecial_nofloor = "No Trigger on Floor Touch";
-	flipspecial_ceiling = "Trigger on Ceiling Touch";
-	triggerspecial_touch = "Trigger on Edge Touch";
-	triggerspecial_headbump = "Trigger on Headbump";
-	triggerline_plane = "Linedef Trigger Requires Plane Touch";
-	triggerline_mobj = "Non-Pushables Can Trigger Linedef";
 	invertprecip = "Invert Precipitation";
 	gravityflip = "Flip Objects in Reverse Gravity";
 	heatwave = "Heat Wave";
 	noclipcamera = "Intangible to the Camera";
+	colormapfog = "Fog Planes";
+	colormapfadesprites = "Fade Fullbright";
+	colormapprotected = "Protected from Tagging";
 	outerspace = "Space Countdown";
 	doublestepup = "Ramp Sector (double step-up/down)";
 	nostepdown = "Non-Ramp Sector (No step-down)";
@@ -104,23 +76,59 @@ sectorflags
 	zoomtubeend = "Zoom Tube End";
 	finishline = "Circuit Finish Line";
 	ropehang = "Rope Hang";
+	jumpflip = "Flip Gravity on Jump";
+	gravityoverride = "Make Reverse Gravity Temporary";
+	flipspecial_nofloor = "No Trigger on Floor Touch";
+	flipspecial_ceiling = "Trigger on Ceiling Touch";
+	triggerspecial_touch = "Trigger on Edge Touch";
+	triggerspecial_headbump = "Trigger on Headbump";
+	triggerline_plane = "Linedef Trigger Requires Plane Touch";
+	triggerline_mobj = "Non-Pushables Can Trigger Linedef";
 }
 
-thingflags
+sectorflagscategories
 {
-	1 = "[1] Extra";
-	2 = "[2] Flip";
-	4 = "[4] Special";
-	8 = "[8] Ambush";
+	invertprecip = "regular";
+	gravityflip = "regular";
+	heatwave = "regular";
+	noclipcamera = "regular";
+	colormapfog = "colormap";
+	colormapfadesprites = "colormap";
+	colormapprotected = "colormap";
+	outerspace = "special";
+	doublestepup = "special";
+	nostepdown = "special";
+	speedpad = "special";
+	starpostactivator = "special";
+	exit = "special";
+	specialstagepit = "special";
+	returnflag = "special";
+	redteambase = "special";
+	blueteambase = "special";
+	fan = "special";
+	supertransform = "special";
+	forcespin = "special";
+	zoomtubestart = "special";
+	zoomtubeend = "special";
+	finishline = "special";
+	ropehang = "special";
+	jumpflip = "special";
+	gravityoverride = "special";
+	flipspecial_nofloor = "trigger";
+	flipspecial_ceiling = "trigger";
+	triggerspecial_touch = "trigger";
+	triggerspecial_headbump = "trigger";
+	triggerline_plane = "trigger";
+	triggerline_mobj = "trigger";
 }
 
 // THING FLAGS
 thingflags_udmf
 {
 	flip = "Flip";
+	absolutez = "Absolute Z height";
 }
 
-
 // Thing flags UDMF translation table
 // This is needed for copy/paste and prefabs to work properly
 // When the UDMF field name is prefixed with ! it is inverted
@@ -130,9 +138,9 @@ thingflagstranslation
 	2 = "flip";
 	4 = "special";
 	8 = "ambush";
+	16 = "absolutez";
 }
 
-
 // DEFAULT SECTOR BRIGHTNESS LEVELS
 sectorbrightness
 {
@@ -171,6 +179,8 @@ sectorbrightness
 	0;
 }
 
+numbrightnesslevels = 32;
+
 /*
 TEXTURES AND FLAT SOURCES
 This tells Doom Builder where to find the information for textures
@@ -221,145 +231,18 @@ universalfields
 {
 	sector
 	{
-		lightalpha
-		{
-			type = 0;
-			default = 25;
-		}
-
-		fadealpha
-		{
-			type = 0;
-			default = 25;
-		}
-
-		fadestart
-		{
-			type = 0;
-			default = 0;
-		}
-
-		fadeend
-		{
-			type = 0;
-			default = 33;
-		}
-
-		foglighting
-		{
-			type = 3;
-			default = false;
-		}
-
-		friction
-		{
-			type = 1;
-			default = 0.90625;
-		}
-
-		triggertag
-		{
-			type = 15;
-			default = 0;
-		}
-
-		triggerer
-		{
-			type = 2;
-			default = "Player";
-		}
 	}
 
 	linedef
 	{
-		arg5
-		{
-			type = 0;
-			default = 0;
-		}
-		arg6
-		{
-			type = 0;
-			default = 0;
-		}
-		arg7
-		{
-			type = 0;
-			default = 0;
-		}
-		arg8
-		{
-			type = 0;
-			default = 0;
-		}
-		arg9
-		{
-			type = 0;
-			default = 0;
-		}
-		stringarg0
-		{
-			type = 2;
-			default = "";
-		}
-		stringarg1
-		{
-			type = 2;
-			default = "";
-		}
-		executordelay
-		{
-			type = 0;
-			default = 0;
-		}
 	}
 
 	sidedef
 	{
-		repeatcnt
-		{
-			type = 0;
-			default = 0;
-		}
 	}
 
 	thing
 	{
-		arg5
-		{
-			type = 0;
-			default = 0;
-		}
-		arg6
-		{
-			type = 0;
-			default = 0;
-		}
-		arg7
-		{
-			type = 0;
-			default = 0;
-		}
-		arg8
-		{
-			type = 0;
-			default = 0;
-		}
-		arg9
-		{
-			type = 0;
-			default = 0;
-		}
-		stringarg0
-		{
-			type = 2;
-			default = "";
-		}
-		stringarg1
-		{
-			type = 2;
-			default = "";
-		}
 	}
 }
 
@@ -378,87 +261,6 @@ allowempty = The nodebuilder is allowed to leave this lump empty.
 scriptbuild = This lump is a text-based script, which should be compiled using current script compiler;
 script = This lump is a text-based script. Specify the filename of the script configuration to use.
 */
-
-doommaplumpnames
-{
-	~MAP
-	{
-		required = true;
-		blindcopy = true;
-		nodebuild = false;
-	}
-
-	THINGS
-	{
-		required = true;
-		nodebuild = true;
-		allowempty = true;
-	}
-
-	LINEDEFS
-	{
-		required = true;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	SIDEDEFS
-	{
-		required = true;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	VERTEXES
-	{
-		required = true;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	SEGS
-	{
-		required = false;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	SSECTORS
-	{
-		required = false;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	NODES
-	{
-		required = false;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	SECTORS
-	{
-		required = true;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	REJECT
-	{
-		required = false;
-		nodebuild = true;
-		allowempty = false;
-	}
-
-	BLOCKMAP
-	{
-		required = false;
-		nodebuild = true;
-		allowempty = true;
-	}
-}
-
 udmfmaplumpnames
 {
 	ZNODES
@@ -682,48 +484,32 @@ thingsfilters
 
 	}
 
-
-	filter3
-	{
-		name = "Normal Gravity";
-		category = "";
-		type = -1;
-
-		fields
-		{
-			2 = false;
-		}
-
-	}
-
-
-	filter4
-	{
-		name = "Reverse Gravity";
-		category = "";
-		type = -1;
-
-		fields
-		{
-			2 = true;
-		}
-
-	}
+	//filter3
+	//{
+	//	name = "Normal Gravity";
+	//	category = "";
+	//	type = -1;
+	//
+	//	fields
+	//	{
+	//		2 = false;
+	//	}
+	//}
+
+	//filter4
+	//{
+	//	name = "Reverse Gravity";
+	//	category = "";
+	//	type = -1;
+	//
+	//	fields
+	//	{
+	//		2 = true;
+	//	}
+	//}
 }
 
 // Special linedefs
-speciallinedefs
-{
-	soundlinedefflag = 64;	// See linedefflags
-	singlesidedflag = 1;	// See linedefflags
-	doublesidedflag = 4;	// See linedefflags
-	impassableflag = 1;
-	upperunpeggedflag = 8;
-	lowerunpeggedflag = 16;
-	repeatmidtextureflag = 1024;
-	pegmidtextureflag = 256;
-}
-
 speciallinedefs_udmf
 {
 	soundlinedefflag = "noclimb";
@@ -734,6 +520,8 @@ speciallinedefs_udmf
 	lowerunpeggedflag = "dontpegbottom";
 	repeatmidtextureflag = "wrapmidtex";
 	pegmidtextureflag = "midpeg";
+	slopeskewflag = "skewtd";
+	nomidtextureskewflag = "noskew";
 }
 
 scriptlumpnames
diff --git a/extras/conf/udb/Includes/SRB222_sectors.cfg b/extras/conf/udb/Includes/SRB222_sectors.cfg
deleted file mode 100644
index 5b3ad4155c176b229eee741b9a1744121b68d4ae..0000000000000000000000000000000000000000
--- a/extras/conf/udb/Includes/SRB222_sectors.cfg
+++ /dev/null
@@ -1,107 +0,0 @@
-sectortypes
-{
-	0 = "Normal";
-	1 = "Damage";
-	2 = "Damage (Water)";
-	3 = "Damage (Fire)";
-	4 = "Damage (Electrical)";
-	5 = "Spikes";
-	6 = "Death Pit (Camera Tilt)";
-	7 = "Death Pit (No Camera Tilt)";
-	8 = "Instant Kill";
-	9 = "Ring Drainer (Floor Touch)";
-	10 = "Ring Drainer (Anywhere in Sector)";
-	11 = "Special Stage Damage";
-	12 = "Space Countdown";
-	13 = "Ramp Sector (double step-up/down)";
-	14 = "Non-Ramp Sector (no step-down)";
-	15 = "Bouncy FOF <deprecated>";
-	16 = "Trigger Line Ex. (Pushable Objects)";
-	32 = "Trigger Line Ex. (Anywhere, All Players)";
-	48 = "Trigger Line Ex. (Floor Touch, All Players)";
-	64 = "Trigger Line Ex. (Anywhere in Sector)";
-	80 = "Trigger Line Ex. (Floor Touch)";
-	96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
-	112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
-	128 = "Check for Linedef Executor on FOFs";
-	144 = "Egg Capsule";
-	160 = "Special Stage Time/Spheres Parameters <deprecated>";
-	176 = "Custom Global Gravity <deprecated>";
-	1280 = "Speed Pad";
-	1536 = "Flip Gravity on Jump";
-	4096 = "Star Post Activator";
-	8192 = "Exit/Special Stage Pit/Return Flag";
-	12288 = "CTF Red Team Base";
-	16384 = "CTF Blue Team Base";
-	20480 = "Fan Sector";
-	24576 = "Super Sonic Transform";
-	28672 = "Force Spin";
-	32768 = "Zoom Tube Start";
-	36864 = "Zoom Tube End";
-	40960 = "Circuit Finish Line";
-	45056 = "Rope Hang";
-	49152 = "Intangible to the Camera";
-}
-
-gen_sectortypes
-{
-	first
-	{
-		0 = "Normal";
-		1 = "Damage";
-		2 = "Damage (Water)";
-		3 = "Damage (Fire)";
-		4 = "Damage (Electrical)";
-		5 = "Spikes";
-		6 = "Death Pit (Camera Tilt)";
-		7 = "Death Pit (No Camera Tilt)";
-		8 = "Instant Kill";
-		9 = "Ring Drainer (Floor Touch)";
-		10 = "Ring Drainer (Anywhere in Sector)";
-		11 = "Special Stage Damage";
-		12 = "Space Countdown";
-		13 = "Ramp Sector (double step-up/down)";
-		14 = "Non-Ramp Sector (no step-down)";
-		15 = "Bouncy FOF <deprecated>";
-	}
-
-	second
-	{
-		0 = "Normal";
-		16 = "Trigger Line Ex. (Pushable Objects)";
-		32 = "Trigger Line Ex. (Anywhere, All Players)";
-		48 = "Trigger Line Ex. (Floor Touch, All Players)";
-		64 = "Trigger Line Ex. (Anywhere in Sector)";
-		80 = "Trigger Line Ex. (Floor Touch)";
-		96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
-		112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
-		128 = "Check for Linedef Executor on FOFs";
-		144 = "Egg Capsule";
-		160 = "Special Stage Time/Spheres Parameters <deprecated>";
-		176 = "Custom Global Gravity <deprecated>";
-	}
-
-	third
-	{
-		0 = "Normal";
-		1280 = "Speed Pad";
-		1536 = "Flip Gravity on Jump";
-	}
-
-	fourth
-	{
-		0 = "Normal";
-		4096 = "Star Post Activator";
-		8192 = "Exit/Special Stage Pit/Return Flag";
-		12288 = "CTF Red Team Base";
-		16384 = "CTF Blue Team Base";
-		20480 = "Fan Sector";
-		24576 = "Super Sonic Transform";
-		28672 = "Force Spin";
-		32768 = "Zoom Tube Start";
-		36864 = "Zoom Tube End";
-		40960 = "Circuit Finish Line";
-		45056 = "Rope Hang";
-		49152 = "Intangible to the Camera";
-	}
-}
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index b4508c91ea568aecc702ad1bf9927f079e820bd1..9eb227974dfd0b33de593a7ced83b42fab7d6738 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -3,3175 +3,8 @@
 // 8-Dark_Gray 9-Blue 10-Green 11-Cyan 12-Red 13-Magenta
 // 14-Yellow 15-White 16-Pink 17-Orange 18-Gold 19-Cream
 
-doom
-{
-	editor
-	{
-		color = 15; // White
-		arrow = 1;
-		title = "<Editor Things>";
-		error = -1;
-		width = 8;
-		height = 16;
-		sort = 1;
-
-		3328 = "3D Mode Start";
-	}
-
-	starts
-	{
-		color = 1; // Blue
-		arrow = 1;
-		title = "Player Starts";
-		width = 16;
-		height = 48;
-		sprite = "PLAYA0";
-
-		1
-		{
-			title = "Player 01 Start";
-			sprite = "PLAYA0";
-		}
-		2
-		{
-			title = "Player 02 Start";
-			sprite = "PLAYA0";
-		}
-		3
-		{
-			title = "Player 03 Start";
-			sprite = "PLAYA0";
-		}
-		4
-		{
-			title = "Player 04 Start";
-			sprite = "PLAYA0";
-		}
-		5
-		{
-			title = "Player 05 Start";
-			sprite = "PLAYA0";
-		}
-		6
-		{
-			title = "Player 06 Start";
-			sprite = "PLAYA0";
-		}
-		7
-		{
-			title = "Player 07 Start";
-			sprite = "PLAYA0";
-		}
-		8
-		{
-			title = "Player 08 Start";
-			sprite = "PLAYA0";
-		}
-		9
-		{
-			title = "Player 09 Start";
-			sprite = "PLAYA0";
-		}
-		10
-		{
-			title = "Player 10 Start";
-			sprite = "PLAYA0";
-		}
-		11
-		{
-			title = "Player 11 Start";
-			sprite = "PLAYA0";
-		}
-		12
-		{
-			title = "Player 12 Start";
-			sprite = "PLAYA0";
-		}
-		13
-		{
-			title = "Player 13 Start";
-			sprite = "PLAYA0";
-		}
-		14
-		{
-			title = "Player 14 Start";
-			sprite = "PLAYA0";
-		}
-		15
-		{
-			title = "Player 15 Start";
-			sprite = "PLAYA0";
-		}
-		16
-		{
-			title = "Player 16 Start";
-			sprite = "PLAYA0";
-		}
-		17
-		{
-			title = "Player 17 Start";
-			sprite = "PLAYA0";
-		}
-		18
-		{
-			title = "Player 18 Start";
-			sprite = "PLAYA0";
-		}
-		19
-		{
-			title = "Player 19 Start";
-			sprite = "PLAYA0";
-		}
-		20
-		{
-			title = "Player 20 Start";
-			sprite = "PLAYA0";
-		}
-		21
-		{
-			title = "Player 21 Start";
-			sprite = "PLAYA0";
-		}
-		22
-		{
-			title = "Player 22 Start";
-			sprite = "PLAYA0";
-		}
-		23
-		{
-			title = "Player 23 Start";
-			sprite = "PLAYA0";
-		}
-		24
-		{
-			title = "Player 24 Start";
-			sprite = "PLAYA0";
-		}
-		25
-		{
-			title = "Player 25 Start";
-			sprite = "PLAYA0";
-		}
-		26
-		{
-			title = "Player 26 Start";
-			sprite = "PLAYA0";
-		}
-		27
-		{
-			title = "Player 27 Start";
-			sprite = "PLAYA0";
-		}
-		28
-		{
-			title = "Player 28 Start";
-			sprite = "PLAYA0";
-		}
-		29
-		{
-			title = "Player 29 Start";
-			sprite = "PLAYA0";
-		}
-		30
-		{
-			title = "Player 30 Start";
-			sprite = "PLAYA0";
-		}
-		31
-		{
-			title = "Player 31 Start";
-			sprite = "PLAYA0";
-		}
-		32
-		{
-			title = "Player 32 Start";
-			sprite = "PLAYA0";
-		}
-		33
-		{
-			title = "Match Start";
-			sprite = "NDRNA2A8";
-		}
-		34
-		{
-			title = "CTF Red Team Start";
-			sprite = "SIGNG0";
-		}
-		35
-		{
-			title = "CTF Blue Team Start";
-			sprite = "SIGNE0";
-		}
-	}
-
-	enemies
-	{
-		color = 9; // Light_Blue
-		arrow = 1;
-		title = "Enemies";
-
-		100
-		{
-			title = "Crawla (Blue)";
-			sprite = "POSSA1";
-			width = 24;
-			height = 32;
-		}
-		101
-		{
-			title = "Crawla (Red)";
-			sprite = "SPOSA1";
-			width = 24;
-			height = 32;
-		}
-		102
-		{
-			title = "Stupid Dumb Unnamed RoboFish";
-			sprite = "FISHA0";
-			width = 8;
-			height = 28;
-		}
-		103
-		{
-			title = "Buzz (Gold)";
-			sprite = "BUZZA1";
-			width = 28;
-			height = 40;
-		}
-		104
-		{
-			title = "Buzz (Red)";
-			sprite = "RBUZA1";
-			width = 28;
-			height = 40;
-		}
-		108
-		{
-			title = "Deton";
-			sprite = "DETNA1";
-			width = 20;
-			height = 32;
-		}
-		110
-		{
-			title = "Turret";
-			sprite = "TRETA1";
-			width = 16;
-			height = 32;
-		}
-		111
-		{
-			title = "Pop-up Turret";
-			sprite = "TURRI1";
-			width = 12;
-			height = 64;
-		}
-		122
-		{
-			title = "Spring Shell (Green)";
-			sprite = "SSHLA1";
-			width = 24;
-			height = 40;
-		}
-		125
-		{
-			title = "Spring Shell (Yellow)";
-			sprite = "SSHLI1";
-			width = 24;
-			height = 40;
-		}
-		109
-		{
-			title = "Skim";
-			sprite = "SKIMA1";
-			width = 16;
-			height = 24;
-		}
-		113
-		{
-			title = "Jet Jaw";
-			sprite = "JJAWA3A7";
-			width = 12;
-			height = 20;
-		}
-		126
-		{
-			title = "Crushstacean";
-			sprite = "CRABA0";
-			width = 24;
-			height = 32;
-		}
-		138
-		{
-			title = "Banpyura";
-			sprite = "CR2BA0";
-			width = 24;
-			height = 32;
-		}
-		117
-		{
-			title = "Robo-Hood";
-			sprite = "ARCHA1";
-			width = 24;
-			height = 32;
-		}
-		118
-		{
-			title = "Lance-a-Bot";
-			sprite = "CBFSA1";
-			width = 32;
-			height = 72;
-		}
-		1113
-		{
-			title = "Suspicious Lance-a-Bot Statue";
-			sprite = "CBBSA1";
-			width = 32;
-			height = 72;
-		}
-		119
-		{
-			title = "Egg Guard";
-			sprite = "ESHIA1";
-			width = 16;
-			height = 48;
-		}
-		115
-		{
-			title = "Bird Aircraft Strike Hazard";
-			sprite = "VLTRF1";
-			width = 12;
-			height = 24;
-		}
-		120
-		{
-			title = "Green Snapper";
-			sprite = "GSNPA1";
-			width = 24;
-			height = 24;
-		}
-		121
-		{
-			title = "Minus";
-			sprite = "MNUSA0";
-			width = 24;
-			height = 32;
-		}
-		134
-		{
-			title = "Canarivore";
-			sprite = "CANAA0";
-			width = 12;
-			height = 80;
-			hangs = 1;
-		}
-		123
-		{
-			title = "Unidus";
-			sprite = "UNIDA1";
-			width = 18;
-			height = 36;
-		}
-		135
-		{
-			title = "Pterabyte Spawner";
-			sprite = "PTERA2A8";
-			width = 16;
-			height = 16;
-		}
-		136
-		{
-			title = "Pyre Fly";
-			sprite = "PYREA0";
-			width = 24;
-			height = 34;
-		}
-		137
-		{
-			title = "Dragonbomber";
-			sprite = "DRABA1";
-			width = 28;
-			height = 48;
-		}
-		105
-		{
-			title = "Jetty-Syn Bomber";
-			sprite = "JETBB1";
-			width = 20;
-			height = 50;
-		}
-		106
-		{
-			title = "Jetty-Syn Gunner";
-			sprite = "JETGB1";
-			width = 20;
-			height = 48;
-		}
-		112
-		{
-			title = "Spincushion";
-			sprite = "SHRPA1";
-			width = 16;
-			height = 24;
-		}
-		114
-		{
-			title = "Snailer";
-			sprite = "SNLRA3A7";
-			width = 24;
-			height = 48;
-		}
-		129
-		{
-			title = "Penguinator";
-			sprite = "PENGA1";
-			width = 24;
-			height = 32;
-		}
-		130
-		{
-			title = "Pophat";
-			sprite = "POPHA1";
-			width = 24;
-			height = 32;
-		}
-		107
-		{
-			title = "Crawla Commander";
-			sprite = "CCOMA1";
-			width = 16;
-			height = 32;
-		}
-		131
-		{
-			title = "Spinbobert";
-			sprite = "SBOBB0";
-			width = 32;
-			height = 32;
-		}
-		132
-		{
-			title = "Cacolantern";
-			sprite = "CACOA0";
-			width = 32;
-			height = 32;
-		}
-		133
-		{
-			title = "Hangster";
-			sprite = "HBATC1";
-			width = 24;
-			height = 24;
-			hangs = 1;
-		}
-		127
-		{
-			title = "Hive Elemental";
-			sprite = "HIVEA0";
-			width = 32;
-			height = 80;
-		}
-		128
-		{
-			title = "Bumblebore";
-			sprite = "BUMBA1";
-			width = 16;
-			height = 32;
-		}
-		124
-		{
-			title = "Buggle";
-			sprite = "BBUZA1";
-			width = 20;
-			height = 24;
-		}
-		116
-		{
-			title = "Pointy";
-			sprite = "PNTYA1";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	bosses
-	{
-		color = 8; // Dark_Gray
-		arrow = 1;
-		title = "Bosses";
-
-		200
-		{
-			title = "Egg Mobile";
-			sprite = "EGGMA1";
-			width = 24;
-			height = 76;
-		}
-		201
-		{
-			title = "Egg Slimer";
-			sprite = "EGGNA1";
-			width = 24;
-			height = 76;
-		}
-		202
-		{
-			title = "Sea Egg";
-			sprite = "EGGOA1";
-			width = 32;
-			height = 116;
-		}
-		203
-		{
-			title = "Egg Colosseum";
-			sprite = "EGGPA1";
-			width = 24;
-			height = 76;
-		}
-		204
-		{
-			title = "Fang";
-			sprite = "FANGA1";
-			width = 24;
-			height = 60;
-		}
-		206
-		{
-			title = "Brak Eggman (Old)";
-			sprite = "BRAKB1";
-			width = 48;
-			height = 160;
-		}
-		207
-		{
-			title = "Metal Sonic (Race)";
-			sprite = "METLI1";
-			width = 16;
-			height = 48;
-		}
-		208
-		{
-			title = "Metal Sonic (Battle)";
-			sprite = "METLC1";
-			width = 16;
-			height = 48;
-		}
-		209
-		{
-			title = "Brak Eggman";
-			sprite = "BRAK01";
-			width = 48;
-			height = 160;
-		}
-		290
-		{
-			arrow = 0;
-			title = "Boss Escape Point";
-			width = 8;
-			height = 16;
-			sprite = "internal:eggmanend";
-		}
-		291
-		{
-			arrow = 0;
-			title = "Egg Capsule Center";
-			width = 8;
-			height = 16;
-			sprite = "internal:capsule";
-		}
-		292
-		{
-			arrow = 0;
-			title = "Boss Waypoint";
-			width = 8;
-			height = 16;
-			sprite = "internal:eggmanway";
-		}
-		293
-		{
-			title = "Metal Sonic Gather Point";
-			sprite = "internal:metal";
-			width = 8;
-			height = 16;
-		}
-		294
-		{
-			title = "Fang Waypoint";
-			sprite = "internal:eggmanway";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	rings
-	{
-		color = 14; // Yellow
-		title = "Rings and Weapon Panels";
-		width = 24;
-		height = 24;
-		sprite = "RINGA0";
-
-		300
-		{
-			title = "Ring";
-			sprite = "RINGA0";
-			width = 16;
-		}
-		301
-		{
-			title = "Bounce Ring";
-			sprite = "internal:RNGBA0";
-		}
-		302
-		{
-			title = "Rail Ring";
-			sprite = "internal:RNGRA0";
-		}
-		303
-		{
-			title = "Infinity Ring";
-			sprite = "internal:RNGIA0";
-		}
-		304
-		{
-			title = "Automatic Ring";
-			sprite = "internal:RNGAA0";
-		}
-		305
-		{
-			title = "Explosion Ring";
-			sprite = "internal:RNGEA0";
-		}
-		306
-		{
-			title = "Scatter Ring";
-			sprite = "internal:RNGSA0";
-		}
-		307
-		{
-			title = "Grenade Ring";
-			sprite = "internal:RNGGA0";
-		}
-		308
-		{
-			title = "CTF Team Ring (Red)";
-			sprite = "internal:RRNGA0";
-			width = 16;
-		}
-		309
-		{
-			title = "CTF Team Ring (Blue)";
-			sprite = "internal:BRNGA0";
-			width = 16;
-		}
-		330
-		{
-			title = "Bounce Ring Panel";
-			sprite = "internal:PIKBA0";
-		}
-		331
-		{
-			title = "Rail Ring Panel";
-			sprite = "internal:PIKRA0";
-		}
-		332
-		{
-			title = "Automatic Ring Panel";
-			sprite = "internal:PIKAA0";
-		}
-		333
-		{
-			title = "Explosion Ring Panel";
-			sprite = "internal:PIKEA0";
-		}
-		334
-		{
-			title = "Scatter Ring Panel";
-			sprite = "internal:PIKSA0";
-		}
-		335
-		{
-			title = "Grenade Ring Panel";
-			sprite = "internal:PIKGA0";
-		}
-	}
-
-	collectibles
-	{
-		color = 10; // Light_Green
-		title = "Other Collectibles";
-		width = 16;
-		height = 32;
-		sort = 1;
-		sprite = "CEMGA0";
-
-		310
-		{
-			title = "CTF Red Flag";
-			sprite = "RFLGA0";
-			width = 24;
-			height = 64;
-		}
-		311
-		{
-			title = "CTF Blue Flag";
-			sprite = "BFLGA0";
-			width = 24;
-			height = 64;
-		}
-		312
-		{
-			title = "Emerald Token";
-			sprite = "TOKEA0";
-			width = 16;
-			height = 32;
-		}
-		313
-		{
-			title = "Chaos Emerald 1 (Green)";
-			sprite = "CEMGA0";
-		}
-		314
-		{
-			title = "Chaos Emerald 2 (Purple)";
-			sprite = "CEMGB0";
-		}
-		315
-		{
-			title = "Chaos Emerald 3 (Blue)";
-			sprite = "CEMGC0";
-		}
-		316
-		{
-			title = "Chaos Emerald 4 (Cyan)";
-			sprite = "CEMGD0";
-		}
-		317
-		{
-			title = "Chaos Emerald 5 (Orange)";
-			sprite = "CEMGE0";
-		}
-		318
-		{
-			title = "Chaos Emerald 6 (Red)";
-			sprite = "CEMGF0";
-		}
-		319
-		{
-			title = "Chaos Emerald 7 (Gray)";
-			sprite = "CEMGG0";
-		}
-		320
-		{
-			title = "Emerald Hunt Location";
-			sprite = "SHRDA0";
-		}
-		321
-		{
-			title = "Match Chaos Emerald Spawn";
-			sprite = "CEMGA0";
-		}
-		322
-		{
-			title = "Emblem";
-			sprite = "EMBMA0";
-			width = 16;
-			height = 30;
-		}
-	}
-
-	boxes
-	{
-		color = 7; // Gray
-		blocking = 2;
-		title = "Monitors";
-		width = 18;
-		height = 40;
-
-		400
-		{
-			title = "Super Ring (10 Rings)";
-			sprite = "TVRIA0";
-		}
-		401
-		{
-			title = "Pity Shield";
-			sprite = "TVPIA0";
-		}
-		402
-		{
-			title = "Attraction Shield";
-			sprite = "TVATA0";
-		}
-		403
-		{
-			title = "Force Shield";
-			sprite = "TVFOA0";
-		}
-		404
-		{
-			title = "Armageddon Shield";
-			sprite = "TVARA0";
-		}
-		405
-		{
-			title = "Whirlwind Shield";
-			sprite = "TVWWA0";
-		}
-		406
-		{
-			title = "Elemental Shield";
-			sprite = "TVELA0";
-		}
-		407
-		{
-			title = "Super Sneakers";
-			sprite = "TVSSA0";
-		}
-		408
-		{
-			title = "Invincibility";
-			sprite = "TVIVA0";
-		}
-		409
-		{
-			title = "Extra Life";
-			sprite = "TV1UA0";
-		}
-		410
-		{
-			title = "Eggman";
-			sprite = "TVEGA0";
-		}
-		411
-		{
-			title = "Teleporter";
-			sprite = "TVMXA0";
-		}
-		413
-		{
-			title = "Gravity Boots";
-			sprite = "TVGVA0";
-		}
-		414
-		{
-			title = "CTF Team Ring Monitor (Red)";
-			sprite = "TRRIA0";
-		}
-		415
-		{
-			title = "CTF Team Ring Monitor (Blue)";
-			sprite = "TBRIA0";
-		}
-		416
-		{
-			title = "Recycler";
-			sprite = "TVRCA0";
-		}
-		418
-		{
-			title = "Score (1,000 Points)";
-			sprite = "TV1KA0";
-		}
-		419
-		{
-			title = "Score (10,000 Points)";
-			sprite = "TVTKA0";
-		}
-		420
-		{
-			title = "Flame Shield";
-			sprite = "TVFLA0";
-		}
-		421
-		{
-			title = "Water Shield";
-			sprite = "TVBBA0";
-		}
-		422
-		{
-			title = "Lightning Shield";
-			sprite = "TVZPA0";
-		}
-	}
-
-	boxes2
-	{
-		color = 18; // Gold
-		blocking = 2;
-		title = "Monitors (Respawning)";
-		width = 20;
-		height = 44;
-
-		431
-		{
-			title = "Pity Shield (Respawn)";
-			sprite = "TVPIB0";
-		}
-		432
-		{
-			title = "Attraction Shield (Respawn)";
-			sprite = "TVATB0";
-		}
-		433
-		{
-			title = "Force Shield (Respawn)";
-			sprite = "TVFOB0";
-		}
-		434
-		{
-			title = "Armageddon Shield (Respawn)";
-			sprite = "TVARB0";
-		}
-		435
-		{
-			title = "Whirlwind Shield (Respawn)";
-			sprite = "TVWWB0";
-		}
-		436
-		{
-			title = "Elemental Shield (Respawn)";
-			sprite = "TVELB0";
-		}
-		437
-		{
-			title = "Super Sneakers (Respawn)";
-			sprite = "TVSSB0";
-		}
-		438
-		{
-			title = "Invincibility (Respawn)";
-			sprite = "TVIVB0";
-		}
-		440
-		{
-			title = "Eggman (Respawn)";
-			sprite = "TVEGB0";
-		}
-		443
-		{
-			title = "Gravity Boots (Respawn)";
-			sprite = "TVGVB0";
-		}
-		450
-		{
-			title = "Flame Shield (Respawn)";
-			sprite = "TVFLB0";
-		}
-		451
-		{
-			title = "Water Shield (Respawn)";
-			sprite = "TVBBB0";
-		}
-		452
-		{
-			title = "Lightning Shield (Respawn)";
-			sprite = "TVZPB0";
-		}
-	}
-
-	generic
-	{
-		color = 11; // Light_Cyan
-		title = "Generic Items & Hazards";
-
-		500
-		{
-			title = "Air Bubble Patch";
-			sprite = "BUBLE0";
-			width = 8;
-			height = 16;
-		}
-		501
-		{
-			title = "Signpost";
-			sprite = "SIGND0";
-			width = 8;
-			height = 32;
-		}
-		502
-		{
-			arrow = 1;
-			title = "Star Post";
-			sprite = "STPTA0M0";
-			width = 64;
-			height = 128;
-		}
-		520
-		{
-			title = "Bomb Sphere";
-			sprite = "SPHRD0";
-			width = 16;
-			height = 24;
-		}
-		521
-		{
-			title = "Spikeball";
-			sprite = "SPIKA0";
-			width = 12;
-			height = 8;
-		}
-		522
-		{
-			title = "Wall Spike";
-			sprite = "WSPKALAR";
-			width = 16;
-			height = 14;
-			arrow = 1;
-		}
-		523
-		{
-			title = "Spike";
-			sprite = "USPKA0";
-			width = 8;
-			height = 32;
-		}
-		1130
-		{
-			title = "Small Mace";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1131
-		{
-			title = "Big Mace";
-			sprite = "BMCEA0";
-			width = 34;
-			height = 68;
-		}
-		1136
-		{
-			title = "Small Fireball";
-			sprite = "SFBRA0";
-			width = 17;
-			height = 34;
-		}
-		1137
-		{
-			title = "Large Fireball";
-			sprite = "BFBRA0";
-			width = 34;
-			height = 68;
-		}
-	}
-
-	springs
-	{
-		color = 12; // Light_Red
-		title = "Springs and Fans";
-		width = 20;
-		height = 16;
-		sprite = "RSPRD2";
-
-		540
-		{
-			title = "Fan";
-			sprite = "FANSA0D0";
-			width = 16;
-			height = 8;
-		}
-		541
-		{
-			title = "Gas Jet";
-			sprite = "STEMD0";
-			width = 32;
-		}
-		542
-		{
-			title = "Bumper";
-			sprite = "BUMPA0";
-			width = 32;
-			height = 64;
-		}
-		543
-		{
-			title = "Balloon";
-			sprite = "BLONA0";
-			width = 32;
-			height = 64;
-		}
-		550
-		{
-			title = "Yellow Spring";
-			sprite = "SPRYA0";
-		}
-		551
-		{
-			title = "Red Spring";
-			sprite = "SPRRA0";
-		}
-		552
-		{
-			title = "Blue Spring";
-			sprite = "SPRBA0";
-		}
-		555
-		{
-			arrow = 1;
-			title = "Diagonal Yellow Spring";
-			sprite = "YSPRD2";
-			width = 16;
-		}
-		556
-		{
-			arrow = 1;
-			title = "Diagonal Red Spring";
-			sprite = "RSPRD2";
-			width = 16;
-		}
-		557
-		{
-			arrow = 1;
-			title = "Diagonal Blue Spring";
-			sprite = "BSPRD2";
-			width = 16;
-		}
-		558
-		{
-			arrow = 1;
-			title = "Horizontal Yellow Spring";
-			sprite = "SSWYD2D8";
-			width = 16;
-			height = 32;
-		}
-		559
-		{
-			arrow = 1;
-			title = "Horizontal Red Spring";
-			sprite = "SSWRD2D8";
-			width = 16;
-			height = 32;
-		}
-		560
-		{
-			arrow = 1;
-			title = "Horizontal Blue Spring";
-			sprite = "SSWBD2D8";
-			width = 16;
-			height = 32;
-		}
-		1134
-		{
-			title = "Yellow Spring Ball";
-			sprite = "YSPBA0";
-			width = 17;
-			height = 34;
-		}
-		1135
-		{
-			title = "Red Spring Ball";
-			sprite = "RSPBA0";
-			width = 17;
-			height = 34;
-		}
-		544
-		{
-			arrow = 1;
-			title = "Yellow Boost Panel";
-			sprite = "BSTYA0";
-			width = 28;
-			height = 2;
-		}
-		545
-		{
-			arrow = 1;
-			title = "Red Boost Panel";
-			sprite = "BSTRA0";
-			width = 28;
-			height = 2;
-		}
-	}
-
-	patterns
-	{
-		color = 5; // Magenta
-		arrow = 1;
-		title = "Special Placement Patterns";
-		width = 16;
-		height = 384;
-		sprite = "RINGA0";
-
-		600
-		{
-			arrow = 0;
-			title = "5 Vertical Rings (Yellow Spring)";
-			sprite = "RINGA0";
-		}
-		601
-		{
-			arrow = 0;
-			title = "5 Vertical Rings (Red Spring)";
-			sprite = "RINGA0";
-			height = 1024;
-		}
-		602
-		{
-			title = "5 Diagonal Rings (Yellow Spring)";
-			sprite = "RINGA0";
-			height = 32;
-		}
-		603
-		{
-			title = "10 Diagonal Rings (Red Spring)";
-			sprite = "RINGA0";
-			height = 32;
-		}
-		604
-		{
-			title = "Circle of Rings";
-			sprite = "RINGA0";
-			width = 96;
-			height = 192;
-		}
-		605
-		{
-			title = "Circle of Rings (Big)";
-			sprite = "RINGA0";
-			width = 192;
-		}
-		606
-		{
-			title = "Circle of Blue Spheres";
-			sprite = "SPHRA0";
-			width = 96;
-			height = 192;
-		}
-		607
-		{
-			title = "Circle of Blue Spheres (Big)";
-			sprite = "SPHRA0";
-			width = 192;
-		}
-		608
-		{
-			title = "Circle of Rings and Spheres";
-			sprite = "SPHRA0";
-			width = 96;
-			height = 192;
-		}
-		609
-		{
-			title = "Circle of Rings and Spheres (Big)";
-			sprite = "SPHRA0";
-			width = 192;
-		}
-	}
-
-	invisible
-	{
-		color = 15; // White
-		title = "Misc. Invisible";
-		width = 0;
-		height = 0;
-		sprite = "UNKNA0";
-		sort = 1;
-		fixedsize = true;
-		blocking = 0;
-
-		700
-		{
-			title = "Water Ambience A (Large)";
-			sprite = "internal:ambiance";
-		}
-
-		701
-		{
-			title = "Water Ambience B (Large)";
-			sprite = "internal:ambiance";
-		}
-
-		702
-		{
-			title = "Water Ambience C (Medium)";
-			sprite = "internal:ambiance";
-		}
-
-		703
-		{
-			title = "Water Ambience D (Medium)";
-			sprite = "internal:ambiance";
-		}
-
-		704
-		{
-			title = "Water Ambience E (Small)";
-			sprite = "internal:ambiance";
-		}
-
-		705
-		{
-			title = "Water Ambience F (Small)";
-			sprite = "internal:ambiance";
-		}
-
-		706
-		{
-			title = "Water Ambience G (Extra Large)";
-			sprite = "internal:ambiance";
-		}
-
-		707
-		{
-			title = "Water Ambience H (Extra Large)";
-			sprite = "internal:ambiance";
-		}
-
-		708
-		{
-			title = "Disco Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		709
-		{
-			title = "Volcano Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		710
-		{
-			title = "Machine Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		750
-		{
-			title = "Slope Vertex";
-			sprite = "internal:vertexslope";
-		}
-
-		751
-		{
-			arrow = 1;
-			title = "Teleport Destination";
-			sprite = "internal:tele";
-		}
-
-		752
-		{
-			arrow = 1;
-			title = "Alternate View Point";
-			sprite = "internal:view";
-		}
-
-		753
-		{
-			title = "Zoom Tube Waypoint";
-			sprite = "internal:zoom";
-		}
-
-		754
-		{
-			title = "Push Point";
-			sprite = "GWLGA0";
-		}
-		755
-		{
-			title = "Pull Point";
-			sprite = "GWLRA0";
-		}
-		756
-		{
-			title = "Blast Linedef Executor";
-			sprite = "TOADA0";
-			width = 32;
-			height = 16;
-		}
-		757
-		{
-			title = "Fan Particle Generator";
-			sprite = "PRTLA0";
-			width = 8;
-			height = 16;
-		}
-		758
-		{
-			title = "Object Angle Anchor";
-			sprite = "internal:view";
-		}
-		760
-		{
-			title = "PolyObject Anchor";
-			sprite = "internal:polyanchor";
-		}
-		761
-		{
-			title = "PolyObject Spawn Point";
-			sprite = "internal:polycenter";
-		}
-		762
-		{
-			title = "PolyObject Spawn Point (Crush)";
-			sprite = "internal:polycentercrush";
-		}
-		780
-		{
-			title = "Skybox View Point";
-			sprite = "internal:skyb";
-		}
-	}
-
-	greenflower
-	{
-		color = 10; // Green
-		title = "Greenflower";
-
-		800
-		{
-			title = "GFZ Flower";
-			sprite = "FWR1A0";
-			width = 16;
-			height = 40;
-		}
-		801
-		{
-			title = "Sunflower";
-			sprite = "FWR2A0";
-			width = 16;
-			height = 96;
-		}
-		802
-		{
-			title = "Budding Flower";
-			sprite = "FWR3A0";
-			width = 8;
-			height = 32;
-		}
-		803
-		{
-			title = "Blueberry Bush";
-			sprite = "BUS3A0";
-			width = 16;
-			height = 32;
-		}
-		804
-		{
-			title = "Berry Bush";
-			sprite = "BUS1A0";
-			width = 16;
-			height = 32;
-		}
-		805
-		{
-			title = "Bush";
-			sprite = "BUS2A0";
-			width = 16;
-			height = 32;
-		}
-		806
-		{
-			title = "GFZ Tree";
-			sprite = "TRE1A0";
-			width = 20;
-			height = 128;
-		}
-		807
-		{
-			title = "GFZ Berry Tree";
-			sprite = "TRE1B0";
-			width = 20;
-			height = 128;
-		}
-		808
-		{
-			title = "GFZ Cherry Tree";
-			sprite = "TRE1C0";
-			width = 20;
-			height = 128;
-		}
-		809
-		{
-			title = "Checkered Tree";
-			sprite = "TRE2A0";
-			width = 20;
-			height = 200;
-		}
-		810
-		{
-			title = "Checkered Tree (Sunset)";
-			sprite = "TRE2B0";
-			width = 20;
-			height = 200;
-		}
-		811
-		{
-			title = "Polygon Tree";
-			sprite = "TRE4A0";
-			width = 20;
-			height = 200;
-		}
-		812
-		{
-			title = "Bush Tree";
-			sprite = "TRE5A0";
-			width = 20;
-			height = 200;
-		}
-		813
-		{
-			title = "Red Bush Tree";
-			sprite = "TRE5B0";
-			width = 20;
-			height = 200;
-		}
-	}
-
-	technohill
-	{
-		color = 10; // Green
-		title = "Techno Hill";
-
-		900
-		{
-			title = "THZ Steam Flower";
-			sprite = "THZPA0";
-			width = 8;
-			height = 32;
-		}
-		901
-		{
-			title = "Alarm";
-			sprite = "ALRMA0";
-			width = 8;
-			height = 16;
-			hangs = 1;
-		}
-		902
-		{
-			title = "THZ Spin Flower (Red)";
-			sprite = "FWR5A0";
-			width = 16;
-			height = 64;
-		}
-		903
-		{
-			title = "THZ Spin Flower (Yellow)";
-			sprite = "FWR6A0";
-			width = 16;
-			height = 64;
-		}
-		904
-		{
-			arrow = 1;
-			title = "Whistlebush";
-			sprite = "THZTA0";
-			width = 16;
-			height = 64;
-		}
-	}
-
-	deepsea
-	{
-		color = 10; // Green
-		title = "Deep Sea";
-
-		1000
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Gargoyle";
-			sprite = "GARGA1";
-			width = 16;
-			height = 40;
-		}
-		1009
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Gargoyle (Big)";
-			sprite = "GARGB1";
-			width = 32;
-			height = 80;
-		}
-		1001
-		{
-			title = "Seaweed";
-			sprite = "SEWEA0";
-			width = 24;
-			height = 56;
-		}
-		1002
-		{
-			title = "Dripping Water";
-			sprite = "DRIPD0";
-			width = 8;
-			height = 16;
-			hangs = 1;
-		}
-		1003
-		{
-			title = "Coral (Green)";
-			sprite = "CORLA0";
-			width = 29;
-			height = 40;
-		}
-		1004
-		{
-			title = "Coral (Red)";
-			sprite = "CORLB0";
-			width = 30;
-			height = 53;
-		}
-		1005
-		{
-			title = "Coral (Orange)";
-			sprite = "CORLC0";
-			width = 28;
-			height = 41;
-		}
-		1006
-		{
-			title = "Blue Crystal";
-			sprite = "BCRYA1";
-			width = 8;
-			height = 16;
-		}
-		1007
-		{
-			title = "Kelp";
-			sprite = "KELPA0";
-			width = 16;
-			height = 292;
-		}
-		1008
-		{
-			title = "Stalagmite (DSZ1)";
-			sprite = "DSTGA0";
-			width = 8;
-			height = 116;
-		}
-		1010
-		{
-			arrow = 1;
-			title = "Light Beam";
-			sprite = "LIBEARAL";
-			width = 16;
-			height = 16;
-		}
-		1011
-		{
-			title = "Stalagmite (DSZ2)";
-			sprite = "DSTGA0";
-			width = 8;
-			height = 116;
-		}
-		1012
-		{
-			arrow = 1;
-			title = "Big Floating Mine";
-			width = 28;
-			height = 56;
-			sprite = "BMNEA1";
-		}
-		1013
-		{
-			title = "Animated Kelp";
-			sprite = "ALGAA0";
-			width = 48;
-			height = 120;
-		}
-		1014
-		{
-			title = "Large Coral (Brown)";
-			sprite = "CORLD0";
-			width = 56;
-			height = 112;
-		}
-		1015
-		{
-			title = "Large Coral (Beige)";
-			sprite = "CORLE0";
-			width = 56;
-			height = 112;
-		}
-	}
-
-	castleeggman
-	{
-		color = 10; // Green
-		title = "Castle Eggman";
-
-		1100
-		{
-			title = "Chain (Decorative)";
-			sprite = "CHANA0";
-			width = 4;
-			height = 128;
-			hangs = 1;
-		}
-		1101
-		{
-			title = "Torch";
-			sprite = "FLAMA0E0";
-			width = 8;
-			height = 32;
-		}
-		1102
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Eggman Statue";
-			sprite = "ESTAA1";
-			width = 32;
-			height = 240;
-		}
-		1103
-		{
-			title = "CEZ Flower";
-			sprite = "FWR4A0";
-			width = 16;
-			height = 40;
-		}
-		1104
-		{
-			title = "Mace Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1105
-		{
-			title = "Chain with Maces Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1106
-		{
-			title = "Chained Spring Spawnpoint";
-			sprite = "YSPBA0";
-			width = 17;
-			height = 34;
-		}
-		1107
-		{
-			title = "Chain Spawnpoint";
-			sprite = "BMCHA0";
-			width = 17;
-			height = 34;
-		}
-		1108
-		{
-			arrow = 1;
-			title = "Hidden Chain Spawnpoint";
-			sprite = "internal:chain3";
-			width = 17;
-			height = 34;
-		}
-		1109
-		{
-			title = "Firebar Spawnpoint";
-			sprite = "BFBRA0";
-			width = 17;
-			height = 34;
-		}
-		1110
-		{
-			title = "Custom Mace Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1111
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Crawla Statue";
-			sprite = "CSTAA1";
-			width = 16;
-			height = 40;
-		}
-		1112
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Lance-a-Bot Statue";
-			sprite = "CBBSA1";
-			width = 32;
-			height = 72;
-		}
-		1114
-		{
-			title = "Pine Tree";
-			sprite = "PINEA0";
-			width = 16;
-			height = 628;
-		}
-		1115
-		{
-			title = "CEZ Shrub (Small)";
-			sprite = "CEZBA0";
-			width = 16;
-			height = 24;
-		}
-		1116
-		{
-			title = "CEZ Shrub (Large)";
-			sprite = "CEZBB0";
-			width = 32;
-			height = 48;
-		}
-		1117
-		{
-			arrow = 1;
-			title = "Pole Banner (Red)";
-			sprite = "BANRA0";
-			width = 40;
-			height = 224;
-		}
-		1118
-		{
-			arrow = 1;
-			title = "Pole Banner (Blue)";
-			sprite = "BANRA0";
-			width = 40;
-			height = 224;
-		}
-		1119
-		{
-			title = "Candle";
-			sprite = "CNDLA0";
-			width = 8;
-			height = 48;
-		}
-		1120
-		{
-			title = "Candle Pricket";
-			sprite = "CNDLB0";
-			width = 8;
-			height = 176;
-		}
-		1121
-		{
-			title = "Flame Holder";
-			sprite = "FLMHA0";
-			width = 24;
-			height = 80;
-		}
-		1122
-		{
-			title = "Fire Torch";
-			sprite = "CTRCA0";
-			width = 16;
-			height = 80;
-		}
-		1123
-		{
-			title = "Cannonball Launcher";
-			sprite = "internal:cannonball";
-			width = 8;
-			height = 16;
-		}
-		1124
-		{
-			blocking = 2;
-			title = "Cannonball";
-			sprite = "CBLLA0";
-			width = 20;
-			height = 40;
-		}
-		1125
-		{
-			title = "Brambles";
-			sprite = "CABRALAR";
-			width = 48;
-			height = 32;
-		}
-		1126
-		{
-			title = "Invisible Lockon Object";
-			sprite = "LCKNC0";
-			width = 16;
-			height = 32;
-		}
-		1127
-		{
-			title = "Spectator Eggrobo";
-			sprite = "EGR1A1";
-			width = 20;
-			height = 72;
-		}
-		1128
-		{
-			arrow = 1;
-			title = "Waving Flag (Red)";
-			sprite = "CFLGA0";
-			width = 8;
-			height = 208;
-		}
-		1129
-		{
-			arrow = 1;
-			title = "Waving Flag (Blue)";
-			sprite = "CFLGA0";
-			width = 8;
-			height = 208;
-		}
-	}
-
-	aridcanyon
-	{
-		color = 10; // Green
-		title = "Arid Canyon";
-
-		1200
-		{
-			title = "Tumbleweed (Big)";
-			sprite = "BTBLA0";
-			width = 24;
-			height = 48;
-		}
-		1201
-		{
-			title = "Tumbleweed (Small)";
-			sprite = "STBLA0";
-			width = 12;
-			height = 24;
-		}
-		1202
-		{
-			arrow = 1;
-			title = "Rock Spawner";
-			sprite = "ROIAA0";
-			width = 8;
-			height = 16;
-		}
-		1203
-		{
-			title = "Tiny Red Flower Cactus";
-			sprite = "CACTA0";
-			width = 13;
-			height = 24;
-		}
-		1204
-		{
-			title = "Small Red Flower Cactus";
-			sprite = "CACTB0";
-			width = 15;
-			height = 52;
-		}
-		1205
-		{
-			title = "Tiny Blue Flower Cactus";
-			sprite = "CACTC0";
-			width = 13;
-			height = 24;
-		}
-		1206
-		{
-			title = "Small Blue Flower Cactus";
-			sprite = "CACTD0";
-			width = 15;
-			height = 52;
-		}
-		1207
-		{
-			title = "Prickly Pear";
-			sprite = "CACTE0";
-			width = 32;
-			height = 96;
-		}
-		1208
-		{
-			title = "Barrel Cactus";
-			sprite = "CACTF0";
-			width = 20;
-			height = 128;
-		}
-		1209
-		{
-			title = "Tall Barrel Cactus";
-			sprite = "CACTG0";
-			width = 24;
-			height = 224;
-		}
-		1210
-		{
-			title = "Armed Cactus";
-			sprite = "CACTH0";
-			width = 24;
-			height = 256;
-		}
-		1211
-		{
-			title = "Ball Cactus";
-			sprite = "CACTI0";
-			width = 48;
-			height = 96;
-		}
-		1212
-		{
-			title = "Caution Sign";
-			sprite = "WWSGAR";
-			width = 22;
-			height = 64;
-		}
-		1213
-		{
-			title = "Cacti Sign";
-			sprite = "WWS2AR";
-			width = 22;
-			height = 64;
-		}
-		1214
-		{
-			title = "Sharp Turn Sign";
-			sprite = "WWS3ALAR";
-			width = 16;
-			height = 192;
-		}
-		1215
-		{
-			title = "Mine Oil Lamp";
-			sprite = "OILLA0";
-			width = 22;
-			height = 64;
-			hangs = 1;
-		}
-		1216
-		{
-			title = "TNT Barrel";
-			sprite = "BARRA1";
-			width = 24;
-			height = 63;
-		}
-		1217
-		{
-			title = "TNT Proximity Shell";
-			sprite = "REMTA0";
-			width = 64;
-			height = 40;
-		}
-		1218
-		{
-			title = "Dust Devil";
-			sprite = "TAZDCR";
-			width = 80;
-			height = 416;
-		}
-		1219
-		{
-			title = "Minecart Spawner";
-			sprite = "MCRTCLFR";
-			width = 22;
-			height = 32;
-		}
-		1220
-		{
-			title = "Minecart Stopper";
-			sprite = "MCRTIR";
-			width = 32;
-			height = 32;
-		}
-		1221
-		{
-			title = "Minecart Saloon Door";
-			sprite = "SALDARAL";
-			width = 96;
-			height = 160;
-		}
-		1222
-		{
-			title = "Train Cameo Spawner";
-			sprite = "TRAEBRBL";
-			width = 28;
-			height = 32;
-		}
-		1223
-		{
-			title = "Train Dust Spawner";
-			sprite = "ADSTA0";
-			width = 4;
-			height = 4;
-		}
-		1224
-		{
-			title = "Train Steam Spawner";
-			sprite = "STEAA0";
-			width = 4;
-			height = 4;
-		}
-		1229
-		{
-			title = "Minecart Switch Point";
-			sprite = "internal:zoom";
-			width = 8;
-			height = 16;
-		}
-		1230
-		{
-			title = "Tiny Cactus";
-			sprite = "CACTJ0";
-			width = 13;
-			height = 28;
-		}
-		1231
-		{
-			title = "Small Cactus";
-			sprite = "CACTK0";
-			width = 15;
-			height = 60;
-		}
-	}
-
-	redvolcano
-	{
-		color = 10; // Green
-		title = "Red Volcano";
-
-		1300
-		{
-			arrow = 1;
-			title = "Flame Jet (Horizontal)";
-			sprite = "internal:flameh";
-			width = 16;
-			height = 40;
-		}
-		1301
-		{
-			title = "Flame Jet (Vertical)";
-			sprite = "internal:flamev";
-			width = 16;
-			height = 40;
-		}
-		1302
-		{
-			title = "Spinning Flame Jet (Counter-Clockwise)";
-			sprite = "internal:flame2";
-			width = 16;
-			height = 24;
-		}
-		1303
-		{
-			title = "Spinning Flame Jet (Clockwise)";
-			sprite = "internal:flame1";
-			width = 16;
-			height = 24;
-		}
-		1304
-		{
-			title = "Lavafall";
-			sprite = "LFALF0";
-			width = 30;
-			height = 32;
-		}
-		1305
-		{
-			title = "Rollout Rock";
-			sprite = "PUMIA1A5";
-			width = 30;
-			height = 60;
-		}
-		1306
-		{
-			title = "Big Fern";
-			sprite = "JPLAB0";
-			width = 32;
-			height = 48;
-		}
-		1307
-		{
-			title = "Jungle Palm";
-			sprite = "JPLAC0";
-			width = 32;
-			height = 48;
-		}
-		1308
-		{
-			title = "Torch Flower";
-			sprite = "TFLOA0";
-			width = 14;
-			height = 110;
-		}
-		1309
-		{
-			title = "RVZ1 Wall Vine (Long)";
-			sprite = "WVINALAR";
-			width = 1;
-			height = 288;
-		}
-		1310
-		{
-			title = "RVZ1 Wall Vine (Short)";
-			sprite = "WVINBLBR";
-			width = 1;
-			height = 288;
-		}
-	}
-
-	botanicserenity
-	{
-		color = 10; // Green
-		title = "Botanic Serenity";
-		width = 16;
-		height = 32;
-		sprite = "BSZ1A0";
-		1400
-		{
-			title = "Tall Flower (Red)";
-			sprite = "BSZ1A0";
-		}
-		1401
-		{
-			title = "Tall Flower (Purple)";
-			sprite = "BSZ1B0";
-		}
-		1402
-		{
-			title = "Tall Flower (Blue)";
-			sprite = "BSZ1C0";
-		}
-		1403
-		{
-			title = "Tall Flower (Cyan)";
-			sprite = "BSZ1D0";
-		}
-		1404
-		{
-			title = "Tall Flower (Yellow)";
-			sprite = "BSZ1E0";
-		}
-		1405
-		{
-			title = "Tall Flower (Orange)";
-			sprite = "BSZ1F0";
-		}
-		1410
-		{
-			title = "Medium Flower (Red)";
-			sprite = "BSZ2A0";
-		}
-		1411
-		{
-			title = "Medium Flower (Purple)";
-			sprite = "BSZ2B0";
-		}
-		1412
-		{
-			title = "Medium Flower (Blue)";
-			sprite = "BSZ2C0";
-		}
-		1413
-		{
-			title = "Medium Flower (Cyan)";
-			sprite = "BSZ2D0";
-		}
-		1414
-		{
-			title = "Medium Flower (Yellow)";
-			sprite = "BSZ2E0";
-		}
-		1415
-		{
-			title = "Medium Flower (Orange)";
-			sprite = "BSZ2F0";
-		}
-		1420
-		{
-			title = "Short Flower (Red)";
-			sprite = "BSZ3A0";
-		}
-		1421
-		{
-			title = "Short Flower (Purple)";
-			sprite = "BSZ3B0";
-		}
-		1422
-		{
-			title = "Short Flower (Blue)";
-			sprite = "BSZ3C0";
-		}
-		1423
-		{
-			title = "Short Flower (Cyan)";
-			sprite = "BSZ3D0";
-		}
-		1424
-		{
-			title = "Short Flower (Yellow)";
-			sprite = "BSZ3E0";
-		}
-		1425
-		{
-			title = "Short Flower (Orange)";
-			sprite = "BSZ3F0";
-		}
-		1430
-		{
-			title = "Tulip (Red)";
-			sprite = "BST1A0";
-		}
-		1431
-		{
-			title = "Tulip (Purple)";
-			sprite = "BST2A0";
-		}
-		1432
-		{
-			title = "Tulip (Blue)";
-			sprite = "BST3A0";
-		}
-		1433
-		{
-			title = "Tulip (Cyan)";
-			sprite = "BST4A0";
-		}
-		1434
-		{
-			title = "Tulip (Yellow)";
-			sprite = "BST5A0";
-		}
-		1435
-		{
-			title = "Tulip (Orange)";
-			sprite = "BST6A0";
-		}
-		1440
-		{
-			title = "Cluster (Red)";
-			sprite = "BSZ5A0";
-		}
-		1441
-		{
-			title = "Cluster (Purple)";
-			sprite = "BSZ5B0";
-		}
-		1442
-		{
-			title = "Cluster (Blue)";
-			sprite = "BSZ5C0";
-		}
-		1443
-		{
-			title = "Cluster (Cyan)";
-			sprite = "BSZ5D0";
-		}
-		1444
-		{
-			title = "Cluster (Yellow)";
-			sprite = "BSZ5E0";
-		}
-		1445
-		{
-			title = "Cluster (Orange)";
-			sprite = "BSZ5F0";
-		}
-		1450
-		{
-			title = "Bush (Red)";
-			sprite = "BSZ6A0";
-		}
-		1451
-		{
-			title = "Bush (Purple)";
-			sprite = "BSZ6B0";
-		}
-		1452
-		{
-			title = "Bush (Blue)";
-			sprite = "BSZ6C0";
-		}
-		1453
-		{
-			title = "Bush (Cyan)";
-			sprite = "BSZ6D0";
-		}
-		1454
-		{
-			title = "Bush (Yellow)";
-			sprite = "BSZ6E0";
-		}
-		1455
-		{
-			title = "Bush (Orange)";
-			sprite = "BSZ6F0";
-		}
-		1460
-		{
-			title = "Vine (Red)";
-			sprite = "BSZ7A0";
-		}
-		1461
-		{
-			title = "Vine (Purple)";
-			sprite = "BSZ7B0";
-		}
-		1462
-		{
-			title = "Vine (Blue)";
-			sprite = "BSZ7C0";
-		}
-		1463
-		{
-			title = "Vine (Cyan)";
-			sprite = "BSZ7D0";
-		}
-		1464
-		{
-			title = "Vine (Yellow)";
-			sprite = "BSZ7E0";
-		}
-		1465
-		{
-			title = "Vine (Orange)";
-			sprite = "BSZ7F0";
-		}
-		1470
-		{
-			title = "BSZ Shrub";
-			sprite = "BSZ8A0";
-		}
-		1471
-		{
-			title = "BSZ Clover";
-			sprite = "BSZ8B0";
-		}
-		1473
-		{
-			title = "Palm Tree (Big)";
-			width = 16;
-			height = 160;
-			sprite = "BSZ8D0";
-		}
-		1475
-		{
-			title = "Palm Tree (Small)";
-			width = 16;
-			height = 80;
-			sprite = "BSZ8F0";
-		}
-	}
-
-	azuretemple
-	{
-		color = 10; // Green
-		title = "Azure Temple";
-
-		1500
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1501
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Up)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1502
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Down)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1503
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Long)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1504
-		{
-			title = "ATZ Target";
-			sprite = "RCRYB0";
-			width = 24;
-			height = 32;
-		}
-		1505
-		{
-			title = "Green Flame";
-			sprite = "CFLMA0E0";
-			width = 8;
-			height = 32;
-		}
-		1506
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Blue Gargoyle";
-			sprite = "BGARD1";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	dreamhill
-	{
-		color = 10; // Green
-		title = "Dream Hill";
-
-		1600
-		{
-			title = "Spring Tree";
-			sprite = "TRE6A0";
-			width = 16;
-			height = 32;
-		}
-		1601
-		{
-			title = "Shleep";
-			sprite = "SHLPA0";
-			width = 24;
-			height = 32;
-		}
-		1602
-		{
-			title = "Nightopian";
-			sprite = "NTPNA1";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	nightstrk
-	{
-		color = 13; // Pink
-		title = "NiGHTS Track";
-		width = 8;
-		height = 4096;
-		sprite = "UNKNA0";
-
-		1700
-		{
-			title = "Axis";
-			sprite = "internal:axis1";
-			circle = 1;
-		}
-		1701
-		{
-			title = "Axis Transfer";
-			sprite = "internal:axis2";
-		}
-		1702
-		{
-			title = "Axis Transfer Line";
-			sprite = "internal:axis3";
-		}
-		1710
-		{
-			title = "Ideya Capture";
-			sprite = "CAPSA0";
-			width = 72;
-			height = 144;
-		}
-	}
-
-	nights
-	{
-		color = 13; // Pink
-		title = "NiGHTS Items";
-		width = 16;
-		height = 32;
-
-		1703
-		{
-			title = "Ideya Drone";
-			sprite = "NDRNA1";
-			width = 16;
-			height = 56;
-		}
-		1704
-		{
-			arrow = 1;
-			title = "NiGHTS Bumper";
-			sprite = "NBMPG3G7";
-			width = 32;
-			height = 64;
-		}
-		1705
-		{
-			arrow = 1;
-			title = "Hoop (Generic)";
-			sprite = "HOOPA0";
-			width = 80;
-			height = 160;
-		}
-		1706
-		{
-			title = "Blue Sphere";
-			sprite = "SPHRA0";
-			width = 16;
-			height = 24;
-		}
-		1707
-		{
-			title = "Super Paraloop";
-			sprite = "NPRUA0";
-		}
-		1708
-		{
-			title = "Drill Refill";
-			sprite = "NPRUB0";
-		}
-		1709
-		{
-			title = "Nightopian Helper";
-			sprite = "NPRUC0";
-		}
-		1711
-		{
-			title = "Extra Time";
-			sprite = "NPRUD0";
-		}
-		1712
-		{
-			title = "Link Freeze";
-			sprite = "NPRUE0";
-		}
-		1713
-		{
-			arrow = 1;
-			title = "Hoop (Customizable)";
-			sprite = "HOOPA0";
-			width = 80;
-			height = 160;
-		}
-		1714
-		{
-			title = "Ideya Anchor Point";
-			sprite = "internal:axis1";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	mario
-	{
-		color = 6; // Brown
-		title = "Mario";
-
-		1800
-		{
-			title = "Coin";
-			sprite = "COINA0";
-			width = 16;
-			height = 24;
-		}
-		1801
-		{
-			arrow = 1;
-			title = "Goomba";
-			sprite = "GOOMA0";
-			width = 24;
-			height = 32;
-		}
-		1802
-		{
-			arrow = 1;
-			title = "Goomba (Blue)";
-			sprite = "BGOMA0";
-			width = 24;
-			height = 32;
-		}
-		1803
-		{
-			title = "Fire Flower";
-			sprite = "FFWRB0";
-			width = 16;
-			height = 32;
-		}
-		1804
-		{
-			title = "Koopa Shell";
-			sprite = "SHLLA1";
-			width = 16;
-			height = 20;
-		}
-		1805
-		{
-			title = "Puma (Jumping Fireball)";
-			sprite = "PUMAA0";
-			width = 8;
-			height = 16;
-		}
-		1806
-		{
-			title = "King Bowser";
-			sprite = "KOOPA0";
-			width = 16;
-			height = 48;
-		}
-		1807
-		{
-			title = "Axe";
-			sprite = "MAXEA0";
-			width = 8;
-			height = 16;
-		}
-		1808
-		{
-			title = "Bush (Short)";
-			sprite = "MUS1A0";
-			width = 16;
-			height = 32;
-		}
-		1809
-		{
-			title = "Bush (Tall)";
-			sprite = "MUS2A0";
-			width = 16;
-			height = 32;
-		}
-		1810
-		{
-			title = "Toad";
-			sprite = "TOADA0";
-			width = 8;
-			height = 32;
-		}
-	}
-
-	christmasdisco
-	{
-		color = 10; // Green
-		title = "Christmas & Disco";
-
-		1850
-		{
-			title = "Christmas Pole";
-			sprite = "XMS1A0";
-			width = 16;
-			height = 40;
-		}
-		1851
-		{
-			title = "Candy Cane";
-			sprite = "XMS2A0";
-			width = 8;
-			height = 32;
-		}
-		1852
-		{
-			blocking = 2;
-			title = "Snowman";
-			sprite = "XMS3A0";
-			width = 16;
-			height = 64;
-		}
-		1853
-		{
-			blocking = 2;
-			title = "Snowman (With Hat)";
-			sprite = "XMS3B0";
-			width = 16;
-			height = 80;
-		}
-		1854
-		{
-			title = "Lamp Post";
-			sprite = "XMS4A0";
-			width = 8;
-			height = 120;
-		}
-		1855
-		{
-			title = "Lamp Post (Snow)";
-			sprite = "XMS4B0";
-			width = 8;
-			height = 120;
-		}
-		1856
-		{
-			title = "Hanging Star";
-			sprite = "XMS5A0";
-			width = 4;
-			height = 80;
-			hangs = 1;
-		}
-		1857
-		{
-			title = "Berry Bush (Snow)";
-			sprite = "BUS1B0";
-			width = 16;
-			height = 32;
-		}
-		1858
-		{
-			title = "Bush (Snow)";
-			sprite = "BUS2B0";
-			width = 16;
-			height = 32;
-		}
-		1859
-		{
-			title = "Blueberry Bush (Snow)";
-			sprite = "BUS3B0";
-			width = 16;
-			height = 32;
-		}
-		1875
-		{
-			title = "Disco Ball";
-			sprite = "DBALA0";
-			width = 16;
-			height = 54;
-			hangs = 1;
-		}
-		1876
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Eggman Disco Statue";
-			sprite = "ESTAB1";
-			width = 20;
-			height = 96;
-		}
-	}
-
-	stalagmites
-	{
-		color = 10; // Green
-		title = "Stalagmites";
-		width = 16;
-		height = 40;
-
-		1900
-		{
-			title = "Brown Stalagmite (Tall)";
-			sprite = "STLGA0";
-			width = 16;
-			height = 40;
-		}
-		1901
-		{
-			title = "Brown Stalagmite";
-			sprite = "STLGB0";
-			width = 16;
-			height = 40;
-		}
-		1902
-		{
-			title = "Orange Stalagmite (Tall)";
-			sprite = "STLGC0";
-			width = 16;
-			height = 40;
-		}
-		1903
-		{
-			title = "Orange Stalagmite";
-			sprite = "STLGD0";
-			width = 16;
-			height = 40;
-		}
-		1904
-		{
-			title = "Red Stalagmite (Tall)";
-			sprite = "STLGE0";
-			width = 16;
-			height = 40;
-		}
-		1905
-		{
-			title = "Red Stalagmite";
-			sprite = "STLGF0";
-			width = 16;
-			height = 40;
-		}
-		1906
-		{
-			title = "Gray Stalagmite (Tall)";
-			sprite = "STLGG0";
-			width = 24;
-			height = 96;
-		}
-		1907
-		{
-			title = "Gray Stalagmite";
-			sprite = "STLGH0";
-			width = 16;
-			height = 40;
-		}
-		1908
-		{
-			title = "Blue Stalagmite (Tall)";
-			sprite = "STLGI0";
-			width = 16;
-			height = 40;
-		}
-		1909
-		{
-			title = "Blue Stalagmite";
-			sprite = "STLGJ0";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	hauntedheights
-	{
-		color = 10; // Green
-		title = "Haunted Heights";
-
-		2000
-		{
-			title = "Smashing Spikeball";
-			sprite = "FMCEA0";
-			width = 18;
-			height = 28;
-		}
-		2001
-		{
-			title = "HHZ Grass";
-			sprite = "HHZMA0";
-			width = 16;
-			height = 40;
-		}
-		2002
-		{
-			title = "HHZ Tentacle 1";
-			sprite = "HHZMB0";
-			width = 16;
-			height = 40;
-		}
-		2003
-		{
-			title = "HHZ Tentacle 2";
-			sprite = "HHZMC0";
-			width = 16;
-			height = 40;
-		}
-		2004
-		{
-			title = "HHZ Stalagmite (Tall)";
-			sprite = "HHZME0";
-			width = 16;
-			height = 40;
-		}
-		2005
-		{
-			title = "HHZ Stalagmite (Short)";
-			sprite = "HHZMF0";
-			width = 16;
-			height = 40;
-		}
-		2006
-		{
-			title = "Jack-o'-lantern 1";
-			sprite = "PUMKA0";
-			width = 16;
-			height = 40;
-		}
-		2007
-		{
-			title = "Jack-o'-lantern 2";
-			sprite = "PUMKB0";
-			width = 16;
-			height = 40;
-		}
-		2008
-		{
-			title = "Jack-o'-lantern 3";
-			sprite = "PUMKC0";
-			width = 16;
-			height = 40;
-		}
-		2009
-		{
-			title = "Purple Mushroom";
-			sprite = "SHRMD0";
-			width = 16;
-			height = 48;
-		}
-		2010
-		{
-			title = "HHZ Tree";
-			sprite = "HHPLC0";
-			width = 12;
-			height = 40;
-		}
-	}
-
-	frozenhillside
-	{
-		color = 10; // Green
-		title = "Frozen Hillside";
-
-		2100
-		{
-			title = "Ice Shard (Small)";
-			sprite = "FHZIA0";
-			width = 8;
-			height = 32;
-		}
-		2101
-		{
-			title = "Ice Shard (Large)";
-			sprite = "FHZIB0";
-			width = 8;
-			height = 32;
-		}
-		2102
-		{
-			title = "Crystal Tree (Aqua)";
-			sprite = "TRE3A0";
-			width = 20;
-			height = 200;
-		}
-		2103
-		{
-			title = "Crystal Tree (Pink)";
-			sprite = "TRE3B0";
-			width = 20;
-			height = 200;
-		}
-		2104
-		{
-			title = "Amy Cameo";
-			sprite = "ROSYA1";
-			width = 16;
-			height = 48;
-		}
-		2105
-		{
-			title = "Mistletoe";
-			sprite = "XMS6A0";
-			width = 52;
-			height = 106;
-		}
-	}
-
-	tutorial
-	{
-		color = 10; // Green
-		title = "Tutorial";
-
-		799
-		{
-			title = "Tutorial Plant";
-			sprite = "TUPFH0";
-			width = 40;
-			height = 144;
-		}
-	}
-
-	flickies
-	{
-		color = 10; // Green
-		title = "Flickies";
-		width = 8;
-		height = 20;
-
-		2200
-		{
-			title = "Bluebird";
-			sprite = "FL01A1";
-		}
-		2201
-		{
-			title = "Rabbit";
-			sprite = "FL02A1";
-		}
-		2202
-		{
-			title = "Chicken";
-			sprite = "FL03A1";
-		}
-		2203
-		{
-			title = "Seal";
-			sprite = "FL04A1";
-		}
-		2204
-		{
-			title = "Pig";
-			sprite = "FL05A1";
-		}
-		2205
-		{
-			title = "Chipmunk";
-			sprite = "FL06A1";
-		}
-		2206
-		{
-			title = "Penguin";
-			sprite = "FL07A1";
-		}
-		2207
-		{
-			title = "Fish";
-			sprite = "FL08A1";
-		}
-		2208
-		{
-			title = "Ram";
-			sprite = "FL09A1";
-		}
-		2209
-		{
-			title = "Puffin";
-			sprite = "FL10A1";
-		}
-		2210
-		{
-			title = "Cow";
-			sprite = "FL11A1";
-		}
-		2211
-		{
-			title = "Rat";
-			sprite = "FL12A1";
-		}
-		2212
-		{
-			title = "Bear";
-			sprite = "FL13A1";
-		}
-		2213
-		{
-			title = "Dove";
-			sprite = "FL14A1";
-		}
-		2214
-		{
-			title = "Cat";
-			sprite = "FL15A1";
-		}
-		2215
-		{
-			title = "Canary";
-			sprite = "FL16A1";
-		}
-		2216
-		{
-			title = "Spider";
-			sprite = "FS01A1";
-		}
-		2217
-		{
-			title = "Bat";
-			sprite = "FS02A0";
-		}
-	}
-}
-
 udmf
 {
-	editor
-	{
-		color = 15; // White
-		arrow = 1;
-		title = "<Editor Things>";
-		error = -1;
-		width = 8;
-		height = 16;
-		sort = 1;
-
-		3328 = "3D Mode Start";
-	}
 
 	starts
 	{
@@ -3974,7 +807,7 @@ udmf
 
 	bosses
 	{
-		color = 8; // Dark_Gray
+		color = 4; // Dark Red
 		arrow = 1;
 		title = "Bosses";
 
@@ -4352,98 +1185,198 @@ udmf
 	{
 		color = 14; // Yellow
 		title = "Rings and Weapon Panels";
-		width = 24;
+		width = 16;
 		height = 24;
 		sprite = "RINGA0";
-		arg0
-		{
-			title = "Float?";
-			type = 11;
-			enum = "yesno";
-		}
 
 		300
 		{
 			title = "Ring";
 			sprite = "RINGA0";
-			width = 16;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		301
 		{
 			title = "Bounce Ring";
-			sprite = "internal:RNGBA0";
+			sprite = "RNGBA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		302
 		{
 			title = "Rail Ring";
-			sprite = "internal:RNGRA0";
+			sprite = "RNGRA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		303
 		{
 			title = "Infinity Ring";
-			sprite = "internal:RNGIA0";
+			sprite = "RNGIA0";
+			width = 24;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		304
 		{
 			title = "Automatic Ring";
-			sprite = "internal:RNGAA0";
+			sprite = "RNGAA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		305
 		{
 			title = "Explosion Ring";
-			sprite = "internal:RNGEA0";
+			sprite = "RNGEA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		306
 		{
 			title = "Scatter Ring";
-			sprite = "internal:RNGSA0";
+			sprite = "RNGSA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		307
 		{
 			title = "Grenade Ring";
-			sprite = "internal:RNGGA0";
+			sprite = "RNGGA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		308
 		{
 			title = "CTF Team Ring (Red)";
-			sprite = "internal:RRNGA0";
-			width = 16;
+			sprite = "internal:TRNGA0R";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		309
 		{
 			title = "CTF Team Ring (Blue)";
-			sprite = "internal:BRNGA0";
-			width = 16;
+			sprite = "internal:TRNGA0B";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		330
 		{
 			title = "Bounce Ring Panel";
-			sprite = "internal:PIKBA0";
+			sprite = "PIKBA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		331
 		{
 			title = "Rail Ring Panel";
-			sprite = "internal:PIKRA0";
+			sprite = "PIKRA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		332
 		{
 			title = "Automatic Ring Panel";
-			sprite = "internal:PIKAA0";
+			sprite = "PIKAA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		333
 		{
 			title = "Explosion Ring Panel";
-			sprite = "internal:PIKEA0";
+			sprite = "PIKEA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		334
 		{
 			title = "Scatter Ring Panel";
-			sprite = "internal:PIKSA0";
+			sprite = "PIKSA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		335
 		{
 			title = "Grenade Ring Panel";
-			sprite = "internal:PIKGA0";
+			sprite = "PIKGA0";
+			width = 24;
+			height = 40;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 	}
 
@@ -4452,7 +1385,7 @@ udmf
 		color = 10; // Light_Green
 		title = "Other Collectibles";
 		width = 16;
-		height = 32;
+		height = 24;
 		sort = 1;
 		sprite = "CEMGA0";
 
@@ -4522,6 +1455,7 @@ udmf
 		{
 			title = "Emerald Hunt Location";
 			sprite = "SHRDA0";
+			height = 32;
 			arg0
 			{
 				title = "Float?";
@@ -4562,16 +1496,16 @@ udmf
 		title = "Monitors";
 		width = 18;
 		height = 40;
-		arg0
-		{
-			title = "Death trigger tag";
-			type = 15;
-		}
 
 		400
 		{
 			title = "Super Ring (10 Rings)";
 			sprite = "TVRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4583,6 +1517,11 @@ udmf
 		{
 			title = "Pity Shield";
 			sprite = "TVPIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4594,6 +1533,11 @@ udmf
 		{
 			title = "Attraction Shield";
 			sprite = "TVATA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4605,6 +1549,11 @@ udmf
 		{
 			title = "Force Shield";
 			sprite = "TVFOA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4616,6 +1565,11 @@ udmf
 		{
 			title = "Armageddon Shield";
 			sprite = "TVARA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4627,6 +1581,11 @@ udmf
 		{
 			title = "Whirlwind Shield";
 			sprite = "TVWWA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4638,6 +1597,11 @@ udmf
 		{
 			title = "Elemental Shield";
 			sprite = "TVELA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4649,6 +1613,11 @@ udmf
 		{
 			title = "Super Sneakers";
 			sprite = "TVSSA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4660,6 +1629,11 @@ udmf
 		{
 			title = "Invincibility";
 			sprite = "TVIVA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4671,6 +1645,11 @@ udmf
 		{
 			title = "Extra Life";
 			sprite = "TV1UA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4692,11 +1671,21 @@ udmf
 		{
 			title = "Eggman";
 			sprite = "TVEGA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		411
 		{
 			title = "Teleporter";
 			sprite = "TVMXA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4708,21 +1697,41 @@ udmf
 		{
 			title = "Gravity Boots";
 			sprite = "TVGVA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		414
 		{
 			title = "CTF Team Ring Monitor (Red)";
 			sprite = "TRRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		415
 		{
 			title = "CTF Team Ring Monitor (Blue)";
 			sprite = "TBRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		416
 		{
 			title = "Recycler";
 			sprite = "TVRCA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4734,16 +1743,31 @@ udmf
 		{
 			title = "Score (1,000 Points)";
 			sprite = "TV1KA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		419
 		{
 			title = "Score (10,000 Points)";
 			sprite = "TVTKA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		420
 		{
 			title = "Flame Shield";
 			sprite = "TVFLA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4755,6 +1779,11 @@ udmf
 		{
 			title = "Water Shield";
 			sprite = "TVBBA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4766,6 +1795,11 @@ udmf
 		{
 			title = "Lightning Shield";
 			sprite = "TVZPA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4782,76 +1816,136 @@ udmf
 		title = "Monitors (Respawning)";
 		width = 20;
 		height = 44;
-		arg0
-		{
-			title = "Death trigger tag";
-			type = 15;
-		}
 
 		431
 		{
 			title = "Pity Shield (Respawn)";
 			sprite = "TVPIB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		432
 		{
 			title = "Attraction Shield (Respawn)";
 			sprite = "TVATB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		433
 		{
 			title = "Force Shield (Respawn)";
 			sprite = "TVFOB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		434
 		{
 			title = "Armageddon Shield (Respawn)";
 			sprite = "TVARB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		435
 		{
 			title = "Whirlwind Shield (Respawn)";
 			sprite = "TVWWB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		436
 		{
 			title = "Elemental Shield (Respawn)";
 			sprite = "TVELB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		437
 		{
 			title = "Super Sneakers (Respawn)";
 			sprite = "TVSSB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		438
 		{
 			title = "Invincibility (Respawn)";
 			sprite = "TVIVB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		440
 		{
 			title = "Eggman (Respawn)";
 			sprite = "TVEGB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		443
 		{
 			title = "Gravity Boots (Respawn)";
 			sprite = "TVGVB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		450
 		{
 			title = "Flame Shield (Respawn)";
 			sprite = "TVFLB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		451
 		{
 			title = "Water Shield (Respawn)";
 			sprite = "TVBBB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		452
 		{
 			title = "Lightning Shield (Respawn)";
 			sprite = "TVZPB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 	}
 
@@ -5235,13 +2329,13 @@ udmf
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Yellow Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalyellow";
 		}
 		601
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Red Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalred";
 			height = 1024;
 		}
 		602
@@ -5259,41 +2353,47 @@ udmf
 		604
 		{
 			title = "Circle of Rings";
-			sprite = "RINGA0";
+			sprite = "internal:circlering";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		605
 		{
 			title = "Circle of Rings (Big)";
-			sprite = "RINGA0";
+			sprite = "internal:circlebigring";
 			width = 192;
+			centerhitbox = true;
 		}
 		606
 		{
 			title = "Circle of Blue Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circlesphere";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		607
 		{
 			title = "Circle of Blue Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigsphere";
 			width = 192;
+			centerhitbox = true;
 		}
 		608
 		{
 			title = "Circle of Rings and Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circleringsphere";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		609
 		{
 			title = "Circle of Rings and Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigringsphere";
 			width = 192;
+			centerhitbox = true;
 		}
 		610
 		{
@@ -5322,6 +2422,7 @@ udmf
 			sprite = "RINGA0";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 			arg0
 			{
 				title = "Number of items";
@@ -5429,7 +2530,7 @@ udmf
 		756
 		{
 			title = "Blast Linedef Executor";
-			sprite = "TOADA0";
+			sprite = "internal:blastexec";
 			width = 32;
 			height = 16;
 			arg0
@@ -5441,7 +2542,7 @@ udmf
 		757
 		{
 			title = "Fan Particle Generator";
-			sprite = "PRTLA0";
+			sprite = "internal:fanparticles";
 			width = 8;
 			height = 16;
 			arg0
@@ -5490,11 +2591,6 @@ udmf
 			title = "PolyObject Spawn Point";
 			sprite = "internal:polycenter";
 		}
-		762
-		{
-			title = "PolyObject Spawn Point (Crush)";
-			sprite = "internal:polycentercrush";
-		}
 		780
 		{
 			title = "Skybox View Point";
@@ -5514,7 +2610,7 @@ udmf
 
 	greenflower
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Greenflower";
 
 		800
@@ -5619,7 +2715,7 @@ udmf
 
 	technohill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Techno Hill";
 
 		900
@@ -5663,7 +2759,7 @@ udmf
 
 	deepsea
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Deep Sea";
 
 		1000
@@ -5823,7 +2919,7 @@ udmf
 
 	castleeggman
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Castle Eggman";
 
 		1100
@@ -6386,7 +3482,7 @@ udmf
 
 	aridcanyon
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Arid Canyon";
 
 		1200
@@ -6511,6 +3607,7 @@ udmf
 			sprite = "WWSGAR";
 			width = 22;
 			height = 64;
+			wallsprite = true;
 		}
 		1213
 		{
@@ -6518,6 +3615,7 @@ udmf
 			sprite = "WWS2AR";
 			width = 22;
 			height = 64;
+			wallsprite = true;
 		}
 		1214
 		{
@@ -6525,6 +3623,7 @@ udmf
 			sprite = "WWS3ALAR";
 			width = 16;
 			height = 192;
+			wallsprite = true;
 		}
 		1215
 		{
@@ -6644,7 +3743,7 @@ udmf
 
 	redvolcano
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Red Volcano";
 
 		1300
@@ -6789,7 +3888,7 @@ udmf
 
 	botanicserenity
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Botanic Serenity";
 		width = 16;
 		height = 32;
@@ -7032,7 +4131,7 @@ udmf
 
 	azuretemple
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Azure Temple";
 
 		1500
@@ -7144,7 +4243,7 @@ udmf
 
 	dreamhill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Dream Hill";
 
 		1600
@@ -7178,8 +4277,8 @@ udmf
 
 	nightstrk
 	{
-		color = 13; // Pink
-		title = "NiGHTS Track";
+		color = 16; // Light Pink
+		title = "NiGHTS Track & Misc.";
 		width = 8;
 		height = 4096;
 		sprite = "UNKNA0";
@@ -7207,8 +4306,8 @@ udmf
 				type = 11;
 				enum
 				{
-					0 = "Clockwise";
-					1 = "Counterclockwise";
+					0 = "Counterclockwise";
+					1 = "Clockwise";
 				}
 			}
 		}
@@ -7238,30 +4337,6 @@ udmf
 				title = "Order";
 			}
 		}
-		1710
-		{
-			title = "Ideya Capture";
-			sprite = "CAPSA0";
-			width = 72;
-			height = 144;
-			arg0
-			{
-				title = "Mare";
-			}
-			arg1
-			{
-				title = "Required spheres";
-			}
-		}
-	}
-
-	nights
-	{
-		color = 13; // Pink
-		title = "NiGHTS Items";
-		width = 16;
-		height = 32;
-
 		1703
 		{
 			title = "Ideya Drone";
@@ -7294,11 +4369,46 @@ udmf
 			}
 			arg4
 			{
-				title = "Die upon time up?";
-				type = 11;
-				enum = "noyes";
+				title = "Die upon time up?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1710
+		{
+			title = "Ideya Capture";
+			sprite = "CAPSA0";
+			width = 72;
+			height = 144;
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Required spheres";
+			}
+		}
+		1714
+		{
+			title = "Ideya Anchor Point";
+			sprite = "internal:ideya";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Mare";
 			}
 		}
+	}
+
+	nights
+	{
+		color = 13; // Pink
+		title = "NiGHTS Items";
+		width = 16;
+		height = 32;
+		
 		1704
 		{
 			arrow = 1;
@@ -7399,25 +4509,15 @@ udmf
 		{
 			arrow = 1;
 			title = "Hoop";
-			sprite = "HOOPA0";
+			sprite = "internal:nightshoop";
 			width = 80;
 			height = 160;
+			centerhitbox = true;
 			arg0
 			{
 				title = "Radius";
 			}
 		}
-		1714
-		{
-			title = "Ideya Anchor Point";
-			sprite = "internal:axis1";
-			width = 8;
-			height = 16;
-			arg0
-			{
-				title = "Mare";
-			}
-		}
 	}
 
 	mario
@@ -7528,7 +4628,7 @@ udmf
 
 	christmasdisco
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Christmas & Disco";
 
 		1850
@@ -7643,7 +4743,7 @@ udmf
 
 	stalagmites
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Stalagmites";
 		width = 16;
 		height = 40;
@@ -7722,7 +4822,7 @@ udmf
 
 	hauntedheights
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Haunted Heights";
 
 		2000
@@ -7828,7 +4928,7 @@ udmf
 
 	frozenhillside
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Frozen Hillside";
 
 		2100
@@ -7883,7 +4983,7 @@ udmf
 
 	tutorial
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Tutorial";
 
 		799
@@ -7901,65 +5001,170 @@ udmf
 
 	flickies
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Flickies";
 		width = 8;
 		height = 20;
-		arg0
-		{
-			title = "Radius";
-		}
-		arg1
-		{
-			title = "Flags";
-			type = 12;
-			enum
-			{
-				1 = "Move aimlessly";
-				2 = "No movement";
-				4 = "Hop";
-			}
-		}
 
 		2200
 		{
 			title = "Bluebird";
 			sprite = "FL01A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2201
 		{
 			title = "Rabbit";
 			sprite = "FL02A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2202
 		{
 			title = "Chicken";
 			sprite = "FL03A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2203
 		{
 			title = "Seal";
 			sprite = "FL04A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2204
 		{
 			title = "Pig";
 			sprite = "FL05A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2205
 		{
 			title = "Chipmunk";
 			sprite = "FL06A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2206
 		{
 			title = "Penguin";
 			sprite = "FL07A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2207
 		{
 			title = "Fish";
 			sprite = "FL08A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 			arg2
 			{
 				title = "Color";
@@ -7989,51 +5194,214 @@ udmf
 		{
 			title = "Ram";
 			sprite = "FL09A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2209
 		{
 			title = "Puffin";
 			sprite = "FL10A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2210
 		{
 			title = "Cow";
 			sprite = "FL11A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2211
 		{
 			title = "Rat";
 			sprite = "FL12A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2212
 		{
 			title = "Bear";
 			sprite = "FL13A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2213
 		{
 			title = "Dove";
 			sprite = "FL14A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2214
 		{
 			title = "Cat";
 			sprite = "FL15A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2215
 		{
 			title = "Canary";
 			sprite = "FL16A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2216
 		{
 			title = "Spider";
 			sprite = "FS01A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2217
 		{
 			title = "Bat";
 			sprite = "FS02A0";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 	}
-}
+	
+	editor
+	{
+		color = 15; // White
+		arrow = 1;
+		title = "3D Mode Start";
+		error = -1;
+		width = 8;
+		height = 16;
+		sort = 1;
+
+		3328 = "3D Mode Start";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/SRB2_22Doom.cfg b/extras/conf/udb/SRB2_22Doom.cfg
deleted file mode 100644
index 9e733aa394581a8d48dab2c592e46008b2142f11..0000000000000000000000000000000000000000
--- a/extras/conf/udb/SRB2_22Doom.cfg
+++ /dev/null
@@ -1,32 +0,0 @@
-/************************************************************************\
-	Ultimate Doom Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
-\************************************************************************/
-
-// This is required to prevent accidental use of a different configuration
-type = "Doom Builder 2 Game Configuration";
-
-// This is the title to show for this game
-game = "Sonic Robo Blast 2 - 2.2 (Doom format)";
-
-// This is the simplified game engine/sourceport name
-engine = "zdoom";
-
-// Settings common to all games and all map formats
-include("Includes\\SRB222_common.cfg", "common");
-
-// Settings common to Doom map format
-include("Includes\\SRB222_common.cfg", "mapformat_doom");
-
-include("Includes\\Game_SRB222.cfg");
-
-// Script lumps detection
-scriptlumpnames
-{
-	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
-}
-
-//Default things filters
-thingsfilters
-{
-	include("Includes\\SRB222_misc.cfg", "thingsfilters");
-}
\ No newline at end of file
diff --git a/src/Android.mk b/src/Android.mk
index a461da2242c7ab813831c95c1d442353756b0907..035d48887727c2a6d6b63a6acb38fc7ec65a9342 100644
--- a/src/Android.mk
+++ b/src/Android.mk
@@ -76,7 +76,7 @@ LOCAL_SRC_FILES :=      am_map.c \
                         android/i_system.c \
                         android/i_video.c
 
-LOCAL_CFLAGS += -DPLATFORM_ANDROID -DNONX86 -DLINUX -DDEBUGMODE -DNOASM -DNOPIX -DUNIXCOMMON -DNOTERMIOS
+LOCAL_CFLAGS += -DPLATFORM_ANDROID -DNONX86 -DLINUX -DDEBUGMODE -DNOPIX -DUNIXCOMMON -DNOTERMIOS
 
 LOCAL_MODULE := libsrb2
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2f4467a322026aa73b4281b97daeae8800855deb..b926b3b7a3372d206df781fd65bbf7a947ddaef3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,23 +1,150 @@
-add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32)
+include(clang-tidy-default)
+
+add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
+	comptime.c
+	md5.c
+	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
+	deh_tables.c
+	z_zone.c
+	f_finale.c
+	f_wipe.c
+	g_demo.c
+	g_game.c
+	g_input.c
+	am_map.c
+	command.c
+	console.c
+	hu_stuff.c
+	i_time.c
+	y_inter.c
+	st_stuff.c
+	m_aatree.c
+	m_anigif.c
+	m_argv.c
+	m_bbox.c
+	m_cheat.c
+	m_cond.c
+	m_easing.c
+	m_fixed.c
+	m_menu.c
+	m_misc.c
+	m_perfstats.c
+	m_random.c
+	m_queue.c
+	info.c
+	p_ceilng.c
+	p_enemy.c
+	p_floor.c
+	p_inter.c
+	p_lights.c
+	p_map.c
+	p_maputl.c
+	p_mobj.c
+	p_polyobj.c
+	p_saveg.c
+	p_setup.c
+	p_sight.c
+	p_spec.c
+	p_telept.c
+	p_tick.c
+	p_user.c
+	p_slopes.c
+	tables.c
+	r_bsp.c
+	r_data.c
+	r_draw.c
+	r_fps.c
+	r_main.c
+	r_plane.c
+	r_segs.c
+	r_skins.c
+	r_sky.c
+	r_splats.c
+	r_things.c
+	r_bbox.c
+	r_textures.c
+	r_patch.c
+	r_patchrotation.c
+	r_picformats.c
+	r_portal.c
+	screen.c
+	taglist.c
+	v_video.c
+	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
+	lua_script.c
+	lua_baselib.c
+	lua_mathlib.c
+	lua_hooklib.c
+	lua_consolelib.c
+	lua_infolib.c
+	lua_mobjlib.c
+	lua_playerlib.c
+	lua_skinlib.c
+	lua_thinkerlib.c
+	lua_maplib.c
+	lua_taglib.c
+	lua_polyobjlib.c
+	lua_blockmaplib.c
+	lua_hudlib.c
+	lua_hudlib_drawlist.c
+	lua_inputlib.c
+)
 
-if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
-	# On MinGW with internal libraries, link the standard library statically
-	target_link_options(SRB2SDL2 PRIVATE "-static")
-endif()
+# This updates the modification time for comptime.c at the
+# end of building so when the build system is ran next time,
+# that file gets flagged. comptime.c will always be rebuilt.
+#
+# This begs the question, why always rebuild comptime.c?
+# Some things like the git commit must be checked each time
+# the program is built. But the build system determines which
+# files should be rebuilt before anything else. So
+# comptime.c, which only needs to be rebuilt based on
+# information known at build time, must be told to rebuild
+# before that information can be ascertained.
+add_custom_command(
+	TARGET SRB2SDL2
+	POST_BUILD
+	COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_CURRENT_SOURCE_DIR}/comptime.c
+)
 
-# Core sources
-target_sourcefile(c)
-target_sources(SRB2SDL2 PRIVATE comptime.c md5.c config.h.in)
+# config.h is generated by this command. It should be done at
+# build time for accurate git information and before anything
+# that needs it, obviously.
+add_custom_target(_SRB2_reconf ALL
+	COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake
+	WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
+)
+add_dependencies(SRB2SDL2 _SRB2_reconf)
 
-set(SRB2_ASM_SOURCES vid_copy.s)
+if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
+	target_link_options(SRB2SDL2 PRIVATE "-Wl,--disable-dynamicbase")
+	if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
+		# On MinGW with internal libraries, link the standard library statically
+		target_link_options(SRB2SDL2 PRIVATE "-static")
+	endif()
+endif()
 
-set(SRB2_NASM_SOURCES tmap_mmx.nas tmap.nas)
+target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17)
 
 ### Configuration
-set(SRB2_CONFIG_USEASM OFF CACHE BOOL
-	"Enable NASM tmap implementation for software mode speedup.")
-set(SRB2_CONFIG_YASM OFF CACHE BOOL
-	"Use YASM in place of NASM.")
 set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL
 	"Compile a development build of SRB2.")
 
@@ -74,33 +201,6 @@ if("${SRB2_CONFIG_HWRENDER}")
 	endif()
 endif()
 
-if(${SRB2_CONFIG_USEASM})
-	#SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm.
-	if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
-		set(SRB2_ASM_FLAGS "-DLINUX ${SRB2_ASM_FLAGS}")
-	endif()
-
-	if(${SRB2_CONFIG_YASM})
-		set(CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS} nas)
-		set(CMAKE_ASM_YASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
-		enable_language(ASM_YASM)
-	else()
-		set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS} nas)
-		set(CMAKE_ASM_NASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
-		enable_language(ASM_NASM)
-	endif()
-
-	set(SRB2_USEASM ON)
-	target_compile_definitions(SRB2SDL2 PRIVATE -DUSEASM)
-	target_compile_options(SRB2SDL2 PRIVATE -msse3 -mfpmath=sse)
-
-	target_sources(SRB2SDL2 PRIVATE ${SRB2_ASM_SOURCES}
-		${SRB2_NASM_SOURCES})
-else()
-	set(SRB2_USEASM OFF)
-	target_compile_definitions(SRB2SDL2 PRIVATE -DNONX86 -DNORUSEASM)
-endif()
-
 # Targets
 
 # If using CCACHE, then force it.
@@ -289,6 +389,9 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
 endif()
 
 add_subdirectory(sdl)
+if(SRB2_CONFIG_ENABLE_TESTS)
+	add_subdirectory(tests)
+endif()
 
 # strip debug symbols into separate file when using gcc.
 # to be consistent with Makefile, don't generate for OS X.
@@ -329,3 +432,11 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRA
 		COMMENT "Copying runtime DLLs"
 	)
 endif()
+
+# Setup clang-tidy
+if(SRB2_CONFIG_ENABLE_CLANG_TIDY_C)
+	target_set_default_clang_tidy(SRB2SDL2 C "-*,clang-analyzer-*,-clang-analyzer-cplusplus-*")
+endif()
+if(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX)
+	target_set_default_clang_tidy(SRB2SDL2 CXX "-*,clang-analyzer-*,modernize-*")
+endif()
diff --git a/src/Makefile b/src/Makefile
index 36b1a7efabea7416f42c624fc03ab93d2a15c05f..539c2fa743cc46071c94b8736c49802e6034e62c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -47,8 +47,6 @@
 # HAVE_MINIUPNPC=1 - Enable automated port forwarding.
 #                    Already enabled by default for 32-bit
 #                    Windows.
-# NOASM=1 - Disable hand optimized assembly code for the
-#           Software renderer.
 # NOPNG=1 - Disable PNG graphics support. (TODO: double
 #           check netplay compatible.)
 # NOCURL=1 - Disable libcurl--HTTP capability.
@@ -88,7 +86,6 @@
 #        executable.
 # WINDOWSHELL=1 - Use Windows commands.
 # PREFIX= - Prefix to many commands, for cross compiling.
-# YASM=1 - Use Yasm instead of NASM assembler.
 # STABS=1 - ?
 # ECHO=1 - Print out each command in the build process.
 # NOECHOFILENAMES=1 - Don't print out each that is being
@@ -144,25 +141,9 @@ endif
 
 OBJDUMP_OPTS?=--wide --source --line-numbers
 
-OBJCOPY:=$(call Prefix,objcopy)
-OBJDUMP:=$(call Prefix,objdump)
-WINDRES:=$(call Prefix,windres)
-
-ifdef YASM
-NASM?=yasm
-else
-NASM?=nasm
-endif
-
-ifdef YASM
-ifdef STABS
-NASMOPTS?=-g stabs
-else
-NASMOPTS?=-g dwarf2
-endif
-else
-NASMOPTS?=-g
-endif
+OBJCOPY?=$(call Prefix,objcopy)
+OBJDUMP?=$(call Prefix,objdump)
+WINDRES?=$(call Prefix,windres)
 
 GZIP?=gzip
 GZIP_OPTS?=-9 -f -n
@@ -187,8 +168,6 @@ makedir:=../make
 opts:=-DCOMPVERSION -g
 libs:=
 
-nasm_format:=
-
 # This is a list of variables names, of which if defined,
 # also defines the name as a macro to the compiler.
 passthru_opts:=
@@ -316,7 +295,6 @@ endif
 
 LD:=$(CC)
 cc:=$(cc) $(opts)
-nasm=$(NASM) $(NASMOPTS) -f $(nasm_format)
 ifdef UPX
 upx=$(UPX) $(UPX_OPTS)
 endif
@@ -393,7 +371,6 @@ $(objdir)/%.$(1) : %.$(2) | $$$$(@D)/
 endef
 
 $(eval $(call _recipe,o,c,$(cc) -c -o $$@ $$<))
-$(eval $(call _recipe,o,nas,$(nasm) -o $$@ $$<))
 $(eval $(call _recipe,o,s,$(cc) $(asflags) -c -o $$@ $$<))
 $(eval $(call _recipe,res,rc,$(windres) -i $$< -o $$@))
 
@@ -414,3 +391,5 @@ ifdef WINDOWSHELL
 else
 	@:
 endif
+
+#$(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 aca4987213419adb6d93056a8413c2d0330ae057..9e27369462b00c407f02cb95dbda22ef284a91d1 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 8ba33383bb2f0c8169e92f91c6c8fea25c6c852f..a66779c07a77c72a4ae0c085ff738ce58e62cf73 100644
--- a/src/Makefile.d/features.mk
+++ b/src/Makefile.d/features.mk
@@ -1,75 +1,72 @@
-#
-# 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 NOASM
-ifndef NONX86
-sources+=tmap.nas tmap_mmx.nas
-opts+=-DUSEASM
-endif
-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+=\
+	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
+
+ifdef NONET
+NOCURL=1
+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))))
diff --git a/src/Makefile.d/nix.mk b/src/Makefile.d/nix.mk
index 767b64c12be4bf42fede8e07e80cba68151ef92a..9adf3f0f14fdc2b052e48053cabedee48a1a7edd 100644
--- a/src/Makefile.d/nix.mk
+++ b/src/Makefile.d/nix.mk
@@ -9,10 +9,6 @@ opts+=-DUNIXCOMMON -DLUA_USE_POSIX
 # instead of addresses
 libs+=-lm -rdynamic
 
-ifndef nasm_format
-nasm_format:=elf -DLINUX
-endif
-
 ifndef NOHW
 opts+=-I/usr/X11R6/include
 libs+=-L/usr/X11R6/lib
@@ -29,13 +25,12 @@ 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
 #ifdef SOLARIS
 #NOIPX=1
-#NOASM=1
 #opts+=-I/usr/local/include -I/opt/sfw/include \
 #		-DSOLARIS -DINADDR_NONE=INADDR_ANY -DBSD_COMP
 #libs+=-L/opt/sfw/lib -lsocket -lnsl
diff --git a/src/Makefile.d/platform.mk b/src/Makefile.d/platform.mk
index c5ac71a20adc24766a961cb544af54cf3a1b59b1..d19143e4cf6040dc161b201553db3942b123ee39 100644
--- a/src/Makefile.d/platform.mk
+++ b/src/Makefile.d/platform.mk
@@ -39,7 +39,6 @@ else ifdef SOLARIS # FIXME: UNTESTED
 UNIX=1
 platform=solaris
 else ifdef CYGWIN32 # FIXME: UNTESTED
-nasm_format=win32
 platform=cygwin
 else ifdef MINGW
 ifdef MINGW64
diff --git a/src/Makefile.d/sdl.mk b/src/Makefile.d/sdl.mk
index 99ca624e69f2f18c10625c93585f14681636f36e..a1bfa33038bbacebada85790a7565fceb9440985 100644
--- a/src/Makefile.d/sdl.mk
+++ b/src/Makefile.d/sdl.mk
@@ -56,13 +56,6 @@ SDL_LDFLAGS?=$(shell $(SDL_CONFIG) \
 $(eval $(call Propogate_flags,SDL))
 endif
 
-# use the x86 asm code
-ifndef CYGWIN32
-ifndef NOASM
-USEASM=1
-endif
-endif
-
 ifdef MINGW
 ifndef NOSDLMAIN
 SDLMAIN=1
diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk
index 0e48ed68359523e70b4ba0a6d8eade4b81d1d9ca..73a3d9e453ecaa0a01e32e15f71326bd7be57920 100644
--- a/src/Makefile.d/win32.mk
+++ b/src/Makefile.d/win32.mk
@@ -17,8 +17,6 @@ sources+=win32/Srb2win.rc
 opts+=-DSTDC_HEADERS
 libs+=-ladvapi32 -lkernel32 -lmsvcrt -luser32
 
-nasm_format:=win32
-
 SDL?=1
 
 ifndef NOHW
diff --git a/src/Sourcefile b/src/Sourcefile
index de90bb60910286242ae4b62b41af5d9ad57706ad..f2b408c665d28306ce9c778646f6139b6874099a 100644
--- a/src/Sourcefile
+++ b/src/Sourcefile
@@ -64,6 +64,7 @@ r_skins.c
 r_sky.c
 r_splats.c
 r_things.c
+r_bbox.c
 r_textures.c
 r_patch.c
 r_patchrotation.c
@@ -80,8 +81,8 @@ mserv.c
 http-mserv.c
 i_tcp.c
 lzf.c
-vid_copy.s
 b_bot.c
+u_list.c
 lua_script.c
 lua_baselib.c
 lua_mathlib.c
diff --git a/src/android/i_system.c b/src/android/i_system.c
index f0edcc17efb6445c32cfc7c68452714b55cd8909..9d798d452fb1b36460553fd35870894be2a3e406 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -24,7 +24,7 @@ static INT64 start_time; // as microseconds since the epoch
 
 // I should probably return how much memory is remaining
 // for this process, considering Android's process memory limit.
-UINT32 I_GetFreeMem(UINT32 *total)
+size_t I_GetFreeMem(size_t *total)
 {
   // what the heck?  sysinfo() is partially missing in bionic?
   /* struct sysinfo si; */
@@ -278,4 +278,26 @@ char *I_ClipboardPaste(void)
 
 void I_RegisterSysCommands(void) {}
 
+// This is identical to the SDL implementation.
+size_t I_GetRandomBytes(char *destination, size_t count)
+{
+  FILE *rndsource;
+  size_t actual_bytes;
+
+  if (!(rndsource = fopen("/dev/urandom", "r")))
+	  if (!(rndsource = fopen("/dev/random", "r")))
+		  actual_bytes = 0;
+
+  if (rndsource)
+  {
+	  actual_bytes = fread(destination, 1, count, rndsource);
+	  fclose(rndsource);
+  }
+
+  if (actual_bytes == 0)
+    I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+
+  return actual_bytes;
+}
+
 #include "../sdl/dosstr.c"
diff --git a/src/asm_defs.inc b/src/asm_defs.inc
deleted file mode 100644
index 48f8da0d8f582f28ad09674eec97b4af840f40b9..0000000000000000000000000000000000000000
--- a/src/asm_defs.inc
+++ /dev/null
@@ -1,43 +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  asm_defs.inc
-/// \brief must match the C structures
-
-#ifndef __ASM_DEFS__
-#define __ASM_DEFS__
-
-// this makes variables more noticable,
-// and make the label match with C code
-
-// Linux, unlike DOS, has no "_" 19990119 by Kin
-// and nasm needs .data code segs under linux 20010210 by metzgermeister
-// FIXME: nasm ignores these settings, so I put the macros into the makefile
-#ifdef __ELF__
-#define C(label) label
-#define CODE_SEG .data
-#else
-#define C(label) _##label
-#define CODE_SEG .text
-#endif
-
-/* This is a more readable way to access the arguments passed from C code   */
-/* PLEASE NOTE: it is supposed that all arguments passed from C code are    */
-/*              32bit integer (INT32, long, and most *pointers)               */
-#define ARG1      8(%ebp)
-#define ARG2      12(%ebp)
-#define ARG3      16(%ebp)
-#define ARG4      20(%ebp)
-#define ARG5      24(%ebp)
-#define ARG6      28(%ebp)
-#define ARG7      32(%ebp)
-#define ARG8      36(%ebp)
-#define ARG9      40(%ebp)      //(c)tm ... Allegro by Shawn Hargreaves.
-
-#endif
diff --git a/src/b_bot.c b/src/b_bot.c
index d1465f891d481dd8314507b8f6b5e2c795abb1dd..57f7623042d318df981af823ac59d1865c44fb75 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -631,7 +631,8 @@ void B_HandleFlightIndicator(player_t *player)
 	}
 
 	// otherwise, update its visibility
-	if (P_IsLocalPlayer(player->botleader))
+	tails->hnext->drawonlyforplayer = player->botleader; // Hide it from the other player in splitscreen, and yourself when spectating
+	if (P_IsLocalPlayer(player->botleader)) // Only display it on your own view. Don't display it for spectators
 		tails->hnext->flags2 &= ~MF2_DONTDRAW;
 	else
 		tails->hnext->flags2 |= MF2_DONTDRAW;
diff --git a/src/blua/CMakeLists.txt b/src/blua/CMakeLists.txt
index 4e9c67d2f348a8bfed899e4002d25136284b031f..892bf534addc810434bdd00960ebe7a92ef7bde4 100644
--- a/src/blua/CMakeLists.txt
+++ b/src/blua/CMakeLists.txt
@@ -1 +1,28 @@
-target_sourcefile(c)
+target_sources(SRB2SDL2 PRIVATE
+	lapi.c
+	lbaselib.c
+	ldo.c
+	lfunc.c
+	linit.c
+	liolib.c
+	llex.c
+	lmem.c
+	lobject.c
+	lstate.c
+	lstrlib.c
+	ltablib.c
+	lundump.c
+	lzio.c
+	lauxlib.c
+	lcode.c
+	ldebug.c
+	ldump.c
+	lgc.c
+	lopcodes.c
+	lparser.c
+	lstring.c
+	ltable.c
+	ltm.c
+	lvm.c
+	loslib.c
+)
diff --git a/src/blua/Sourcefile b/src/blua/Sourcefile
index f99c89c8dfb8e8b5da643cb2c8625a764e84580d..dae94310981fce03d6aefd30b6e7e5781a4a4eae 100644
--- a/src/blua/Sourcefile
+++ b/src/blua/Sourcefile
@@ -23,3 +23,4 @@ lstring.c
 ltable.c
 ltm.c
 lvm.c
+loslib.c
diff --git a/src/blua/linit.c b/src/blua/linit.c
index d17390b20a01dca0cff486c4c25cc5e31cd9570a..dcf05d9f2c925e49457514c57f99afe7bf66c1d8 100644
--- a/src/blua/linit.c
+++ b/src/blua/linit.c
@@ -18,6 +18,7 @@ static const luaL_Reg lualibs[] = {
   {"", luaopen_base},
   {LUA_TABLIBNAME, luaopen_table},
   {LUA_IOLIBNAME, luaopen_io},
+  {LUA_OSLIBNAME, luaopen_os},
   {LUA_STRLIBNAME, luaopen_string},
   {NULL, NULL}
 };
diff --git a/src/blua/loslib.c b/src/blua/loslib.c
new file mode 100644
index 0000000000000000000000000000000000000000..ba2e1e3846c77809d0fe06673243b537417ea4ea
--- /dev/null
+++ b/src/blua/loslib.c
@@ -0,0 +1,167 @@
+/*
+** $Id: loslib.c,v 1.19.1.3 2008/01/18 16:38:18 roberto Exp $
+** Standard Operating System library
+** See Copyright Notice in lua.h
+*/
+
+
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define loslib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+static int os_clock (lua_State *L) {
+  lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC);
+  return 1;
+}
+
+
+/*
+** {======================================================
+** Time/Date operations
+** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S,
+**   wday=%w+1, yday=%j, isdst=? }
+** =======================================================
+*/
+
+static void setfield (lua_State *L, const char *key, int value) {
+  lua_pushinteger(L, value);
+  lua_setfield(L, -2, key);
+}
+
+static void setboolfield (lua_State *L, const char *key, int value) {
+  if (value < 0)  /* undefined? */
+    return;  /* does not set field */
+  lua_pushboolean(L, value);
+  lua_setfield(L, -2, key);
+}
+
+static int getboolfield (lua_State *L, const char *key) {
+  int res;
+  lua_getfield(L, -1, key);
+  res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1);
+  lua_pop(L, 1);
+  return res;
+}
+
+
+static int getfield (lua_State *L, const char *key, int d) {
+  int res;
+  lua_getfield(L, -1, key);
+  if (lua_isnumber(L, -1))
+    res = (int)lua_tointeger(L, -1);
+  else {
+    if (d < 0)
+      return luaL_error(L, "field " LUA_QS " missing in date table", key);
+    res = d;
+  }
+  lua_pop(L, 1);
+  return res;
+}
+
+
+static int os_date (lua_State *L) {
+  const char *s = luaL_optstring(L, 1, "%c");
+  time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL));
+  struct tm *stm;
+  if (*s == '!') {  /* UTC? */
+    stm = gmtime(&t);
+    s++;  /* skip `!' */
+  }
+  else
+    stm = localtime(&t);
+  if (stm == NULL)  /* invalid date? */
+    lua_pushnil(L);
+  else if (strcmp(s, "*t") == 0) {
+    lua_createtable(L, 0, 9);  /* 9 = number of fields */
+    setfield(L, "sec", stm->tm_sec);
+    setfield(L, "min", stm->tm_min);
+    setfield(L, "hour", stm->tm_hour);
+    setfield(L, "day", stm->tm_mday);
+    setfield(L, "month", stm->tm_mon+1);
+    setfield(L, "year", stm->tm_year+1900);
+    setfield(L, "wday", stm->tm_wday+1);
+    setfield(L, "yday", stm->tm_yday+1);
+    setboolfield(L, "isdst", stm->tm_isdst);
+  }
+  else {
+    char cc[3];
+    luaL_Buffer b;
+    cc[0] = '%'; cc[2] = '\0';
+    luaL_buffinit(L, &b);
+    for (; *s; s++) {
+      if (*s != '%' || *(s + 1) == '\0')  /* no conversion specifier? */
+        luaL_addchar(&b, *s);
+      else {
+        size_t reslen;
+        char buff[200];  /* should be big enough for any conversion result */
+        cc[1] = *(++s);
+        reslen = strftime(buff, sizeof(buff), cc, stm);
+        luaL_addlstring(&b, buff, reslen);
+      }
+    }
+    luaL_pushresult(&b);
+  }
+  return 1;
+}
+
+
+static int os_time (lua_State *L) {
+  time_t t;
+  if (lua_isnoneornil(L, 1))  /* called without args? */
+    t = time(NULL);  /* get current time */
+  else {
+    struct tm ts;
+    luaL_checktype(L, 1, LUA_TTABLE);
+    lua_settop(L, 1);  /* make sure table is at the top */
+    ts.tm_sec = getfield(L, "sec", 0);
+    ts.tm_min = getfield(L, "min", 0);
+    ts.tm_hour = getfield(L, "hour", 12);
+    ts.tm_mday = getfield(L, "day", -1);
+    ts.tm_mon = getfield(L, "month", -1) - 1;
+    ts.tm_year = getfield(L, "year", -1) - 1900;
+    ts.tm_isdst = getboolfield(L, "isdst");
+    t = mktime(&ts);
+  }
+  if (t == (time_t)(-1))
+    lua_pushnil(L);
+  else
+    lua_pushnumber(L, (lua_Number)t);
+  return 1;
+}
+
+
+static int os_difftime (lua_State *L) {
+  lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)),
+                             (time_t)(luaL_optnumber(L, 2, 0))));
+  return 1;
+}
+
+/* }====================================================== */
+
+static const luaL_Reg syslib[] = {
+  {"clock",     os_clock},
+  {"date",      os_date},
+  {"difftime",  os_difftime},
+  {"time",      os_time},
+  {NULL, NULL}
+};
+
+/* }====================================================== */
+
+
+
+LUALIB_API int luaopen_os (lua_State *L) {
+  luaL_register(L, LUA_OSLIBNAME, syslib);
+  return 1;
+}
diff --git a/src/blua/lualib.h b/src/blua/lualib.h
index 4ea97edf3d22d585b57390b2db435b4ceec73330..7127e4d77e56c1d2aee9c7079b59c5a3adc959a2 100644
--- a/src/blua/lualib.h
+++ b/src/blua/lualib.h
@@ -24,6 +24,9 @@ LUALIB_API int (luaopen_table) (lua_State *L);
 #define LUA_IOLIBNAME	"io"
 LUALIB_API int (luaopen_io) (lua_State *L);
 
+#define LUA_OSLIBNAME	"os"
+LUALIB_API int (luaopen_os) (lua_State *L);
+
 #define LUA_STRLIBNAME	"string"
 LUALIB_API int (luaopen_string) (lua_State *L);
 
diff --git a/src/command.c b/src/command.c
index 9d5886745ae171993cbe69c1664ee920846845d2..e1a43522da9946fe73b10efcf4f250008318fb50 100644
--- a/src/command.c
+++ b/src/command.c
@@ -34,6 +34,7 @@
 #include "lua_script.h"
 #include "d_netfil.h" // findfile
 #include "r_data.h" // Color_cons_t
+#include "d_main.h" // D_IsPathAllowed
 
 //========
 // protos.
@@ -75,6 +76,7 @@ CV_PossibleValue_t CV_OnOff[] = {{0, "Off"}, {1, "On"}, {0, NULL}};
 CV_PossibleValue_t CV_YesNo[] = {{0, "No"}, {1, "Yes"}, {0, NULL}};
 CV_PossibleValue_t CV_Unsigned[] = {{0, "MIN"}, {999999999, "MAX"}, {0, NULL}};
 CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}};
+CV_PossibleValue_t CV_TrueFalse[] = {{0, "False"}, {1, "True"}, {0, NULL}};
 
 // Filter consvars by EXECVERSION
 // First implementation is 26 (2.1.21), so earlier configs default at 25 (2.1.20)
@@ -679,25 +681,58 @@ static void COM_ExecuteString(char *ptext)
 //                            SCRIPT COMMANDS
 // =========================================================================
 
+static void print_alias(void)
+{
+	cmdalias_t *a;
+
+	CONS_Printf("\x82""Current alias commands:\n");
+	for (a = com_alias; a; a = a->next)
+	{
+		CONS_Printf("%s : %s", a->name, a->value);
+	}
+}
+
+static void add_alias(char *newname, char *newcmd)
+{
+	cmdalias_t *a;
+
+	// Check for existing aliases first
+	for (a = com_alias; a; a = a->next)
+	{
+		if (!stricmp(newname, a->name))
+		{
+			Z_Free(a->value); // Free old cmd 
+			a->value = newcmd;
+			return;
+		}
+	}
+
+	// No alias found, add it instead
+	a = ZZ_Alloc(sizeof *a);
+	a->next = com_alias;
+	com_alias = a;
+
+	a->name = newname;
+	a->value = newcmd;
+}
+
 /** Creates a command name that replaces another command.
   */
 static void COM_Alias_f(void)
 {
-	cmdalias_t *a;
+	char *name;
+	char *zcmd;
 	char cmd[1024];
 	size_t i, c;
 
 	if (COM_Argc() < 3)
 	{
 		CONS_Printf(M_GetText("alias <name> <command>: create a shortcut command that executes other command(s)\n"));
+		print_alias();
 		return;
 	}
 
-	a = ZZ_Alloc(sizeof *a);
-	a->next = com_alias;
-	com_alias = a;
-
-	a->name = Z_StrDup(COM_Argv(1));
+	name = Z_StrDup(COM_Argv(1));
 
 	// copy the rest of the command line
 	cmd[0] = 0; // start out with a null string
@@ -709,8 +744,8 @@ static void COM_Alias_f(void)
 			strcat(cmd, " ");
 	}
 	strcat(cmd, "\n");
-
-	a->value = Z_StrDup(cmd);
+	zcmd = Z_StrDup(cmd);
+	add_alias(name, zcmd);
 }
 
 /** Prints a line of text to the console.
@@ -781,6 +816,9 @@ static void COM_Exec_f(void)
 		return;
 	}
 
+	if (!D_CheckPathAllowed(COM_Argv(1), "tried to exec"))
+		return;
+
 	// load file
 	// Try with Argv passed verbatim first, for back compat
 	FIL_ReadFile(COM_Argv(1), &buf);
@@ -859,9 +897,11 @@ static void COM_Help_f(void)
 			{
 				CONS_Printf(" Possible values:\n");
 				if (cvar->PossibleValue == CV_YesNo)
-					CONS_Printf("  Yes or No (On or Off, 1 or 0)\n");
+					CONS_Printf("  Yes or No (On or Off, True or False, 1 or 0)\n");
 				else if (cvar->PossibleValue == CV_OnOff)
-					CONS_Printf("  On or Off (Yes or No, 1 or 0)\n");
+					CONS_Printf("  On or Off (Yes or No, True or False, 1 or 0)\n");
+				else if (cvar->PossibleValue == CV_TrueFalse)
+					CONS_Printf("  True or False (On or Off, Yes or No, 1 or 0)\n");
 				else if (cvar->PossibleValue == Color_cons_t)
 				{
 					for (i = 1; i < numskincolors; ++i)
@@ -1012,7 +1052,7 @@ static void COM_Toggle_f(void)
 	if (CV_Immutable(cvar))
 		return;
 
-	if (!(cvar->PossibleValue == CV_YesNo || cvar->PossibleValue == CV_OnOff))
+	if (!(cvar->PossibleValue == CV_YesNo || cvar->PossibleValue == CV_OnOff || cvar->PossibleValue == CV_TrueFalse))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("%s is not a boolean value\n"), COM_Argv(1));
 		return;
@@ -1503,12 +1543,12 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 						goto found;
 			}
 			// Not found ... but wait, there's hope!
-			if (var->PossibleValue == CV_OnOff || var->PossibleValue == CV_YesNo)
+			if (var->PossibleValue == CV_OnOff || var->PossibleValue == CV_YesNo || var->PossibleValue == CV_TrueFalse)
 			{
 				overrideval = -1;
-				if (!stricmp(valstr, "on") || !stricmp(valstr, "yes"))
+				if (!stricmp(valstr, "on") || !stricmp(valstr, "yes") || !stricmp(valstr, "true"))
 					overrideval = 1;
-				else if (!stricmp(valstr, "off") || !stricmp(valstr, "no"))
+				else if (!stricmp(valstr, "off") || !stricmp(valstr, "no") || !stricmp(valstr, "false"))
 					overrideval = 0;
 
 				if (overrideval != -1)
diff --git a/src/command.h b/src/command.h
index 69d1890d34d068ac67eb48c10e5c0ec5b8764b7f..619d8c1dcab07a7ab5dfbe20e98cae409c77fb9d 100644
--- a/src/command.h
+++ b/src/command.h
@@ -177,6 +177,7 @@ extern CV_PossibleValue_t CV_OnOff[];
 extern CV_PossibleValue_t CV_YesNo[];
 extern CV_PossibleValue_t CV_Unsigned[];
 extern CV_PossibleValue_t CV_Natural[];
+extern CV_PossibleValue_t CV_TrueFalse[];
 
 // Filter consvars by version
 extern consvar_t cv_execversion;
diff --git a/src/comptime.c b/src/comptime.c
index 398eda0743706cecb4a22d1996f8949564f1fe07..386b53f46904df87fc5f64749cc11db6e6821e3f 100644
--- a/src/comptime.c
+++ b/src/comptime.c
@@ -11,6 +11,9 @@
 #include "config.h"
 const char *compbranch = SRB2_COMP_BRANCH;
 const char *comprevision = SRB2_COMP_REVISION;
+const char *compnote = SRB2_COMP_NOTE;
+const char *comptype = CMAKE_BUILD_TYPE;
+const int compoptimized = SRB2_COMP_OPTIMIZED;
 
 #elif (defined(COMPVERSION))
 #include "comptime.h"
@@ -21,5 +24,12 @@ const char *comprevision = "illegal";
 
 #endif
 
+const int compuncommitted =
+#if (defined(COMPVERSION_UNCOMMITTED))
+1;
+#else
+0;
+#endif
+
 const char *compdate = __DATE__;
 const char *comptime = __TIME__;
diff --git a/src/config.h.in b/src/config.h.in
index 928705b30f048334688ce78a1c3a127a80a1d170..6d49a698934bf8b498f6bfb402bbaa7f8ff93191 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -11,8 +11,18 @@
 
 #ifdef CMAKECONFIG
 
-#define SRB2_COMP_REVISION    "${SRB2_COMP_REVISION}"
-#define SRB2_COMP_BRANCH      "${SRB2_COMP_BRANCH}"
+#define SRB2_COMP_REVISION       "${SRB2_COMP_REVISION}"
+#define SRB2_COMP_BRANCH         "${SRB2_COMP_BRANCH}"
+#define SRB2_COMP_NOTE           "${SRB2_COMP_NOTE}"
+// This is done with configure_file instead of defines in order to avoid
+// recompiling the whole target whenever the working directory state changes
+#cmakedefine SRB2_COMP_UNCOMMITTED
+#ifdef SRB2_COMP_UNCOMMITTED
+#define COMPVERSION_UNCOMMITTED
+#endif
+
+#define CMAKE_BUILD_TYPE         "${CMAKE_BUILD_TYPE}"
+#cmakedefine01 SRB2_COMP_OPTIMIZED
 
 #endif
 
@@ -27,12 +37,15 @@
  * Last updated 2020 / 10 / 02 - v2.2.8 - patch.pk3
  * 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  "86ae55cae4e0a93ceda868635706a093"
+#define ASSET_HASH_ZONES_PK3  "1c8adf8d079ecb87d00081f158acf3c7"
 #define ASSET_HASH_PLAYER_DTA "2e7aaae8a6b1b77d90ffe7606ceadb6c"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "7d467a883f7887b3c311798ee2f56b6a"
+#define ASSET_HASH_PATCH_PK3  "3c7b73f34af7e9a7bceb2d5260f76172"
 #endif
 
 #endif
diff --git a/src/console.c b/src/console.c
index 33d59046e41b85d78511998052edc6692c6aaa89..21b608ce4df88ac5719cb8c43bb739e314450cbd 100644
--- a/src/console.c
+++ b/src/console.c
@@ -61,7 +61,7 @@ static boolean con_started = false; // console has been initialised
 static boolean con_forcepic = true; // at startup toggle console translucency when first off
        boolean con_recalc;          // set true when screen size has changed
 
-static tic_t con_tick; // console ticker for anim or blinking prompt cursor
+static tic_t con_tick; // console ticker for blinking prompt cursor
                         // con_scrollup should use time (currenttime - lasttime)..
 
 static boolean consoletoggle; // true when console key pushed, ticker will handle
@@ -72,8 +72,8 @@ static INT32 con_curlines;  // vid lines currently used by console
 
        INT32 con_clipviewtop; // (useless)
 
-static INT32 con_hudlines;        // number of console heads up message lines
-static INT32 con_hudtime[MAXHUDLINES];      // remaining time of display for hud msg lines
+static UINT8  con_hudlines;             // number of console heads up message lines
+static UINT32 con_hudtime[MAXHUDLINES]; // remaining time of display for hud msg lines
 
        INT32 con_clearlines;      // top screen lines to refresh when view reduced
        boolean con_hudupdate;   // when messages scroll, we need a backgrnd refresh
@@ -110,6 +110,7 @@ static void CON_RecalcSize(void);
 static void CON_ChangeHeight(void);
 
 static void CON_DrawBackpic(void);
+static void CONS_height_Change(void);
 static void CONS_hudlines_Change(void);
 static void CONS_backcolor_Change(void);
 
@@ -125,10 +126,13 @@ static void CONS_backcolor_Change(void);
 static char con_buffer[CON_BUFFERSIZE];
 
 // how many seconds the hud messages lasts on the screen
-static consvar_t cons_msgtimeout = CVAR_INIT ("con_hudtime", "5", CV_SAVE, CV_Unsigned, NULL);
+// CV_Unsigned can overflow when multiplied by TICRATE later, so let's use a 3-year limit instead
+static CV_PossibleValue_t hudtime_cons_t[] = {{0, "MIN"}, {99999999, "MAX"}, {0, NULL}};
+static consvar_t cons_hudtime = CVAR_INIT ("con_hudtime", "5", CV_SAVE, hudtime_cons_t, NULL);
 
 // number of lines displayed on the HUD
-static consvar_t cons_hudlines = CVAR_INIT ("con_hudlines", "5", CV_CALL|CV_SAVE, CV_Unsigned, CONS_hudlines_Change);
+static CV_PossibleValue_t hudlines_cons_t[] = {{0, "MIN"}, {MAXHUDLINES, "MAX"}, {0, NULL}};
+static consvar_t cons_hudlines = CVAR_INIT ("con_hudlines", "5", CV_CALL|CV_SAVE, hudlines_cons_t, CONS_hudlines_Change);
 
 // number of lines console move per frame
 // (con_speed needs a limit, apparently)
@@ -136,7 +140,7 @@ static CV_PossibleValue_t speed_cons_t[] = {{0, "MIN"}, {64, "MAX"}, {0, NULL}};
 static consvar_t cons_speed = CVAR_INIT ("con_speed", "8", CV_SAVE, speed_cons_t, NULL);
 
 // percentage of screen height to use for console
-static consvar_t cons_height = CVAR_INIT ("con_height", "50", CV_SAVE, CV_Unsigned, NULL);
+static consvar_t cons_height = CVAR_INIT ("con_height", "50", CV_CALL|CV_SAVE, CV_Unsigned, CONS_height_Change);
 
 static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"}, {0, NULL}};
 // whether to use console background picture, or translucent mode
@@ -156,6 +160,18 @@ consvar_t cons_backcolor = CVAR_INIT ("con_backcolor", "Green", CV_CALL|CV_SAVE,
 
 static void CON_Print(char *msg);
 
+// Change the console height on demand
+//
+static void CONS_height_Change(void)
+{
+	Lock_state();
+
+	if (con_destlines > 0 && !con_startup) // If the console is open (as in, not using "bind")...
+		CON_ChangeHeight(); // ...update its height now, not only when it's closed and re-opened
+
+	Unlock_state();
+}
+
 //
 //
 static void CONS_hudlines_Change(void)
@@ -168,11 +184,6 @@ static void CONS_hudlines_Change(void)
 	for (i = 0; i < con_hudlines; i++)
 		con_hudtime[i] = 0;
 
-	if (cons_hudlines.value < 1)
-		cons_hudlines.value = 1;
-	else if (cons_hudlines.value > MAXHUDLINES)
-		cons_hudlines.value = MAXHUDLINES;
-
 	con_hudlines = cons_hudlines.value;
 
 	Unlock_state();
@@ -382,16 +393,16 @@ static void CON_SetupColormaps(void)
 
 	//                      0x1       0x3                           0x9                           0xF
 	colset(magentamap, 177, 177, 178, 178, 178, 180, 180, 180, 182, 182, 182, 182, 184, 184, 184, 185);
-	colset(yellowmap,   82,  82,  73,  73,  73,  64,  64,  64,  66,  66,  66,  66,  67,  67,  67,  68);
-	colset(lgreenmap,   96,  96,  98,  98,  98, 101, 101, 101, 104, 104, 104, 104, 106, 106, 106, 107);
-	colset(bluemap,    146, 146, 147, 147, 147, 149, 149, 149, 152, 152, 152, 152, 155, 155, 155, 157);
-	colset(redmap,      32,  32,  33,  33,  33,  35,  35,  35,  39,  39,  39,  39,  42,  42,  42,  44);
+	colset(yellowmap,   82,  82,  73,  73,  73,  74,  74,  74,  66,  66,  66,  66,  67,  67,  67,  68);
+	colset(lgreenmap,   96,  96,  98,  98,  98, 100, 100, 100, 103, 103, 103, 103, 105, 105, 105, 107);
+	colset(bluemap,    146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 149, 150, 150, 150, 151);
+	colset(redmap,      32,  32,  33,  33,  33,  34,  34,  34,  35,  35,  35,  35,  37,  37,  37,  39);
 	colset(graymap,      8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23);
 	colset(orangemap,   50,  50,  52,  52,  52,  54,  54,  54,  56,  56,  56,  56,  59,  59,  59,  60);
 	colset(skymap,     129, 129, 130, 130, 130, 131, 131, 131, 133, 133, 133, 133, 135, 135, 135, 136);
 	colset(purplemap,  160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 163, 164, 164, 164, 165);
 	colset(aquamap,    120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 125);
-	colset(peridotmap,  72,  72, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
+	colset(peridotmap,  73,  73, 188, 188, 188, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
 	colset(azuremap,   144, 144, 145, 145, 145, 146, 146, 146, 170, 170, 170, 170, 171, 171, 171, 172);
 	colset(brownmap,   219, 219, 221, 221, 221, 222, 222, 222, 224, 224, 224, 224, 227, 227, 227, 229);
 	colset(rosymap,    200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 205);
@@ -464,7 +475,7 @@ void CON_Init(void)
 
 		Unlock_state();
 
-		CV_RegisterVar(&cons_msgtimeout);
+		CV_RegisterVar(&cons_hudtime);
 		CV_RegisterVar(&cons_hudlines);
 		CV_RegisterVar(&cons_speed);
 		CV_RegisterVar(&cons_height);
@@ -643,33 +654,39 @@ static void CON_ChangeHeight(void)
 //
 static void CON_MoveConsole(void)
 {
-	fixed_t conspeed;
+	static fixed_t fracmovement = 0;
 
 	Lock_state();
 
-	conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
-
 	// instant
 	if (!cons_speed.value)
 	{
 		con_curlines = con_destlines;
+		Unlock_state();
 		return;
 	}
 
-	// up/down move to dest
-	if (con_curlines < con_destlines)
+	// Not instant - Increment fracmovement fractionally
+	fracmovement += FixedMul(cons_speed.value*vid.fdupy, renderdeltatics);
+
+	if (con_curlines < con_destlines) // Move the console downwards
 	{
-		con_curlines += FixedInt(conspeed);
-		if (con_curlines > con_destlines)
-			con_curlines = con_destlines;
+		con_curlines += FixedInt(fracmovement); // Move by fracmovement's integer value
+		if (con_curlines > con_destlines) // If we surpassed the destination...
+			con_curlines = con_destlines; // ...clamp to it!
 	}
-	else if (con_curlines > con_destlines)
+	else // Move the console upwards
 	{
-		con_curlines -= FixedInt(conspeed);
+		con_curlines -= FixedInt(fracmovement);
 		if (con_curlines < con_destlines)
 			con_curlines = con_destlines;
+
+		if (con_destlines == 0) // If the console is being closed, not just moved up...
+			con_tick = 0; // ...don't show the blinking cursor
 	}
 
+	fracmovement %= FRACUNIT; // Reset fracmovement's integer value, but keep the fraction
+
 	Unlock_state();
 }
 
@@ -752,10 +769,6 @@ void CON_Ticker(void)
 			CON_ChangeHeight();
 	}
 
-	// console movement
-	if (con_destlines != con_curlines)
-		CON_MoveConsole();
-
 	// clip the view, so that the part under the console is not drawn
 	con_clipviewtop = -1;
 	if (cons_backpic.value) // clip only when using an opaque background
@@ -777,9 +790,8 @@ void CON_Ticker(void)
 	// make overlay messages disappear after a while
 	for (i = 0; i < con_hudlines; i++)
 	{
-		con_hudtime[i]--;
-		if (con_hudtime[i] < 0)
-			con_hudtime[i] = 0;
+		if (con_hudtime[i])
+			con_hudtime[i]--;
 	}
 
 	Unlock_state();
@@ -1328,7 +1340,8 @@ boolean CON_Responder(event_t *ev)
 static void CON_Linefeed(void)
 {
 	// set time for heads up messages
-	con_hudtime[con_cy%con_hudlines] = cons_msgtimeout.value*TICRATE;
+	if (con_hudlines)
+		con_hudtime[con_cy%con_hudlines] = cons_hudtime.value*TICRATE;
 
 	con_cy++;
 	con_cx = 0;
@@ -1684,7 +1697,7 @@ static void CON_DrawHudlines(void)
 	INT32 charwidth = 8 * con_scalefactor;
 	INT32 charheight = 8 * con_scalefactor;
 
-	if (con_hudlines <= 0)
+	if (!con_hudlines)
 		return;
 
 	if (chat_on && OLDCHAT)
@@ -1692,7 +1705,7 @@ static void CON_DrawHudlines(void)
 	else
 		y = 0;
 
-	for (i = con_cy - con_hudlines+1; i <= con_cy; i++)
+	for (i = con_cy - con_hudlines; i <= con_cy; i++)
 	{
 		size_t c;
 		INT32 x;
@@ -1809,41 +1822,41 @@ static void CON_DrawConsole(void)
 	}
 
 	// draw console text lines from top to bottom
-	if (con_curlines < minheight)
-		return;
-
-	i = con_cy - con_scrollup;
+	if (con_curlines >= minheight)
+	{
+		i = con_cy - con_scrollup;
 
-	// skip the last empty line due to the cursor being at the start of a new line
-	i--;
+		// skip the last empty line due to the cursor being at the start of a new line
+		i--;
 
-	i -= (con_curlines - minheight) / charheight;
+		i -= (con_curlines - minheight) / charheight;
 
-	if (rendermode == render_none) return;
+		if (rendermode == render_none) return;
 
-	for (y = (con_curlines-minheight) % charheight; y <= con_curlines-minheight; y += charheight, i++)
-	{
-		INT32 x;
-		size_t c;
+		for (y = (con_curlines-minheight) % charheight; y <= con_curlines-minheight; y += charheight, i++)
+		{
+			INT32 x;
+			size_t c;
 
-		p = (UINT8 *)&con_buffer[((i > 0 ? i : 0)%con_totallines)*con_width];
+			p = (UINT8 *)&con_buffer[((i > 0 ? i : 0)%con_totallines)*con_width];
 
-		for (c = 0, x = charwidth; c < con_width; c++, x += charwidth, p++)
-		{
-			while (*p & 0x80)
+			for (c = 0, x = charwidth; c < con_width; c++, x += charwidth, p++)
 			{
-				charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
-				p++;
-				c++;
+				while (*p & 0x80)
+				{
+					charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
+					p++;
+					c++;
+				}
+				if (c >= con_width)
+					break;
+				V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
 			}
-			if (c >= con_width)
-				break;
-			V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
 		}
 	}
 
 	// draw prompt if enough place (not while game startup)
-	if ((con_curlines == con_destlines) && (con_curlines >= minheight) && !con_startup)
+	if ((con_curlines >= (minheight-charheight)) && !con_startup)
 		CON_DrawInput();
 }
 
@@ -1866,11 +1879,15 @@ void CON_Drawer(void)
 			CON_ClearHUD();
 	}
 
+	// console movement
+	if (con_curlines != con_destlines)
+		CON_MoveConsole();
+
 	if (con_curlines > 0)
 		CON_DrawConsole();
 	else if (gamestate == GS_LEVEL
 	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
-	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
+	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS)
 		CON_DrawHudlines();
 
 	Unlock_state();
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 94384e46ab56f79cb13e8bbeb8fae3748f5dca30..1ec9cf1e94b9fcbe2c135109cd4fb52031945620 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -120,6 +120,8 @@ 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;
 
@@ -1293,6 +1295,7 @@ static boolean CL_AskFileList(INT32 firstfile)
 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;
@@ -1309,18 +1312,23 @@ static boolean CL_SendJoin(void)
 	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], cv_playername2.zstring, MAXPLAYERNAME);
+	strncpy(netbuffer->u.clientcfg.names[1], player2name, MAXPLAYERNAME);
 
 	return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
 }
 
 static INT32 FindRejoinerNum(SINT8 node)
 {
-	char strippednodeaddress[64];
+	char addressbuffer[64];
 	const char *nodeaddress;
-	char *port;
+	const char *strippednodeaddress;
 	INT32 i;
 
 	// Make sure there is no dead dress before proceeding to the stripping
@@ -1331,10 +1339,8 @@ static INT32 FindRejoinerNum(SINT8 node)
 		return -1;
 
 	// Strip the address of its port
-	strcpy(strippednodeaddress, nodeaddress);
-	port = strchr(strippednodeaddress, ':');
-	if (port)
-		*port = '\0';
+	strcpy(addressbuffer, nodeaddress);
+	strippednodeaddress = I_NetSplitAddress(addressbuffer, NULL);
 
 	// Check if any player matches the stripped address
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -1506,6 +1512,7 @@ static boolean SV_SendServerConfig(INT32 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);
 
@@ -2486,7 +2493,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 		{
 			if (!snake)
 			{
-				F_MenuPresTicker(true); // title sky
+				F_MenuPresTicker(); // title sky
 				F_TitleScreenTicker(true);
 				F_TitleScreenDrawer();
 			}
@@ -2601,6 +2608,8 @@ static void CL_ConnectToServer(void)
 	}
 	while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
 
+	if (netgame)
+		F_StartWaitingPlayers();
 	DEBFILE(va("Synchronisation Finished\n"));
 
 	displayplayer = consoleplayer;
@@ -2734,7 +2743,6 @@ static void Command_ClearBans(void)
 static void Ban_Load_File(boolean warning)
 {
 	FILE *f;
-	size_t i;
 	const char *address, *mask;
 	char buffer[MAX_WADPATH];
 
@@ -2752,7 +2760,7 @@ static void Ban_Load_File(boolean warning)
 
 	Ban_Clear();
 
-	for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++)
+	for (; fgets(buffer, (int)sizeof(buffer), f);)
 	{
 		address = strtok(buffer, " \t\r\n");
 		mask = strtok(NULL, " \t\r\n");
@@ -3644,6 +3652,9 @@ void SV_ResetServer(void)
 
 	CV_RevertNetVars();
 
+	// Ensure synched when creating a new server
+	M_CopyGameData(serverGamedata, clientGamedata);
+
 	DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
 }
 
@@ -3767,14 +3778,13 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 
 		if (server && I_GetNodeAddress)
 		{
+			char addressbuffer[64];
 			const char *address = I_GetNodeAddress(node);
-			char *port = NULL;
 			if (address) // MI: fix msvcrt.dll!_mbscat crash?
 			{
-				strcpy(playeraddress[newplayernum], address);
-				port = strchr(playeraddress[newplayernum], ':');
-				if (port)
-					*port = '\0';
+				strcpy(addressbuffer, address);
+				strcpy(playeraddress[newplayernum],
+						I_NetSplitAddress(addressbuffer, NULL));
 			}
 		}
 	}
@@ -4406,6 +4416,8 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				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);
 			}
 
@@ -4515,6 +4527,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 		netconsole = 0;
 	else
 		netconsole = nodetoplayer[node];
+
 #ifdef PARANOIA
 	if (netconsole >= MAXPLAYERS)
 		I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
@@ -4553,21 +4566,32 @@ static void HandlePacketFromPlayer(SINT8 node)
 			// Update the nettics
 			nettics[node] = realend;
 
-			// Don't do anything for packets of type NODEKEEPALIVE?
-			if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
-				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
+			// 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[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
+			G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
 
 			// Check ticcmd for "speed hacks"
-			if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
-				|| netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
+			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);
@@ -4579,9 +4603,10 @@ static void HandlePacketFromPlayer(SINT8 node)
 			// Splitscreen cmd
 			if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
 				&& nodetoplayer2[node] >= 0)
-				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
+				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)
@@ -4618,6 +4643,21 @@ static void HandlePacketFromPlayer(SINT8 node)
 				}
 			}
 			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 */
@@ -5044,39 +5084,66 @@ static INT16 Consistancy(void)
 	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++;
+	{
+		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 += 4;
+		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 += 2;
-			G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
+			netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD);
 			packetsize = sizeof (client2cmd_pak);
+			G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
 		}
-		else
-			packetsize = sizeof (clientcmd_pak);
 
 		HSendPacket(servernode, false, 0, packetsize);
 	}
@@ -5087,7 +5154,7 @@ static void CL_SendClientCmd(void)
 		if (localtextcmd[0])
 		{
 			netbuffer->packettype = PT_TEXTCMD;
-			M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
+			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;
@@ -5467,9 +5534,82 @@ static inline void PingUpdate(void)
 	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 gametime = 0;
 	static tic_t resptime = 0;
 	tic_t nowtime;
 	INT32 i;
@@ -5480,6 +5620,7 @@ void NetUpdate(void)
 
 	if (realtics <= 0) // nothing new to update
 		return;
+
 	if (realtics > 5)
 	{
 		if (server)
@@ -5488,19 +5629,72 @@ void NetUpdate(void)
 			realtics = 5;
 	}
 
-	gametime = nowtime;
-
-	if (server)
+	if (server && dedicated && gamestate == GS_LEVEL)
 	{
-		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++;
+		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;
 
@@ -5531,24 +5725,25 @@ void NetUpdate(void)
 	}
 	else
 	{
-		if (!demoplayback)
+		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] && nettics[i] < firstticstosend)
-				{
+			{
+				if (!nodeingame[i])
+					continue;
+				if (nettics[i] < firstticstosend)
 					firstticstosend = nettics[i];
-
-					if (maketic + 1 >= nettics[i] + BACKUPTICS)
-						Net_ConnectionTimeout(i);
-				}
-
-			// Don't erase tics not acknowledged
-			counts = realtics;
+				if (maketic + counts >= nettics[i] + (BACKUPTICS - TICRATE))
+					Net_ConnectionTimeout(i);
+			}
 
 			if (maketic + counts >= firstticstosend + BACKUPTICS)
 				counts = firstticstosend+BACKUPTICS-maketic-1;
@@ -5566,20 +5761,10 @@ void NetUpdate(void)
 	}
 
 	Net_AckTicker();
-
-	// Handle timeouts to prevent definitive freezes from happenning
-	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);
-	}
+	HandleNodeTimeouts();
 
 	nowtime /= NEWTICRATERATIO;
+
 	if (nowtime > resptime)
 	{
 		resptime = nowtime;
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index baadafb0e57898cb864a70db858c174844a2cd8f..49fb5fc1db1507a75c1b379451c27141146d1036 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -77,6 +77,8 @@ 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
+
 	// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
 
 	PT_CANFAIL,       // This is kind of a priority. Anything bigger than CANFAIL
@@ -158,6 +160,7 @@ typedef struct
 
 	UINT8 gametype;
 	UINT8 modifiedgame;
+	UINT8 usedCheats;
 
 	char server_context[8]; // Unique context id, generated at server startup.
 } ATTRPACK serverconfig_pak;
@@ -397,6 +400,7 @@ 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);
@@ -411,6 +415,9 @@ 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);
diff --git a/src/d_main.c b/src/d_main.c
index 6903369e41a8cd3df16f20da4be4ded003bb6b92..24c70843a3bd03d383651d7423914a7fd4d3f0e5 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -70,6 +70,8 @@
 #include "filesrch.h" // refreshdirmenu
 #include "g_input.h" // tutorial mode control scheming
 #include "m_perfstats.h"
+#include "m_random.h"
+#include "command.h"
 
 #ifdef CMAKECONFIG
 #include "config.h"
@@ -458,6 +460,13 @@ static void D_Display(void)
 
 		case GS_WAITINGPLAYERS:
 			// The clientconnect drawer is independent...
+			if (netgame)
+			{
+				// I don't think HOM from nothing drawing is independent...
+				F_WaitingPlayersDrawer();
+				HU_Erase();
+				HU_Drawer();
+			}
 		case GS_DEDICATEDSERVER:
 		case GS_NULL:
 			break;
@@ -1208,6 +1217,15 @@ D_ConvertVersionNumbers (void)
 #endif
 }
 
+static void Command_assert(void)
+{
+#if !defined(NDEBUG) || defined(PARANOIA)
+	CONS_Printf("Yes, assertions are enabled.\n");
+#else
+	CONS_Printf("No, assertions are NOT enabled.\n");
+#endif
+}
+
 //
 // D_SRB2Main
 //
@@ -1221,6 +1239,11 @@ void D_SRB2Main(void)
 	/* break the version string into version numbers, for netplay */
 	D_ConvertVersionNumbers();
 
+	if (!strcmp(compbranch, ""))
+	{
+		compbranch = "detached HEAD";
+	}
+
 	// Print GPL notice for our console users (Linux)
 	CONS_Printf(
 	"\n\nSonic Robo Blast 2\n"
@@ -1334,11 +1357,12 @@ void D_SRB2Main(void)
 	snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
 	I_mkdir(addonsdir, 0755);
 
-	// rand() needs seeded regardless of password
-	srand((unsigned int)time(NULL));
-	rand();
-	rand();
-	rand();
+	// seed M_Random because it is necessary; seed P_Random for scripts that
+	// might want to use random numbers immediately at start
+	if (!M_RandomSeedFromOS())
+		M_RandomSeed((UINT32)time(NULL)); // less good but serviceable
+
+	P_SetRandSeed(M_RandomizedSeed());
 
 	if (M_CheckParm("-password") && M_IsNextParm())
 		D_SetPassword(M_GetNextParm());
@@ -1350,9 +1374,14 @@ void D_SRB2Main(void)
 	CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
 	Z_Init();
 
+	clientGamedata = M_NewGameDataStruct();
+	serverGamedata = M_NewGameDataStruct();
+
 	// Do this up here so that WADs loaded through the command line can use ExecCfg
 	COM_Init();
 
+	COM_AddCommand("assert", Command_assert, COM_LUA);
+
 	// Add any files specified on the command line with
 	// "-file <file>" or "-folder <folder>" to the add-on list
 	if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
@@ -1473,7 +1502,15 @@ void D_SRB2Main(void)
 	//--------------------------------------------------------- CONFIG.CFG
 	M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
 
-	G_LoadGameData();
+	if (M_CheckParm("-gamedata") && M_IsNextParm())
+	{
+		// Moved from G_LoadGameData itself, as it would cause some crazy
+		// confusion issues when loading mods.
+		strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
+	}
+
+	G_LoadGameData(clientGamedata);
+	M_CopyGameData(serverGamedata, clientGamedata);
 
 #if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)
 	VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
@@ -1500,7 +1537,7 @@ void D_SRB2Main(void)
 		else
 		{
 			if (!M_CheckParm("-server"))
-				G_SetGameModified(true);
+				G_SetUsedCheats(true);
 			autostart = true;
 		}
 	}
@@ -1700,14 +1737,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))
-				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);
 			}
 		}
@@ -1777,3 +1815,85 @@ const char *D_Home(void)
 	if (usehome) return userhome;
 	else return NULL;
 }
+
+static boolean check_top_dir(const char **path, const char *top)
+{
+	// empty string does NOT match
+	if (!strcmp(top, ""))
+		return false;
+
+	if (!startswith(*path, top))
+		return false;
+
+	*path += strlen(top);
+
+	// if it doesn't already end with a path separator,
+	// check if a separator follows
+	if (!endswith(top, PATHSEP))
+	{
+		if (startswith(*path, PATHSEP))
+			*path += strlen(PATHSEP);
+		else
+			return false;
+	}
+
+	return true;
+}
+
+static int cmp_strlen_desc(const void *a, const void *b)
+{
+	return ((int)strlen(*(const char*const*)b) - (int)strlen(*(const char*const*)a));
+}
+
+boolean D_IsPathAllowed(const char *path)
+{
+	const char *paths[] = {
+		srb2home,
+		srb2path,
+		cv_addons_folder.string
+	};
+
+	const size_t n_paths = sizeof paths / sizeof *paths;
+
+	size_t i;
+
+	// Sort folder paths by longest to shortest so
+	// overlapping paths work. E.g.:
+	// Path 1: /home/james/.srb2/addons
+	// Path 2: /home/james/.srb2
+	qsort(paths, n_paths, sizeof *paths, cmp_strlen_desc);
+
+	// These paths are allowed to be absolute
+	// path is offset so ".." can be checked only in the
+	// rest of the path
+	for (i = 0; i < n_paths; ++i)
+	{
+		if (check_top_dir(&path, paths[i]))
+			break;
+	}
+
+	// Only if none of the presets matched
+	if (i == n_paths)
+	{
+		// Cannot be an absolute path
+		if (M_IsPathAbsolute(path))
+			return false;
+	}
+
+	// Cannot traverse upwards
+	if (strstr(path, ".."))
+		return false;
+
+	return true;
+}
+
+boolean D_CheckPathAllowed(const char *path, const char *why)
+{
+	if (!D_IsPathAllowed(path))
+	{
+		CONS_Alert(CONS_WARNING, "%s: %s, location is not allowed\n", why, path);
+		return false;
+	}
+
+	return true;
+}
diff --git a/src/d_main.h b/src/d_main.h
index 197423fb335f2d258add109a007d8febc0845986..0a29f929b178dc6ca592fe59a278564418c3825c 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -44,6 +44,9 @@ void D_ProcessEvents(void);
 
 const char *D_Home(void);
 
+boolean D_IsPathAllowed(const char *path);
+boolean D_CheckPathAllowed(const char *path, const char *why);
+
 //
 // BASE LEVEL
 //
diff --git a/src/d_net.c b/src/d_net.c
index 7de3dba56ab9b660a89a09005485fe809810c306..6d8c72942bbe8cd6714db4c6761a17e13f70bdef 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -869,6 +869,9 @@ static void DebugPrintpacket(const char *header)
 				(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic, doomcom->remotenode),
 				(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom, doomcom->remotenode));
 			break;
+		case PT_BASICKEEPALIVE:
+			fprintf(debugfile, "    wipetime\n");
+			break;
 		case PT_TEXTCMD:
 		case PT_TEXTCMD2:
 			fprintf(debugfile, "    length %d\n    ", netbuffer->u.textcmd[0]);
@@ -1207,26 +1210,32 @@ static void Internal_FreeNodenum(INT32 nodenum)
 	(void)nodenum;
 }
 
+char *I_NetSplitAddress(char *host, char **port)
+{
+	boolean v4 = (strchr(host, '.') != NULL);
+
+	host = strtok(host, v4 ? ":" : "[]");
+
+	if (port)
+		*port = strtok(NULL, ":");
+
+	return host;
+}
+
 SINT8 I_NetMakeNode(const char *hostname)
 {
 	SINT8 newnode = -1;
 	if (I_NetMakeNodewPort)
 	{
 		char *localhostname = strdup(hostname);
-		char  *t = localhostname;
-		const char *port;
+		char *port;
 		if (!localhostname)
 			return newnode;
-		// retrieve portnum from address!
-		strtok(localhostname, ":");
-		port = strtok(NULL, ":");
 
-		// remove the port in the hostname as we've it already
-		while ((*t != ':') && (*t != '\0'))
-			t++;
-		*t = '\0';
+		// retrieve portnum from address!
+		hostname = I_NetSplitAddress(localhostname, &port);
 
-		newnode = I_NetMakeNodewPort(localhostname, port);
+		newnode = I_NetMakeNodewPort(hostname, port);
 		free(localhostname);
 	}
 	return newnode;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 5dff34bc85d05e4d6ee62b34702546beef5a3379..3426c1f835d8025fcb18b245a90a2fd6c4c42c70 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -49,7 +49,7 @@
 #include "m_anigif.h"
 #include "md5.h"
 #include "m_perfstats.h"
-#include "hardware/u_list.h" // TODO: this should be a standard utility class
+#include "u_list.h"
 
 #ifdef NETGAME_DEVMODE
 #define CV_RESTRICT CV_NETVAR
@@ -599,6 +599,7 @@ void D_RegisterServerCommands(void)
 	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);
@@ -613,6 +614,10 @@ void D_RegisterServerCommands(void)
 
 	CV_RegisterVar(&cv_allowseenames);
 
+	// Other filesrch.c consvars are defined in D_RegisterClientCommands
+	CV_RegisterVar(&cv_addons_option);
+	CV_RegisterVar(&cv_addons_folder);
+
 	CV_RegisterVar(&cv_dummyconsvar);
 }
 
@@ -771,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);
@@ -794,8 +801,8 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_digitaldeadzone2);
 
 	// filesrch.c
-	CV_RegisterVar(&cv_addons_option);
-	CV_RegisterVar(&cv_addons_folder);
+	//CV_RegisterVar(&cv_addons_option); // These two are now defined
+	//CV_RegisterVar(&cv_addons_folder); // in D_RegisterServerCommands
 	CV_RegisterVar(&cv_addons_md5);
 	CV_RegisterVar(&cv_addons_showall);
 	CV_RegisterVar(&cv_addons_search_type);
@@ -869,10 +876,15 @@ void D_RegisterClientCommands(void)
 	// screen.c
 	CV_RegisterVar(&cv_fullscreen);
 	CV_RegisterVar(&cv_renderview);
+	CV_RegisterVar(&cv_renderhitboxinterpolation);
+	CV_RegisterVar(&cv_renderhitboxgldepth);
+	CV_RegisterVar(&cv_renderhitbox);
 	CV_RegisterVar(&cv_renderer);
 	CV_RegisterVar(&cv_scr_depth);
 	CV_RegisterVar(&cv_scr_width);
 	CV_RegisterVar(&cv_scr_height);
+	CV_RegisterVar(&cv_scr_width_w);
+	CV_RegisterVar(&cv_scr_height_w);
 
 	CV_RegisterVar(&cv_soundtest);
 
@@ -1633,9 +1645,14 @@ static void Command_Playdemo_f(void)
 {
 	char name[256];
 
-	if (COM_Argc() != 2)
+	if (COM_Argc() < 2)
 	{
-		CONS_Printf(M_GetText("playdemo <demoname>: playback a demo\n"));
+		CONS_Printf("playdemo <demoname> [-addfiles / -force]:\n");
+		CONS_Printf(M_GetText(
+					"Play back a demo file. The full path from your SRB2 directory must be given.\n\n"
+
+					"* With \"-addfiles\", any required files are added from a list contained within the demo file.\n"
+					"* With \"-force\", the demo is played even if the necessary files have not been added.\n"));
 		return;
 	}
 
@@ -1657,6 +1674,16 @@ static void Command_Playdemo_f(void)
 
 	CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name);
 
+	demofileoverride = DFILE_OVERRIDE_NONE;
+	if (strcmp(COM_Argv(2), "-addfiles") == 0)
+	{
+		demofileoverride = DFILE_OVERRIDE_LOAD;
+	}
+	else if (strcmp(COM_Argv(2), "-force") == 0)
+	{
+		demofileoverride = DFILE_OVERRIDE_SKIP;
+	}
+
 	// Internal if no extension, external if one exists
 	// If external, convert the file name to a path in SRB2's home directory
 	if (FIL_CheckExtension(name))
@@ -1877,8 +1904,8 @@ static void Command_Map_f(void)
 	size_t option_gametype;
 	const char *gametypename;
 	boolean newresetplayers;
-
-	boolean mustmodifygame;
+	boolean prevent_cheat;
+	boolean set_cheated;
 
 	INT32 newmapnum;
 
@@ -1899,21 +1926,34 @@ static void Command_Map_f(void)
 	option_gametype =   COM_CheckPartialParm("-g");
 	newresetplayers = ! COM_CheckParm("-noresetplayers");
 
-	mustmodifygame =
-		!( netgame     || multiplayer ) &&
-		(!modifiedgame || savemoddata );
+	prevent_cheat = !( usedCheats ) && !( option_force || cv_debug );
+	set_cheated = false;
 
-	if (mustmodifygame && !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)
@@ -1921,7 +1961,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? */
@@ -1934,7 +1974,9 @@ static void Command_Map_f(void)
 	}
 
 	if (!( first_option = COM_FirstOption() ))
+	{
 		first_option = COM_Argc();
+	}
 
 	if (first_option < 2)
 	{
@@ -1957,11 +1999,6 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (mustmodifygame && option_force)
-	{
-		G_SetGameModified(false);
-	}
-
 	// new gametype value
 	// use current one by default
 	if (option_gametype)
@@ -2003,15 +2040,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"));
@@ -2021,23 +2056,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))
+	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
@@ -2054,6 +2099,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);
@@ -2095,11 +2145,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);
@@ -2121,12 +2173,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	if (demoplayback && !timingdemo)
 		precache = false;
 
-	if (resetplayer && !FLS)
-	{
-		emeralds = 0;
-		memset(&luabanks, 0, sizeof(luabanks));
-	}
-
 	if (modeattacking)
 	{
 		SetPlayerSkinByNum(0, cv_chooseskin.value-1);
@@ -2167,7 +2213,7 @@ static void Command_Pause(void)
 
 	if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
 	{
-		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) || (marathonmode && gamestate == GS_INTERMISSION))
+		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_WAITINGPLAYERS) || (marathonmode && gamestate == GS_INTERMISSION))
 		{
 			CONS_Printf(M_GetText("You can't pause here.\n"));
 			return;
@@ -2330,7 +2376,7 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum)
 	}
 
 	for (i = 0; i < MAXPLAYERS; i++)
-		players[i].score = 0;
+		players[i].score = players[i].recordscore = 0;
 
 	CONS_Printf(M_GetText("Scores have been reset by the server.\n"));
 }
@@ -3834,7 +3880,7 @@ static void Command_ListWADS_f(void)
 static void Command_Version_f(void)
 {
 #ifdef DEVELOP
-	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
+	CONS_Printf("Sonic Robo Blast 2 %s %s %s (%s %s) ", compbranch, comprevision, compnote, compdate, comptime);
 #else
 	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision, compbranch);
 #endif
@@ -3868,11 +3914,6 @@ static void Command_Version_f(void)
 	else // 16-bit? 128-bit?
 		CONS_Printf("Bits Unknown ");
 
-	// No ASM?
-#ifdef NOASM
-	CONS_Printf("\x85" "NOASM " "\x80");
-#endif
-
 	// Debug build
 #ifdef _DEBUG
 	CONS_Printf("\x85" "DEBUG " "\x80");
@@ -3941,18 +3982,12 @@ void ItemFinder_OnChange(void)
 	if (!cv_itemfinder.value)
 		return; // it's fine.
 
-	if (!M_SecretUnlocked(SECRET_ITEMFINDER))
+	if (!M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata))
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
 		CV_StealthSetValue(&cv_itemfinder, 0);
 		return;
 	}
-	else if (netgame || multiplayer)
-	{
-		CONS_Printf(M_GetText("This only works in single player.\n"));
-		CV_StealthSetValue(&cv_itemfinder, 0);
-		return;
-	}
 }
 
 /** Deals with a pointlimit change by printing the change to the console.
@@ -4244,9 +4279,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
@@ -4301,7 +4333,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 
 static void Ringslinger_OnChange(void)
 {
-	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && cv_ringslinger.value && !cv_debug)
+	if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && cv_ringslinger.value && !cv_debug)
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
 		CV_StealthSetValue(&cv_ringslinger, 0);
@@ -4309,12 +4341,12 @@ static void Ringslinger_OnChange(void)
 	}
 
 	if (cv_ringslinger.value) // Only if it's been turned on
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 }
 
 static void Gravity_OnChange(void)
 {
-	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && !cv_debug
+	if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && !cv_debug
 		&& strcmp(cv_gravity.string, cv_gravity.defaultvalue))
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
@@ -4330,7 +4362,7 @@ static void Gravity_OnChange(void)
 #endif
 
 	if (!CV_IsSetToDefault(&cv_gravity))
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 	gravity = cv_gravity.value;
 }
 
@@ -4547,25 +4579,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))
 	{
@@ -4575,6 +4619,11 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
+	if (G_CoopGametype() && cheat)
+	{
+		G_SetUsedCheats(false);
+	}
+
 	G_ExitLevel();
 }
 
@@ -4611,6 +4660,7 @@ void Command_ExitGame_f(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	automapactive = false;
 	memset(&luabanks, 0, sizeof(luabanks));
 
 	if (dirmenu)
@@ -4646,7 +4696,7 @@ static void Fishcake_OnChange(void)
 	// so don't make modifiedgame always on!
 	if (cv_debug)
 	{
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 	}
 
 	else if (cv_debug != cv_fishcake.value)
@@ -4663,11 +4713,11 @@ static void Fishcake_OnChange(void)
 static void Command_Isgamemodified_f(void)
 {
 	if (savemoddata)
-		CONS_Printf(M_GetText("modifiedgame is true, but you can save emblem and time data in this mod.\n"));
+		CONS_Printf(M_GetText("modifiedgame is true, but you can save time data in this mod.\n"));
 	else if (modifiedgame)
-		CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
+		CONS_Printf(M_GetText("modifiedgame is true, time data can't be saved\n"));
 	else
-		CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n"));
+		CONS_Printf(M_GetText("modifiedgame is false, you can save time data\n"));
 }
 
 static void Command_Cheats_f(void)
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 26bf4d5c6bfbcfe9e481b35b84f38c44360ec3fa..8bbc801d0ef700868482fd982346dff5296c5e14 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -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/d_netfil.c
index e60af2c2c2db2de4b7271b343a1982c40d98b2aa..3fef7568128f34bda6d8716cf5d227a19495b9de 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -498,7 +498,7 @@ INT32 CL_CheckFiles(void)
 		CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
 
 		// Check in already loaded files
-		for (j = mainwads; wadfiles[j]; j++)
+		for (j = mainwads; j < numwadfiles; j++)
 		{
 			nameonly(strcpy(wadfilename, wadfiles[j]->filename));
 			if (!stricmp(wadfilename, fileneeded[i].filename) &&
diff --git a/src/d_player.h b/src/d_player.h
index 756c7141e3707c37a6f2ea0dde7a723aac12e834..7ad5b9f811962cb56ffcf55030f2ff1d2aea331d 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -249,6 +249,38 @@ typedef enum
 	CR_FAN
 } carrytype_t; // pw_carry
 
+typedef enum
+{
+	STR_NONE = 0, // All strong powers can stack onto each other
+
+	// Attack powers
+	STR_ANIM = 0x1, // remove powers when leaving current animation
+	STR_PUNCH = 0x2, // frontal attack (knuckles glide)
+	STR_TAIL = 0x4, // rear attack
+	STR_STOMP = 0x8, // falling onto object (fang bounce)
+	STR_UPPER = 0x10, // moving upwards into object (tails fly)
+	STR_GUARD = 0x20, //protect against damage
+	STR_HEAVY = 0x40, // ignore vertical rebound
+	STR_DASH = 0x80, // special type for machine dashmode, automatically removes your powers when leaving dashmode
+
+	// Environment powers
+	STR_WALL = 0x100, // fof busting
+	STR_FLOOR = 0x200,
+	STR_CEILING = 0x400,
+	STR_SPRING = 0x800, // power up hit springs
+	STR_SPIKE = 0x1000, // break spikes
+
+	// Shortcuts
+	STR_ATTACK = STR_PUNCH|STR_TAIL|STR_STOMP|STR_UPPER,
+	STR_BUST = STR_WALL|STR_FLOOR|STR_CEILING,
+	STR_FLY = STR_ANIM|STR_UPPER,
+	STR_GLIDE = STR_ANIM|STR_PUNCH,
+	STR_TWINSPIN = STR_ANIM|STR_ATTACK|STR_BUST|STR_SPRING|STR_SPIKE,
+	STR_MELEE = STR_ANIM|STR_PUNCH|STR_HEAVY|STR_WALL|STR_FLOOR|STR_SPRING|STR_SPIKE,
+	STR_BOUNCE = STR_ANIM|STR_STOMP|STR_FLOOR,
+	STR_METAL = STR_DASH|STR_SPIKE
+} strongtype_t; // pw_strong
+
 // Player powers. (don't edit this comment)
 typedef enum
 {
@@ -293,6 +325,8 @@ typedef enum
 
 	pw_ignorelatch, // Don't grab onto CR_GENERIC, add 32768 (powers[pw_ignorelatch] & 1<<15) to avoid ALL not-NiGHTS CR_ types
 
+	pw_strong, // Additional properties for powerful attacks
+
 	NUMPOWERS
 } powertype_t;
 
@@ -407,6 +441,7 @@ typedef struct player_s
 
 	// playing animation.
 	panim_t panim;
+	UINT8 stronganim;
 
 	// For screen flashing (bright).
 	UINT16 flashcount;
@@ -418,7 +453,8 @@ typedef struct player_s
 	INT32 skin;
 	UINT32 availabilities;
 
-	UINT32 score; // player score
+	UINT32 score; // player score (total)
+	UINT32 recordscore; // player score (per life / map)
 	fixed_t dashspeed; // dashing speed
 
 	fixed_t normalspeed; // Normal ground
diff --git a/src/d_think.h b/src/d_think.h
index bdb5db3f54135545d27b0018943f0c995fffdcd4..efc1589bf62e277a67ab309f05d33d66af741865 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -17,6 +17,8 @@
 #ifndef __D_THINK__
 #define __D_THINK__
 
+#include "doomdef.h"
+
 #ifdef __GNUG__
 #pragma interface
 #endif
@@ -49,6 +51,11 @@ typedef struct thinker_s
 	// killough 11/98: count of how many other objects reference
 	// this one using pointers. Used for garbage collection.
 	INT32 references;
+
+#ifdef PARANOIA
+	INT32 debug_mobjtype;
+	tic_t debug_time;
+#endif
 } thinker_t;
 
 #endif
diff --git a/src/deh_lua.c b/src/deh_lua.c
index 1a87e38a5a80a7c44e6cffb16870f7bedb414782..0b789547b266aeb238701ca6b34c4f8702c334fb 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -10,20 +10,7 @@
 /// \file  deh_lua.c
 /// \brief Lua SOC library
 
-#include "g_game.h"
-#include "s_sound.h"
-#include "z_zone.h"
-#include "m_menu.h"
-#include "m_misc.h"
-#include "p_local.h"
-#include "st_stuff.h"
-#include "fastcmp.h"
-#include "lua_script.h"
-#include "lua_libs.h"
-
-#include "dehacked.h"
 #include "deh_lua.h"
-#include "deh_tables.h"
 
 // freeslot takes a name (string only!)
 // and allocates it to the appropriate free slot.
@@ -89,6 +76,8 @@ static inline int lib_freeslot(lua_State *L)
 				strncpy(sprnames[j],word,4);
 				//sprnames[j][4] = 0;
 				used_spr[(j-SPR_FIRSTFREESLOT)/8] |= 1<<(j%8); // Okay, this sprite slot has been named now.
+				// Lua needs to update the value in _G if it exists
+				LUA_UpdateSprName(word, j);
 				lua_pushinteger(L, j);
 				r++;
 				break;
@@ -188,6 +177,9 @@ static inline int lib_freeslot(lua_State *L)
 		lua_remove(L, 1);
 		continue;
 	}
+
+	R_RefreshSprite2();
+
 	return r;
 }
 
@@ -195,39 +187,54 @@ static inline int lib_freeslot(lua_State *L)
 // Arguments: mobj_t actor, int var1, int var2
 static int action_call(lua_State *L)
 {
-	//actionf_t *action = lua_touserdata(L,lua_upvalueindex(1));
 	actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
 	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+
 	var1 = (INT32)luaL_optinteger(L, 3, 0);
 	var2 = (INT32)luaL_optinteger(L, 4, 0);
+
 	if (!actor)
+	{
 		return LUA_ErrInvalid(L, "mobj_t");
+	}
+
 	action->acp1(actor);
 	return 0;
 }
 
 // Hardcoded A_Action name to call for super() or NULL if super() would be invalid.
 // Set in lua_infolib.
-const char *superactions[MAXRECURSION];
-UINT8 superstack = 0;
+const char *luaactions[MAX_ACTION_RECURSION];
+UINT8 luaactionstack = 0;
 
 static int lib_dummysuper(lua_State *L)
 {
-	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;;
+	// TODO: Now that the restriction on only being allowed in state changes was lifted,
+	// it'd be nice to have super extend to Lua A_ functions too :)
+	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions!");
 }
 
-static inline int lib_getenum(lua_State *L)
+static void CacheAndPushConstant(lua_State *L, const char *name, lua_Integer value)
+{
+	// "cache" into _G
+	lua_pushstring(L, name);
+	lua_pushinteger(L, value);
+	lua_rawset(L, LUA_GLOBALSINDEX);
+	// push
+	lua_pushinteger(L, value);
+}
+
+// Search for a matching constant variable.
+// Result is stored into _G for faster subsequent use. (Except for SPR_ in the SOC parser)
+static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 {
-	const char *word, *p;
+	const char *p;
 	fixed_t i;
-	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
-	if (lua_type(L,2) != LUA_TSTRING)
-		return 0;
-	word = lua_tostring(L,2);
+
 	if (strlen(word) == 1) { // Assume sprite frame if length 1.
 		if (*word >= 'A' && *word <= '~')
 		{
-			lua_pushinteger(L, *word-'A');
+			CacheAndPushConstant(L, word, *word-'A');
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
@@ -237,7 +244,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; MOBJFLAG_LIST[i]; i++)
 			if (fastcmp(p, MOBJFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mobjflag '%s' could not be found.\n", word);
@@ -247,7 +254,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; MOBJFLAG2_LIST[i]; i++)
 			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mobjflag2 '%s' could not be found.\n", word);
@@ -257,12 +264,12 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; MOBJEFLAG_LIST[i]; i++)
 			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (fastcmp(p, "REVERSESUPER"))
 		{
-			lua_pushinteger(L, (lua_Integer)MFE_REVERSESUPER);
+			CacheAndPushConstant(L, word, (lua_Integer)MFE_REVERSESUPER);
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "mobjeflag '%s' could not be found.\n", word);
@@ -270,9 +277,9 @@ static inline int lib_getenum(lua_State *L)
 	}
 	else if (fastncmp("MTF_", word, 4)) {
 		p = word+4;
-		for (i = 0; i < 4; i++)
-			if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+		for (i = 0; MAPTHINGFLAG_LIST[i]; i++)
+			if (fastcmp(p, MAPTHINGFLAG_LIST[i])) {
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mapthingflag '%s' could not be found.\n", word);
@@ -282,17 +289,17 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; PLAYERFLAG_LIST[i]; i++)
 			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (fastcmp(p, "FULLSTASIS"))
 		{
-			lua_pushinteger(L, (lua_Integer)PF_FULLSTASIS);
+			CacheAndPushConstant(L, word, (lua_Integer)PF_FULLSTASIS);
 			return 1;
 		}
 		else if (fastcmp(p, "USEDOWN")) // Remove case when 2.3 nears release...
 		{
-			lua_pushinteger(L, (lua_Integer)PF_SPINDOWN);
+			CacheAndPushConstant(L, word, (lua_Integer)PF_SPINDOWN);
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "playerflag '%s' could not be found.\n", word);
@@ -302,7 +309,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word;
 		for (i = 0; Gametype_ConstantNames[i]; i++)
 			if (fastcmp(p, Gametype_ConstantNames[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word);
@@ -312,7 +319,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; GAMETYPERULE_LIST[i]; i++)
 			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "game type rule '%s' could not be found.\n", word);
@@ -322,7 +329,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; TYPEOFLEVEL[i].name; i++)
 			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
-				lua_pushinteger(L, TYPEOFLEVEL[i].flag);
+				CacheAndPushConstant(L, word, TYPEOFLEVEL[i].flag);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "typeoflevel '%s' could not be found.\n", word);
@@ -330,9 +337,10 @@ static inline int lib_getenum(lua_State *L)
 	}
 	else if (fastncmp("ML_", word, 3)) {
 		p = word+3;
+
 		for (i = 0; ML_LIST[i]; i++)
 			if (fastcmp(p, ML_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		// Aliases
@@ -415,13 +423,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_STATES[i])
 				break;
 			if (fastcmp(p, FREE_STATES[i])) {
-				lua_pushinteger(L, S_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, S_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < S_FIRSTFREESLOT; i++)
 			if (fastcmp(p, STATE_LIST[i]+2)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "state '%s' does not exist.\n", word);
@@ -432,13 +440,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_MOBJS[i])
 				break;
 			if (fastcmp(p, FREE_MOBJS[i])) {
-				lua_pushinteger(L, MT_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, MT_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < MT_FIRSTFREESLOT; i++)
 			if (fastcmp(p, MOBJTYPE_LIST[i]+3)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "mobjtype '%s' does not exist.\n", word);
@@ -447,7 +455,12 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSPRITES; i++)
 			if (!sprnames[i][4] && fastncmp(p,sprnames[i],4)) {
-				lua_pushinteger(L, i);
+				// updating overridden sprnames is not implemented for soc parser,
+				// so don't use cache
+				if (mathlib)
+					lua_pushinteger(L, i);
+				else
+					CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
@@ -462,12 +475,12 @@ static inline int lib_getenum(lua_State *L)
 				// the spr2names entry will have "_" on the end, as in "RUN_"
 				if (spr2names[i][3] == '_' && !p[3]) {
 					if (fastncmp(p,spr2names[i],3)) {
-						lua_pushinteger(L, i);
+						CacheAndPushConstant(L, word, i);
 						return 1;
 					}
 				}
 				else if (fastncmp(p,spr2names[i],4)) {
-					lua_pushinteger(L, i);
+					CacheAndPushConstant(L, word, i);
 					return 1;
 				}
 			}
@@ -478,7 +491,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fastcmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return 0;
@@ -487,7 +500,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "sfx '%s' could not be found.\n", word);
@@ -496,7 +509,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+2;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "sfx '%s' could not be found.\n", word);
@@ -506,7 +519,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMPOWERS; i++)
 			if (fasticmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return 0;
@@ -515,7 +528,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMPOWERS; i++)
 			if (fastcmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "power '%s' could not be found.\n", word);
@@ -524,7 +537,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMHUDITEMS; i++)
 			if (fastcmp(p, HUDITEMS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "huditem '%s' could not be found.\n", word);
@@ -536,13 +549,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_SKINCOLORS[i])
 				break;
 			if (fastcmp(p, FREE_SKINCOLORS[i])) {
-				lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, SKINCOLOR_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
 			if (fastcmp(p, COLOR_ENUMS[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "skincolor '%s' could not be found.\n", word);
@@ -553,7 +566,7 @@ static inline int lib_getenum(lua_State *L)
 		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
 			if (*p == NIGHTSGRADE_LIST[i])
 			{
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word);
@@ -563,70 +576,127 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMMENUTYPES; i++)
 			if (fastcmp(p, MENUTYPES_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "menutype '%s' could not be found.\n", word);
 		return 0;
 	}
-	else if (!mathlib && fastncmp("A_",word,2)) {
+
+	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
+	{
+		CacheAndPushConstant(L, word, (lua_Integer)BT_SPIN);
+		return 1;
+	}
+
+	for (i = 0; INT_CONST[i].n; i++)
+		if (fastcmp(word,INT_CONST[i].n)) {
+			CacheAndPushConstant(L, word, INT_CONST[i].v);
+			return 1;
+		}
+
+	return 0;
+}
+
+static inline int lib_getenum(lua_State *L)
+{
+	const char *word;
+	fixed_t i;
+	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
+	if (lua_type(L,2) != LUA_TSTRING)
+		return 0;
+	word = lua_tostring(L,2);
+
+	// check actions, super and globals first, as they don't have _G caching implemented
+	// so they benefit from being checked first
+
+	if (!mathlib && fastncmp("A_",word,2)) {
 		char *caps;
-		// Try to get a Lua action first.
-		/// \todo Push a closure that sets superactions[] and superstack.
+
+		// Hardcoded actions come first.
+		// Trying to call them will invoke LUA_CallAction, which will handle super properly.
+		// Retrieving them from this metatable allows them to be case-insensitive!
+		for (i = 0; actionpointers[i].name; i++)
+		{
+			if (fasticmp(word, actionpointers[i].name))
+			{
+				// We push the actionf_t* itself as userdata!
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
+				return 1;
+			}
+		}
+
+		// Now try to get Lua actions.
+		/// \todo Push a closure that sets luaactions[] and luaactionstack.
+		/// This would be part one of a step to get super functions working for custom A_ functions.
+		/// Custom functions.
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
+
 		// actions are stored in all uppercase.
 		caps = Z_StrDup(word);
 		strupr(caps);
 		lua_getfield(L, -1, caps);
 		Z_Free(caps);
+
 		if (!lua_isnil(L, -1))
+		{
 			return 1; // Success! :D That was easy.
+		}
+
 		// Welp, that failed.
 		lua_pop(L, 2); // pop nil and LREG_ACTIONS
-
-		// Hardcoded actions as callable Lua functions!
-		// Retrieving them from this metatable allows them to be case-insensitive!
-		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(word, actionpointers[i].name)) {
-				// We push the actionf_t* itself as userdata!
-				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
-				return 1;
-			}
 		return 0;
 	}
 	else if (!mathlib && fastcmp("super",word))
 	{
-		if (!superstack)
+		if (!luaactionstack)
 		{
+			// Not in A_ action routine
 			lua_pushcfunction(L, lib_dummysuper);
 			return 1;
 		}
+
 		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(superactions[superstack-1], actionpointers[i].name)) {
+		{
+			if (fasticmp(luaactions[luaactionstack-1], actionpointers[i].name))
+			{
 				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
 				return 1;
 			}
-		return 0;
-	}
+		}
 
-	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
-	{
-		lua_pushinteger(L, (lua_Integer)BT_SPIN);
+		// Not a hardcoded A_ action.
+		lua_pushcfunction(L, lib_dummysuper);
 		return 1;
 	}
-
-	for (i = 0; INT_CONST[i].n; i++)
-		if (fastcmp(word,INT_CONST[i].n)) {
-			lua_pushinteger(L, INT_CONST[i].v);
-			return 1;
-		}
+	else if ((!mathlib && LUA_PushGlobals(L, word)) || ScanConstants(L, mathlib, word))
+		return 1;
 
 	if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
 
-	// DYNAMIC variables too!!
-	// Try not to add anything that would break netgames or timeattack replays here.
-	// You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime.
-	return LUA_PushGlobals(L, word);
+	return 0;
+}
+
+// If a sprname has been "cached" to _G, update it to a new value.
+void LUA_UpdateSprName(const char *name, lua_Integer value)
+{
+	char fullname[9] = "SPR_XXXX";
+
+	if (!gL)
+		return;
+
+	strncpy(&fullname[4], name, 4);
+	lua_pushstring(gL, fullname);
+	lua_rawget(gL, LUA_GLOBALSINDEX);
+
+	if (!lua_isnil(gL, -1))
+	{
+		lua_pushstring(gL, name);
+		lua_pushinteger(gL, value);
+		lua_rawset(gL, LUA_GLOBALSINDEX);
+	}
+
+	lua_pop(gL, 1); // pop the rawget result
 }
 
 int LUA_EnumLib(lua_State *L)
diff --git a/src/deh_lua.h b/src/deh_lua.h
index c400351b8c576969f9c5293b2756a97be446f2d4..1bec371ccb42d5b76549103d9068c5e471825c98 100644
--- a/src/deh_lua.h
+++ b/src/deh_lua.h
@@ -13,6 +13,21 @@
 #ifndef __DEH_LUA_H__
 #define __DEH_LUA_H__
 
+#include "g_game.h"
+#include "s_sound.h"
+#include "z_zone.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "p_local.h"
+#include "st_stuff.h"
+#include "fastcmp.h"
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#include "dehacked.h"
+#include "deh_tables.h"
+
+void LUA_UpdateSprName(const char *name, lua_Integer value);
 boolean LUA_SetLuaAction(void *state, const char *actiontocompare);
 const char *LUA_GetActionName(void *action);
 void LUA_SetActionByName(void *state, const char *actiontocompare);
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 2af7b65bfc6f04b107e0f669fe425dc38f3a1775..2193cd875cd00898c035c0d47d030f189045572c 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -45,6 +45,7 @@
 #include "dehacked.h"
 #include "deh_soc.h"
 #include "deh_lua.h" // included due to some LUA_SetLuaAction hack smh
+// also used for LUA_UpdateSprName
 #include "deh_tables.h"
 
 // Loops through every constant and operation in word and performs its calculations, returning the final value.
@@ -439,6 +440,8 @@ void readfreeslots(MYFILE *f)
 					strncpy(sprnames[i],word,4);
 					//sprnames[i][4] = 0;
 					used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now.
+					// Lua needs to update the value in _G if it exists
+					LUA_UpdateSprName(word, i);
 					break;
 				}
 			}
@@ -513,6 +516,8 @@ void readfreeslots(MYFILE *f)
 	} while (!myfeof(f)); // finish when the line is empty
 
 	Z_Free(s);
+
+	R_RefreshSprite2();
 }
 
 void readthing(MYFILE *f, INT32 num)
@@ -907,7 +912,7 @@ static void readspriteframe(MYFILE *f, spriteinfo_t *sprinfo, UINT8 frame)
 			else if (fastcmp(word, "YPIVOT"))
 				sprinfo->pivot[frame].y = value;
 			else if (fastcmp(word, "ROTAXIS"))
-				sprinfo->pivot[frame].rotaxis = value;
+				deh_warning("SpriteInfo: ROTAXIS is deprecated and will be removed.");
 			else
 			{
 				f->curpos = lastline;
@@ -2907,7 +2912,9 @@ static boolean GoodDataFileName(const char *s)
 	p = s + strlen(s) - strlen(tail);
 	if (p <= s) return false; // too short
 	if (!fasticmp(p, tail)) return false; // doesn't end in .dat
-	if (fasticmp(s, "gamedata.dat")) return false;
+
+	if (fasticmp(s, "gamedata.dat")) return false; // Don't overwrite default gamedata
+	if (fasticmp(s, "main.dat")) return false; // Don't overwrite default time attack replays
 
 	return true;
 }
@@ -3835,6 +3842,10 @@ void readmaincfg(MYFILE *f)
 			{
 				useContinues = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
 			}
+			else if (fastcmp(word, "SHAREEMBLEMS"))
+			{
+				shareEmblems = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
 
 			else if (fastcmp(word, "GAMEDATA"))
 			{
@@ -3845,7 +3856,7 @@ void readmaincfg(MYFILE *f)
 				if (!GoodDataFileName(word2))
 					I_Error("Maincfg: bad data file name '%s'\n", word2);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
 				strlwr(gamedatafilename);
 				savemoddata = true;
diff --git a/src/deh_tables.c b/src/deh_tables.c
index c3b2cfccd64aae8601e045c4587d3b435036ab97..0801cf935a184d5a30d0edd215fe90e2b68db737 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -198,6 +198,7 @@ actionpointer_t actionpointers[] =
 	{{A_Boss3TakeDamage},        "A_BOSS3TAKEDAMAGE"},
 	{{A_Boss3Path},              "A_BOSS3PATH"},
 	{{A_Boss3ShockThink},        "A_BOSS3SHOCKTHINK"},
+	{{A_Shockwave},              "A_SHOCKWAVE"},
 	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
 	{{A_LinedefExecuteFromArg},  "A_LINEDEFEXECUTEFROMARG"},
 	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
@@ -4409,11 +4410,12 @@ const char *const MOBJEFLAG_LIST[] = {
 	NULL
 };
 
-const char *const MAPTHINGFLAG_LIST[4] = {
+const char *const MAPTHINGFLAG_LIST[] = {
 	"EXTRA", // Extra flag for objects.
 	"OBJECTFLIP", // Reverse gravity flag for objects.
 	"OBJECTSPECIAL", // Special flag used with certain objects.
-	"AMBUSH" // Deaf monsters/do not react to sound.
+	"AMBUSH", // Deaf monsters/do not react to sound.
+	"ABSOLUTEZ" // Absolute spawn height flag for objects.
 };
 
 const char *const PLAYERFLAG_LIST[] = {
@@ -4550,6 +4552,7 @@ const char *const MSF_LIST[] = {
 const char *const SSF_LIST[] = {
 	"OUTERSPACE",
 	"DOUBLESTEPUP",
+	"NOSTEPDOWN",
 	"WINDCURRENT",
 	"CONVEYOR",
 	"SPEEDPAD",
@@ -4566,6 +4569,8 @@ const char *const SSF_LIST[] = {
 	"ZOOMTUBEEND",
 	"FINISHLINE",
 	"ROPEHANG",
+	"JUMPFLIP",
+	"GRAVITYOVERRIDE",
 	NULL
 };
 
@@ -4609,66 +4614,111 @@ const char *COLOR_ENUMS[] = {
 	// Desaturated
 	"AETHER",     	// SKINCOLOR_AETHER,
 	"SLATE",     	// SKINCOLOR_SLATE,
+	"MOONSTONE",   	// SKINCOLOR_MOONSTONE,
 	"BLUEBELL",   	// SKINCOLOR_BLUEBELL,
 	"PINK",     	// SKINCOLOR_PINK,
+	"ROSEWOOD",   	// SKINCOLOR_ROSEWOOD,
 	"YOGURT",     	// SKINCOLOR_YOGURT,
+	"LATTE",     	// SKINCOLOR_LATTE,
 	"BROWN",     	// SKINCOLOR_BROWN,
+	"BOULDER",   	// SKINCOLOR_BOULDER
 	"BRONZE",     	// SKINCOLOR_BRONZE,
-	"TAN",     		// SKINCOLOR_TAN,
+	"SEPIA",     	// SKINCOLOR_SEPIA,
+	"ECRU",     	// SKINCOLOR_ECRU,
+	"TAN",      	// SKINCOLOR_TAN,
 	"BEIGE",     	// SKINCOLOR_BEIGE,
+	"ROSEBUSH",  	// SKINCOLOR_ROSEBUSH,
 	"MOSS",     	// SKINCOLOR_MOSS,
 	"AZURE",     	// SKINCOLOR_AZURE,
-	"LAVENDER",     // SKINCOLOR_LAVENDER,
+	"EGGPLANT",  	// SKINCOLOR_EGGPLANT,
+	"LAVENDER",   	// SKINCOLOR_LAVENDER,
 
 	// Viv's vivid colours (toast 21/07/17)
+	// Tweaks & additions (Lach, sphere, Alice, MotorRoach 26/10/22)
 	"RUBY",     	// SKINCOLOR_RUBY,
+	"CHERRY",     	// SKINCOLOR_CHERRY,
 	"SALMON",     	// SKINCOLOR_SALMON,
+	"PEPPER",     	// SKINCOLOR_PEPPER,
 	"RED",     		// SKINCOLOR_RED,
 	"CRIMSON",     	// SKINCOLOR_CRIMSON,
 	"FLAME",     	// SKINCOLOR_FLAME,
+	"GARNET",      	// SKINCOLOR_GARNET,
 	"KETCHUP",     	// SKINCOLOR_KETCHUP,
 	"PEACHY",     	// SKINCOLOR_PEACHY,
 	"QUAIL",     	// SKINCOLOR_QUAIL,
+	"FOUNDATION",   // SKINCOLOR_FOUNDATION,
 	"SUNSET",     	// SKINCOLOR_SUNSET,
 	"COPPER",     	// SKINCOLOR_COPPER,
 	"APRICOT",     	// SKINCOLOR_APRICOT,
 	"ORANGE",     	// SKINCOLOR_ORANGE,
 	"RUST",     	// SKINCOLOR_RUST,
+	"TANGERINE",   	// SKINCOLOR_TANGERINE,
+	"TOPAZ",     	// SKINCOLOR_TOPAZ,
 	"GOLD",     	// SKINCOLOR_GOLD,
 	"SANDY",     	// SKINCOLOR_SANDY,
+	"GOLDENROD",   	// SKINCOLOR_GOLDENROD,
 	"YELLOW",     	// SKINCOLOR_YELLOW,
 	"OLIVE",     	// SKINCOLOR_OLIVE,
+	"PEAR",     	// SKINCOLOR_PEAR,
+	"LEMON",     	// SKINCOLOR_LEMON,
 	"LIME",     	// SKINCOLOR_LIME,
 	"PERIDOT",     	// SKINCOLOR_PERIDOT,
 	"APPLE",     	// SKINCOLOR_APPLE,
+	"HEADLIGHT",	// SKINCOLOR_HEADLIGHT,
+	"CHARTREUSE",   // SKINCOLOR_CHARTREUSE,
 	"GREEN",     	// SKINCOLOR_GREEN,
 	"FOREST",     	// SKINCOLOR_FOREST,
-	"EMERALD",     	// SKINCOLOR_EMERALD,
+	"SHAMROCK",    	// SKINCOLOR_SHAMROCK,
+	"JADE",     	// SKINCOLOR_JADE,
 	"MINT",     	// SKINCOLOR_MINT,
+	"MASTER",     	// SKINCOLOR_MASTER,
+	"EMERALD",     	// SKINCOLOR_EMERALD,
 	"SEAFOAM",     	// SKINCOLOR_SEAFOAM,
+	"ISLAND",     	// SKINCOLOR_ISLAND,
+	"BOTTLE",     	// SKINCOLOR_BOTTLE,
 	"AQUA",     	// SKINCOLOR_AQUA,
 	"TEAL",     	// SKINCOLOR_TEAL,
+	"OCEAN",     	// SKINCOLOR_OCEAN,
 	"WAVE",     	// SKINCOLOR_WAVE,
 	"CYAN",     	// SKINCOLOR_CYAN,
+	"TURQUOISE",    // SKINCOLOR_TURQUOISE,
+	"AQUAMARINE",  	// SKINCOLOR_AQUAMARINE,
 	"SKY",     		// SKINCOLOR_SKY,
+	"MARINE",     	// SKINCOLOR_MARINE,
 	"CERULEAN",     // SKINCOLOR_CERULEAN,
+	"DREAM",     	// SKINCOLOR_DREAM,
 	"ICY",     		// SKINCOLOR_ICY,
+	"DAYBREAK",     // SKINCOLOR_DAYBREAK,
 	"SAPPHIRE",     // SKINCOLOR_SAPPHIRE,
+	"ARCTIC",     	// SKINCOLOR_ARCTIC,
 	"CORNFLOWER",   // SKINCOLOR_CORNFLOWER,
 	"BLUE",     	// SKINCOLOR_BLUE,
 	"COBALT",     	// SKINCOLOR_COBALT,
+	"MIDNIGHT",     // SKINCOLOR_MIDNIGHT,
+	"GALAXY",     	// SKINCOLOR_GALAXY,
 	"VAPOR",     	// SKINCOLOR_VAPOR,
 	"DUSK",     	// SKINCOLOR_DUSK,
+	"MAJESTY",     	// SKINCOLOR_MAJESTY,
 	"PASTEL",     	// SKINCOLOR_PASTEL,
 	"PURPLE",     	// SKINCOLOR_PURPLE,
-	"BUBBLEGUM",    // SKINCOLOR_BUBBLEGUM,
+	"NOBLE",     	// SKINCOLOR_NOBLE,
+	"FUCHSIA",     	// SKINCOLOR_FUCHSIA,
+	"BUBBLEGUM",   	// SKINCOLOR_BUBBLEGUM,
+	"SIBERITE",   	// SKINCOLOR_SIBERITE,
 	"MAGENTA",     	// SKINCOLOR_MAGENTA,
 	"NEON",     	// SKINCOLOR_NEON,
 	"VIOLET",     	// SKINCOLOR_VIOLET,
+	"ROYAL",     	// SKINCOLOR_ROYAL,
 	"LILAC",     	// SKINCOLOR_LILAC,
+	"MAUVE",     	// SKINCOLOR_MAUVE,
+	"EVENTIDE",   	// SKINCOLOR_EVENTIDE,
 	"PLUM",     	// SKINCOLOR_PLUM,
 	"RASPBERRY",  	// SKINCOLOR_RASPBERRY,
+	"TAFFY",     	// SKINCOLOR_TAFFY,
 	"ROSY",     	// SKINCOLOR_ROSY,
+	"FANCY",  		// SKINCOLOR_FANCY,
+	"SANGRIA",     	// SKINCOLOR_SANGRIA,
+	"VOLCANIC",    	// SKINCOLOR_VOLCANIC,
 
 	// Super special awesome Super flashing colors!
 	"SUPERSILVER1",	// SKINCOLOR_SUPERSILVER1
@@ -4768,7 +4818,9 @@ const char *const POWERS_LIST[] = {
 
 	"JUSTLAUNCHED",
 
-	"IGNORELATCH"
+	"IGNORELATCH",
+
+	"STRONG"
 };
 
 const char *const HUDITEMS_LIST[] = {
@@ -5121,6 +5173,30 @@ struct int_const_s const INT_CONST[] = {
 	{"CR_DUSTDEVIL",CR_DUSTDEVIL},
 	{"CR_FAN",CR_FAN},
 
+	// Strong powers
+	{"STR_NONE",STR_NONE},
+	{"STR_ANIM",STR_ANIM},
+	{"STR_PUNCH",STR_PUNCH},
+	{"STR_TAIL",STR_TAIL},
+	{"STR_STOMP",STR_STOMP},
+	{"STR_UPPER",STR_UPPER},
+	{"STR_GUARD",STR_GUARD},
+	{"STR_HEAVY",STR_HEAVY},
+	{"STR_DASH",STR_DASH},
+	{"STR_WALL",STR_WALL},
+	{"STR_FLOOR",STR_FLOOR},
+	{"STR_CEILING",STR_CEILING},
+	{"STR_SPRING",STR_SPRING},
+	{"STR_SPIKE",STR_SPIKE},
+	{"STR_ATTACK",STR_ATTACK},
+	{"STR_BUST",STR_BUST},
+	{"STR_FLY",STR_FLY},
+	{"STR_GLIDE",STR_GLIDE},
+	{"STR_TWINSPIN",STR_TWINSPIN},
+	{"STR_MELEE",STR_MELEE},
+	{"STR_BOUNCE",STR_BOUNCE},
+	{"STR_METAL",STR_METAL},
+
 	// Ring weapons (ringweapons_t)
 	// Useful for A_GiveWeapon
 	{"RW_AUTO",RW_AUTO},
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 8943ab71a4a2c29e5740f707ee240c6d6f0bba7c..42716f9b4bd271c98aca83737f419670d862d7e5 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -30,6 +30,7 @@ extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite
 	memset(FREE_MOBJS,0,sizeof(char *) * NUMMOBJFREESLOTS);\
 	memset(FREE_SKINCOLORS,0,sizeof(char *) * NUMCOLORFREESLOTS);\
 	memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
+	memset(actionsoverridden, LUA_REFNIL, sizeof(actionsoverridden));\
 }
 
 struct flickytypes_s {
@@ -61,7 +62,7 @@ extern const char *const MOBJTYPE_LIST[];
 extern const char *const MOBJFLAG_LIST[];
 extern const char *const MOBJFLAG2_LIST[]; // \tMF2_(\S+).*// (.+) --> \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/dehacked.c b/src/dehacked.c
index 17768eb7f4496107503a50fedbbffc0da0812b0b..fd2a701715e17ce7dab0b6c829147113844972fe 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -575,7 +575,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	} // end while
 
 	if (gamedataadded)
-		G_LoadGameData();
+		G_LoadGameData(clientGamedata);
 
 	if (gamestate == GS_TITLESCREEN)
 	{
diff --git a/src/dehacked.h b/src/dehacked.h
index 902404df7e0b1dffa533a3222554c2e564646aa9..b39f090352adf6b7e7e5d744d5644e78da48ee58 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -40,9 +40,9 @@ extern boolean gamedataadded;
 extern boolean titlechanged;
 extern boolean introchanged;
 
-#define MAXRECURSION 30
-extern const char *superactions[MAXRECURSION];
-extern UINT8 superstack;
+#define MAX_ACTION_RECURSION 30
+extern const char *luaactions[MAX_ACTION_RECURSION];
+extern UINT8 luaactionstack;
 
 // If the dehacked patch does not match this version, we throw a warning
 #define PATCHVERSION 220
diff --git a/src/doomdata.h b/src/doomdata.h
index 4c5bdefaf968f073462ab37b295cf85b5185954e..276e03297b6f0d453ad0d0c65fc8bd9196d9680c 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 2bab63829b0afeefdc7d995ec8a3d72a1767fd21..45d6645faa0bc0ff884e11cb5722e4967f679560 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -104,8 +104,18 @@
 #include <io.h>
 #endif
 
+FILE *fopenfile(const char*, const char*);
+
 //#define NOMD5
 
+// If you don't disable ALL debug first, you get ALL debug enabled
+#if !defined (NDEBUG)
+#define PACKETDROP
+#define PARANOIA
+#define RANGECHECK
+#define ZDEBUG
+#endif
+
 // Uncheck this to compile debugging code
 //#define RANGECHECK
 //#ifndef PARANOIA
@@ -150,7 +160,7 @@ extern char logfilename[1024];
 
 // Does this version require an added patch file?
 // Comment or uncomment this as necessary.
-// #define USE_PATCH_DTA
+#define USE_PATCH_DTA
 
 // Enforce a limit of loaded WAD files.
 //#define ENFORCE_WAD_LIMIT
@@ -259,66 +269,111 @@ typedef enum
 	// Desaturated
 	SKINCOLOR_AETHER,
 	SKINCOLOR_SLATE,
+	SKINCOLOR_MOONSTONE,
 	SKINCOLOR_BLUEBELL,
 	SKINCOLOR_PINK,
+	SKINCOLOR_ROSEWOOD,
 	SKINCOLOR_YOGURT,
+	SKINCOLOR_LATTE,
 	SKINCOLOR_BROWN,
+	SKINCOLOR_BOULDER,
 	SKINCOLOR_BRONZE,
+	SKINCOLOR_SEPIA,
+	SKINCOLOR_ECRU,
 	SKINCOLOR_TAN,
 	SKINCOLOR_BEIGE,
+	SKINCOLOR_ROSEBUSH,
 	SKINCOLOR_MOSS,
 	SKINCOLOR_AZURE,
+	SKINCOLOR_EGGPLANT,
 	SKINCOLOR_LAVENDER,
 
 	// Viv's vivid colours (toast 21/07/17)
+	// Tweaks & additions (Lach, sphere, Alice, MotorRoach 26/10/22)
 	SKINCOLOR_RUBY,
+	SKINCOLOR_CHERRY,
 	SKINCOLOR_SALMON,
+	SKINCOLOR_PEPPER,
 	SKINCOLOR_RED,
 	SKINCOLOR_CRIMSON,
 	SKINCOLOR_FLAME,
+	SKINCOLOR_GARNET,
 	SKINCOLOR_KETCHUP,
 	SKINCOLOR_PEACHY,
 	SKINCOLOR_QUAIL,
+	SKINCOLOR_FOUNDATION,
 	SKINCOLOR_SUNSET,
 	SKINCOLOR_COPPER,
 	SKINCOLOR_APRICOT,
 	SKINCOLOR_ORANGE,
 	SKINCOLOR_RUST,
+	SKINCOLOR_TANGERINE,
+	SKINCOLOR_TOPAZ,
 	SKINCOLOR_GOLD,
 	SKINCOLOR_SANDY,
+	SKINCOLOR_GOLDENROD,
 	SKINCOLOR_YELLOW,
 	SKINCOLOR_OLIVE,
+	SKINCOLOR_PEAR,
+	SKINCOLOR_LEMON,
 	SKINCOLOR_LIME,
 	SKINCOLOR_PERIDOT,
 	SKINCOLOR_APPLE,
+	SKINCOLOR_HEADLIGHT,
+	SKINCOLOR_CHARTREUSE,
 	SKINCOLOR_GREEN,
 	SKINCOLOR_FOREST,
-	SKINCOLOR_EMERALD,
+	SKINCOLOR_SHAMROCK,
+	SKINCOLOR_JADE,
 	SKINCOLOR_MINT,
+	SKINCOLOR_MASTER,
+	SKINCOLOR_EMERALD,
 	SKINCOLOR_SEAFOAM,
+	SKINCOLOR_ISLAND,
+	SKINCOLOR_BOTTLE,
 	SKINCOLOR_AQUA,
 	SKINCOLOR_TEAL,
+	SKINCOLOR_OCEAN,
 	SKINCOLOR_WAVE,
 	SKINCOLOR_CYAN,
+	SKINCOLOR_TURQUOISE,
+	SKINCOLOR_AQUAMARINE,
 	SKINCOLOR_SKY,
+	SKINCOLOR_MARINE,
 	SKINCOLOR_CERULEAN,
+	SKINCOLOR_DREAM,
 	SKINCOLOR_ICY,
+	SKINCOLOR_DAYBREAK,
 	SKINCOLOR_SAPPHIRE, // sweet mother, i cannot weave – slender aphrodite has overcome me with longing for a girl
+	SKINCOLOR_ARCTIC,
 	SKINCOLOR_CORNFLOWER,
 	SKINCOLOR_BLUE,
 	SKINCOLOR_COBALT,
+	SKINCOLOR_MIDNIGHT,
+	SKINCOLOR_GALAXY,
 	SKINCOLOR_VAPOR,
 	SKINCOLOR_DUSK,
+	SKINCOLOR_MAJESTY,
 	SKINCOLOR_PASTEL,
 	SKINCOLOR_PURPLE,
+	SKINCOLOR_NOBLE,
+	SKINCOLOR_FUCHSIA,
 	SKINCOLOR_BUBBLEGUM,
+	SKINCOLOR_SIBERITE,
 	SKINCOLOR_MAGENTA,
 	SKINCOLOR_NEON,
 	SKINCOLOR_VIOLET,
+	SKINCOLOR_ROYAL,
 	SKINCOLOR_LILAC,
+	SKINCOLOR_MAUVE,
+	SKINCOLOR_EVENTIDE,
 	SKINCOLOR_PLUM,
 	SKINCOLOR_RASPBERRY,
+	SKINCOLOR_TAFFY,
 	SKINCOLOR_ROSY,
+	SKINCOLOR_FANCY,
+	SKINCOLOR_SANGRIA,
+	SKINCOLOR_VOLCANIC,
 
 	FIRSTSUPERCOLOR,
 
@@ -590,7 +645,16 @@ UINT32 quickncasehash (const char *p, size_t n)
 #define PUNCTUATION "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
 
 // Compile date and time and revision.
-extern const char *compdate, *comptime, *comprevision, *compbranch;
+extern const char
+	*compdate,
+	*comptime,
+	*comprevision,
+	*compbranch,
+	*compnote,
+	*comptype;
+extern int
+	compuncommitted,
+	compoptimized;
 
 // Disabled code and code under testing
 // None of these that are disabled in the normal build are guaranteed to work perfectly
diff --git a/src/doomstat.h b/src/doomstat.h
index 632381b1c8b35bb00896c869723bab80d6fecbca..fdd0d0b834ff58601cce67881e399709f9277d8b 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -75,6 +75,7 @@ extern SINT8 startinglivesbalance[maxgameovers+1];
 extern boolean modifiedgame;
 extern UINT16 mainwads;
 extern boolean savemoddata; // This mod saves time/emblem data.
+extern boolean usedCheats;
 extern boolean disableSpeedAdjust; // Don't alter the duration of player states if true
 extern boolean imcontinuing; // Temporary flag while continuing
 extern boolean metalrecording;
@@ -131,8 +132,6 @@ extern INT32 postimgparam2;
 extern INT32 viewwindowx, viewwindowy;
 extern INT32 viewwidth, scaledviewwidth;
 
-extern boolean gamedataloaded;
-
 // Player taking events, and displaying.
 extern INT32 consoleplayer;
 extern INT32 displayplayer;
@@ -250,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
 
@@ -494,8 +494,6 @@ typedef struct
 extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES];
 extern UINT32 lastcustomtol;
 
-extern tic_t totalplaytime;
-
 extern boolean stagefailed;
 
 // Emeralds stored as bits to throw savegame hackers off.
@@ -514,52 +512,6 @@ extern INT32 luabanks[NUM_LUABANKS];
 
 extern INT32 nummaprings; //keep track of spawned rings/coins
 
-/** Time attack information, currently a very small structure.
-  */
-typedef struct
-{
-	tic_t time;   ///< Time in which the level was finished.
-	UINT32 score; ///< Score when the level was finished.
-	UINT16 rings; ///< Rings when the level was finished.
-} recorddata_t;
-
-/** Setup for one NiGHTS map.
-  * These are dynamically allocated because I am insane
-  */
-#define GRADE_F 0
-#define GRADE_E 1
-#define GRADE_D 2
-#define GRADE_C 3
-#define GRADE_B 4
-#define GRADE_A 5
-#define GRADE_S 6
-
-typedef struct
-{
-	// 8 mares, 1 overall (0)
-	UINT8	nummares;
-	UINT32	score[9];
-	UINT8	grade[9];
-	tic_t	time[9];
-} nightsdata_t;
-
-extern nightsdata_t *nightsrecords[NUMMAPS];
-extern recorddata_t *mainrecords[NUMMAPS];
-
-// mapvisited is now a set of flags that says what we've done in the map.
-#define MV_VISITED      1
-#define MV_BEATEN       2
-#define MV_ALLEMERALDS  4
-#define MV_ULTIMATE     8
-#define MV_PERFECT     16
-#define MV_PERFECTRA   32
-#define MV_MAX         63 // used in gamedata check, update whenever MV's are added
-#define MV_MP         128
-extern UINT8 mapvisited[NUMMAPS];
-
-// Temporary holding place for nights data for the current map
-extern nightsdata_t ntemprecords;
-
 extern UINT32 token; ///< Number of tokens collected in a level
 extern UINT32 tokenlist; ///< List of tokens collected
 extern boolean gottoken; ///< Did you get a token? Used for end of act
@@ -592,9 +544,12 @@ extern UINT8 useBlackRock;
 
 extern UINT8 use1upSound;
 extern UINT8 maxXtraLife; // Max extra lives from rings
+
 extern UINT8 useContinues;
 #define continuesInSession (!multiplayer && (ultimatemode || (useContinues && !marathonmode) || (!modeattacking && !(cursaveslot > 0))))
 
+extern UINT8 shareEmblems;
+
 extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations
 
 // For racing
@@ -615,10 +570,6 @@ extern INT32 cheats;
 
 extern tic_t hidetime;
 
-extern UINT32 timesBeaten; // # of times the game has been beaten.
-extern UINT32 timesBeatenWithEmeralds;
-extern UINT32 timesBeatenUltimate;
-
 // ===========================
 // Internal parameters, fixed.
 // ===========================
diff --git a/src/doomtype.h b/src/doomtype.h
index d29bd35f25b29c0df2be5c79d4d01c4f1308c49d..4070e346a1b13dd516a91504f056cebc4defb20d 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -17,6 +17,10 @@
 #ifndef __DOOMTYPE__
 #define __DOOMTYPE__
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #ifdef _WIN32
 //#define WIN32_LEAN_AND_MEAN
 #define RPC_NO_WINDOWS_H
@@ -78,7 +82,9 @@ typedef long ssize_t;
 #endif
 	#define strncasecmp             strnicmp
 	#define strcasecmp              stricmp
+#ifndef __cplusplus
 	#define inline                  __inline
+#endif
 #elif defined (__WATCOMC__)
 	#include <dos.h>
 	#include <sys\types.h>
@@ -94,29 +100,26 @@ typedef long ssize_t;
 	#define strnicmp(x,y,n) strncasecmp(x,y,n)
 #endif
 
-char *strcasestr(const char *in, const char *what);
+char *nongnu_strcasestr(const char *in, const char *what);
+#ifndef _GNU_SOURCE
+#define strcasestr nongnu_strcasestr
+#endif
 #define stristr strcasestr
 
-#if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap
-	#define true 1
-	#define false 0
-	#define min(x,y) (((x)<(y)) ? (x) : (y))
-	#define max(x,y) (((x)>(y)) ? (x) : (y))
+int startswith (const char *base, const char *tag);
+int endswith (const char *base, const char *tag);
 
-#ifdef macintosh
-	#define stricmp strcmp
-	#define strnicmp strncmp
+#if defined (_WIN32) || defined (__HAIKU__)
+#define HAVE_DOSSTR_FUNCS
 #endif
 
-	#define boolean INT32
-
-	#ifndef O_BINARY
-	#define O_BINARY 0
+#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 //macintosh
-
-#if defined (_WIN32) || defined (__HAIKU__)
-#define HAVE_DOSSTR_FUNCS
 #endif
 
 #ifndef HAVE_DOSSTR_FUNCS
@@ -126,7 +129,7 @@ int strlwr(char *n); // from dosstr.c
 
 #include <stddef.h> // 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
@@ -141,22 +144,24 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 
 /* Boolean type definition */
 
-// \note __BYTEBOOL__ used to be set above if "macintosh" was defined,
-// if macintosh's version of boolean type isn't needed anymore, then isn't this macro pointless now?
-#ifndef __BYTEBOOL__
-	#define __BYTEBOOL__
-
-	//faB: clean that up !!
-	#if defined( _MSC_VER)  && (_MSC_VER >= 1800) // MSVC 2013 and forward
-		#include "stdbool.h"
-	#elif defined (_WIN32)
-		#define false   FALSE           // use windows types
-		#define true    TRUE
-		#define boolean BOOL
-	#else
-		typedef enum {false, true} boolean;
-	#endif
-#endif // __BYTEBOOL__
+// Note: C++ bool and C99/C11 _Bool are NOT compatible.
+// Historically, boolean was win32 BOOL on Windows. For equivalence, it's now
+// int32_t. "true" and "false" are only declared for C code; in C++, conversion
+// between "bool" and "int32_t" takes over.
+#ifndef _WIN32
+typedef int32_t boolean;
+#else
+#define boolean BOOL
+#endif
+
+#ifndef __cplusplus
+#ifndef _WIN32
+enum {false = 0, true = 1};
+#else
+#define false FALSE
+#define true TRUE
+#endif
+#endif
 
 /* 7.18.2.1  Limits of exact-width integer types */
 
@@ -384,4 +389,8 @@ unset_bit_array (bitarray_t * const array, const int value)
 
 typedef UINT64 precise_t;
 
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
 #endif //__DOOMTYPE__
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index 997115ad0c8da17a2aa66e727ab166b500a6dee4..70e1ef4ec2b7aba9699731c0d743733750bc26fc 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -8,19 +8,24 @@ UINT8 graphics_started = 0;
 
 UINT8 keyboard_started = 0;
 
-UINT32 I_GetFreeMem(UINT32 *total)
+size_t I_GetFreeMem(size_t *total)
 {
 	*total = 0;
 	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;
 }
 
@@ -180,7 +185,14 @@ const char *I_ClipboardPaste(void)
 	return NULL;
 }
 
-void I_RegisterSysCommands(void) {}
+size_t I_GetRandomBytes(char *destination, size_t amount)
+{
+	(void)destination;
+	(void)amount;
+	return 0;
+}
+
+void I_RegisterSysCommands(void){}
 
 void I_GetCursorPosition(INT32 *x, INT32 *y)
 {
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index 3b0a12a328df587e1cd20d0312cf96ce6c8df847..bb796b6767a1d55990209ba46cd8245377b013d9 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -57,6 +57,8 @@ const char *VID_GetModeName(INT32 modenum)
 	return NULL;
 }
 
+UINT32 I_GetRefreshRate(void) { return 35; }
+
 void I_UpdateNoBlit(void){}
 
 void I_FinishUpdate(void){}
diff --git a/src/f_finale.c b/src/f_finale.c
index fe94b924c104699f3140c55ec27e408a50eeb330..91c06b31652ab2902668fdb82009519ee6811108 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -63,7 +63,6 @@ static tic_t stoptimer;
 static boolean keypressed = false;
 
 // (no longer) De-Demo'd Title Screen
-static INT32 menuanimtimer; // Title screen: background animation timing
 mobj_t *titlemapcameraref = NULL;
 
 // menu presentation state
@@ -75,6 +74,8 @@ INT32 curbgyspeed;
 boolean curbghide;
 boolean hidetitlemap;		// WARNING: set to false by M_SetupNextMenu and M_ClearMenus
 
+static fixed_t curbgx = 0;
+static fixed_t curbgy = 0;
 static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
 static UINT32 demoIdleLeft;
@@ -223,7 +224,6 @@ static INT32 cutscene_writeptr = 0;
 static INT32 cutscene_textcount = 0;
 static INT32 cutscene_textspeed = 0;
 static UINT8 cutscene_boostspeed = 0;
-static tic_t cutscene_lasttextwrite = 0;
 
 // STJR Intro
 char stjrintro[9] = "STJRI000";
@@ -239,11 +239,6 @@ static UINT8 F_WriteText(void)
 {
 	INT32 numtowrite = 1;
 	const char *c;
-	tic_t ltw = I_GetTime();
-
-	if (cutscene_lasttextwrite == ltw)
-		return 1; // singletics prevention
-	cutscene_lasttextwrite = ltw;
 
 	if (cutscene_boostspeed)
 	{
@@ -337,7 +332,7 @@ static tic_t introscenetime[NUMINTROSCENES] =
 };
 
 // custom intros
-void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer);
+void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer, boolean FLS);
 
 void F_StartIntro(void)
 {
@@ -349,7 +344,7 @@ void F_StartIntro(void)
 		if (!cutscenes[introtoplay - 1])
 			D_StartTitle();
 		else
-			F_StartCustomCutscene(introtoplay - 1, false, false);
+			F_StartCustomCutscene(introtoplay - 1, false, false, false);
 		return;
 	}
 
@@ -1067,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\"",
@@ -1095,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:
@@ -1202,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
@@ -1257,7 +1256,7 @@ void F_StartCredits(void)
 
 	if (creditscutscene)
 	{
-		F_StartCustomCutscene(creditscutscene - 1, false, false);
+		F_StartCustomCutscene(creditscutscene - 1, false, false, false);
 		return;
 	}
 
@@ -1279,14 +1278,23 @@ void F_CreditDrawer(void)
 	UINT16 i;
 	INT16 zagpos = (timetonext - finalecount - animtimer) % 32;
 	fixed_t y = (80<<FRACBITS) - (animtimer<<FRACBITS>>1);
+	UINT8 colornum;
+	const UINT8 *colormap;
+
+	if (players[consoleplayer].skincolor)
+		colornum = players[consoleplayer].skincolor;
+	else
+		colornum = cv_playercolor.value;
+
+	colormap = R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE);
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Zig Zagz
-	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
-	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawFixedPatch(-16*FRACUNIT, zagpos<<FRACBITS, FRACUNIT, V_SNAPTOLEFT, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY), colormap);
+	V_DrawFixedPatch(-16*FRACUNIT, (zagpos - 320)<<FRACBITS, FRACUNIT, V_SNAPTOLEFT, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY), colormap);
+	V_DrawFixedPatch((BASEVIDWIDTH + 16)*FRACUNIT, zagpos<<FRACBITS, FRACUNIT, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY), colormap);
+	V_DrawFixedPatch((BASEVIDWIDTH + 16)*FRACUNIT, (zagpos - 320)<<FRACBITS, FRACUNIT, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY), colormap);
 
 	// Draw background pictures first
 	for (i = 0; credits_pics[i].patch; i++)
@@ -1411,7 +1419,7 @@ boolean F_CreditResponder(event_t *event)
 			break;
 	}
 
-	if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug)
+	if (!(serverGamedata->timesBeaten) && !(netgame || multiplayer) && !cv_debug)
 		return false;
 
 	if (event->type != ev_keydown)
@@ -1573,27 +1581,19 @@ void F_GameEvaluationDrawer(void)
 #if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables
 	if (finalecount >= 5*TICRATE)
 	{
+		INT32 startcoord = 32;
 		V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
 
-		if (!(netgame) && (!modifiedgame || savemoddata))
+		for (i = 0; i < MAXUNLOCKABLES; i++)
 		{
-			INT32 startcoord = 32;
-
-			for (i = 0; i < MAXUNLOCKABLES; i++)
+			if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
+				&& unlockables[i].type && !unlockables[i].nocecho)
 			{
-				if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
-					&& unlockables[i].type && !unlockables[i].nocecho)
-				{
-					if (unlockables[i].unlocked)
-						V_DrawString(8, startcoord, 0, unlockables[i].name);
-					startcoord += 8;
-				}
+				if (clientGamedata->unlocked[i])
+					V_DrawString(8, startcoord, 0, unlockables[i].name);
+				startcoord += 8;
 			}
 		}
-		else if (netgame)
-			V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
-		else
-			V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
 	}
 #endif
 
@@ -1648,37 +1648,29 @@ void F_GameEvaluationTicker(void)
 		sparklloop = 0;
 	}
 
-	if (finalecount == 5*TICRATE)
+	if (G_CoopGametype() && !stagefailed && finalecount == 5*TICRATE)
 	{
-		if (netgame || multiplayer) // modify this when we finally allow unlocking stuff in 2P
+		serverGamedata->timesBeaten++;
+		clientGamedata->timesBeaten++;
+
+		if (ALL7EMERALDS(emeralds))
 		{
-			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
-			HU_SetCEchoDuration(6);
-			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!");
-			S_StartSound(NULL, sfx_s3k68);
+			serverGamedata->timesBeatenWithEmeralds++;
+			clientGamedata->timesBeatenWithEmeralds++;
 		}
-		else if (!modifiedgame || savemoddata)
-		{
-			++timesBeaten;
-
-			if (ALL7EMERALDS(emeralds))
-				++timesBeatenWithEmeralds;
 
-			if (ultimatemode)
-				++timesBeatenUltimate;
+		if (ultimatemode)
+		{
+			serverGamedata->timesBeatenUltimate++;
+			clientGamedata->timesBeatenUltimate++;
+		}
 
-			if (M_UpdateUnlockablesAndExtraEmblems())
-				S_StartSound(NULL, sfx_s3k68);
+		M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
 
-			G_SaveGameData();
-		}
-		else
-		{
-			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
-			HU_SetCEchoDuration(6);
-			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Modified games can't unlock extras!");
+		if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 			S_StartSound(NULL, sfx_s3k68);
-		}
+
+		G_SaveGameData(clientGamedata);
 	}
 }
 
@@ -2192,7 +2184,7 @@ void F_EndingDrawer(void)
 			//colset(linkmap,  164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret
 			V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), str);
 			V_DrawCharacter(32, BASEVIDHEIGHT-16, '>'|(trans<<V_ALPHASHIFT), false);
-			V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
+			V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((serverGamedata->timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
 		}
 
 		if (finalecount > STOPPINGPOINT-(20+(2*TICRATE)))
@@ -2258,7 +2250,8 @@ void F_GameEndTicker(void)
 
 void F_InitMenuPresValues(void)
 {
-	menuanimtimer = 0;
+	curbgx = 0;
+	curbgy = 0;
 	prevMenuId = 0;
 	activeMenuId = MainDef.menuid;
 
@@ -2267,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;
@@ -2291,17 +2284,11 @@ void F_InitMenuPresValues(void)
 //
 // F_SkyScroll
 //
-void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
+void F_SkyScroll(const char *patchname)
 {
-	INT32 xscrolled, x, xneg = (scrollxspeed > 0) - (scrollxspeed < 0), tilex;
-	INT32 yscrolled, y, yneg = (scrollyspeed > 0) - (scrollyspeed < 0), tiley;
-	boolean xispos = (scrollxspeed >= 0), yispos = (scrollyspeed >= 0);
+	INT32 x, basey = 0;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	INT16 patwidth, patheight;
-	INT32 pw, ph; // scaled by dupz
 	patch_t *pat;
-	INT32 i, j;
-	fixed_t fracmenuanimtimer, xscrolltimer, yscrolltimer;
 
 	if (rendermode == render_none)
 		return;
@@ -2312,43 +2299,34 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 		return;
 	}
 
-	if (!scrollxspeed && !scrollyspeed)
+	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
+
+	if (!curbgxspeed && !curbgyspeed)
 	{
-		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY));
+		V_DrawPatchFill(pat);
+		W_UnlockCachedPatch(pat);
 		return;
 	}
 
-	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
-
-	patwidth = pat->width;
-	patheight = pat->height;
-	pw = patwidth * dupz;
-	ph = patheight * dupz;
+	// Modulo the background scrolling to prevent jumps from integer overflows
+	// We already load the background patch here, so we can modulo it here
+	// to avoid also having to load the patch in F_MenuPresTicker
+	curbgx %= pat->width  * 16;
+	curbgy %= pat->height * 16;
 
-	tilex = max(FixedCeil(FixedDiv(vid.width, pw)) >> FRACBITS, 1)+2; // one tile on both sides of center
-	tiley = max(FixedCeil(FixedDiv(vid.height, ph)) >> FRACBITS, 1)+2;
+	// Ooh, fancy frame interpolation
+	x     = ((curbgx*dupz) + FixedInt((rendertimefrac-FRACUNIT) * curbgxspeed*dupz)) / 16;
+	basey = ((curbgy*dupz) + FixedInt((rendertimefrac-FRACUNIT) * curbgyspeed*dupz)) / 16;
 
-	fracmenuanimtimer = (menuanimtimer * FRACUNIT) - (FRACUNIT - rendertimefrac);
-	xscrolltimer = ((fracmenuanimtimer*scrollxspeed)/16 + patwidth*xneg*FRACUNIT) % (patwidth * FRACUNIT);
-	yscrolltimer = ((fracmenuanimtimer*scrollyspeed)/16 + patheight*yneg*FRACUNIT) % (patheight * FRACUNIT);
+	if (x     > 0) // Make sure that we don't leave the left or top sides empty
+		x     -= pat->width  * dupz;
+	if (basey > 0)
+		basey -= pat->height * dupz;
 
-	// coordinate offsets
-	xscrolled = FixedInt(xscrolltimer * dupz);
-	yscrolled = FixedInt(yscrolltimer * dupz);
-
-	for (x = (xispos) ? -pw*(tilex-1)+pw : 0, i = 0;
-		i < tilex;
-		x += pw, i++)
+	for (; x < vid.width; x += pat->width * dupz)
 	{
-		for (y = (yispos) ? -ph*(tiley-1)+ph : 0, j = 0;
-			j < tiley;
-			y += ph, j++)
-		{
-			V_DrawScaledPatch(
-				(xispos) ? xscrolled - x : x + xscrolled,
-				(yispos) ? yscrolled - y : y + yscrolled,
-				V_NOSCALESTART, pat);
-		}
+		for (INT32 y = basey; y < vid.height; y += pat->height * dupz)
+			V_DrawScaledPatch(x, y, V_NOSCALESTART, pat);
 	}
 
 	W_UnlockCachedPatch(pat);
@@ -2671,7 +2649,7 @@ void F_TitleScreenDrawer(void)
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 	else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS)
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		F_SkyScroll(curbgname);
 
 	// Don't draw outside of the title screen, or if the patch isn't there.
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
@@ -3426,10 +3404,10 @@ luahook:
 
 // separate animation timer for backgrounds, since we also count
 // during GS_TIMEATTACK
-void F_MenuPresTicker(boolean run)
+void F_MenuPresTicker(void)
 {
-	if (run)
-		menuanimtimer++;
+	curbgx += curbgxspeed;
+	curbgy += curbgyspeed;
 }
 
 // (no longer) De-Demo'd Title Screen
@@ -3535,6 +3513,7 @@ void F_TitleScreenTicker(boolean run)
 		}
 
 		titledemo = true;
+		demofileoverride = DFILE_OVERRIDE_NONE;
 		G_DoPlayDemo(dname);
 	}
 }
@@ -3859,7 +3838,7 @@ static INT32 scenenum, cutnum;
 static INT32 picxpos, picypos, picnum, pictime, picmode, numpics, pictoloop;
 static INT32 textxpos, textypos;
 static boolean cutsceneover = false;
-static boolean runningprecutscene = false, precutresetplayer = false;
+static boolean runningprecutscene = false, precutresetplayer = false, precutFLS = false;
 
 static void F_AdvanceToNextScene(void)
 {
@@ -3928,7 +3907,7 @@ void F_EndCutScene(void)
 	if (runningprecutscene)
 	{
 		if (server)
-			D_MapChange(gamemap, gametype, ultimatemode, precutresetplayer, 0, true, false);
+			D_MapChange(gamemap, gametype, ultimatemode, precutresetplayer, 0, true, precutFLS);
 	}
 	else
 	{
@@ -3943,7 +3922,7 @@ void F_EndCutScene(void)
 	}
 }
 
-void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer)
+void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer, boolean FLS)
 {
 	if (!cutscenes[cutscenenum])
 		return;
@@ -3962,6 +3941,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 	cutsceneover = false;
 	runningprecutscene = precutscene;
 	precutresetplayer = resetplayer;
+	precutFLS = FLS;
 
 	scenenum = picnum = 0;
 	cutnum = cutscenenum;
@@ -4694,3 +4674,36 @@ void F_TextPromptTicker(void)
 			animtimer--;
 	}
 }
+
+// ================
+//  WAITINGPLAYERS
+// ================
+
+void F_StartWaitingPlayers(void)
+{
+	wipegamestate = GS_TITLESCREEN; // technically wiping from title screen
+	finalecount = 0;
+}
+
+void F_WaitingPlayersTicker(void)
+{
+	if (paused)
+		return;
+
+	finalecount++;
+
+	// dumb hack, only start the music on the 1st tick so if you instantly go into the map you aren't hearing a tic of music
+	if (finalecount == 2)
+		S_ChangeMusicInternal("_CHSEL", true);
+}
+
+void F_WaitingPlayersDrawer(void)
+{
+	const char *waittext1 = "You will join";
+	const char *waittext2 = "next level...";
+
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
+	V_DrawCreditString((160 - (V_CreditStringWidth(waittext1)>>1))<<FRACBITS, 48<<FRACBITS, 0, waittext1);
+	V_DrawCreditString((160 - (V_CreditStringWidth(waittext2)>>1))<<FRACBITS, 64<<FRACBITS, 0, waittext2);
+}
diff --git a/src/f_finale.h b/src/f_finale.h
index e37b45253b52b9d32ef3fff2086a59d8318f600f..cb71775d05fc109aa4c3468c19cfe381eff72635 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -40,7 +40,7 @@ void F_TextPromptTicker(void);
 void F_GameEndDrawer(void);
 void F_IntroDrawer(void);
 void F_TitleScreenDrawer(void);
-void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname);
+void F_SkyScroll(const char *patchname);
 
 void F_GameEvaluationDrawer(void);
 void F_StartGameEvaluation(void);
@@ -52,7 +52,7 @@ void F_EndingDrawer(void);
 void F_CreditTicker(void);
 void F_CreditDrawer(void);
 
-void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer);
+void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer, boolean FLS);
 void F_CutsceneDrawer(void);
 void F_EndCutScene(void);
 
@@ -74,6 +74,10 @@ void F_StartContinue(void);
 void F_ContinueTicker(void);
 void F_ContinueDrawer(void);
 
+void F_StartWaitingPlayers(void);
+void F_WaitingPlayersTicker(void);
+void F_WaitingPlayersDrawer(void);
+
 extern INT32 finalecount;
 extern INT32 titlescrollxspeed;
 extern INT32 titlescrollyspeed;
@@ -131,7 +135,7 @@ extern UINT16 curtttics;
 #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
 
 void F_InitMenuPresValues(void);
-void F_MenuPresTicker(boolean run);
+void F_MenuPresTicker(void);
 
 //
 // WIPE
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 6014fb7f9471d851532a3df4a94b01c1b04ef6e8..4bcfb029b2ed1815e895ff126127bb628c6709fb 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -614,6 +614,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 
 		if (moviemode)
 			M_SaveFrame();
+
+		NetKeepAlive(); // Update the network so we don't cause timeouts
 	}
 
 	WipeInAction = false;
diff --git a/src/filesrch.c b/src/filesrch.c
index 23b8e7f409b5c450f0d507b2075bebb611efa508..9ee64f5ba658752e40a57c9ff0c0d2302ed85ecf 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -39,6 +39,7 @@
 
 #define SUFFIX	"*"
 #define	SLASH	"\\"
+#define	S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)
 #define	S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
 
 #ifndef INVALID_FILE_ATTRIBUTES
@@ -307,6 +308,39 @@ closedir (DIR * dirp)
 }
 #endif
 
+// fopen but it REALLY only works on regular files
+// Turns out, on linux, anyway, you can fopen directories
+// in read mode. (It's supposed to fail in write mode
+// though!!)
+FILE *fopenfile(const char *path, const char *mode)
+{
+	FILE *h = fopen(path, mode);
+
+	if (h != NULL)
+	{
+		struct stat st;
+		int eno;
+
+		if (fstat(fileno(h), &st) == -1)
+		{
+			eno = errno;
+		}
+		else if (!S_ISREG(st.st_mode))
+		{
+			eno = EACCES; // set some kinda error
+		}
+		else
+		{
+			return h; // ok
+		}
+
+		fclose(h);
+		errno = eno;
+	}
+
+	return NULL;
+}
+
 static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"},
 #if 1
 												{1, "HOME"}, {2, "SRB2"},
diff --git a/src/g_demo.c b/src/g_demo.c
index 0403da16da95c5d4e41a614fa2640797ece217f2..4b9ff56e80f5711faabd18b53a9474f2c0a60c5e 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -39,6 +39,7 @@
 #include "v_video.h"
 #include "lua_hook.h"
 #include "md5.h" // demo checksums
+#include "d_netfil.h" // G_CheckDemoExtraFiles
 
 boolean timingdemo; // if true, exit with report on completion
 boolean nodrawers; // for comparative timing purposes
@@ -49,6 +50,7 @@ static char demoname[64];
 boolean demorecording;
 boolean demoplayback;
 boolean titledemo; // Title Screen demo can be cancelled by any key
+demo_file_override_e demofileoverride;
 static UINT8 *demobuffer = NULL;
 static UINT8 *demo_p, *demotime_p;
 static UINT8 *demoend;
@@ -56,6 +58,7 @@ static UINT8 demoflags;
 static UINT16 demoversion;
 boolean singledemo; // quit after playing a demo from cmdline
 boolean demo_start; // don't start playing demo right away
+boolean demo_forwardmove_rng; // old demo backwards compatibility
 boolean demosynced = true; // console warning message
 
 boolean metalrecording; // recording as metal sonic
@@ -95,7 +98,7 @@ demoghost *ghosts = NULL;
 // DEMO RECORDING
 //
 
-#define DEMOVERSION 0x000f
+#define DEMOVERSION 0x0010
 #define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
 
 #define DF_GHOST        0x01 // This demo contains ghost data too!
@@ -1413,6 +1416,10 @@ void G_BeginRecording(void)
 	char name[MAXCOLORNAME+1];
 	player_t *player = &players[consoleplayer];
 
+	char *filename;
+	UINT16 totalfiles;
+	UINT8 *m;
+
 	if (demo_p)
 		return;
 	memset(name,0,sizeof(name));
@@ -1435,23 +1442,43 @@ void G_BeginRecording(void)
 	M_Memcpy(demo_p, mapmd5, 16); demo_p += 16;
 
 	WRITEUINT8(demo_p,demoflags);
+
+	// file list
+	m = demo_p;/* file count */
+	demo_p += 2;
+
+	totalfiles = 0;
+	for (i = mainwads; ++i < numwadfiles; )
+	{
+		if (wadfiles[i]->important)
+		{
+			nameonly(( filename = va("%s", wadfiles[i]->filename) ));
+			WRITESTRINGL(demo_p, filename, MAX_WADPATH);
+			WRITEMEM(demo_p, wadfiles[i]->md5sum, 16);
+
+			totalfiles++;
+		}
+	}
+
+	WRITEUINT16(m, totalfiles);
+
 	switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
 	{
-	case ATTACKING_NONE: // 0
-		break;
-	case ATTACKING_RECORD: // 1
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		WRITEUINT16(demo_p,0); // rings
-		break;
-	case ATTACKING_NIGHTS: // 2
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		break;
-	default: // 3
-		break;
+		case ATTACKING_NONE: // 0
+			break;
+		case ATTACKING_RECORD: // 1
+			demotime_p = demo_p;
+			WRITEUINT32(demo_p,UINT32_MAX); // time
+			WRITEUINT32(demo_p,0); // score
+			WRITEUINT16(demo_p,0); // rings
+			break;
+		case ATTACKING_NIGHTS: // 2
+			demotime_p = demo_p;
+			WRITEUINT32(demo_p,UINT32_MAX); // time
+			WRITEUINT32(demo_p,0); // score
+			break;
+		default: // 3
+			break;
 	}
 
 	WRITEUINT32(demo_p,P_GetInitSeed());
@@ -1483,18 +1510,18 @@ void G_BeginRecording(void)
 	// Stats
 	WRITEUINT8(demo_p,player->charability);
 	WRITEUINT8(demo_p,player->charability2);
-	WRITEUINT8(demo_p,player->actionspd>>FRACBITS);
-	WRITEUINT8(demo_p,player->mindash>>FRACBITS);
-	WRITEUINT8(demo_p,player->maxdash>>FRACBITS);
-	WRITEUINT8(demo_p,player->normalspeed>>FRACBITS);
-	WRITEUINT8(demo_p,player->runspeed>>FRACBITS);
+	WRITEFIXED(demo_p,player->actionspd);
+	WRITEFIXED(demo_p,player->mindash);
+	WRITEFIXED(demo_p,player->maxdash);
+	WRITEFIXED(demo_p,player->normalspeed);
+	WRITEFIXED(demo_p,player->runspeed);
 	WRITEUINT8(demo_p,player->thrustfactor);
 	WRITEUINT8(demo_p,player->accelstart);
 	WRITEUINT8(demo_p,player->acceleration);
 	WRITEFIXED(demo_p,player->height);
 	WRITEFIXED(demo_p,player->spinheight);
-	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
-	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
+	WRITEFIXED(demo_p,player->camerascale);
+	WRITEFIXED(demo_p,player->shieldscale);
 
 	// Trying to convert it back to % causes demo desync due to precision loss.
 	// Don't do it.
@@ -1590,6 +1617,183 @@ void G_BeginMetal(void)
 	oldmetal.angle = mo->angle>>24;
 }
 
+static void G_LoadDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version)
+{
+	UINT16 totalfiles;
+	char filename[MAX_WADPATH];
+	UINT8 md5sum[16];
+	filestatus_t ncs;
+	boolean toomany = false;
+	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)
+	{
+		if (toomany)
+			SKIPSTRING((*pp));
+		else
+		{
+			strlcpy(filename, (char *)(*pp), sizeof filename);
+			SKIPSTRING((*pp));
+		}
+		READMEM((*pp), md5sum, 16);
+
+		if (!toomany)
+		{
+			alreadyloaded = false;
+
+			for (j = 0; j < numwadfiles; ++j)
+			{
+				if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0)
+				{
+					alreadyloaded = true;
+					break;
+				}
+			}
+
+			if (alreadyloaded)
+				continue;
+
+			if (numwadfiles >= MAX_WADFILES)
+				toomany = true;
+			else
+				ncs = findfile(filename, md5sum, false);
+
+			if (toomany)
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("Too many files loaded to add anymore for demo playback\n"));
+				if (!CON_Ready())
+					M_StartMessage(M_GetText("There are too many files loaded to add this demo's addons.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING);
+			}
+			else if (ncs != FS_FOUND)
+			{
+				if (ncs == FS_NOTFOUND)
+					CONS_Alert(CONS_NOTICE, M_GetText("You do not have a copy of %s\n"), filename);
+				else if (ncs == FS_MD5SUMBAD)
+					CONS_Alert(CONS_NOTICE, M_GetText("Checksum mismatch on %s\n"), filename);
+				else
+					CONS_Alert(CONS_NOTICE, M_GetText("Unknown error finding file %s\n"), filename);
+
+				if (!CON_Ready())
+					M_StartMessage(M_GetText("There were errors trying to add this demo's addons. Check the console for more information.\n\nDemo playback may desync.\n\nPress ESC\n"), NULL, MM_NOTHING);
+			}
+			else
+			{
+				P_AddWadFile(filename);
+			}
+		}
+	}
+}
+
+static void G_SkipDemoExtraFiles(UINT8 **pp, UINT16 this_demo_version)
+{
+	UINT16 totalfiles;
+	UINT16 i;
+
+	if (this_demo_version < 0x0010)
+	{
+		// demo has no file list
+		return;
+	}
+
+	totalfiles = READUINT16((*pp));
+	for (i = 0; i < totalfiles; ++i)
+	{
+		SKIPSTRING((*pp));// file name
+		(*pp) += 16;// md5
+	}
+}
+
+// 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, UINT16 this_demo_version)
+{
+	UINT16 totalfiles, filesloaded, nmusfilecount;
+	char filename[MAX_WADPATH];
+	UINT8 md5sum[16];
+	boolean toomany = false;
+	boolean alreadyloaded;
+	UINT16 i, j;
+	UINT8 error = DFILE_ERROR_NONE;
+
+	if (this_demo_version < 0x0010)
+	{
+		// demo has no file list
+		return DFILE_ERROR_NONE;
+	}
+
+	totalfiles = READUINT16((*pp));
+	filesloaded = 0;
+	for (i = 0; i < totalfiles; ++i)
+	{
+		if (toomany)
+			SKIPSTRING((*pp));
+		else
+		{
+			strlcpy(filename, (char *)(*pp), sizeof filename);
+			SKIPSTRING((*pp));
+		}
+		READMEM((*pp), md5sum, 16);
+
+		if (!toomany)
+		{
+			alreadyloaded = false;
+			nmusfilecount = 0;
+
+			for (j = 0; j < numwadfiles; ++j)
+			{
+				if (wadfiles[j]->important && j > mainwads)
+					nmusfilecount++;
+				else
+					continue;
+
+				if (memcmp(md5sum, wadfiles[j]->md5sum, 16) == 0)
+				{
+					alreadyloaded = true;
+
+					if (i != nmusfilecount-1 && error < DFILE_ERROR_OUTOFORDER)
+						error |= DFILE_ERROR_OUTOFORDER;
+
+					break;
+				}
+			}
+
+			if (alreadyloaded)
+			{
+				filesloaded++;
+				continue;
+			}
+
+			if (numwadfiles >= MAX_WADFILES)
+				error = DFILE_ERROR_CANNOTLOAD;
+			else if (!quick && findfile(filename, md5sum, false) != FS_FOUND)
+				error = DFILE_ERROR_CANNOTLOAD;
+			else if (error < DFILE_ERROR_INCOMPLETEOUTOFORDER)
+				error |= DFILE_ERROR_NOTLOADED;
+		} else
+			error = DFILE_ERROR_CANNOTLOAD;
+	}
+
+	// Get final file count
+	nmusfilecount = 0;
+
+	for (j = 0; j < numwadfiles; ++j)
+		if (wadfiles[j]->important && j > mainwads)
+			nmusfilecount++;
+
+	if (!error && filesloaded < nmusfilecount)
+		error = DFILE_ERROR_EXTRAFILES;
+
+	return error;
+}
+
 void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings)
 {
 	if (!demorecording || !demotime_p)
@@ -1618,10 +1822,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
@@ -1637,15 +1840,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, newversion);
 	aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK);
 	I_Assert(aflags);
 	if (flags & DF_RECORDATTACK)
@@ -1687,7 +1890,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	switch(oldversion) // demoversion
 	{
 	case DEMOVERSION: // latest always supported
-	case 0x000e: // The previous demoversions also 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;
@@ -1710,6 +1914,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 		p += 2; // gamemap
 	p += 16; // mapmd5
 	flags = READUINT8(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);
@@ -1829,8 +2034,10 @@ void G_DoPlayDemo(char *defdemoname)
 	version = READUINT8(demo_p);
 	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
@@ -1871,6 +2078,69 @@ void G_DoPlayDemo(char *defdemoname)
 	demo_p += 16; // mapmd5
 
 	demoflags = READUINT8(demo_p);
+
+	if (titledemo)
+	{
+		// Titledemos should always play and ought to always be compatible with whatever wadlist is running.
+		G_SkipDemoExtraFiles(&demo_p, demoversion);
+	}
+	else if (demofileoverride == DFILE_OVERRIDE_LOAD)
+	{
+		G_LoadDemoExtraFiles(&demo_p, demoversion);
+	}
+	else if (demofileoverride == DFILE_OVERRIDE_SKIP)
+	{
+		G_SkipDemoExtraFiles(&demo_p, demoversion);
+	}
+	else
+	{
+		UINT8 error = G_CheckDemoExtraFiles(&demo_p, false, demoversion);
+
+		if (error)
+		{
+			switch (error)
+			{
+				case DFILE_ERROR_NOTLOADED:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are not loaded.\n\nUse\n\"playdemo %s -addfiles\"\nto load them and play the demo.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_OUTOFORDER:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are loaded out of order.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_INCOMPLETEOUTOFORDER:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo are not loaded, and some are out of order.\n\nUse\n\"playdemo %s -addfiles\"\nto load needed files and play the demo.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_CANNOTLOAD:
+					snprintf(msg, 1024,
+						M_GetText("Required files for this demo cannot be loaded.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+
+				case DFILE_ERROR_EXTRAFILES:
+					snprintf(msg, 1024,
+						M_GetText("You have additional files loaded beyond the demo's file list.\n\nUse\n\"playdemo %s -force\"\nto play the demo anyway.\n"),
+					pdemoname);
+					break;
+			}
+
+			CONS_Alert(CONS_ERROR, "%s", msg);
+			M_StartMessage(msg, NULL, MM_NOTHING);
+			Z_Free(pdemoname);
+			Z_Free(demobuffer);
+			demoplayback = false;
+			titledemo = false;
+			return;
+		}
+	}
+
 	modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT;
 	CON_ToggleOff();
 
@@ -1913,18 +2183,18 @@ void G_DoPlayDemo(char *defdemoname)
 
 	charability = READUINT8(demo_p);
 	charability2 = READUINT8(demo_p);
-	actionspd = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	mindash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	maxdash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	normalspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	runspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	actionspd = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	mindash = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	maxdash = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	normalspeed = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	runspeed = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	thrustfactor = READUINT8(demo_p);
 	accelstart = READUINT8(demo_p);
 	acceleration = READUINT8(demo_p);
 	height = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	spinheight = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
-	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	camerascale = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	shieldscale = (demoversion < 0x0010) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	jumpfactor = READFIXED(demo_p);
 	followitem = READUINT32(demo_p);
 
@@ -2026,6 +2296,88 @@ void G_DoPlayDemo(char *defdemoname)
 	demo_start = true;
 }
 
+//
+// Check if a replay can be loaded from the menu
+//
+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)
+		n--;
+	if (n != defdemoname)
+		n++;
+	pdemoname = ZZ_Alloc(strlen(n)+1);
+	strcpy(pdemoname,n);
+
+	// Internal if no extension, external if one exists
+	if (FIL_CheckExtension(defdemoname))
+	{
+		//FIL_DefaultExtension(defdemoname, ".lmp");
+		if (!FIL_ReadFile(defdemoname, &demobuffer))
+		{
+			return DFILE_ERROR_NOTDEMO;
+		}
+		demo_p = demobuffer;
+	}
+	// load demo resource from WAD
+	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	else // it's an internal demo
+	{
+		demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
+	}
+
+	// read demo header
+	if (memcmp(demo_p, DEMOHEADER, 12))
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 12; // DEMOHEADER
+
+	demo_p++; // version
+	demo_p++; // subversion
+	our_demo_version = READUINT16(demo_p);
+	switch(our_demo_version)
+	{
+	case 0x000d:
+	case 0x000e:
+	case 0x000f:
+	case DEMOVERSION: // latest always supported
+		break;
+#ifdef OLD22DEMOCOMPAT
+	case 0x000c:
+		break;
+#endif
+	// too old, cannot support.
+	default:
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 16; // demo checksum
+	if (memcmp(demo_p, "PLAY", 4))
+	{
+		return DFILE_ERROR_NOTDEMO;
+	}
+	demo_p += 4; // "PLAY"
+	demo_p += 2; // gamemap
+	demo_p += 16; // mapmd5
+
+	demo_p++; // demoflags
+
+	return G_CheckDemoExtraFiles(&demo_p, true, our_demo_version);
+}
+
 void G_AddGhost(char *defdemoname)
 {
 	INT32 i;
@@ -2085,6 +2437,7 @@ void G_AddGhost(char *defdemoname)
 	ghostversion = READUINT16(p);
 	switch(ghostversion)
 	{
+	case 0x000f:
 	case 0x000d:
 	case 0x000e:
 	case DEMOVERSION: // latest always supported
@@ -2130,6 +2483,9 @@ void G_AddGhost(char *defdemoname)
 		Z_Free(buffer);
 		return;
 	}
+
+	G_SkipDemoExtraFiles(&p, ghostversion); // Don't wanna modify the file list for ghosts.
+
 	switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
 	{
 	case ATTACKING_NONE: // 0
@@ -2161,17 +2517,12 @@ void G_AddGhost(char *defdemoname)
 	// Ghosts do not have a player structure to put this in.
 	p++; // charability
 	p++; // charability2
-	p++; // actionspd
-	p++; // mindash
-	p++; // maxdash
-	p++; // normalspeed
-	p++; // runspeed
+	p += (ghostversion < 0x0010) ? 5 : 5 * sizeof(fixed_t); // actionspd, mindash, maxdash, normalspeed, and runspeed
 	p++; // thrustfactor
 	p++; // accelstart
 	p++; // acceleration
 	p += (ghostversion < 0x000e) ? 2 : 2 * sizeof(fixed_t); // height and spinheight
-	p++; // camerascale
-	p++; // shieldscale
+	p += (ghostversion < 0x0010) ? 2 : 2 * sizeof(fixed_t); // camerascale and shieldscale
 	p += 4; // jumpfactor
 	p += 4; // followitem
 
@@ -2347,6 +2698,7 @@ void G_DoPlayMetal(void)
 	switch(metalversion)
 	{
 	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:
diff --git a/src/g_demo.h b/src/g_demo.h
index f25315a58c9b6040f38baca3d7f50abc9050a69e..379c57428a6db9daeef1b5d6ab6368436224c828 100644
--- a/src/g_demo.h
+++ b/src/g_demo.h
@@ -26,9 +26,19 @@
 extern boolean demoplayback, titledemo, demorecording, timingdemo;
 extern tic_t demostarttime;
 
+typedef enum
+{
+	DFILE_OVERRIDE_NONE = 0, // Show errors normally
+	DFILE_OVERRIDE_LOAD, // Forcefully load demo, add files beforehand
+	DFILE_OVERRIDE_SKIP, // Forcefully load demo, skip file list
+} demo_file_override_e;
+
+extern demo_file_override_e demofileoverride;
+
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
 extern boolean demo_start;
+extern boolean demo_forwardmove_rng;
 extern boolean demosynced;
 
 extern mobj_t *metalplayback;
@@ -53,6 +63,18 @@ typedef enum
 	GHC_RETURNSKIN // ditto
 } ghostcolor_t;
 
+// G_CheckDemoExtraFiles: checks if our loaded WAD list matches the demo's.
+typedef enum
+{
+	DFILE_ERROR_NONE = 0, // No file error
+	DFILE_ERROR_NOTLOADED, // Files are not loaded, but can be without a restart.
+	DFILE_ERROR_OUTOFORDER, // Files are loaded, but out of order.
+	DFILE_ERROR_INCOMPLETEOUTOFORDER, // Some files are loaded out of order, but others are not.
+	DFILE_ERROR_CANNOTLOAD, // Files are missing and cannot be loaded.
+	DFILE_ERROR_EXTRAFILES, // Extra files outside of the replay's file list are loaded.
+	DFILE_ERROR_NOTDEMO = UINT8_MAX, // This replay isn't even a replay...
+} demo_file_error_e;
+
 // Record/playback tics
 void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
 void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
@@ -83,5 +105,6 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
 INT32 G_ConvertOldFrameFlags(INT32 frame);
+UINT8 G_CheckDemoForError(char *defdemoname);
 
 #endif // __G_DEMO__
diff --git a/src/g_game.c b/src/g_game.c
index 8c497a72d087dc4e3dc96bd46d377b0f1d482056..619ed8c89d722280375a30fe3059c58785ae61b2 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -96,6 +96,7 @@ SINT8 startinglivesbalance[maxgameovers+1] = {3, 5, 7, 9, 12, 15, 20, 25, 30, 40
 UINT16 mainwads = 0;
 boolean modifiedgame; // Set if homebrew PWAD stuff has been added.
 boolean savemoddata = false;
+boolean usedCheats = false; // Set when a gamedata-preventing cheat command is used.
 UINT8 paused;
 UINT8 modeattacking = ATTACKING_NONE;
 boolean disableSpeedAdjust = false;
@@ -155,6 +156,7 @@ textprompt_t *textprompts[MAX_PROMPTS];
 
 INT16 nextmapoverride;
 UINT8 skipstats;
+INT16 nextgametype = -1;
 
 // Pointers to each CTF flag
 mobj_t *redflag;
@@ -184,18 +186,6 @@ INT32 tokenbits; // Used for setting token bits
 // Old Special Stage
 INT32 sstimer; // Time allotted in the special stage
 
-tic_t totalplaytime;
-boolean gamedataloaded = false;
-
-// Time attack data for levels
-// These are dynamically allocated for space reasons now
-recorddata_t *mainrecords[NUMMAPS]   = {NULL};
-nightsdata_t *nightsrecords[NUMMAPS] = {NULL};
-UINT8 mapvisited[NUMMAPS];
-
-// Temporary holding place for nights data for the current map
-nightsdata_t ntemprecords;
-
 UINT32 bluescore, redscore; // CTF and Team Match team scores
 
 // ring count... for PERFECT!
@@ -226,6 +216,7 @@ UINT8 ammoremovaltics = 2*TICRATE;
 UINT8 use1upSound = 0;
 UINT8 maxXtraLife = 2; // Max extra lives from rings
 UINT8 useContinues = 0; // Set to 1 to enable continues outside of no-save scenarioes
+UINT8 shareEmblems = 0; // Set to 1 to share all picked up emblems in multiplayer
 
 UINT8 introtoplay;
 UINT8 creditscutscene;
@@ -251,11 +242,6 @@ INT32 cheats; //for multiplayer cheat commands
 
 tic_t hidetime;
 
-// Grading
-UINT32 timesBeaten;
-UINT32 timesBeatenWithEmeralds;
-UINT32 timesBeatenUltimate;
-
 typedef struct joystickvector2_s
 {
 	INT32 xaxis;
@@ -330,6 +316,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);
@@ -411,8 +399,8 @@ consvar_t cv_cam_lockonboss[2] = {
 
 consvar_t cv_moveaxis = CVAR_INIT ("joyaxis_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_sideaxis = CVAR_INIT ("joyaxis_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_lookaxis = CVAR_INIT ("joyaxis_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_turnaxis = CVAR_INIT ("joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_lookaxis = CVAR_INIT ("joyaxis_look", "X-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_turnaxis = CVAR_INIT ("joyaxis_turn", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_jumpaxis = CVAR_INIT ("joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_spinaxis = CVAR_INIT ("joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_fireaxis = CVAR_INIT ("joyaxis_fire", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
@@ -422,8 +410,8 @@ consvar_t cv_digitaldeadzone = CVAR_INIT ("joy_digdeadzone", "0.25", CV_FLOAT|CV
 
 consvar_t cv_moveaxis2 = CVAR_INIT ("joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_sideaxis2 = CVAR_INIT ("joyaxis2_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_lookaxis2 = CVAR_INIT ("joyaxis2_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_turnaxis2 = CVAR_INIT ("joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_lookaxis2 = CVAR_INIT ("joyaxis2_look", "X-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_turnaxis2 = CVAR_INIT ("joyaxis2_turn", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_jumpaxis2 = CVAR_INIT ("joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_spinaxis2 = CVAR_INIT ("joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_fireaxis2 = CVAR_INIT ("joyaxis2_fire", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
@@ -451,295 +439,323 @@ INT16 rw_maximums[NUM_WEAPONS] =
 };
 
 // Allocation for time and nights data
-void G_AllocMainRecordData(INT16 i)
+void G_AllocMainRecordData(INT16 i, gamedata_t *data)
 {
-	if (!mainrecords[i])
-		mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL);
-	memset(mainrecords[i], 0, sizeof(recorddata_t));
+	if (!data->mainrecords[i])
+		data->mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL);
+	memset(data->mainrecords[i], 0, sizeof(recorddata_t));
 }
 
-void G_AllocNightsRecordData(INT16 i)
+void G_AllocNightsRecordData(INT16 i, gamedata_t *data)
 {
-	if (!nightsrecords[i])
-		nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL);
-	memset(nightsrecords[i], 0, sizeof(nightsdata_t));
+	if (!data->nightsrecords[i])
+		data->nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL);
+	memset(data->nightsrecords[i], 0, sizeof(nightsdata_t));
 }
 
 // MAKE SURE YOU SAVE DATA BEFORE CALLING THIS
-void G_ClearRecords(void)
+void G_ClearRecords(gamedata_t *data)
 {
 	INT16 i;
 	for (i = 0; i < NUMMAPS; ++i)
 	{
-		if (mainrecords[i])
+		if (data->mainrecords[i])
 		{
-			Z_Free(mainrecords[i]);
-			mainrecords[i] = NULL;
+			Z_Free(data->mainrecords[i]);
+			data->mainrecords[i] = NULL;
 		}
-		if (nightsrecords[i])
+		if (data->nightsrecords[i])
 		{
-			Z_Free(nightsrecords[i]);
-			nightsrecords[i] = NULL;
+			Z_Free(data->nightsrecords[i]);
+			data->nightsrecords[i] = NULL;
 		}
 	}
 }
 
 // For easy retrieval of records
-UINT32 G_GetBestScore(INT16 map)
+UINT32 G_GetBestScore(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1])
+	if (!data->mainrecords[map-1])
 		return 0;
 
-	return mainrecords[map-1]->score;
+	return data->mainrecords[map-1]->score;
 }
 
-tic_t G_GetBestTime(INT16 map)
+tic_t G_GetBestTime(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1] || mainrecords[map-1]->time <= 0)
+	if (!data->mainrecords[map-1] || data->mainrecords[map-1]->time <= 0)
 		return (tic_t)UINT32_MAX;
 
-	return mainrecords[map-1]->time;
+	return data->mainrecords[map-1]->time;
 }
 
-UINT16 G_GetBestRings(INT16 map)
+UINT16 G_GetBestRings(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1])
+	if (!data->mainrecords[map-1])
 		return 0;
 
-	return mainrecords[map-1]->rings;
+	return data->mainrecords[map-1]->rings;
 }
 
-UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare)
+UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1])
+	if (!data->nightsrecords[map-1])
 		return 0;
 
-	return nightsrecords[map-1]->score[mare];
+	return data->nightsrecords[map-1]->score[mare];
 }
 
-tic_t G_GetBestNightsTime(INT16 map, UINT8 mare)
+tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1] || nightsrecords[map-1]->time[mare] <= 0)
+	if (!data->nightsrecords[map-1] || data->nightsrecords[map-1]->time[mare] <= 0)
 		return (tic_t)UINT32_MAX;
 
-	return nightsrecords[map-1]->time[mare];
+	return data->nightsrecords[map-1]->time[mare];
 }
 
-UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare)
+UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1])
+	if (!data->nightsrecords[map-1])
 		return 0;
 
-	return nightsrecords[map-1]->grade[mare];
+	return data->nightsrecords[map-1]->grade[mare];
 }
 
 // For easy adding of NiGHTS records
-void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare)
+void G_AddTempNightsRecords(player_t *player, UINT32 pscore, tic_t ptime, UINT8 mare)
 {
-	ntemprecords.score[mare] = pscore;
-	ntemprecords.grade[mare] = P_GetGrade(pscore, gamemap, mare - 1);
-	ntemprecords.time[mare] = ptime;
+	const UINT8 playerID = player - players;
+
+	I_Assert(player != NULL);
+
+	ntemprecords[playerID].score[mare] = pscore;
+	ntemprecords[playerID].grade[mare] = P_GetGrade(pscore, gamemap, mare - 1);
+	ntemprecords[playerID].time[mare] = ptime;
 
 	// Update nummares
 	// Note that mare "0" is overall, mare "1" is the first real mare
-	if (ntemprecords.nummares < mare)
-		ntemprecords.nummares = mare;
+	if (ntemprecords[playerID].nummares < mare)
+		ntemprecords[playerID].nummares = mare;
 }
 
 //
-// G_UpdateRecordReplays
+// G_SetMainRecords
 //
 // Update replay files/data, etc. for Record Attack
 // See G_SetNightsRecords for NiGHTS Attack.
 //
-static void G_UpdateRecordReplays(void)
+static void G_SetMainRecords(gamedata_t *data, player_t *player)
 {
-	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
-	char *gpath;
-	char lastdemo[256], bestdemo[256];
 	UINT8 earnedEmblems;
 
+	I_Assert(player != NULL);
+
 	// Record new best time
-	if (!mainrecords[gamemap-1])
-		G_AllocMainRecordData(gamemap-1);
+	if (!data->mainrecords[gamemap-1])
+		G_AllocMainRecordData(gamemap-1, data);
 
-	if (players[consoleplayer].score > mainrecords[gamemap-1]->score)
-		mainrecords[gamemap-1]->score = players[consoleplayer].score;
+	if (player->recordscore > data->mainrecords[gamemap-1]->score)
+		data->mainrecords[gamemap-1]->score = player->recordscore;
 
-	if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time))
-		mainrecords[gamemap-1]->time = players[consoleplayer].realtime;
+	if ((data->mainrecords[gamemap-1]->time == 0) || (player->realtime < data->mainrecords[gamemap-1]->time))
+		data->mainrecords[gamemap-1]->time = player->realtime;
 
-	if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings)
-		mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
+	if ((UINT16)(player->rings) > data->mainrecords[gamemap-1]->rings)
+		data->mainrecords[gamemap-1]->rings = (UINT16)(player->rings);
 
-	// Save demo!
-	bestdemo[255] = '\0';
-	lastdemo[255] = '\0';
-	G_SetDemoTime(players[consoleplayer].realtime, players[consoleplayer].score, (UINT16)(players[consoleplayer].rings));
-	G_CheckDemoStatus();
+	if (modeattacking)
+	{
+		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+		char *gpath;
+		char lastdemo[256], bestdemo[256];
 
-	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
-	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
+		// Save demo!
+		bestdemo[255] = '\0';
+		lastdemo[255] = '\0';
+		G_SetDemoTime(player->realtime, player->recordscore, (UINT16)(player->rings));
+		G_CheckDemoStatus();
 
-	if ((gpath = malloc(glen)) == NULL)
-		I_Error("Out of memory for replay filepath\n");
+		I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
+		I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
 
-	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
-	snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
+		if ((gpath = malloc(glen)) == NULL)
+			I_Error("Out of memory for replay filepath\n");
 
-	if (FIL_FileExists(lastdemo))
-	{
-		UINT8 *buf;
-		size_t len = FIL_ReadFile(lastdemo, &buf);
+		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+		snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
 
-		snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
-		{ // Better time, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
-		}
+		if (FIL_FileExists(lastdemo))
+		{
+			UINT8 *buf;
+			size_t len = FIL_ReadFile(lastdemo, &buf);
+
+			snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+			if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
+			{ // Better time, save this demo.
+				if (FIL_FileExists(bestdemo))
+					remove(bestdemo);
+				FIL_WriteFile(bestdemo, buf, len);
+				CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
+			}
 
-		snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
-		{ // Better score, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
-		}
+			snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+			if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
+			{ // Better score, save this demo.
+				if (FIL_FileExists(bestdemo))
+					remove(bestdemo);
+				FIL_WriteFile(bestdemo, buf, len);
+				CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
+			}
 
-		snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<2)))
-		{ // Better rings, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW MOST RINGS!"), M_GetText("Saved replay as"), bestdemo);
-		}
+			snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+			if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<2)))
+			{ // Better rings, save this demo.
+				if (FIL_FileExists(bestdemo))
+					remove(bestdemo);
+				FIL_WriteFile(bestdemo, buf, len);
+				CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW MOST RINGS!"), M_GetText("Saved replay as"), bestdemo);
+			}
 
-		//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
+			//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
 
-		Z_Free(buf);
+			Z_Free(buf);
+		}
+		free(gpath);
 	}
-	free(gpath);
 
 	// Check emblems when level data is updated
-	if ((earnedEmblems = M_CheckLevelEmblems()))
+	if ((earnedEmblems = M_CheckLevelEmblems(data)))
+	{
 		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+	}
 
 	// Update timeattack menu's replay availability.
 	Nextmap_OnChange();
 }
 
-void G_SetNightsRecords(void)
+static void G_SetNightsRecords(gamedata_t *data, player_t *player)
 {
-	INT32 i;
+	nightsdata_t *const ntemprecord = &ntemprecords[player - players];
 	UINT32 totalscore = 0;
 	tic_t totaltime = 0;
-	UINT8 earnedEmblems;
+	INT32 i;
 
-	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
-	char *gpath;
-	char lastdemo[256], bestdemo[256];
+	UINT8 earnedEmblems;
 
-	if (!ntemprecords.nummares)
+	if (!ntemprecord->nummares)
+	{
 		return;
+	}
 
 	// Set overall
 	{
 		UINT8 totalrank = 0, realrank = 0;
 
-		for (i = 1; i <= ntemprecords.nummares; ++i)
+		for (i = 1; i <= ntemprecord->nummares; ++i)
 		{
-			totalscore += ntemprecords.score[i];
-			totalrank += ntemprecords.grade[i];
-			totaltime += ntemprecords.time[i];
+			totalscore += ntemprecord->score[i];
+			totalrank += ntemprecord->grade[i];
+			totaltime += ntemprecord->time[i];
 		}
 
 		// Determine overall grade
-		realrank = (UINT8)((FixedDiv((fixed_t)totalrank << FRACBITS, ntemprecords.nummares << FRACBITS) + (FRACUNIT/2)) >> FRACBITS);
+		realrank = (UINT8)((FixedDiv((fixed_t)totalrank << FRACBITS, ntemprecord->nummares << FRACBITS) + (FRACUNIT/2)) >> FRACBITS);
 
 		// You need ALL rainbow As to get a rainbow A overall
-		if (realrank == GRADE_S && (totalrank / ntemprecords.nummares) != GRADE_S)
+		if (realrank == GRADE_S && (totalrank / ntemprecord->nummares) != GRADE_S)
+		{
 			realrank = GRADE_A;
+		}
 
-		ntemprecords.score[0] = totalscore;
-		ntemprecords.grade[0] = realrank;
-		ntemprecords.time[0] = totaltime;
+		ntemprecord->score[0] = totalscore;
+		ntemprecord->grade[0] = realrank;
+		ntemprecord->time[0] = totaltime;
 	}
 
 	// Now take all temp records and put them in the actual records
 	{
 		nightsdata_t *maprecords;
 
-		if (!nightsrecords[gamemap-1])
-			G_AllocNightsRecordData(gamemap-1);
-		maprecords = nightsrecords[gamemap-1];
+		if (!data->nightsrecords[gamemap-1])
+		{
+			G_AllocNightsRecordData(gamemap-1, data);
+		}
 
-		if (maprecords->nummares != ntemprecords.nummares)
-			maprecords->nummares = ntemprecords.nummares;
+		maprecords = data->nightsrecords[gamemap-1];
+
+		if (maprecords->nummares != ntemprecord->nummares)
+		{
+			maprecords->nummares = ntemprecord->nummares;
+		}
 
-		for (i = 0; i < ntemprecords.nummares + 1; ++i)
+		for (i = 0; i < ntemprecord->nummares + 1; ++i)
 		{
-			if (maprecords->score[i] < ntemprecords.score[i])
-				maprecords->score[i] = ntemprecords.score[i];
-			if (maprecords->grade[i] < ntemprecords.grade[i])
-				maprecords->grade[i] = ntemprecords.grade[i];
-			if (!maprecords->time[i] || maprecords->time[i] > ntemprecords.time[i])
-				maprecords->time[i] = ntemprecords.time[i];
+			if (maprecords->score[i] < ntemprecord->score[i])
+				maprecords->score[i] = ntemprecord->score[i];
+			if (maprecords->grade[i] < ntemprecord->grade[i])
+				maprecords->grade[i] = ntemprecord->grade[i];
+			if (!maprecords->time[i] || maprecords->time[i] > ntemprecord->time[i])
+				maprecords->time[i] = ntemprecord->time[i];
 		}
 	}
 
-	memset(&ntemprecords, 0, sizeof(nightsdata_t));
+	memset(&ntemprecords[player - players], 0, sizeof(nightsdata_t));
 
-	// Save demo!
-	bestdemo[255] = '\0';
-	lastdemo[255] = '\0';
-	G_SetDemoTime(totaltime, totalscore, 0);
-	G_CheckDemoStatus();
+	if (modeattacking)
+	{
+		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+		char *gpath;
+		char lastdemo[256], bestdemo[256];
 
-	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
-	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
+		// Save demo!
+		bestdemo[255] = '\0';
+		lastdemo[255] = '\0';
+		G_SetDemoTime(totaltime, totalscore, 0);
+		G_CheckDemoStatus();
 
-	if ((gpath = malloc(glen)) == NULL)
-		I_Error("Out of memory for replay filepath\n");
+		I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
+		I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
 
-	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
-	snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
+		if ((gpath = malloc(glen)) == NULL)
+			I_Error("Out of memory for replay filepath\n");
 
-	if (FIL_FileExists(lastdemo))
-	{
-		UINT8 *buf;
-		size_t len = FIL_ReadFile(lastdemo, &buf);
+		sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+		snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
 
-		snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);;
-		if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
-		{ // Better time, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
-		}
+		if (FIL_FileExists(lastdemo))
+		{
+			UINT8 *buf;
+			size_t len = FIL_ReadFile(lastdemo, &buf);
+
+			snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);;
+			if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
+			{ // Better time, save this demo.
+				if (FIL_FileExists(bestdemo))
+					remove(bestdemo);
+				FIL_WriteFile(bestdemo, buf, len);
+				CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
+			}
 
-		snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
-		{ // Better score, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
-		}
+			snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+			if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
+			{ // Better score, save this demo.
+				if (FIL_FileExists(bestdemo))
+					remove(bestdemo);
+				FIL_WriteFile(bestdemo, buf, len);
+				CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
+			}
 
-		//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
+			//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
 
-		Z_Free(buf);
+			Z_Free(buf);
+		}
+		free(gpath);
 	}
-	free(gpath);
 
-	if ((earnedEmblems = M_CheckLevelEmblems()))
+	if ((earnedEmblems = M_CheckLevelEmblems(data)))
+	{
 		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+	}
 
 	// If the mare count changed, this will update the score display
 	Nextmap_OnChange();
@@ -755,7 +771,24 @@ void G_SetGameModified(boolean silent)
 	savemoddata = false;
 
 	if (!silent)
-		CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to record statistics.\n"));
+		CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to play Record Attack.\n"));
+
+	// If in record attack recording, cancel it.
+	if (modeattacking)
+		M_EndModeAttackRun();
+	else if (marathonmode)
+		Command_ExitGame_f();
+}
+
+void G_SetUsedCheats(boolean silent)
+{
+	if (usedCheats)
+		return;
+
+	usedCheats = true;
+
+	if (!silent)
+		CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to save progress.\n"));
 
 	// If in record attack recording, cancel it.
 	if (modeattacking)
@@ -1383,7 +1416,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		if (
 			P_MobjWasRemoved(ticcmd_ztargetfocus[forplayer]) ||
 			!ticcmd_ztargetfocus[forplayer]->health ||
-			(ticcmd_ztargetfocus[forplayer]->flags2 & MF2_FRET) ||
 			(ticcmd_ztargetfocus[forplayer]->type == MT_EGGMOBILE3 && !ticcmd_ztargetfocus[forplayer]->movecount) // Sea Egg is moving around underground and shouldn't be tracked
 		)
 			P_SetTarget(&ticcmd_ztargetfocus[forplayer], NULL);
@@ -1412,6 +1444,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 			// I assume this is netgame-safe because gunslinger spawns this for only the local player...... *sweats intensely*
 			newtarget = P_SpawnMobj(ticcmd_ztargetfocus[forplayer]->x, ticcmd_ztargetfocus[forplayer]->y, ticcmd_ztargetfocus[forplayer]->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 			P_SetTarget(&newtarget->target, ticcmd_ztargetfocus[forplayer]);
+			newtarget->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 
 			if (player->mo && P_AproxDistance(
 				player->mo->x - ticcmd_ztargetfocus[forplayer]->x,
@@ -1914,6 +1947,7 @@ void G_PreLevelTitleCard(void)
 		ST_runTitleCard();
 		ST_preLevelTitleCardDrawer();
 		I_FinishUpdate(); // page flip or blit buffer
+		NetKeepAlive(); // Prevent timeouts
 
 		if (moviemode)
 			M_SaveFrame();
@@ -2048,7 +2082,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;
@@ -2106,7 +2140,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;
 		}
 	}
@@ -2141,9 +2175,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;
@@ -2286,7 +2320,7 @@ void G_Ticker(boolean run)
 					p->lives = startinglivesbalance[0];
 					p->continues = 1;
 
-					p->score = 0;
+					p->score = p->recordscore = 0;
 
 					// The latter two should clear by themselves, but just in case
 					p->pflags &= ~(PF_TAGIT|PF_GAMETYPEOVER|PF_FULLSTASIS);
@@ -2390,7 +2424,8 @@ void G_Ticker(boolean run)
 			break;
 
 		case GS_TIMEATTACK:
-			F_MenuPresTicker(run);
+			if (run)
+				F_MenuPresTicker();
 			break;
 
 		case GS_INTRO:
@@ -2435,13 +2470,17 @@ void G_Ticker(boolean run)
 		case GS_TITLESCREEN:
 			if (titlemapinaction)
 				P_Ticker(run);
-				// then intentionally fall through
-			/* FALLTHRU */
-		case GS_WAITINGPLAYERS:
-			F_MenuPresTicker(run);
+			if (run)
+				F_MenuPresTicker();
 			F_TitleScreenTicker(run);
 			break;
 
+		case GS_WAITINGPLAYERS:
+			if (netgame)
+				F_WaitingPlayersTicker();
+			HU_Ticker();
+			break;
+
 		case GS_DEDICATEDSERVER:
 		case GS_NULL:
 			break; // do nothing
@@ -2825,6 +2864,9 @@ void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
 
 	R_ResetMobjInterpolationState(players[playernum].mo);
 
+	if (players[playernum].bot) // don't reset the camera for bots
+		return;
+
 	if (playernum == consoleplayer)
 		P_ResetCamera(&players[playernum], &camera);
 	else if (playernum == secondarydisplayplayer)
@@ -3149,6 +3191,9 @@ void G_DoReborn(INT32 playernum)
 
 	if (resetlevel)
 	{
+		// Don't give completion emblems for reloading the level...
+		stagefailed = true;
+
 		// reload the level from scratch
 		if (countdowntimeup)
 		{
@@ -3156,6 +3201,7 @@ void G_DoReborn(INT32 playernum)
 			{
 				if (!playeringame[i])
 					continue;
+				players[i].recordscore = 0;
 				players[i].starpostscale = 0;
 				players[i].starpostangle = 0;
 				players[i].starposttime = 0;
@@ -3418,9 +3464,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)
 {
@@ -3818,7 +3862,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)) // Don't pick locked maps.
+		 && (!M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps.
 		)
 			okmaps[numokmaps++] = ix;
 
@@ -3835,42 +3879,80 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 //
 // G_UpdateVisited
 //
-static void G_UpdateVisited(void)
+static void G_UpdateVisited(gamedata_t *data, player_t *player, boolean silent)
 {
-	boolean spec = G_IsSpecialStage(gamemap);
 	// Update visitation flags?
-	if ((!modifiedgame || savemoddata) // Not modified
-		&& !multiplayer && !demoplayback && (gametype == GT_COOP) // SP/RA/NiGHTS mode
+	if (!demoplayback
+		&& G_CoopGametype() // Campaign mode
 		&& !stagefailed) // Did not fail the stage
 	{
 		UINT8 earnedEmblems;
+		UINT16 totalrings = 0;
+		INT32 i;
 
 		// Update visitation flags
-		mapvisited[gamemap-1] |= MV_BEATEN;
+		data->mapvisited[gamemap-1] |= MV_BEATEN;
+
 		// eh, what the hell
 		if (ultimatemode)
-			mapvisited[gamemap-1] |= MV_ULTIMATE;
+			data->mapvisited[gamemap-1] |= MV_ULTIMATE;
+
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i])
+			{
+				continue;
+			}
+
+			totalrings += players[i].rings;
+		}
+
 		// may seem incorrect but IS possible in what the main game uses as mp special stages, and nummaprings will be -1 in NiGHTS
-		if (nummaprings > 0 && players[consoleplayer].rings >= nummaprings)
+		if (nummaprings > 0 && totalrings >= nummaprings)
 		{
-			mapvisited[gamemap-1] |= MV_PERFECT;
+			data->mapvisited[gamemap-1] |= MV_PERFECT;
 			if (modeattacking)
-				mapvisited[gamemap-1] |= MV_PERFECTRA;
+				data->mapvisited[gamemap-1] |= MV_PERFECTRA;
 		}
-		if (!spec)
+
+		if (!G_IsSpecialStage(gamemap))
 		{
 			// not available to special stages because they can only really be done in one order in an unmodified game, so impossible for first six and trivial for seventh
 			if (ALL7EMERALDS(emeralds))
-				mapvisited[gamemap-1] |= MV_ALLEMERALDS;
+				data->mapvisited[gamemap-1] |= MV_ALLEMERALDS;
 		}
 
-		if (modeattacking == ATTACKING_RECORD)
-			G_UpdateRecordReplays();
-		else if (modeattacking == ATTACKING_NIGHTS)
-			G_SetNightsRecords();
-
-		if ((earnedEmblems = M_CompletionEmblems()))
+		if ((earnedEmblems = M_CompletionEmblems(data)) && !silent)
+		{
 			CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+		}
+
+		if (silent)
+		{
+			M_CheckLevelEmblems(data);
+		}
+		else
+		{
+			if (mapheaderinfo[gamemap-1]->menuflags & LF2_RECORDATTACK)
+				G_SetMainRecords(data, player);
+			else if (mapheaderinfo[gamemap-1]->menuflags & LF2_NIGHTSATTACK)
+				G_SetNightsRecords(data, player);
+		}
+	}
+}
+
+static void G_UpdateAllVisited(void)
+{
+	// Update server
+	G_UpdateVisited(serverGamedata, &players[serverplayer], true);
+
+	// Update client
+	G_UpdateVisited(clientGamedata, &players[consoleplayer], false);
+
+	if (splitscreen)
+	{
+		// Allow P2 to get emblems too, why not :)
+		G_UpdateVisited(clientGamedata, &players[secondarydisplayplayer], false);
 	}
 }
 
@@ -3894,6 +3976,9 @@ static boolean CanSaveLevel(INT32 mapnum)
 
 static void G_HandleSaveLevel(void)
 {
+	// Update records & emblems
+	G_UpdateAllVisited();
+
 	// do this before running the intermission or custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
 	if (nextmap >= 1100-1)
 	{
@@ -3908,14 +3993,18 @@ static void G_HandleSaveLevel(void)
 					remove(liveeventbackup);
 				cursaveslot = 0;
 			}
-			else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
+			else if (!usedCheats && !(netgame || multiplayer || ultimatemode || demorecording || metalrecording || modeattacking))
+			{
 				G_SaveGame((UINT32)cursaveslot, spstage_start);
+			}
 		}
 	}
 	// and doing THIS here means you don't lose your progress if you close the game mid-intermission
 	else if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
-		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(lastmap+1))
+		&& !usedCheats && cursaveslot > 0 && CanSaveLevel(lastmap+1))
+	{
 		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
+	}
 }
 
 //
@@ -3965,6 +4054,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,
@@ -3973,8 +4069,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));
@@ -4029,7 +4125,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<<i)))
 				{
@@ -4054,7 +4150,7 @@ static void G_DoCompleted(void)
 		if (cv_advancemap.value == 0) // Stay on same map.
 			nextmap = prevmap;
 		else if (cv_advancemap.value == 2) // Go to random map.
-			nextmap = RandMap(G_TOLFlag(gametype), prevmap);
+			nextmap = RandMap(G_TOLFlag(gametype_to_use), prevmap);
 	}
 
 	// We are committed to this map now.
@@ -4063,12 +4159,10 @@ static void G_DoCompleted(void)
 	if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
 		P_AllocMapHeader(nextmap);
 
-	// If the current gametype has no intermission screen set, then don't start it.
 	Y_DetermineIntermissionType();
 
 	if ((skipstats && !modeattacking) || (modeattacking && stagefailed) || (intertype == int_none))
 	{
-		G_UpdateVisited();
 		G_HandleSaveLevel();
 		G_AfterIntermission();
 	}
@@ -4077,7 +4171,6 @@ static void G_DoCompleted(void)
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
 		Y_LoadIntermissionData();
-		G_UpdateVisited();
 		G_HandleSaveLevel();
 	}
 }
@@ -4105,7 +4198,7 @@ void G_AfterIntermission(void)
 		&& stagefailed == false)
 	{
 		// Start a custom cutscene.
-		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
+		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false, false);
 	}
 	else
 	{
@@ -4131,12 +4224,21 @@ static void G_DoWorldDone(void)
 {
 	if (server)
 	{
+		INT16 gametype_to_use;
+
+		if (nextgametype >= 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;
@@ -4179,7 +4281,7 @@ static void G_DoContinued(void)
 {
 	player_t *pl = &players[consoleplayer];
 	I_Assert(!netgame && !multiplayer);
-	I_Assert(pl->continues > 0);
+	//I_Assert(pl->continues > 0);
 
 	if (pl->continues)
 		pl->continues--;
@@ -4191,8 +4293,10 @@ static void G_DoContinued(void)
 	tokenlist = 0;
 	token = 0;
 
-	if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && (!modifiedgame || savemoddata) && cursaveslot > 0)
+	if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0)
+	{
 		G_SaveGameOver((UINT32)cursaveslot, true);
+	}
 
 	// Reset # of lives
 	pl->lives = (ultimatemode) ? 1 : startinglivesbalance[numgameovers];
@@ -4257,13 +4361,17 @@ void G_LoadGameSettings(void)
 	S_InitRuntimeSounds();
 }
 
+#define GAMEDATA_ID 0x86E4A27C // Change every major version, as usual
+#define COMPAT_GAMEDATA_ID 0xFCAFE211 // Can be removed entirely for 2.3
+
 // G_LoadGameData
 // Loads the main data file, which stores information such as emblems found, etc.
-void G_LoadGameData(void)
+void G_LoadGameData(gamedata_t *data)
 {
 	size_t length;
 	INT32 i, j;
-	UINT8 modded = false;
+
+	UINT32 versionID;
 	UINT8 rtemp;
 
 	//For records
@@ -4274,34 +4382,51 @@ void G_LoadGameData(void)
 	UINT8 recmares;
 	INT32 curmare;
 
+	// Stop saving, until we successfully load it again.
+	data->loaded = false;
+
+	// Backwards compat stuff
+	INT32 max_emblems = MAXEMBLEMS;
+	INT32 max_extraemblems = MAXEXTRAEMBLEMS;
+	INT32 max_unlockables = MAXUNLOCKABLES;
+	INT32 max_conditionsets = MAXCONDITIONSETS;
+
 	// Clear things so previously read gamedata doesn't transfer
 	// to new gamedata
-	G_ClearRecords(); // main and nights records
-	M_ClearSecrets(); // emblems, unlocks, maps visited, etc
-	totalplaytime = 0; // total play time (separate from all)
+	G_ClearRecords(data); // main and nights records
+	M_ClearSecrets(data); // emblems, unlocks, maps visited, etc
+	data->totalplaytime = 0; // total play time (separate from all)
 
 	if (M_CheckParm("-nodata"))
-		return; // Don't load.
-
-	// Allow saving of gamedata beyond this point
-	gamedataloaded = true;
-
-	if (M_CheckParm("-gamedata") && M_IsNextParm())
 	{
-		strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
+		// Don't load at all.
+		return;
 	}
 
 	if (M_CheckParm("-resetdata"))
-		return; // Don't load (essentially, reset).
+	{
+		// Don't load, but do save. (essentially, reset)
+		data->loaded = true;
+		return;
+	}
 
 	length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &savebuffer);
-	if (!length) // Aw, no game data. Their loss!
+	if (!length)
+	{
+		// No gamedata. We can save a new one.
+		data->loaded = true;
 		return;
+	}
 
 	save_p = savebuffer;
 
 	// Version check
-	if (READUINT32(save_p) != 0xFCAFE211)
+	versionID = READUINT32(save_p);
+	if (versionID != GAMEDATA_ID
+#ifdef COMPAT_GAMEDATA_ID // backwards compat behavior
+		&& versionID != COMPAT_GAMEDATA_ID
+#endif
+		)
 	{
 		const char *gdfolder = "the SRB2 folder";
 		if (strcmp(srb2home,"."))
@@ -4312,54 +4437,95 @@ void G_LoadGameData(void)
 		I_Error("Game data is from another version of SRB2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder);
 	}
 
-	totalplaytime = READUINT32(save_p);
+#ifdef COMPAT_GAMEDATA_ID // Account for lower MAXUNLOCKABLES and MAXEXTRAEMBLEMS from older versions
+	if (versionID == COMPAT_GAMEDATA_ID)
+	{
+		max_extraemblems = 16;
+		max_unlockables = 32;
+	}
+#endif
+
+	data->totalplaytime = READUINT32(save_p);
+
+#ifdef COMPAT_GAMEDATA_ID
+	if (versionID == COMPAT_GAMEDATA_ID)
+	{
+		// We'll temporarily use the old condition when loading an older file.
+		// The proper mod-specific hash will get saved in afterwards.
+		boolean modded = READUINT8(save_p);
+
+		if (modded && !savemoddata)
+		{
+			goto datacorrupt;
+		}
+		else if (modded != true && modded != false)
+		{
+			goto datacorrupt;
+		}
+
+		// make a backup of the old data
+		char currentfilename[64];
+		char backupfilename[69];
+		char bak[5];
 
-	modded = READUINT8(save_p);
+		strcpy(bak, ".bak");
+		strcpy(currentfilename, gamedatafilename);
+		STRBUFCPY(backupfilename, strcat(currentfilename, bak));
 
-	// Aha! Someone's been screwing with the save file!
-	if ((modded && !savemoddata))
-		goto datacorrupt;
-	else if (modded != true && modded != false)
-		goto datacorrupt;
+		FIL_WriteFile(va(pandf, srb2home, backupfilename), savebuffer, length);
+	}
+	else
+#endif
+	{
+		// Quick & dirty hash for what mod this save file is for.
+		UINT32 modID = READUINT32(save_p);
+		UINT32 expectedID = quickncasehash(timeattackfolder, sizeof timeattackfolder);
+
+		if (modID != expectedID)
+		{
+			// Aha! Someone's been screwing with the save file!
+			goto datacorrupt;
+		}
+	}
 
 	// TODO put another cipher on these things? meh, I don't care...
 	for (i = 0; i < NUMMAPS; i++)
-		if ((mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
 			goto datacorrupt;
 
 	// To save space, use one bit per collected/achieved/unlocked flag
-	for (i = 0; i < MAXEMBLEMS;)
+	for (i = 0; i < max_emblems;)
 	{
 		rtemp = READUINT8(save_p);
-		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
-			emblemlocations[j+i].collected = ((rtemp >> j) & 1);
+		for (j = 0; j < 8 && j+i < max_emblems; ++j)
+			data->collected[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
-	for (i = 0; i < MAXEXTRAEMBLEMS;)
+	for (i = 0; i < max_extraemblems;)
 	{
 		rtemp = READUINT8(save_p);
-		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
-			extraemblems[j+i].collected = ((rtemp >> j) & 1);
+		for (j = 0; j < 8 && j+i < max_extraemblems; ++j)
+			data->extraCollected[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
-	for (i = 0; i < MAXUNLOCKABLES;)
+	for (i = 0; i < max_unlockables;)
 	{
 		rtemp = READUINT8(save_p);
-		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
-			unlockables[j+i].unlocked = ((rtemp >> j) & 1);
+		for (j = 0; j < 8 && j+i < max_unlockables; ++j)
+			data->unlocked[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
-	for (i = 0; i < MAXCONDITIONSETS;)
+	for (i = 0; i < max_conditionsets;)
 	{
 		rtemp = READUINT8(save_p);
-		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
-			conditionSets[j+i].achieved = ((rtemp >> j) & 1);
+		for (j = 0; j < 8 && j+i < max_conditionsets; ++j)
+			data->achieved[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
 
-	timesBeaten = READUINT32(save_p);
-	timesBeatenWithEmeralds = READUINT32(save_p);
-	timesBeatenUltimate = READUINT32(save_p);
+	data->timesBeaten = READUINT32(save_p);
+	data->timesBeatenWithEmeralds = READUINT32(save_p);
+	data->timesBeatenUltimate = READUINT32(save_p);
 
 	// Main records
 	for (i = 0; i < NUMMAPS; ++i)
@@ -4374,10 +4540,10 @@ void G_LoadGameData(void)
 
 		if (recscore || rectime || recrings)
 		{
-			G_AllocMainRecordData((INT16)i);
-			mainrecords[i]->score = recscore;
-			mainrecords[i]->time = rectime;
-			mainrecords[i]->rings = recrings;
+			G_AllocMainRecordData((INT16)i, data);
+			data->mainrecords[i]->score = recscore;
+			data->mainrecords[i]->time = rectime;
+			data->mainrecords[i]->rings = recrings;
 		}
 	}
 
@@ -4387,27 +4553,36 @@ void G_LoadGameData(void)
 		if ((recmares = READUINT8(save_p)) == 0)
 			continue;
 
-		G_AllocNightsRecordData((INT16)i);
+		G_AllocNightsRecordData((INT16)i, data);
 
 		for (curmare = 0; curmare < (recmares+1); ++curmare)
 		{
-			nightsrecords[i]->score[curmare] = READUINT32(save_p);
-			nightsrecords[i]->grade[curmare] = READUINT8(save_p);
-			nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+			data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
+			data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
+			data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
 
-			if (nightsrecords[i]->grade[curmare] > GRADE_S)
+			if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
+			{
 				goto datacorrupt;
+			}
 		}
 
-		nightsrecords[i]->nummares = recmares;
+		data->nightsrecords[i]->nummares = recmares;
 	}
 
 	// done
 	Z_Free(savebuffer);
 	save_p = NULL;
 
+	// Don't consider loaded until it's a success!
+	// It used to do this much earlier, but this would cause the gamedata to
+	// save over itself when it I_Errors from the corruption landing point below,
+	// which can accidentally delete players' legitimate data if the code ever has any tiny mistakes!
+	data->loaded = true;
+
 	// Silent update unlockables in case they're out of sync with conditions
-	M_SilentUpdateUnlockablesAndEmblems();
+	M_SilentUpdateUnlockablesAndEmblems(data);
+	M_SilentUpdateSkinAvailabilites();
 
 	return;
 
@@ -4427,7 +4602,7 @@ void G_LoadGameData(void)
 
 // G_SaveGameData
 // Saves the main data file, which stores information such as emblems found, etc.
-void G_SaveGameData(void)
+void G_SaveGameData(gamedata_t *data)
 {
 	size_t length;
 	INT32 i, j;
@@ -4435,7 +4610,7 @@ void G_SaveGameData(void)
 
 	INT32 curmare;
 
-	if (!gamedataloaded)
+	if (!data->loaded)
 		return; // If never loaded (-nodata), don't save
 
 	save_p = savebuffer = (UINT8 *)malloc(GAMEDATASIZE);
@@ -4445,7 +4620,7 @@ void G_SaveGameData(void)
 		return;
 	}
 
-	if (modifiedgame && !savemoddata)
+	if (usedCheats)
 	{
 		free(savebuffer);
 		save_p = savebuffer = NULL;
@@ -4453,23 +4628,22 @@ void G_SaveGameData(void)
 	}
 
 	// Version test
-	WRITEUINT32(save_p, 0xFCAFE211);
+	WRITEUINT32(save_p, GAMEDATA_ID);
 
-	WRITEUINT32(save_p, totalplaytime);
+	WRITEUINT32(save_p, data->totalplaytime);
 
-	btemp = (UINT8)(savemoddata || modifiedgame);
-	WRITEUINT8(save_p, btemp);
+	WRITEUINT32(save_p, quickncasehash(timeattackfolder, sizeof timeattackfolder));
 
 	// TODO put another cipher on these things? meh, I don't care...
 	for (i = 0; i < NUMMAPS; i++)
-		WRITEUINT8(save_p, (mapvisited[i] & MV_MAX));
+		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
 
 	// To save space, use one bit per collected/achieved/unlocked flag
 	for (i = 0; i < MAXEMBLEMS;)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
-			btemp |= (emblemlocations[j+i].collected << j);
+			btemp |= (data->collected[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4477,7 +4651,7 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
-			btemp |= (extraemblems[j+i].collected << j);
+			btemp |= (data->extraCollected[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4485,7 +4659,7 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
-			btemp |= (unlockables[j+i].unlocked << j);
+			btemp |= (data->unlocked[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4493,23 +4667,23 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
-			btemp |= (conditionSets[j+i].achieved << j);
+			btemp |= (data->achieved[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
 
-	WRITEUINT32(save_p, timesBeaten);
-	WRITEUINT32(save_p, timesBeatenWithEmeralds);
-	WRITEUINT32(save_p, timesBeatenUltimate);
+	WRITEUINT32(save_p, data->timesBeaten);
+	WRITEUINT32(save_p, data->timesBeatenWithEmeralds);
+	WRITEUINT32(save_p, data->timesBeatenUltimate);
 
 	// Main records
 	for (i = 0; i < NUMMAPS; i++)
 	{
-		if (mainrecords[i])
+		if (data->mainrecords[i])
 		{
-			WRITEUINT32(save_p, mainrecords[i]->score);
-			WRITEUINT32(save_p, mainrecords[i]->time);
-			WRITEUINT16(save_p, mainrecords[i]->rings);
+			WRITEUINT32(save_p, data->mainrecords[i]->score);
+			WRITEUINT32(save_p, data->mainrecords[i]->time);
+			WRITEUINT16(save_p, data->mainrecords[i]->rings);
 		}
 		else
 		{
@@ -4523,19 +4697,19 @@ void G_SaveGameData(void)
 	// NiGHTS records
 	for (i = 0; i < NUMMAPS; i++)
 	{
-		if (!nightsrecords[i] || !nightsrecords[i]->nummares)
+		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
 		{
 			WRITEUINT8(save_p, 0);
 			continue;
 		}
 
-		WRITEUINT8(save_p, nightsrecords[i]->nummares);
+		WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
 
-		for (curmare = 0; curmare < (nightsrecords[i]->nummares + 1); ++curmare)
+		for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
 		{
-			WRITEUINT32(save_p, nightsrecords[i]->score[curmare]);
-			WRITEUINT8(save_p, nightsrecords[i]->grade[curmare]);
-			WRITEUINT32(save_p, nightsrecords[i]->time[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
+			WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
 		}
 	}
 
@@ -4889,12 +5063,20 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
 		countdown = countdown2 = exitfadestarted = 0;
 
+		if (!FLS)
+		{
+			emeralds = 0;
+			memset(&luabanks, 0, sizeof(luabanks));
+		}
+
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			players[i].playerstate = PST_REBORN;
 			players[i].starpostscale = players[i].starpostangle = players[i].starpostnum = players[i].starposttime = 0;
 			players[i].starpostx = players[i].starposty = players[i].starpostz = 0;
+			players[i].recordscore = 0;
 
+			// default lives, continues and score
 			if (netgame || multiplayer)
 			{
 				if (!FLS || (players[i].lives < 1))
@@ -4954,8 +5136,21 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	automapactive = false;
 	imcontinuing = false;
 
+	// fetch saved data if available
+	if (savedata.lives > 0)
+	{
+		numgameovers = savedata.numgameovers;
+		players[consoleplayer].continues = savedata.continues;
+		players[consoleplayer].lives = savedata.lives;
+		players[consoleplayer].score = savedata.score;
+		if ((botingame = ((botskin = savedata.botskin) != 0)))
+			botcolor = skins[botskin-1].prefcolor;
+		emeralds = savedata.emeralds;
+		savedata.lives = 0;
+	}
+
 	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
-		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer);
+		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer, FLS);
 	else
 		G_DoLoadLevel(resetplayer);
 
diff --git a/src/g_game.h b/src/g_game.h
index 55b771274fdfa290cb0c153ec1e35031e739fe90..9873430b936fd4b79f89d0adbcf714a303fae2ce 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -19,6 +19,7 @@
 #include "d_event.h"
 #include "g_demo.h"
 #include "m_cheat.h" // objectplacing
+#include "m_cond.h"
 
 extern char gamedatafilename[64];
 extern char timeattackfolder[64];
@@ -48,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;
@@ -183,7 +186,7 @@ boolean G_IsTitleCardAvailable(void);
 // Can be called by the startup code or M_Responder, calls P_SetupLevel.
 void G_LoadGame(UINT32 slot, INT16 mapoverride);
 
-void G_SaveGameData(void);
+void G_SaveGameData(gamedata_t *data);
 
 void G_SaveGame(UINT32 slot, INT16 mapnum);
 
@@ -239,27 +242,27 @@ void G_SetModeAttackRetryFlag(void);
 void G_ClearModeAttackRetryFlag(void);
 boolean G_GetModeAttackRetryFlag(void);
 
-void G_LoadGameData(void);
+void G_LoadGameData(gamedata_t *data);
 void G_LoadGameSettings(void);
 
 void G_SetGameModified(boolean silent);
+void G_SetUsedCheats(boolean silent);
 
 void G_SetGamestate(gamestate_t newstate);
 
 // Gamedata record shit
-void G_AllocMainRecordData(INT16 i);
-void G_AllocNightsRecordData(INT16 i);
-void G_ClearRecords(void);
-
-UINT32 G_GetBestScore(INT16 map);
-tic_t G_GetBestTime(INT16 map);
-UINT16 G_GetBestRings(INT16 map);
-UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare);
-tic_t G_GetBestNightsTime(INT16 map, UINT8 mare);
-UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare);
-
-void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare);
-void G_SetNightsRecords(void);
+void G_AllocMainRecordData(INT16 i, gamedata_t *data);
+void G_AllocNightsRecordData(INT16 i, gamedata_t *data);
+void G_ClearRecords(gamedata_t *data);
+
+UINT32 G_GetBestScore(INT16 map, gamedata_t *data);
+tic_t G_GetBestTime(INT16 map, gamedata_t *data);
+UINT16 G_GetBestRings(INT16 map, gamedata_t *data);
+UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data);
+tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data);
+UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data);
+
+void G_AddTempNightsRecords(player_t *player, UINT32 pscore, tic_t ptime, UINT8 mare);
 
 FUNCMATH INT32 G_TicsToHours(tic_t tics);
 FUNCMATH INT32 G_TicsToMinutes(tic_t tics, boolean full);
diff --git a/src/hardware/CMakeLists.txt b/src/hardware/CMakeLists.txt
index 4e9c67d2f348a8bfed899e4002d25136284b031f..e7819aba97e2065d36f6f920d4725d7b294505f3 100644
--- a/src/hardware/CMakeLists.txt
+++ b/src/hardware/CMakeLists.txt
@@ -1 +1,14 @@
-target_sourcefile(c)
+target_sources(SRB2SDL2 PRIVATE
+	hw_bsp.c
+	hw_draw.c
+	hw_light.c
+	hw_main.c
+	hw_clip.c
+	hw_md2.c
+	hw_cache.c
+	hw_md2load.c
+	hw_md3load.c
+	hw_model.c
+	hw_batching.c
+	r_opengl/r_opengl.c
+)
diff --git a/src/hardware/Sourcefile b/src/hardware/Sourcefile
index 1c05de76cca6d71251023e3e9e7bdde7d8cffaab..6c374621d7b1de61f2b5a5c6fd9171f0685eccbf 100644
--- a/src/hardware/Sourcefile
+++ b/src/hardware/Sourcefile
@@ -8,6 +8,5 @@ hw_cache.c
 hw_md2load.c
 hw_md3load.c
 hw_model.c
-u_list.c
 hw_batching.c
 r_opengl/r_opengl.c
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index d1b84a5eecb4aff512bb75cf7562327675622bf1..dc0b5ee5b00fae1b28591f78ab3cb39627f11645 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_defs.h b/src/hardware/hw_defs.h
index b0859f478bd37d2315d742d0f7c80a580a5300bc..74c4ed7d2eec91d1c846ca0a43f23d636a1024f8 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -93,33 +93,22 @@ typedef struct FVector
 //Hurdler: Transform (coords + angles)
 //BP: transform order : scale(rotation_x(rotation_y(translation(v))))
 
-// Kart features
-//#define USE_FTRANSFORM_ANGLEZ
-//#define USE_FTRANSFORM_MIRROR
-
 // Vanilla features
 #define USE_MODEL_NEXTFRAME
 
 typedef struct
 {
 	FLOAT       x,y,z;           // position
-#ifdef USE_FTRANSFORM_ANGLEZ
 	FLOAT       anglex,angley,anglez;   // aimingangle / viewangle
-#else
-	FLOAT       anglex,angley;   // aimingangle / viewangle
-#endif
 	FLOAT       scalex,scaley,scalez;
 	FLOAT       fovxangle, fovyangle;
 	UINT8       splitscreen;
 	boolean     flip;            // screenflip
 	boolean     roll;
-	SINT8       rollflip;
 	FLOAT       rollangle; // done to not override USE_FTRANSFORM_ANGLEZ
-	UINT8       rotaxis;
 	FLOAT       centerx, centery;
-#ifdef USE_FTRANSFORM_MIRROR
+	FLOAT       rollx, rollz;
 	boolean     mirror;          // SRB2Kart: Encore Mode
-#endif
 	boolean     shearing;        // 14042019
 	float       viewaiming;      // 17052019
 } FTransform;
@@ -136,6 +125,7 @@ typedef struct
 // Predefined shader types
 enum
 {
+	SHADER_NONE = -1,
 	SHADER_DEFAULT = 0,
 
 	SHADER_FLOOR,
@@ -237,7 +227,8 @@ enum EPolyFlags
 	PF_RemoveYWrap      = 0x00010000,   // Forces clamp texture on Y
 	PF_ForceWrapX       = 0x00020000,   // Forces repeat texture on X
 	PF_ForceWrapY       = 0x00040000,   // Forces repeat texture on Y
-	PF_Ripple           = 0x00100000    // Water ripple effect. The current backend doesn't use it for anything.
+	PF_Ripple           = 0x00100000,   // Water ripple effect. The current backend doesn't use it for anything.
+	PF_WireFrame        = 0x00200000,   // Draws vertices as lines instead of triangles
 };
 
 
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 426d2f283b35668e70fe12520ad2dab0b6d23fd5..1c4cd99ab03d34498fa13d132a01ef53af9e6e61 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -51,7 +51,7 @@ EXPORT void HWRAPI(ClearMipMapCache) (void);
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
 
 //Hurdler: added for new development
-EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface);
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float hscale, float vscale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface);
 EXPORT void HWRAPI(CreateModelVBOs) (model_t *model);
 EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
@@ -136,4 +136,3 @@ extern struct hwdriver_s hwdriver;
 #endif //not defined _CREATE_DLL_
 
 #endif //__HWR_DRV_H__
-
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 4c7c56d178c5c1c3c970af621fab52d2e9852a62..d391c415670846fad27d4a22e1e33f46ccb367b3 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -81,6 +81,7 @@ typedef struct gl_vissprite_s
 
 	boolean flip, vflip;
 	boolean precip; // Tails 08-25-2002
+	boolean bbox;
 	boolean rotated;
 	UINT8 translucency;       //alpha level 0-255
 
@@ -88,7 +89,7 @@ typedef struct gl_vissprite_s
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	UINT8 *colormap;
-	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
+	INT32 dispoffset; // copy of mobj->dispoffset, affects ordering but not drawing
 
 	patch_t *gpatch;
 	mobj_t *mobj; // NOTE: This is a precipmobj_t if precip is true !!! Watch out.
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 343fa43bf8757b4ac32283ea18030205df6acee4..8260271bdd9f8bb52948f8d38e46e385f841fe47 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -66,6 +66,7 @@ static void HWR_ProjectSprite(mobj_t *thing);
 #ifdef HWPRECIP
 static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing);
 #endif
+static void HWR_ProjectBoundingBox(mobj_t *thing);
 
 void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap);
 void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
@@ -139,7 +140,7 @@ static fixed_t dup_viewx, dup_viewy, dup_viewz;
 static angle_t dup_viewangle;
 
 static float gl_viewx, gl_viewy, gl_viewz;
-static float gl_viewsin, gl_viewcos;
+float gl_viewsin, gl_viewcos;
 
 // Maybe not necessary with the new T&L code (needs to be checked!)
 static float gl_viewludsin, gl_viewludcos; // look up down kik test
@@ -459,30 +460,30 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatheight;
-			angle = FOFsector->floorpic_angle;
+			scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight;
+			angle = FOFsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatheight;
-			angle = FOFsector->ceilingpic_angle;
+			scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight;
+			angle = FOFsector->ceilingangle;
 		}
 	}
 	else if (gl_frontsector)
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->floor_yoffs)/fflatheight;
-			angle = gl_frontsector->floorpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight;
+			angle = gl_frontsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceiling_yoffs)/fflatheight;
-			angle = gl_frontsector->ceilingpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight;
+			angle = gl_frontsector->ceilingangle;
 		}
 	}
 
@@ -1153,7 +1154,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				texturevpeg = gl_backsector->ceilingheight + textureheight[gl_toptexture] - gl_frontsector->ceilingheight;
 
-			texturevpeg += gl_sidedef->rowoffset;
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_top;
 
 			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
 			texturevpeg %= textureheight[gl_toptexture];
@@ -1162,8 +1163,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
@@ -1213,7 +1214,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				texturevpeg = gl_frontsector->floorheight - gl_backsector->floorheight;
 
-			texturevpeg += gl_sidedef->rowoffset;
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot;
 
 			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
 			texturevpeg %= textureheight[gl_bottomtexture];
@@ -1222,8 +1223,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
@@ -1333,13 +1334,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				// Peg it to the floor
 				if (gl_linedef->flags & ML_MIDPEG)
 				{
-					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset;
+					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 					polytop = polybottom + midtexheight;
 				}
 				// Peg it to the ceiling
 				else
 				{
-					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset;
+					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 					polybottom = polytop - midtexheight;
 				}
 
@@ -1350,9 +1351,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// Skew the texture, but peg it to the floor
 			else if (gl_linedef->flags & ML_MIDPEG)
 			{
-				polybottom = popenbottom + gl_sidedef->rowoffset;
+				polybottom = popenbottom + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 				polytop = polybottom + midtexheight;
-				polybottomslope = popenbottomslope + gl_sidedef->rowoffset;
+				polybottomslope = popenbottomslope + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 				polytopslope = polybottomslope + midtexheight;
 			}
 			// Skew it according to the ceiling's slope
@@ -1407,12 +1408,12 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// Left side
 			wallVerts[3].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Right side
 			wallVerts[2].t = texturevpegslope * grTex->scaleY;
 			wallVerts[1].t = (hS - lS + texturevpegslope) * grTex->scaleY;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// set top/bottom coords
 			// Take the texture peg into account, rather than changing the offsets past
@@ -1474,19 +1475,19 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			// PEGGING
 			if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_NOSKEW)) == (ML_DONTPEGBOTTOM|ML_NOSKEW))
-				texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset;
+				texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 			else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
-				texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset;
+				texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 			else
 				// top of texture at top
-				texturevpeg = gl_sidedef->rowoffset;
+				texturevpeg = gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 
 			grTex = HWR_GetTexture(gl_midtexture);
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Texture correction for slopes
 			if (gl_linedef->flags & ML_NOSKEW) {
@@ -1634,13 +1635,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					// -- Monster Iestyn 26/06/18
 					if (newline)
 					{
-						texturevpeg = sides[newline->sidenum[0]].rowoffset;
+						texturevpeg = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
 						attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM);
 						slopeskew = !!(newline->flags & ML_SKEWTD);
 					}
 					else
 					{
-						texturevpeg = sides[rover->master->sidenum[0]].rowoffset;
+						texturevpeg = sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid;
 						attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM);
 						slopeskew = !!(rover->master->flags & ML_SKEWTD);
 					}
@@ -1672,8 +1673,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 						}
 					}
 
-					wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 				}
 				if (rover->fofflags & FOF_FOG)
 				{
@@ -1698,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)
@@ -1785,17 +1786,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					if (newline)
 					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset)) * grTex->scaleY;
+						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
+						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
 					}
 					else
 					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset)) * grTex->scaleY;
+						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * grTex->scaleY;
+						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * grTex->scaleY;
 					}
 
-					wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 				}
 
 				if (rover->fofflags & FOF_FOG)
@@ -1821,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)
@@ -2719,30 +2720,30 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatheight;
-			angle = FOFsector->floorpic_angle;
+			scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight;
+			angle = FOFsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatheight;
-			angle = FOFsector->ceilingpic_angle;
+			scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight;
+			angle = FOFsector->ceilingangle;
 		}
 	}
 	else if (gl_frontsector)
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->floor_yoffs)/fflatheight;
-			angle = gl_frontsector->floorpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight;
+			angle = gl_frontsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceiling_yoffs)/fflatheight;
-			angle = gl_frontsector->ceilingpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight;
+			angle = gl_frontsector->ceilingangle;
 		}
 	}
 
@@ -3094,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);
 				}
@@ -3140,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);
 				}
@@ -3594,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;
@@ -3605,7 +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);
+	scalemul = FixedMul(scalemul, (interp.radius*2) / gpatch->height);
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(interp.x);
@@ -3717,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
 		{
@@ -4036,6 +4037,54 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		HWR_LinkDrawHackAdd(wallVerts, spr);
 }
 
+static void HWR_DrawBoundingBox(gl_vissprite_t *vis)
+{
+	FOutVector v[24];
+	FSurfaceInfo Surf = {0};
+
+	//
+	// create a cube (side view)
+	//
+	//  5--4  3
+	//        |
+	//        |
+	//  0--1  2
+	//
+	// repeat this 4 times (overhead)
+	//
+	//
+	// 15    16  17    09
+	//    14 13  12 08
+	// 23 18  *--*  07 10
+	//        |  |
+	// 22 19  *--*  06 11
+	//    20 00  01 02
+	// 21    05  04    03
+	//
+
+	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[ 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[ 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[ 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[ 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[ 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));
+	
+	HWR_ProcessPolygon(&Surf, v, 24, (cv_renderhitboxgldepth.value ? 0 : PF_NoDepthTest)|PF_Modulated|PF_NoTexture|PF_WireFrame, SHADER_NONE, false);
+}
+
 // -----------------+
 // HWR_DrawSprite   : Draw flat sprites
 //                  : (monsters, bonuses, weapons, lights, ...)
@@ -4092,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;
@@ -4107,7 +4153,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			angle = viewangle;
 
 		if (!spr->rotated)
-			angle += spr->mobj->rollangle;
+			angle += spr->mobj->spriteroll;
 
 		angle = -angle;
 		angle += ANGLE_90;
@@ -4480,9 +4526,16 @@ static int CompareVisSprites(const void *p1, const void *p2)
 	int transparency1;
 	int transparency2;
 
+	int linkdraw1;
+	int linkdraw2;
+
+	// draw bbox after everything else
+	if (spr1->bbox || spr2->bbox)
+		return (spr1->bbox - spr2->bbox);
+
 	// check for precip first, because then sprX->mobj is actually a precipmobj_t and does not have flags2 or tracer
-	int linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer;
-	int linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer;
+	linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer;
+	linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer;
 
 	// ^ is the XOR operation
 	// if comparing a linkdraw and non-linkdraw sprite or 2 linkdraw sprites with different tracers, then use
@@ -4852,6 +4905,9 @@ static void HWR_DrawSprites(void)
 	for (i = 0; i < gl_visspritecount; i++)
 	{
 		gl_vissprite_t *spr = gl_vsprorder[i];
+		if (spr->bbox)
+			HWR_DrawBoundingBox(spr);
+		else
 #ifdef HWPRECIP
 		if (spr->precip)
 			HWR_DrawPrecipitationSprite(spr);
@@ -4951,8 +5007,15 @@ static void HWR_AddSprites(sector_t *sec)
 	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
 	for (thing = sec->thinglist; thing; thing = thing->snext)
 	{
-		if (R_ThingVisibleWithinDist(thing, limit_dist, hoop_limit_dist))
-			HWR_ProjectSprite(thing);
+		if (R_ThingWithinDist(thing, limit_dist, hoop_limit_dist))
+		{
+			if (R_ThingVisible(thing))
+			{
+				HWR_ProjectSprite(thing);
+			}
+
+			HWR_ProjectBoundingBox(thing);
+		}
 	}
 
 #ifdef HWPRECIP
@@ -5010,6 +5073,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
 	INT32 rollangle = 0;
+	angle_t spriterotangle = 0;
 #endif
 
 	// uncapped/interpolation
@@ -5031,7 +5095,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
-	dispoffset = thing->info->dispoffset;
+	dispoffset = thing->dispoffset;
 
 
 	if (R_UsingFrameInterpolation() && !paused)
@@ -5177,18 +5241,21 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spr_topoffset = spritecachedinfo[lumpoff].topoffset;
 
 #ifdef ROTSPRITE
-	if (thing->rollangle
+	spriterotangle = R_SpriteRotationAngle(&interp);
+
+	if (spriterotangle != 0
 	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		if (papersprite)
 		{
 			// a positive rollangle should should pitch papersprites upwards relative to their facing angle
-			rollangle = R_GetRollAngle(InvAngle(thing->rollangle));
+			rollangle = R_GetRollAngle(InvAngle(spriterotangle));
 		}
 		else
 		{
-			rollangle = R_GetRollAngle(thing->rollangle);
+			rollangle = R_GetRollAngle(spriterotangle);
 		}
+
 		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
 
 		if (rotsprite != NULL)
@@ -5254,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));
@@ -5306,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
@@ -5470,6 +5537,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->vflip = vflip;
 
 	vis->precip = false;
+	vis->bbox = false;
 
 	vis->angle = interp.angle;
 }
@@ -5592,6 +5660,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->gz = vis->gzt - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height);
 
 	vis->precip = true;
+	vis->bbox = false;
 
 	// okay... this is a hack, but weather isn't networked, so it should be ok
 	if (!(thing->precipflags & PCF_THUNK))
@@ -5605,6 +5674,58 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 }
 #endif
 
+static void HWR_ProjectBoundingBox(mobj_t *thing)
+{
+	gl_vissprite_t *vis;
+	float tr_x, tr_y;
+	float tz;
+
+	if (!thing)
+		return;
+
+	if (!R_ThingBoundingBoxVisible(thing))
+		return;
+
+	// uncapped/interpolation
+	boolean interpolate = cv_renderhitboxinterpolation.value;
+	interpmobjstate_t interp = {0};
+
+	if (R_UsingFrameInterpolation() && !paused && interpolate)
+	{
+		R_InterpolateMobjState(thing, rendertimefrac, &interp);
+	}
+	else
+	{
+		R_InterpolateMobjState(thing, FRACUNIT, &interp);
+	}
+
+	// transform the origin point
+	tr_x = FIXED_TO_FLOAT(interp.x) - gl_viewx;
+	tr_y = FIXED_TO_FLOAT(interp.y) - gl_viewy;
+
+	// rotation around vertical axis
+	tz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
+
+	// thing is behind view plane?
+	if (tz < ZCLIP_PLANE)
+		return;
+
+	tr_x += gl_viewx;
+	tr_y += gl_viewy;
+
+	vis = HWR_NewVisSprite();
+	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(interp.height);
+	vis->mobj = thing;
+
+	vis->precip = false;
+	vis->bbox = true;
+}
+
 // ==========================================================================
 // Sky dome rendering, ported from PrBoom+
 // ==========================================================================
@@ -5788,6 +5909,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 			fixed_t rol = AngleFixed(player->viewrollangle);
 			dometransform.rollangle = FIXED_TO_FLOAT(rol);
 			dometransform.roll = true;
+			dometransform.rollx = 1.0f;
+			dometransform.rollz = 0.0f;
 		}
 		dometransform.splitscreen = splitscreen;
 
@@ -6066,6 +6189,8 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 		fixed_t rol = AngleFixed(player->viewrollangle);
 		atransform.rollangle = FIXED_TO_FLOAT(rol);
 		atransform.roll = true;
+		atransform.rollx = 1.0f;
+		atransform.rollz = 0.0f;
 	}
 	atransform.splitscreen = splitscreen;
 
@@ -6280,6 +6405,8 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 		fixed_t rol = AngleFixed(player->viewrollangle);
 		atransform.rollangle = FIXED_TO_FLOAT(rol);
 		atransform.roll = true;
+		atransform.rollx = 1.0f;
+		atransform.rollz = 0.0f;
 	}
 	atransform.splitscreen = splitscreen;
 
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 4a1b412fc6539e64b86ac63a9b20288ac506b52f..9450ca2c56ed56d52916f587136e2e9352f3f4b4 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -115,6 +115,7 @@ extern float gl_viewwindowx, gl_basewindowcentery;
 // BP: big hack for a test in lighting ref : 1249753487AB
 extern fixed_t *hwbbox;
 extern FTransform atransform;
+extern float gl_viewsin, gl_viewcos;
 
 
 // Render stats
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 6b1d0c6fcbd95376c420739b2e4fb763e07644cc..0f834213592e041c866ca05a90d2599476c81c76 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)
@@ -1146,7 +1146,7 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski
 static boolean HWR_AllowModel(mobj_t *mobj)
 {
 	// Signpost overlay. Not needed.
-	if (mobj->state-states == S_PLAY_SIGN)
+	if (mobj->sprite2 == SPR2_SIGN || mobj->state-states == S_PLAY_SIGN)
 		return false;
 
 	// Otherwise, render the model.
@@ -1346,10 +1346,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		const UINT8 hflip = (UINT8)(!(spr->mobj->mirrored) != !R_ThingHorizontallyFlipped(spr->mobj));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
-		spriteinfo_t *sprinfo;
-		angle_t ang;
 		INT32 mod;
-		float finalscale;
 		interpmobjstate_t interp;
 
 		if (R_UsingFrameInterpolation() && !paused)
@@ -1388,12 +1385,10 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		{
 			md2 = &md2_playermodels[(skin_t*)spr->mobj->skin-skins];
 			md2->skin = (skin_t*)spr->mobj->skin-skins;
-			sprinfo = &((skin_t *)spr->mobj->skin)->sprinfo[spr->mobj->sprite2];
 		}
 		else
 		{
 			md2 = &md2_models[spr->mobj->sprite];
-			sprinfo = &spriteinfo[spr->mobj->sprite];
 		}
 
 		// texture loading before model init, so it knows if sprite graphics are used, which
@@ -1455,7 +1450,6 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		}
 
 		//HWD.pfnSetBlend(blend); // This seems to actually break translucency?
-		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
 
 		if (gpatch && hwrPatch && hwrPatch->mipmap->format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
@@ -1591,7 +1585,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);
 
@@ -1614,61 +1608,56 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 
-		p.rollangle = 0.0f;
-		p.rollflip = 1;
-		p.rotaxis = 0;
-		if (spr->mobj->rollangle)
 		{
-			fixed_t anglef = AngleFixed(spr->mobj->rollangle);
-			p.rollangle = FIXED_TO_FLOAT(anglef);
-			p.roll = true;
-
-			// rotation pivot
-			p.centerx = FIXED_TO_FLOAT(spr->mobj->radius/2);
-			p.centery = FIXED_TO_FLOAT(spr->mobj->height/(flip ? -2 : 2));
-
-			// rotation axis
-			if (sprinfo->available)
-				p.rotaxis = (UINT8)(sprinfo->pivot[(spr->mobj->frame & FF_FRAMEMASK)].rotaxis);
-
-			// for NiGHTS specifically but should work everywhere else
-			ang = R_PointToAngle (interp.x, interp.y) - interp.angle;
-			if ((sprframe->rotate & SRF_RIGHT) && (ang < ANGLE_180)) // See from right
-				p.rollflip = 1;
-			else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
-				p.rollflip = -1;
-
-			if (flip)
-				p.rollflip *= -1;
-		}
+			fixed_t anglef = AngleFixed(R_ModelRotationAngle(&interp));
 
-		p.anglex = 0.0f;
+			p.rollangle = 0.0f;
 
-#ifdef USE_FTRANSFORM_ANGLEZ
-		// Slope rotation from Kart
-		p.anglez = 0.0f;
-		if (spr->mobj->standingslope)
-		{
-			fixed_t tempz = spr->mobj->standingslope->normal.z;
-			fixed_t tempy = spr->mobj->standingslope->normal.y;
-			fixed_t tempx = spr->mobj->standingslope->normal.x;
-			fixed_t tempangle = AngleFixed(R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx));
-			p.anglez = FIXED_TO_FLOAT(tempangle);
-			tempangle = -AngleFixed(R_PointToAngle2(0, 0, tempz, tempy));
-			p.anglex = FIXED_TO_FLOAT(tempangle);
+			if (anglef)
+			{
+				fixed_t camAngleDiff = AngleFixed(viewangle) - FLOAT_TO_FIXED(p.angley); // dumb reconversion back, I know
+
+				p.rollangle = FIXED_TO_FLOAT(anglef);
+				p.roll = true;
+
+				// rotation pivot
+				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));
+				p.rollz = FIXED_TO_FLOAT(FINESINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT));
+			}
 		}
-#endif
 
-		// SRB2CBTODO: MD2 scaling support
-		finalscale *= FIXED_TO_FLOAT(spr->mobj->scale);
+#if 0
+		p.anglez = FIXED_TO_FLOAT(AngleFixed(interp.pitch));
+		p.anglex = FIXED_TO_FLOAT(AngleFixed(interp.roll));
+#else
+		p.anglez = 0.f;
+		p.anglex = 0.f;
+#endif
 
 		p.flip = atransform.flip;
-#ifdef USE_FTRANSFORM_MIRROR
-		p.mirror = atransform.mirror; // from Kart
-#endif
+		p.mirror = atransform.mirror;
 
 		HWD.pfnSetShader(SHADER_MODEL);	// model shader
-		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, hflip, &Surf);
+		{
+			float this_scale = FIXED_TO_FLOAT(interp.scale);
+
+			float xs = this_scale * FIXED_TO_FLOAT(interp.spritexscale);
+			float ys = this_scale * FIXED_TO_FLOAT(interp.spriteyscale);
+
+			float ox = xs * FIXED_TO_FLOAT(interp.spritexoffset);
+			float oy = ys * FIXED_TO_FLOAT(interp.spriteyoffset);
+
+			// offset perpendicular to the camera angle
+			p.x -= ox * gl_viewsin;
+			p.y += ox * gl_viewcos;
+			p.z += oy;
+
+			HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, md2->scale * xs, md2->scale * ys, flip, hflip, &Surf);
+		}
 	}
 
 	return true;
diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c
index 4ed03744bfe072db597c0e2d120fd72ccd77a6ca..4b6bce6f7b55834167d496a361c3936cd22cc8ac 100644
--- a/src/hardware/hw_model.c
+++ b/src/hardware/hw_model.c
@@ -15,7 +15,7 @@
 #include "hw_md2load.h"
 #include "hw_md3load.h"
 #include "hw_md2.h"
-#include "u_list.h"
+#include "../u_list.h"
 #include <string.h>
 
 static float PI = (3.1415926535897932384626433832795f);
@@ -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/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 569ddfee84032f9c88650216fa5f2127ce54df33..71cb5ca70375629ee06c7943e0c5be881555a0c1 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1030,6 +1030,12 @@ EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boole
 EXPORT void HWRAPI(SetShader) (int type)
 {
 #ifdef GL_SHADERS
+	if (type == SHADER_NONE)
+	{
+		UnSetShader();
+		return;
+	}
+
 	if (gl_allowshaders != HWD_SHADEROPTION_OFF)
 	{
 		gl_shader_t *shader = gl_shaderstate.current;
@@ -2290,7 +2296,7 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUI
 
 	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
 	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].s);
-	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
+	pglDrawArrays(PolyFlags & PF_WireFrame ? GL_LINES : GL_TRIANGLE_FAN, 0, iNumPts);
 
 	if (PolyFlags & PF_RemoveYWrap)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
@@ -2673,7 +2679,7 @@ EXPORT void HWRAPI(CreateModelVBOs) (model_t *model)
 
 #define BUFFER_OFFSET(i) ((void*)(i))
 
-static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float hscale, float vscale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
 {
 	static GLRGBAFloat poly = {0,0,0,0};
 	static GLRGBAFloat tint = {0,0,0,0};
@@ -2697,10 +2703,11 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 #endif
 
 	// Affect input model scaling
-	scale *= 0.5f;
-	scalex = scale;
-	scaley = scale;
-	scalez = scale;
+	hscale *= 0.5f;
+	vscale *= 0.5f;
+	scalex = hscale;
+	scaley = vscale;
+	scalez = hscale;
 
 	if (duration > 0.0 && tics >= 0.0) // don't interpolate if instantaneous or infinite in length
 	{
@@ -2776,7 +2783,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 	pglEnable(GL_CULL_FACE);
 	pglEnable(GL_NORMALIZE);
 
-#ifdef USE_FTRANSFORM_MIRROR
 	// flipped is if the object is vertically flipped
 	// hflipped is if the object is horizontally flipped
 	// pos->flip is if the screen is flipped vertically
@@ -2789,17 +2795,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 		else
 			pglCullFace(GL_BACK);
 	}
-#else
-	// pos->flip is if the screen is flipped too
-	if (flipped ^ hflipped ^ pos->flip) // If one or three of these are active, but not two, invert the model's culling
-	{
-		pglCullFace(GL_FRONT);
-	}
-	else
-	{
-		pglCullFace(GL_BACK);
-	}
-#endif
 
 	pglPushMatrix(); // should be the same as glLoadIdentity
 	//Hurdler: now it seems to work
@@ -2809,22 +2804,14 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 	if (hflipped)
 		scalez = -scalez;
 
-#ifdef USE_FTRANSFORM_ANGLEZ
-	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f); // rotate by slope from Kart
-#endif
-	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f);
 	pglRotatef(pos->anglex, 1.0f, 0.0f, 0.0f);
+	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
 
 	if (pos->roll)
 	{
-		float roll = (1.0f * pos->rollflip);
 		pglTranslatef(pos->centerx, pos->centery, 0);
-		if (pos->rotaxis == 2) // Z
-			pglRotatef(pos->rollangle, 0.0f, 0.0f, roll);
-		else if (pos->rotaxis == 1) // Y
-			pglRotatef(pos->rollangle, 0.0f, roll, 0.0f);
-		else // X
-			pglRotatef(pos->rollangle, roll, 0.0f, 0.0f);
+		pglRotatef(pos->rollangle, pos->rollx, 0.0f, pos->rollz);
 		pglTranslatef(-pos->centerx, -pos->centery, 0);
 	}
 
@@ -2978,9 +2965,9 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 // -----------------+
 // HWRAPI DrawModel : Draw a model
 // -----------------+
-EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, float duration, float tics, INT32 nextFrameIndex, FTransform *pos, float hscale, float vscale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
 {
-	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, hflipped, Surface);
+	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, hscale, vscale, flipped, hflipped, Surface);
 }
 
 // -----------------+
@@ -2997,13 +2984,9 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 	if (stransform)
 	{
 		used_fov = stransform->fovxangle;
-#ifdef USE_FTRANSFORM_MIRROR
-		// mirroring from Kart
 		if (stransform->mirror)
 			pglScalef(-stransform->scalex, stransform->scaley, -stransform->scalez);
-		else
-#endif
-		if (stransform->flip)
+		else if (stransform->flip)
 			pglScalef(stransform->scalex, -stransform->scaley, -stransform->scalez);
 		else
 			pglScalef(stransform->scalex, stransform->scaley, -stransform->scalez);
diff --git a/src/http-mserv.c b/src/http-mserv.c
index b4dba0db9335da0a07ddbda1b0d280da9ed418b2..df9a71a5c5d6cfcb8f73ecd3eeff371170e76c8e 100644
--- a/src/http-mserv.c
+++ b/src/http-mserv.c
@@ -159,7 +159,7 @@ HMS_connect (const char *format, ...)
 		return NULL;
 	}
 
-	if (cv_masterserver_token.string[0])
+	if (cv_masterserver_token.string && cv_masterserver_token.string[0])
 	{
 		quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0);
 		token_length = ( sizeof "?token="-1 )+ strlen(quack_token);
@@ -216,7 +216,11 @@ HMS_connect (const char *format, ...)
 
 	curl_easy_setopt(curl, CURLOPT_URL, url);
 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
-	curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+#ifndef NO_IPV6
+	if (M_CheckParm("-noipv6"))
+#endif
+		curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
 
 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 23c63e98f57f33f844fb45d104b91522263b109f..e223d320814c6dff885886d6d57a434c0d120493 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2025,7 +2025,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;
@@ -2862,18 +2862,6 @@ static void HU_DrawRankings(void)
 			V_DrawCenteredString(256, 16, 0, va("%d", cv_pointlimit.value));
 		}
 	}
-	else if (gametyperankings[gametype] == GT_COOP)
-	{
-		INT32 totalscore = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (playeringame[i])
-				totalscore += players[i].score;
-		}
-
-		V_DrawCenteredString(256, 8, 0, "TOTAL SCORE");
-		V_DrawCenteredString(256, 16, 0, va("%u", totalscore));
-	}
 	else
 	{
 		if (circuitmap)
@@ -2994,9 +2982,9 @@ static void HU_DrawCoopOverlay(void)
 		V_DrawSmallScaledPatch(148, 172, 0, tokenicon);
 	}
 
-	if (LUA_HudEnabled(hud_tabemblems) && (!modifiedgame || savemoddata))
+	if (LUA_HudEnabled(hud_tabemblems))
 	{
-		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(clientGamedata), numemblems+numextraemblems));
 		V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon);
 	}
 
@@ -3029,6 +3017,15 @@ static void HU_DrawNetplayCoopOverlay(void)
 		V_DrawSmallScaledPatch(148, 6, 0, tokenicon);
 	}
 
+	if (G_CoopGametype() && LUA_HudEnabled(hud_tabemblems))
+	{
+		V_DrawCenteredString(256, 14, 0, "/");
+		V_DrawString(256 + 4, 14, 0, va("%d", numemblems + numextraemblems));
+		V_DrawRightAlignedString(256 - 4, 14, 0, va("%d", M_CountEmblems(clientGamedata)));
+
+		V_DrawSmallScaledPatch(256 - (emblemicon->width / 4), 6, 0, emblemicon);
+	}
+
 	if (!LUA_HudEnabled(hud_coopemeralds))
 		return;
 
diff --git a/src/i_net.h b/src/i_net.h
index 9f2c38c7b5309c6520705ac5cc35a586f8d7e8e3..12a07f183e515f7ca985afb67ac50cb07cf2c313 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -109,6 +109,17 @@ extern boolean (*I_NetCanSend)(void);
 */
 extern void (*I_NetFreeNodenum)(INT32 nodenum);
 
+/**
+	\brief	split a string into address and port
+
+	\param	address	string to split
+
+	\param	port	double pointer to hold port component (optional)
+
+	\return	address component
+*/
+extern char *I_NetSplitAddress(char *address, char **port);
+
 /**	\brief	open a connection with specified address
 
 	\param	address	address to connect to
diff --git a/src/i_system.h b/src/i_system.h
index 957150fe665bfc1cf121365a16bf00469cbd921b..834dd4091487b295fd1faed9d11018e498d79b12 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -40,7 +40,7 @@ extern UINT8 keyboard_started;
 
 	\return	free memory in the system
 */
-UINT32 I_GetFreeMem(UINT32 *total);
+size_t I_GetFreeMem(size_t *total);
 
 /**	\brief	Returns precise time value for performance measurement. The precise
             time should be a monotonically increasing counter, and will wrap.
@@ -49,6 +49,10 @@ UINT32 I_GetFreeMem(UINT32 *total);
   */
 precise_t I_GetPreciseTime(void);
 
+/**	\brief	Fills a buffer with random data, returns amount of data obtained.
+  */
+size_t I_GetRandomBytes(char *destination, size_t count);
+
 /** \brief  Get the precision of precise_t in units per second. Invocations of
             this function for the program's duration MUST return the same value.
   */
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 3820155b83bd4c60b98ece7a8f04638a2bf1f7a1..d95b381f4a65fa86030f9efd44c7164b487df61e 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -340,8 +340,14 @@ static inline void I_UPnP_rem(const char *port, const char * servicetype)
 
 static const char *SOCK_AddrToStr(mysockaddr_t *sk)
 {
-	static char s[64]; // 255.255.255.255:65535 or IPv6:65535
+	static char s[64]; // 255.255.255.255:65535 or
+	// [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535
 #ifdef HAVE_NTOP
+#ifdef HAVE_IPV6
+	int v6 = (sk->any.sa_family == AF_INET6);
+#else
+	int v6 = 0;
+#endif
 	void *addr;
 
 	if(sk->any.sa_family == AF_INET)
@@ -355,14 +361,21 @@ static const char *SOCK_AddrToStr(mysockaddr_t *sk)
 
 	if(addr == NULL)
 		sprintf(s, "No address");
-	else if(inet_ntop(sk->any.sa_family, addr, s, sizeof (s)) == NULL)
+	else if(inet_ntop(sk->any.sa_family, addr, &s[v6], sizeof (s) - v6) == NULL)
 		sprintf(s, "Unknown family type, error #%u", errno);
 #ifdef HAVE_IPV6
-	else if(sk->any.sa_family == AF_INET6 && sk->ip6.sin6_port != 0)
-		strcat(s, va(":%d", ntohs(sk->ip6.sin6_port)));
+	else if(sk->any.sa_family == AF_INET6)
+	{
+		s[0] = '[';
+		strcat(s, "]");
+
+		if (sk->ip6.sin6_port != 0)
+			strcat(s, va(":%d", ntohs(sk->ip6.sin6_port)));
+	}
 #endif
 	else if(sk->any.sa_family == AF_INET  && sk->ip4.sin_port  != 0)
 		strcat(s, va(":%d", ntohs(sk->ip4.sin_port)));
+
 #else
 	if (sk->any.sa_family == AF_INET)
 	{
@@ -427,7 +440,7 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
 			&& (b->ip4.sin_port == 0 || (a->ip4.sin_port == b->ip4.sin_port));
 #ifdef HAVE_IPV6
 	else if (b->any.sa_family == AF_INET6)
-		return memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr))
+		return !memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr))
 			&& (b->ip6.sin6_port == 0 || (a->ip6.sin6_port == b->ip6.sin6_port));
 #endif
 	else
@@ -735,8 +748,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 	unsigned long trueval = true;
 #endif
 	mysockaddr_t straddr;
-	struct sockaddr_in sin;
-	socklen_t len = sizeof(sin);
+	socklen_t len = sizeof(straddr);
 
 	if (s == (SOCKET_TYPE)ERRSOCKET)
 		return (SOCKET_TYPE)ERRSOCKET;
@@ -754,14 +766,12 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 	}
 #endif
 
-	straddr.any = *addr;
+	memcpy(&straddr, addr, addrlen);
 	I_OutputMsg("Binding to %s\n", SOCK_AddrToStr(&straddr));
 
 	if (family == AF_INET)
 	{
-		mysockaddr_t tmpaddr;
-		tmpaddr.any = *addr ;
-		if (tmpaddr.ip4.sin_addr.s_addr == htonl(INADDR_ANY))
+		if (straddr.ip4.sin_addr.s_addr == htonl(INADDR_ANY))
 		{
 			opt = true;
 			opts = (socklen_t)sizeof(opt);
@@ -778,7 +788,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 #ifdef HAVE_IPV6
 	else if (family == AF_INET6)
 	{
-		if (memcmp(addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL
+		if (memcmp(&straddr.ip6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL
 		{
 			opt = true;
 			opts = (socklen_t)sizeof(opt);
@@ -788,7 +798,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 		// make it IPv6 ony
 		opt = true;
 		opts = (socklen_t)sizeof(opt);
-		if (setsockopt(s, SOL_SOCKET, IPV6_V6ONLY, (char *)&opt, opts))
+		if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, opts))
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Could not limit IPv6 bind\n")); // I do not care anymore
 		}
@@ -830,10 +840,17 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 			CONS_Printf(M_GetText("Network system buffer set to: %dKb\n"), opt>>10);
 	}
 
-	if (getsockname(s, (struct sockaddr *)&sin, &len) == -1)
+	if (getsockname(s, &straddr.any, &len) == -1)
 		CONS_Alert(CONS_WARNING, M_GetText("Failed to get port number\n"));
 	else
-		current_port = (UINT16)ntohs(sin.sin_port);
+	{
+		if (family == AF_INET)
+			current_port = (UINT16)ntohs(straddr.ip4.sin_port);
+#ifdef HAVE_IPV6
+		else if (family == AF_INET6)
+			current_port = (UINT16)ntohs(straddr.ip6.sin6_port);
+#endif
+	}
 
 	return s;
 }
@@ -844,7 +861,7 @@ static boolean UDP_Socket(void)
 	struct my_addrinfo *ai, *runp, hints;
 	int gaie;
 #ifdef HAVE_IPV6
-	const INT32 b_ipv6 = M_CheckParm("-ipv6");
+	const INT32 b_ipv6 = !M_CheckParm("-noipv6");
 #endif
 	const char *serv;
 
@@ -1156,6 +1173,7 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 	SINT8 newnode = -1;
 	struct my_addrinfo *ai = NULL, *runp, hints;
 	int gaie;
+	size_t i;
 
 	 if (!port || !port[0])
 		port = DEFAULTPORT;
@@ -1183,13 +1201,24 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 
 	while (runp != NULL)
 	{
-		// find ip of the server
-		if (sendto(mysockets[0], NULL, 0, 0, runp->ai_addr, runp->ai_addrlen) == 0)
+		// test ip address of server
+		for (i = 0; i < mysocketses; ++i)
 		{
-			memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
-			break;
+			/* sendto tests that there is a network to this
+				address */
+			if (runp->ai_addr->sa_family == myfamily[i] &&
+					sendto(mysockets[i], NULL, 0, 0,
+						runp->ai_addr, runp->ai_addrlen) == 0)
+			{
+				memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
+				break;
+			}
 		}
-		runp = runp->ai_next;
+
+		if (i < mysocketses)
+			runp = runp->ai_next;
+		else
+			break;
 	}
 	I_freeaddrinfo(ai);
 	return newnode;
diff --git a/src/info.c b/src/info.c
index 09452a74f54b04dc2a1c702ea515b793e25b311b..36389d849a38e21064e0f4e94b803cccfc9f7ef0 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,68 +21584,113 @@ 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
-	{"Slate",    {0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0xaa, 0xaa, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xad, 0xae, 0xaf}, SKINCOLOR_SILVER,  12, 0,           true}, // SKINCOLOR_SLATE
-	{"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
-	{"Yogurt",   {0xd0, 0x30, 0xd8, 0xd9, 0xda, 0xdb, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe3, 0xe6, 0xe8, 0xe9}, SKINCOLOR_RUST,    7,  V_BROWNMAP,  true}, // SKINCOLOR_YOGURT
-	{"Brown",    {0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef}, SKINCOLOR_TAN,     2,  V_BROWNMAP,  true}, // SKINCOLOR_BROWN
-	{"Bronze",   {0xde, 0xe0, 0xe1, 0xe4, 0xe7, 0xe9, 0xeb, 0xec, 0xed, 0xed, 0xed, 0x19, 0x19, 0x1b, 0x1d, 0x1e}, SKINCOLOR_KETCHUP, 0,  V_BROWNMAP,  true}, // SKINCOLOR_BRONZE
-	{"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
-	{"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
-	{"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
+	{"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
+	{"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
+	{"Yogurt",    {0xd0, 0x30, 0xd8, 0xd9, 0xda, 0xdb, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe3, 0xe6, 0xe8, 0xe9}, SKINCOLOR_RUST,      7,  V_BROWNMAP,  true}, // SKINCOLOR_YOGURT
+	{"Latte",     {  48,  217,  219,  221,  223,  224,  226,  228,   68,   69,   70,   70,   44,   45,   46,   47}, SKINCOLOR_BOTTLE,    12, V_BROWNMAP,  true}, // SKINCOLOR_LATTE
+	{"Brown",     {0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef}, SKINCOLOR_TAN,       2,  V_BROWNMAP,  true}, // SKINCOLOR_BROWN
+	{"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_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_GOLD,      4,  V_PURPLEMAP, true}, // SKINCOLOR_LAVENDER
 
 	// Viv's vivid colours (toast 21/07/17)
+	// 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_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
-	{"Ketchup",    {0x48, 0x49, 0x40, 0x33, 0x34, 0x36, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2b, 0x2c, 0x47, 0x2e, 0x2f}, SKINCOLOR_BRONZE,     8,  V_REDMAP,     true}, // SKINCOLOR_KETCHUP
+	{"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,  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",     {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x2c}, SKINCOLOR_BLUE,       4,  V_ORANGEMAP,  true}, // SKINCOLOR_ORANGE
+	{"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
 	{"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
+	{"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
 	{"Olive",      {0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0xe7, 0xe7, 0xe9, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7, 0xfd}, SKINCOLOR_DUSK,       3,  V_YELLOWMAP,  true}, // SKINCOLOR_OLIVE
+	{"Pear",       {  88,   89,  188,  189,  189,   76,   76,   67,   67,   68,   69,   70,   45,   46,   47,   47}, SKINCOLOR_MARINE,     9,  V_PERIDOTMAP, true}, // SKINCOLOR_PEAR
+	{"Lemon",      {   0,   80,   81,   83,   73,   73,   74,   74,   76,   76,  191,  191,   79,   79,  110,  111}, SKINCOLOR_FUCHSIA,    8,  V_YELLOWMAP,  true}, // SKINCOLOR_LEMON
 	{"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_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
-	{"Emerald",    {0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, SKINCOLOR_RUBY,       4,  V_GREENMAP,   true}, // SKINCOLOR_EMERALD
+	{"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
-	{"Seafoam",    {0x01, 0x58, 0x59, 0x5a, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a, 0x8a, 0xfd, 0xfd}, SKINCOLOR_PLUM,       6,  V_AQUAMAP,    true}, // SKINCOLOR_SEAFOAM
+	{"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
+	{"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
+	{"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
+	{"Aquamarine", {   0,  120,  121,  131,  132,  133,  134,  134,  135,  135,  149,  149,  172,  173,  174,  175}, SKINCOLOR_GARNET,     8,  V_SKYMAP,     true}, // SKINCOLOR_AQUAMARINE
 	{"Sky",        {0x80, 0x80, 0x81, 0x82, 0x83, 0x83, 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x89, 0x8a, 0x8b}, SKINCOLOR_SANDY,      1,  V_SKYMAP,     true}, // SKINCOLOR_SKY
+	{"Marine",     { 144,  146,  147,  147,  148,  135,  136,  136,  137,  137,  127,  118,  119,  111,  111,  111}, SKINCOLOR_PEAR,       13, V_SKYMAP,     true}, // SKINCOLOR_MARINE
 	{"Cerulean",   {0x85, 0x86, 0x87, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0xfd, 0xfd, 0xfd, 0x1f, 0x1f, 0x1f}, SKINCOLOR_NEON,       4,  V_SKYMAP,     true}, // SKINCOLOR_CERULEAN
+	{"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
-	{"Sapphire",   {0x80, 0x83, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, SKINCOLOR_SUNSET,     5,  V_SKYMAP,     true}, // SKINCOLOR_SAPPHIRE
+	{"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_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",     {0x93, 0x94, 0x95, 0x96, 0x98, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfd, 0xfe, 0xfe}, SKINCOLOR_PERIDOT,    5,  V_BLUEMAP,    true}, // SKINCOLOR_COBALT
+	{"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,  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
+	{"Majesty",    {   0,    1,  176,  160,  160,  161,  162,  162,  163,  172,  173,  174,  174,  175,  139,  139}, SKINCOLOR_GOLDENROD,  9,  V_PURPLEMAP,  true}, // SKINCOLOR_MAJESTY
 	{"Pastel",     {0x90, 0x90, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa4, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, SKINCOLOR_BUBBLEGUM,  9,  V_PURPLEMAP,  true}, // SKINCOLOR_PASTEL
 	{"Purple",     {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa7, 0xa7, 0xa8, 0xa8, 0xa9, 0xa9}, SKINCOLOR_FLAME,      7,  V_PURPLEMAP,  true}, // SKINCOLOR_PURPLE
-	{"Bubblegum",  {0x00, 0xd0, 0xd0, 0xc8, 0xc8, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, SKINCOLOR_PASTEL,     8,  V_MAGENTAMAP, true}, // SKINCOLOR_BUBBLEGUM
+	{"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
+	{"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_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_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",   {  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/info.h b/src/info.h
index 502ecd726006ecae626f44a0657bcd4245a01eaa..5c7a9f3fd428579c674c7f969db44f7d71b88f07 100644
--- a/src/info.h
+++ b/src/info.h
@@ -18,6 +18,7 @@
 #include "d_think.h"
 #include "sounds.h"
 #include "m_fixed.h"
+#include "dehacked.h" // MAX_ACTION_RECURSION
 
 // deh_tables.c now has lists for the more named enums! PLEASE keep them up to date!
 // For great modding!!
@@ -151,6 +152,7 @@ enum actionnum
 	A_BOSS3TAKEDAMAGE,
 	A_BOSS3PATH,
 	A_BOSS3SHOCKTHINK,
+	A_SHOCKWAVE,
 	A_LINEDEFEXECUTE,
 	A_LINEDEFEXECUTEFROMARG,
 	A_PLAYSEESOUND,
@@ -415,6 +417,7 @@ void A_Boss1Spikeballs();
 void A_Boss3TakeDamage();
 void A_Boss3Path();
 void A_Boss3ShockThink();
+void A_Shockwave();
 void A_LinedefExecute();
 void A_LinedefExecuteFromArg();
 void A_PlaySeeSound();
@@ -564,7 +567,7 @@ void A_DragonWing();
 void A_DragonSegment();
 void A_ChangeHeight();
 
-extern boolean actionsoverridden[NUMACTIONS];
+extern int actionsoverridden[NUMACTIONS][MAX_ACTION_RECURSION];
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
diff --git a/src/libdivide.h b/src/libdivide.h
index 1a589c7e5508957b0c4a8e15d37cd02c0402f349..71a5ae6be16589bbf08e1a8a764217fd121681de 100644
--- a/src/libdivide.h
+++ b/src/libdivide.h
@@ -581,6 +581,11 @@ static inline void libdivide_u128_shift(uint64_t *u1, uint64_t *u0, int32_t sign
 
 ////////// UINT32
 
+#if defined(__GNUC__) || defined(__clang__) // Suppress intentional compiler warnings
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Waggregate-return"
+#endif
+
 static inline struct libdivide_u32_t libdivide_internal_u32_gen(uint32_t d, int branchfree) {
     struct libdivide_u32_t result;
     uint32_t floor_log_2_d;
@@ -647,6 +652,10 @@ struct libdivide_u32_t libdivide_u32_gen(uint32_t d) {
     return ret;
 }*/
 
+#if defined(__GNUC__) || defined(__clang__) // Stop suppressing intentional compiler warnings
+    #pragma GCC diagnostic pop
+#endif
+
 uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) {
     uint8_t more = denom->more;
     if (!denom->magic) {
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 6e258c82a97427dd976933a42f5772e45a4a3004..1d183cdec6b6a518f38ed32725ed5f19ec4a45cd 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -689,11 +689,12 @@ static int lib_pSpawnLockOn(lua_State *L)
 		return LUA_ErrInvalid(L, "player_t");
 	if (state >= NUMSTATES)
 		return luaL_error(L, "state %d out of range (0 - %d)", state, NUMSTATES-1);
-	if (P_IsLocalPlayer(player)) // Only display it on your own view.
+	if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 	{
 		mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 		P_SetTarget(&visual->target, lockon);
 		visual->flags2 |= MF2_DONTDRAW;
+		visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 		P_SetMobjStateNF(visual, state);
 	}
 	return 0;
@@ -874,6 +875,7 @@ static int lib_pGetClosestAxis(lua_State *L)
 
 static int lib_pSpawnParaloop(lua_State *L)
 {
+	mobj_t *ptmthing = tmthing;
 	fixed_t x = luaL_checkfixed(L, 1);
 	fixed_t y = luaL_checkfixed(L, 2);
 	fixed_t z = luaL_checkfixed(L, 3);
@@ -890,6 +892,7 @@ static int lib_pSpawnParaloop(lua_State *L)
 	if (nstate >= NUMSTATES)
 		return luaL_error(L, "state %d out of range (0 - %d)", nstate, NUMSTATES-1);
 	P_SpawnParaloop(x, y, z, radius, number, type, nstate, rotangle, spawncenter);
+	P_SetTarget(&tmthing, ptmthing);
 	return 0;
 }
 
@@ -1076,7 +1079,8 @@ static int lib_pZMovement(lua_State *L)
 	if (!actor)
 		return LUA_ErrInvalid(L, "mobj_t");
 	lua_pushboolean(L, P_ZMovement(actor));
-	P_CheckPosition(actor, actor->x, actor->y);
+	if (!P_MobjWasRemoved(actor))
+		P_CheckPosition(actor, actor->x, actor->y);
 	P_SetTarget(&tmthing, ptmthing);
 	return 1;
 }
@@ -1104,7 +1108,8 @@ static int lib_pSceneryZMovement(lua_State *L)
 	if (!actor)
 		return LUA_ErrInvalid(L, "mobj_t");
 	lua_pushboolean(L, P_SceneryZMovement(actor));
-	P_CheckPosition(actor, actor->x, actor->y);
+	if (!P_MobjWasRemoved(actor))
+		P_CheckPosition(actor, actor->x, actor->y);
 	P_SetTarget(&tmthing, ptmthing);
 	return 1;
 }
@@ -2451,7 +2456,7 @@ static int lib_pFadeLight(lua_State *L)
 static int lib_pIsFlagAtBase(lua_State *L)
 {
 	mobjtype_t flag = luaL_checkinteger(L, 1);
-	NOHUD
+	//HUDSAFE
 	INLEVEL
 	if (flag >= NUMMOBJTYPES)
 		return luaL_error(L, "mobj type %d out of range (0 - %d)", flag, NUMMOBJTYPES-1);
@@ -2844,6 +2849,22 @@ static int lib_rTextureNumForName(lua_State *L)
 	return 1;
 }
 
+static int lib_rCheckTextureNameForNum(lua_State *L)
+{
+	INT32 num = (INT32)luaL_checkinteger(L, 1);
+	//HUDSAFE
+	lua_pushstring(L, R_CheckTextureNameForNum(num));
+	return 1;
+}
+
+static int lib_rTextureNameForNum(lua_State *L)
+{
+	INT32 num = (INT32)luaL_checkinteger(L, 1);
+	//HUDSAFE
+	lua_pushstring(L, R_TextureNameForNum(num));
+	return 1;
+}
+
 // R_DRAW
 ////////////
 static int lib_rGetColorByName(lua_State *L)
@@ -3523,7 +3544,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;
 
@@ -3531,10 +3552,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);
@@ -3587,14 +3606,14 @@ static int lib_gAddPlayer(lua_State *L)
 		char joinmsg[256];
 
 		// Truncate bot name
-		player_names[newplayernum][sizeof(*player_names) - 7] = '\0'; // The length of colored [BOT] + 1
+		player_names[newplayernum][sizeof(*player_names) - 8] = '\0'; // The length of colored [BOT] + 1
 
 		strcpy(joinmsg, M_GetText("\x82*Bot %s has joined the game (player %d)"));
 		strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
 		HU_AddChatText(joinmsg, false);
 
 		// Append blue [BOT] tag at the end
-		strlcat(player_names[newplayernum], "\x84[BOT]", sizeof(*player_names));
+		strlcat(player_names[newplayernum], "\x84[BOT]\x80", sizeof(*player_names));
 	}
 
 	LUA_PushUserdata(L, newplayer, META_PLAYER);
@@ -3628,6 +3647,16 @@ static int lib_gRemovePlayer(lua_State *L)
 }
 
 
+static int lib_gSetUsedCheats(lua_State *L)
+{
+	// Let large-scale level packs using Lua be able to add cheat commands.
+	boolean silent = lua_optboolean(L, 1);
+	//NOHUD
+	//INLEVEL
+	G_SetUsedCheats(silent);
+	return 0;
+}
+
 static int Lcheckmapnumber (lua_State *L, int idx, const char *fun)
 {
 	if (ISINLEVEL)
@@ -3795,7 +3824,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
@@ -3804,18 +3833,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;
@@ -4191,6 +4223,8 @@ static luaL_Reg lib[] = {
 	// r_data
 	{"R_CheckTextureNumForName",lib_rCheckTextureNumForName},
 	{"R_TextureNumForName",lib_rTextureNumForName},
+	{"R_CheckTextureNameForNum", lib_rCheckTextureNameForNum},
+	{"R_TextureNameForNum", lib_rTextureNameForNum},
 
 	// r_draw
 	{"R_GetColorByName", lib_rGetColorByName},
@@ -4230,6 +4264,7 @@ static luaL_Reg lib[] = {
 	{"G_AddGametype", lib_gAddGametype},
 	{"G_AddPlayer", lib_gAddPlayer},
 	{"G_RemovePlayer", lib_gRemovePlayer},
+	{"G_SetUsedCheats", lib_gSetUsedCheats},
 	{"G_BuildMapName",lib_gBuildMapName},
 	{"G_BuildMapTitle",lib_gBuildMapTitle},
 	{"G_FindMap",lib_gFindMap},
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index dcaad1416011941230285723037a193ade75102e..3783b8f7b6dac036a2e169c807e8e1079f130c76 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -357,7 +357,7 @@ static int lib_cvRegisterVar(lua_State *L)
 			if (lua_islightuserdata(L, 4))
 			{
 				CV_PossibleValue_t *pv = lua_touserdata(L, 4);
-				if (pv == CV_OnOff || pv == CV_YesNo || pv == CV_Unsigned || pv == CV_Natural)
+				if (pv == CV_OnOff || pv == CV_YesNo || pv == CV_Unsigned || pv == CV_Natural || pv == CV_TrueFalse)
 					cvar->PossibleValue = pv;
 				else
 					FIELDERROR("PossibleValue", "CV_PossibleValue_t expected, got unrecognised pointer")
@@ -566,27 +566,59 @@ static luaL_Reg lib[] = {
 	{NULL, NULL}
 };
 
+enum cvar_e
+{
+	cvar_name,
+	cvar_defaultvalue,
+	cvar_flags,
+	cvar_value,
+	cvar_string,
+	cvar_changed,
+};
+
+static const char *const cvar_opt[] = {
+	"name",
+	"defaultvalue",
+	"flags",
+	"value",
+	"string",
+	"changed",
+	NULL,
+};
+
+static int cvar_fields_ref = LUA_NOREF;
+
 static int cvar_get(lua_State *L)
 {
 	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
-	const char *field = luaL_checkstring(L, 2);
+	enum cvar_e field = Lua_optoption(L, 2, -1, cvar_fields_ref);
 
-	if(fastcmp(field,"name"))
+	switch (field)
+	{
+	case cvar_name:
 		lua_pushstring(L, cvar->name);
-	else if(fastcmp(field,"defaultvalue"))
+		break;
+	case cvar_defaultvalue:
 		lua_pushstring(L, cvar->defaultvalue);
-	else if(fastcmp(field,"flags"))
+		break;
+	case cvar_flags:
 		lua_pushinteger(L, cvar->flags);
-	else if(fastcmp(field,"value"))
+		break;
+	case cvar_value:
 		lua_pushinteger(L, cvar->value);
-	else if(fastcmp(field,"string"))
+		break;
+	case cvar_string:
 		lua_pushstring(L, cvar->string);
-	else if(fastcmp(field,"changed"))
+		break;
+	case cvar_changed:
 		lua_pushboolean(L, cvar->changed);
-	else if (devparm)
-		return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
-	else
-		return 0;
+		break;
+	default:
+		if (devparm)
+			return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS ".", lua_tostring(L, 2));
+		else
+			return 0;
+	}
 	return 1;
 }
 
@@ -598,6 +630,8 @@ int LUA_ConsoleLib(lua_State *L)
 		lua_setfield(L, -2, "__index");
 	lua_pop(L,1);
 
+	cvar_fields_ref = Lua_CreateFieldTable(L, cvar_opt);
+
 	// Set empty registry tables
 	lua_newtable(L);
 	lua_setfield(L, LUA_REGISTRYINDEX, "COM_Command");
@@ -618,6 +652,8 @@ int LUA_ConsoleLib(lua_State *L)
 	lua_setglobal(L, "CV_Unsigned");
 	lua_pushlightuserdata(L, CV_Natural);
 	lua_setglobal(L, "CV_Natural");
+	lua_pushlightuserdata(L, CV_TrueFalse);
+	lua_setglobal(L, "CV_TrueFalse");
 
 	// Set global functions
 	lua_pushvalue(L, LUA_GLOBALSINDEX);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 1a05997572f60542ca12d195b697f7aad87da564..6eec91273352db4adc598a80b7e0138795589640 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -95,6 +95,8 @@ static const char *const patch_opt[] = {
 	"topoffset",
 	NULL};
 
+static int patch_fields_ref = LUA_NOREF;
+
 // alignment types for v.drawString
 enum align {
 	align_left = 0,
@@ -196,6 +198,8 @@ static const char *const camera_opt[] = {
 	"momz",
 	NULL};
 
+static int camera_fields_ref = LUA_NOREF;
+
 static int lib_getHudInfo(lua_State *L)
 {
 	UINT32 i;
@@ -276,9 +280,8 @@ static int colormap_get(lua_State *L)
 static int patch_get(lua_State *L)
 {
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
-	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
+	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);
@@ -316,7 +319,7 @@ static int patch_set(lua_State *L)
 static int camera_get(lua_State *L)
 {
 	camera_t *cam = *((camera_t **)luaL_checkudata(L, 1, META_CAMERA));
-	enum cameraf field = luaL_checkoption(L, 2, NULL, camera_opt);
+	enum cameraf field = Lua_optoption(L, 2, -1, camera_fields_ref);
 
 	// cameras should always be valid unless I'm a nutter
 	I_Assert(cam != NULL);
@@ -372,7 +375,7 @@ static int camera_get(lua_State *L)
 static int camera_set(lua_State *L)
 {
 	camera_t *cam = *((camera_t **)luaL_checkudata(L, 1, META_CAMERA));
-	enum cameraf field = luaL_checkoption(L, 2, NULL, camera_opt);
+	enum cameraf field = Lua_optoption(L, 2, -1, camera_fields_ref);
 
 	I_Assert(cam != NULL);
 
@@ -432,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;
 }
@@ -1252,8 +1255,6 @@ static int libd_RandomKey(lua_State *L)
 	INT32 a = (INT32)luaL_checkinteger(L, 1);
 
 	HUDONLY
-	if (a > 65536)
-		LUA_UsageWarning(L, "v.RandomKey: range > 65536 is undefined behavior");
 	lua_pushinteger(L, M_RandomKey(a));
 	return 1;
 }
@@ -1264,13 +1265,6 @@ static int libd_RandomRange(lua_State *L)
 	INT32 b = (INT32)luaL_checkinteger(L, 2);
 
 	HUDONLY
-	if (b < a) {
-		INT32 c = a;
-		a = b;
-		b = c;
-	}
-	if ((b-a+1) > 65536)
-		LUA_UsageWarning(L, "v.RandomRange: range > 65536 is undefined behavior");
 	lua_pushinteger(L, M_RandomRange(a, b));
 	return 1;
 }
@@ -1444,6 +1438,8 @@ int LUA_HudLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	patch_fields_ref = Lua_CreateFieldTable(L, patch_opt);
+
 	luaL_newmetatable(L, META_CAMERA);
 		lua_pushcfunction(L, camera_get);
 		lua_setfield(L, -2, "__index");
@@ -1452,6 +1448,8 @@ int LUA_HudLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	camera_fields_ref = Lua_CreateFieldTable(L, camera_opt);
+
 	luaL_register(L, "hud", lib_hud);
 	return 0;
 }
@@ -1488,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_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c
index f46f207c100dc03e1256938dceda2e4e31b6eff7..c518ba52540ff874480d7cda2c8314a31987e993 100644
--- a/src/lua_hudlib_drawlist.c
+++ b/src/lua_hudlib_drawlist.c
@@ -103,7 +103,7 @@ huddrawlist_h LUA_HUD_CreateDrawList(void)
 {
 	huddrawlist_h drawlist;
 
-	drawlist = (huddrawlist_h) Z_CallocAlign(sizeof(struct huddrawlist_s), PU_STATIC, NULL, 64);
+	drawlist = (huddrawlist_h) Z_Calloc(sizeof(struct huddrawlist_s), PU_STATIC, NULL);
 	drawlist->items = NULL;
 	drawlist->items_capacity = 0;
 	drawlist->items_len = 0;
@@ -160,7 +160,7 @@ static size_t AllocateDrawItem(huddrawlist_h list)
 	{
 		if (list->items_capacity == 0) list->items_capacity = 128;
 		else list->items_capacity *= 2;
-		list->items = (drawitem_t *) Z_ReallocAlign(list->items, sizeof(struct drawitem_s) * list->items_capacity, PU_STATIC, NULL, 64);
+		list->items = (drawitem_t *) Z_Realloc(list->items, sizeof(struct drawitem_s) * list->items_capacity, PU_STATIC, NULL);
 	}
 
 	return list->items_len++;
@@ -177,9 +177,18 @@ static const char *CopyString(huddrawlist_h list, const char* str)
 	lenstr = strlen(str);
 	if (list->strbuf_capacity <= list->strbuf_len + lenstr + 1)
 	{
+		const char *old_offset = list->strbuf;
+		size_t i;
 		if (list->strbuf_capacity == 0) list->strbuf_capacity = 256;
 		else list->strbuf_capacity *= 2;
-		list->strbuf = (char*) Z_ReallocAlign(list->strbuf, sizeof(char) * list->strbuf_capacity, PU_STATIC, NULL, 8);
+		list->strbuf = (char*) Z_Realloc(list->strbuf, sizeof(char) * list->strbuf_capacity, PU_STATIC, NULL);
+
+		// align the string pointers to make sure old pointers don't point towards invalid addresses
+		// this is necessary since Z_ReallocAlign might actually move the string buffer in memory
+		for (i = 0; i < list->items_len; i++)
+		{
+			list->items[i].str += list->strbuf - old_offset;
+		}
 	}
 	const char *result = (const char *) &list->strbuf[list->strbuf_len];
 	strncpy(&list->strbuf[list->strbuf_len], str, lenstr + 1);
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 7388632d3c6cac8e74619ff4b5ac9bfca8ddb2e8..3764acf6a40945489506ce8d94b1b976fa771752 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -66,7 +66,7 @@ const char *const sfxinfo_wopt[] = {
 	"caption",
 	NULL};
 
-boolean actionsoverridden[NUMACTIONS] = {false};
+int actionsoverridden[NUMACTIONS][MAX_ACTION_RECURSION];
 
 //
 // Sprite Names
@@ -319,7 +319,7 @@ static int PopPivotSubTable(spriteframepivot_t *pivot, lua_State *L, int stk, in
 				else if (ikey == 2 || (key && fastcmp(key, "y")))
 					pivot[idx].y = (INT32)value;
 				else if (ikey == 3 || (key && fastcmp(key, "rotaxis")))
-					pivot[idx].rotaxis = (UINT8)value;
+					LUA_UsageWarning(L, "\"rotaxis\" is deprecated and will be removed.")
 				else if (ikey == -1 && (key != NULL))
 					FIELDERROR("pivot key", va("invalid option %s", key));
 				okcool = 1;
@@ -508,8 +508,6 @@ static int pivotlist_get(lua_State *L)
 	const char *field = luaL_checkstring(L, 2);
 	UINT8 frame;
 
-	I_Assert(framepivot != NULL);
-
 	frame = R_Char2Frame(field[0]);
 	if (frame == 255)
 		luaL_error(L, "invalid frame %s", field);
@@ -539,8 +537,6 @@ static int pivotlist_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter spriteframepivot_t in CMD building code!");
 
-	I_Assert(pivotlist != NULL);
-
 	frame = R_Char2Frame(field[0]);
 	if (frame == 255)
 		luaL_error(L, "invalid frame %s", field);
@@ -576,7 +572,10 @@ static int framepivot_get(lua_State *L)
 	else if (fastcmp("y", field))
 		lua_pushinteger(L, framepivot->y);
 	else if (fastcmp("rotaxis", field))
-		lua_pushinteger(L, (UINT8)framepivot->rotaxis);
+	{
+		LUA_UsageWarning(L, "\"rotaxis\" is deprecated and will be removed.");
+		lua_pushinteger(L, 0);
+	}
 	else
 		return luaL_error(L, va("Field %s does not exist in spriteframepivot_t", field));
 
@@ -602,7 +601,7 @@ static int framepivot_set(lua_State *L)
 	else if (fastcmp("y", field))
 		framepivot->y = luaL_checkinteger(L, 3);
 	else if (fastcmp("rotaxis", field))
-		framepivot->rotaxis = luaL_checkinteger(L, 3);
+		LUA_UsageWarning(L, "\"rotaxis\" is deprecated and will be removed.")
 	else
 		return luaL_error(L, va("Field %s does not exist in spriteframepivot_t", field));
 
@@ -645,8 +644,8 @@ static void A_Lua(mobj_t *actor)
 		if (lua_rawequal(gL, -1, -4))
 		{
 			found = true;
-			superactions[superstack] = lua_tostring(gL, -2); // "A_ACTION"
-			++superstack;
+			luaactions[luaactionstack] = lua_tostring(gL, -2); // "A_ACTION"
+			++luaactionstack;
 			lua_pop(gL, 2); // pop the name and function
 			break;
 		}
@@ -661,8 +660,8 @@ static void A_Lua(mobj_t *actor)
 
 	if (found)
 	{
-		--superstack;
-		superactions[superstack] = NULL;
+		--luaactionstack;
+		luaactions[luaactionstack] = NULL;
 	}
 }
 
@@ -812,22 +811,54 @@ boolean LUA_SetLuaAction(void *stv, const char *action)
 	return true; // action successfully set.
 }
 
+static UINT8 superstack[NUMACTIONS];
 boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 {
 	I_Assert(actor != NULL);
 
-	if (!actionsoverridden[actionnum]) // The action is not overriden,
-		return false; // action not called.
+	if (actionsoverridden[actionnum][0] == LUA_REFNIL)
+	{
+		// The action was not overridden at all,
+		// so just call the hardcoded version.
+		return false;
+	}
+
+	if (luaactionstack && fasticmp(actionpointers[actionnum].name, luaactions[luaactionstack-1]))
+	{
+		// The action is calling itself,
+		// so look up the next Lua reference in its stack.
 
-	if (superstack && fasticmp(actionpointers[actionnum].name, superactions[superstack-1])) // the action is calling itself,
-		return false; // let it call the hardcoded function instead.
+		// 0 is just the reference to the one we're calling,
+		// so we increment here.
+		superstack[actionnum]++;
 
+		if (superstack[actionnum] >= MAX_ACTION_RECURSION)
+		{
+			CONS_Alert(CONS_WARNING, "Max Lua super recursion reached! Cool it on calling super!\n");
+			superstack[actionnum] = 0;
+			return false;
+		}
+	}
+
+	if (actionsoverridden[actionnum][superstack[actionnum]] == LUA_REFNIL)
+	{
+		// No Lua reference beyond this point.
+		// Let it call the hardcoded function instead.
+
+		if (superstack[actionnum])
+		{
+			// Decrement super stack
+			superstack[actionnum]--;
+		}
+
+		return false;
+	}
+
+	// Push error function
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
-	// grab function by uppercase name.
-	lua_getfield(gL, LUA_REGISTRYINDEX, LREG_ACTIONS);
-	lua_getfield(gL, -1, actionpointers[actionnum].name);
-	lua_remove(gL, -2); // pop LREG_ACTIONS
+	// Push function by reference.
+	lua_getref(gL, actionsoverridden[actionnum][superstack[actionnum]]);
 
 	if (lua_isnil(gL, -1)) // no match
 	{
@@ -835,7 +866,7 @@ boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 		return false; // action not called.
 	}
 
-	if (superstack == MAXRECURSION)
+	if (luaactionstack >= MAX_ACTION_RECURSION)
 	{
 		CONS_Alert(CONS_WARNING, "Max Lua Action recursion reached! Cool it on the calling A_Action functions from inside A_Action functions!\n");
 		lua_pop(gL, 2); // pop function and error handler
@@ -849,14 +880,20 @@ boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 	lua_pushinteger(gL, var1);
 	lua_pushinteger(gL, var2);
 
-	superactions[superstack] = actionpointers[actionnum].name;
-	++superstack;
+	luaactions[luaactionstack] = actionpointers[actionnum].name;
+	++luaactionstack;
 
 	LUA_Call(gL, 3, 0, -(2 + 3));
 	lua_pop(gL, -1); // Error handler
 
-	--superstack;
-	superactions[superstack] = NULL;
+	if (superstack[actionnum])
+	{
+		// Decrement super stack
+		superstack[actionnum]--;
+	}
+
+	--luaactionstack;
+	luaactions[luaactionstack] = NULL;
 	return true; // action successfully called.
 }
 
@@ -1106,75 +1143,161 @@ static int lib_mobjinfolen(lua_State *L)
 	return 1;
 }
 
+enum mobjinfo_e
+{
+	mobjinfo_doomednum,
+	mobjinfo_spawnstate,
+	mobjinfo_spawnhealth,
+	mobjinfo_seestate,
+	mobjinfo_seesound,
+	mobjinfo_reactiontime,
+	mobjinfo_attacksound,
+	mobjinfo_painstate,
+	mobjinfo_painchance,
+	mobjinfo_painsound,
+	mobjinfo_meleestate,
+	mobjinfo_missilestate,
+	mobjinfo_deathstate,
+	mobjinfo_xdeathstate,
+	mobjinfo_deathsound,
+	mobjinfo_speed,
+	mobjinfo_radius,
+	mobjinfo_height,
+	mobjinfo_dispoffset,
+	mobjinfo_mass,
+	mobjinfo_damage,
+	mobjinfo_activesound,
+	mobjinfo_flags,
+	mobjinfo_raisestate,
+};
+
+const char *const mobjinfo_opt[] = {
+	"doomednum",
+	"spawnstate",
+	"spawnhealth",
+	"seestate",
+	"seesound",
+	"reactiontime",
+	"attacksound",
+	"painstate",
+	"painchance",
+	"painsound",
+	"meleestate",
+	"missilestate",
+	"deathstate",
+	"xdeathstate",
+	"deathsound",
+	"speed",
+	"radius",
+	"height",
+	"dispoffset",
+	"mass",
+	"damage",
+	"activesound",
+	"flags",
+	"raisestate",
+	NULL,
+};
+
+static int mobjinfo_fields_ref = LUA_NOREF;
+
 // mobjinfo_t *, field -> number
 static int mobjinfo_get(lua_State *L)
 {
 	mobjinfo_t *info = *((mobjinfo_t **)luaL_checkudata(L, 1, META_MOBJINFO));
-	const char *field = luaL_checkstring(L, 2);
+	enum mobjinfo_e field = Lua_optoption(L, 2, mobjinfo_doomednum, mobjinfo_fields_ref);
 
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
 
-	if (fastcmp(field,"doomednum"))
+	switch (field)
+	{
+	case mobjinfo_doomednum:
 		lua_pushinteger(L, info->doomednum);
-	else if (fastcmp(field,"spawnstate"))
+		break;
+	case mobjinfo_spawnstate:
 		lua_pushinteger(L, info->spawnstate);
-	else if (fastcmp(field,"spawnhealth"))
+		break;
+	case mobjinfo_spawnhealth:
 		lua_pushinteger(L, info->spawnhealth);
-	else if (fastcmp(field,"seestate"))
+		break;
+	case mobjinfo_seestate:
 		lua_pushinteger(L, info->seestate);
-	else if (fastcmp(field,"seesound"))
+		break;
+	case mobjinfo_seesound:
 		lua_pushinteger(L, info->seesound);
-	else if (fastcmp(field,"reactiontime"))
+		break;
+	case mobjinfo_reactiontime:
 		lua_pushinteger(L, info->reactiontime);
-	else if (fastcmp(field,"attacksound"))
+		break;
+	case mobjinfo_attacksound:
 		lua_pushinteger(L, info->attacksound);
-	else if (fastcmp(field,"painstate"))
+		break;
+	case mobjinfo_painstate:
 		lua_pushinteger(L, info->painstate);
-	else if (fastcmp(field,"painchance"))
+		break;
+	case mobjinfo_painchance:
 		lua_pushinteger(L, info->painchance);
-	else if (fastcmp(field,"painsound"))
+		break;
+	case mobjinfo_painsound:
 		lua_pushinteger(L, info->painsound);
-	else if (fastcmp(field,"meleestate"))
+		break;
+	case mobjinfo_meleestate:
 		lua_pushinteger(L, info->meleestate);
-	else if (fastcmp(field,"missilestate"))
+		break;
+	case mobjinfo_missilestate:
 		lua_pushinteger(L, info->missilestate);
-	else if (fastcmp(field,"deathstate"))
+		break;
+	case mobjinfo_deathstate:
 		lua_pushinteger(L, info->deathstate);
-	else if (fastcmp(field,"xdeathstate"))
+		break;
+	case mobjinfo_xdeathstate:
 		lua_pushinteger(L, info->xdeathstate);
-	else if (fastcmp(field,"deathsound"))
+		break;
+	case mobjinfo_deathsound:
 		lua_pushinteger(L, info->deathsound);
-	else if (fastcmp(field,"speed"))
+		break;
+	case mobjinfo_speed:
 		lua_pushinteger(L, info->speed); // sometimes it's fixed_t, sometimes it's not...
-	else if (fastcmp(field,"radius"))
+		break;
+	case mobjinfo_radius:
 		lua_pushfixed(L, info->radius);
-	else if (fastcmp(field,"height"))
+		break;
+	case mobjinfo_height:
 		lua_pushfixed(L, info->height);
-	else if (fastcmp(field,"dispoffset"))
+		break;
+	case mobjinfo_dispoffset:
 		lua_pushinteger(L, info->dispoffset);
-	else if (fastcmp(field,"mass"))
+		break;
+	case mobjinfo_mass:
 		lua_pushinteger(L, info->mass);
-	else if (fastcmp(field,"damage"))
+		break;
+	case mobjinfo_damage:
 		lua_pushinteger(L, info->damage);
-	else if (fastcmp(field,"activesound"))
+		break;
+	case mobjinfo_activesound:
 		lua_pushinteger(L, info->activesound);
-	else if (fastcmp(field,"flags"))
+		break;
+	case mobjinfo_flags:
 		lua_pushinteger(L, info->flags);
-	else if (fastcmp(field,"raisestate"))
+		break;
+	case mobjinfo_raisestate:
 		lua_pushinteger(L, info->raisestate);
-	else {
+		break;
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, info);
 		lua_rawget(L, -2);
 		if (!lua_istable(L, -1)) { // no extra values table
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", lua_tostring(L, 2));
 			return 0;
 		}
-		lua_getfield(L, -1, field);
+		lua_pushvalue(L, 2); // field name
+		lua_gettable(L, -2);
 		if (lua_isnil(L, -1)) // no value for this field
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", lua_tostring(L, 2));
+		break;
 	}
 	return 1;
 }
@@ -1183,7 +1306,7 @@ static int mobjinfo_get(lua_State *L)
 static int mobjinfo_set(lua_State *L)
 {
 	mobjinfo_t *info = *((mobjinfo_t **)luaL_checkudata(L, 1, META_MOBJINFO));
-	const char *field = luaL_checkstring(L, 2);
+	enum mobjinfo_e field = Lua_optoption(L, 2, -1, mobjinfo_fields_ref);
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
@@ -1193,55 +1316,81 @@ static int mobjinfo_set(lua_State *L)
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
 
-	if (fastcmp(field,"doomednum"))
+	switch (field)
+	{
+	case mobjinfo_doomednum:
 		info->doomednum = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spawnstate"))
+		break;
+	case mobjinfo_spawnstate:
 		info->spawnstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spawnhealth"))
+		break;
+	case mobjinfo_spawnhealth:
 		info->spawnhealth = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"seestate"))
+		break;
+	case mobjinfo_seestate:
 		info->seestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"seesound"))
+		break;
+	case mobjinfo_seesound:
 		info->seesound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"reactiontime"))
+		break;
+	case mobjinfo_reactiontime:
 		info->reactiontime = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"attacksound"))
+		break;
+	case mobjinfo_attacksound:
 		info->attacksound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painstate"))
+		break;
+	case mobjinfo_painstate:
 		info->painstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painchance"))
+		break;
+	case mobjinfo_painchance:
 		info->painchance = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painsound"))
+		break;
+	case mobjinfo_painsound:
 		info->painsound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"meleestate"))
+		break;
+	case mobjinfo_meleestate:
 		info->meleestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"missilestate"))
+		break;
+	case mobjinfo_missilestate:
 		info->missilestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deathstate"))
+		break;
+	case mobjinfo_deathstate:
 		info->deathstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"xdeathstate"))
+		break;
+	case mobjinfo_xdeathstate:
 		info->xdeathstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deathsound"))
+		break;
+	case mobjinfo_deathsound:
 		info->deathsound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"speed"))
+		break;
+	case mobjinfo_speed:
 		info->speed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"radius"))
+		break;
+	case mobjinfo_radius:
 		info->radius = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"height"))
+		break;
+	case mobjinfo_height:
 		info->height = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"dispoffset"))
+		break;
+	case mobjinfo_dispoffset:
 		info->dispoffset = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"mass"))
+		break;
+	case mobjinfo_mass:
 		info->mass = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"damage"))
+		break;
+	case mobjinfo_damage:
 		info->damage = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"activesound"))
+		break;
+	case mobjinfo_activesound:
 		info->activesound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flags"))
+		break;
+	case mobjinfo_flags:
 		info->flags = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"raisestate"))
+		break;
+	case mobjinfo_raisestate:
 		info->raisestate = luaL_checkinteger(L, 3);
-	else {
+		break;
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, info);
@@ -1249,18 +1398,17 @@ static int mobjinfo_set(lua_State *L)
 		if (lua_isnil(L, -1)) {
 			// This index doesn't have a table for extra values yet, let's make one.
 			lua_pop(L, 1);
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "mobjinfo_t", lua_tostring(L, 2));
 			lua_newtable(L);
 			lua_pushlightuserdata(L, info);
 			lua_pushvalue(L, -2); // ext value table
 			lua_rawset(L, -4); // LREG_EXTVARS table
 		}
+		lua_pushvalue(L, 2); // key
 		lua_pushvalue(L, 3); // value to store
-		lua_setfield(L, -2, field);
+		lua_settable(L, -3);
 		lua_pop(L, 2);
 	}
-	//else
-		//return luaL_error(L, LUA_QL("mobjinfo_t") " has no field named " LUA_QS, field);
 	return 0;
 }
 
@@ -1598,7 +1746,7 @@ static int lib_setSkinColor(lua_State *L)
 		else if (i == 6 || (str && fastcmp(str,"accessible"))) {
 			boolean v = lua_toboolean(L, 3);
 			if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-				return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
+				CONS_Alert(CONS_WARNING, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
 			else
 				info->accessible = v;
 		}
@@ -1693,7 +1841,7 @@ static int skincolor_set(lua_State *L)
 	else if (fastcmp(field,"accessible")) {
 		boolean v = lua_toboolean(L, 3);
 		if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-			return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
+			CONS_Alert(CONS_WARNING, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
 		else
 			info->accessible = v;
 	} else
@@ -1788,6 +1936,8 @@ int LUA_InfoLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	mobjinfo_fields_ref = Lua_CreateFieldTable(L, mobjinfo_opt);
+
 	luaL_newmetatable(L, META_SKINCOLOR);
 		lua_pushcfunction(L, skincolor_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 898651520d77ead06f05ccbe767fe618708e4c20..e343979939056ec3c18b482a3bfd3423c446d70e 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -33,12 +33,20 @@ enum sector_e {
 	sector_floorheight,
 	sector_ceilingheight,
 	sector_floorpic,
+	sector_floorxoffset,
+	sector_flooryoffset,
+	sector_floorangle,	
 	sector_ceilingpic,
+	sector_ceilingxoffset,
+	sector_ceilingyoffset,
+	sector_ceilingangle,
 	sector_lightlevel,
 	sector_floorlightlevel,
 	sector_floorlightabsolute,
+	sector_floorlightsec,	
 	sector_ceilinglightlevel,
 	sector_ceilinglightabsolute,
+	sector_ceilinglightsec,
 	sector_special,
 	sector_tag,
 	sector_taglist,
@@ -63,12 +71,20 @@ static const char *const sector_opt[] = {
 	"floorheight",
 	"ceilingheight",
 	"floorpic",
+	"floorxoffset",
+	"flooryoffset",
+	"floorangle",
 	"ceilingpic",
+	"ceilingxoffset",
+	"ceilingyoffset",
+	"ceilingangle",	
 	"lightlevel",
 	"floorlightlevel",
 	"floorlightabsolute",
+	"floorlightsec",
 	"ceilinglightlevel",
 	"ceilinglightabsolute",
+	"ceilinglightsec",	
 	"special",
 	"tag",
 	"taglist",
@@ -88,6 +104,8 @@ static const char *const sector_opt[] = {
 	"gravity",
 	NULL};
 
+static int sector_fields_ref = LUA_NOREF;
+
 enum subsector_e {
 	subsector_valid = 0,
 	subsector_sector,
@@ -104,6 +122,8 @@ static const char *const subsector_opt[] = {
 	"polyList",
 	NULL};
 
+static int subsector_fields_ref = LUA_NOREF;
+
 enum line_e {
 	line_valid = 0,
 	line_v1,
@@ -156,10 +176,18 @@ static const char *const line_opt[] = {
 	"callcount",
 	NULL};
 
+static int line_fields_ref = LUA_NOREF;
+
 enum side_e {
 	side_valid = 0,
 	side_textureoffset,
 	side_rowoffset,
+	side_offsetx_top,
+	side_offsety_top,
+	side_offsetx_mid,
+	side_offsety_mid,
+	side_offsetx_bot,
+	side_offsety_bot,
 	side_toptexture,
 	side_bottomtexture,
 	side_midtexture,
@@ -174,6 +202,12 @@ static const char *const side_opt[] = {
 	"valid",
 	"textureoffset",
 	"rowoffset",
+	"offsetx_top",
+	"offsety_top",
+	"offsetx_mid",
+	"offsety_mid",
+	"offsetx_bot",
+	"offsety_bot",
 	"toptexture",
 	"bottomtexture",
 	"midtexture",
@@ -184,6 +218,8 @@ static const char *const side_opt[] = {
 	"text",
 	NULL};
 
+static int side_fields_ref = LUA_NOREF;
+
 enum vertex_e {
 	vertex_valid = 0,
 	vertex_x,
@@ -204,6 +240,8 @@ static const char *const vertex_opt[] = {
 	"ceilingzset",
 	NULL};
 
+static int vertex_fields_ref = LUA_NOREF;
+
 enum ffloor_e {
 	ffloor_valid = 0,
 	ffloor_topheight,
@@ -256,6 +294,8 @@ static const char *const ffloor_opt[] = {
 	"bouncestrength",
 	NULL};
 
+static int ffloor_fields_ref = LUA_NOREF;
+
 #ifdef HAVE_LUA_SEGS
 enum seg_e {
 	seg_valid = 0,
@@ -285,6 +325,8 @@ static const char *const seg_opt[] = {
 	"polyseg",
 	NULL};
 
+static int seg_fields_ref = LUA_NOREF;
+
 enum node_e {
 	node_valid = 0,
 	node_x,
@@ -305,6 +347,8 @@ static const char *const node_opt[] = {
 	"children",
 	NULL};
 
+static int node_fields_ref = LUA_NOREF;
+
 enum nodechild_e {
 	nodechild_valid = 0,
 	nodechild_right,
@@ -356,6 +400,8 @@ static const char *const slope_opt[] = {
 	"flags",
 	NULL};
 
+static int slope_fields_ref = LUA_NOREF;
+
 // shared by both vector2_t and vector3_t
 enum vector_e {
 	vector_x = 0,
@@ -575,7 +621,7 @@ static int sectorlines_num(lua_State *L)
 static int sector_get(lua_State *L)
 {
 	sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	enum sector_e field = luaL_checkoption(L, 2, sector_opt[0], sector_opt);
+	enum sector_e field = Lua_optoption(L, 2, sector_valid, sector_fields_ref);
 	INT16 i;
 
 	if (!sector)
@@ -607,6 +653,21 @@ static int sector_get(lua_State *L)
 		lua_pushlstring(L, levelflat->name, i);
 		return 1;
 	}
+	case sector_floorxoffset:
+	{
+		lua_pushfixed(L, sector->floorxoffset);
+		return 1;
+	}
+	case sector_flooryoffset:
+	{
+		lua_pushfixed(L, sector->flooryoffset);
+		return 1;
+	}
+	case sector_floorangle: 
+	{
+		lua_pushangle(L, sector->floorangle);
+		return 1;
+	}	
 	case sector_ceilingpic: // ceilingpic
 	{
 		levelflat_t *levelflat = &levelflats[sector->ceilingpic];
@@ -616,6 +677,21 @@ static int sector_get(lua_State *L)
 		lua_pushlstring(L, levelflat->name, i);
 		return 1;
 	}
+	case sector_ceilingxoffset:
+	{
+		lua_pushfixed(L, sector->ceilingxoffset);
+		return 1;
+	}
+	case sector_ceilingyoffset:
+	{
+		lua_pushfixed(L, sector->ceilingyoffset);
+		return 1;
+	}
+	case sector_ceilingangle:
+	{
+		lua_pushangle(L, sector->ceilingangle);
+		return 1;
+	}	
 	case sector_lightlevel:
 		lua_pushinteger(L, sector->lightlevel);
 		return 1;
@@ -625,12 +701,18 @@ static int sector_get(lua_State *L)
 	case sector_floorlightabsolute:
 		lua_pushboolean(L, sector->floorlightabsolute);
 		return 1;
+	case sector_floorlightsec:
+		lua_pushinteger(L, sector->floorlightsec);
+		return 1;		
 	case sector_ceilinglightlevel:
 		lua_pushinteger(L, sector->ceilinglightlevel);
 		return 1;
 	case sector_ceilinglightabsolute:
 		lua_pushboolean(L, sector->ceilinglightabsolute);
 		return 1;
+	case sector_ceilinglightsec:
+		lua_pushinteger(L, sector->ceilinglightsec);
+		return 1;		
 	case sector_special:
 		lua_pushinteger(L, sector->special);
 		return 1;
@@ -697,7 +779,7 @@ static int sector_get(lua_State *L)
 static int sector_set(lua_State *L)
 {
 	sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	enum sector_e field = luaL_checkoption(L, 2, sector_opt[0], sector_opt);
+	enum sector_e field = Lua_optoption(L, 2, sector_valid, sector_fields_ref);
 
 	if (!sector)
 		return luaL_error(L, "accessed sector_t doesn't exist anymore.");
@@ -718,8 +800,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;
@@ -751,9 +834,27 @@ static int sector_set(lua_State *L)
 	case sector_floorpic:
 		sector->floorpic = P_AddLevelFlatRuntime(luaL_checkstring(L, 3));
 		break;
+	case sector_floorxoffset:
+		sector->floorxoffset = luaL_checkfixed(L, 3);
+		break;
+	case sector_flooryoffset:
+		sector->flooryoffset = luaL_checkfixed(L, 3);
+		break;
+	case sector_floorangle:
+		sector->floorangle = luaL_checkangle(L, 3);
+		break;				
 	case sector_ceilingpic:
 		sector->ceilingpic = P_AddLevelFlatRuntime(luaL_checkstring(L, 3));
 		break;
+	case sector_ceilingxoffset:
+		sector->ceilingxoffset = luaL_checkfixed(L, 3);
+		break;
+	case sector_ceilingyoffset:
+		sector->ceilingyoffset = luaL_checkfixed(L, 3);
+		break;
+	case sector_ceilingangle:
+		sector->ceilingangle = luaL_checkangle(L, 3);
+		break;
 	case sector_lightlevel:
 		sector->lightlevel = (INT16)luaL_checkinteger(L, 3);
 		break;
@@ -763,12 +864,18 @@ static int sector_set(lua_State *L)
 	case sector_floorlightabsolute:
 		sector->floorlightabsolute = luaL_checkboolean(L, 3);
 		break;
+	case sector_floorlightsec:
+		sector->floorlightsec = (INT32)luaL_checkinteger(L, 3);
+		break;		
 	case sector_ceilinglightlevel:
 		sector->ceilinglightlevel = (INT16)luaL_checkinteger(L, 3);
 		break;
 	case sector_ceilinglightabsolute:
 		sector->ceilinglightabsolute = luaL_checkboolean(L, 3);
 		break;
+	case sector_ceilinglightsec:
+		sector->ceilinglightsec = (INT32)luaL_checkinteger(L, 3);
+		break;		
 	case sector_special:
 		sector->special = (INT16)luaL_checkinteger(L, 3);
 		break;
@@ -814,7 +921,7 @@ static int sector_num(lua_State *L)
 static int subsector_get(lua_State *L)
 {
 	subsector_t *subsector = *((subsector_t **)luaL_checkudata(L, 1, META_SUBSECTOR));
-	enum subsector_e field = luaL_checkoption(L, 2, subsector_opt[0], subsector_opt);
+	enum subsector_e field = Lua_optoption(L, 2, subsector_valid, subsector_fields_ref);
 
 	if (!subsector)
 	{
@@ -898,7 +1005,7 @@ static int linestringargs_len(lua_State *L)
 static int line_get(lua_State *L)
 {
 	line_t *line = *((line_t **)luaL_checkudata(L, 1, META_LINE));
-	enum line_e field = luaL_checkoption(L, 2, line_opt[0], line_opt);
+	enum line_e field = Lua_optoption(L, 2, line_valid, line_fields_ref);
 
 	if (!line)
 	{
@@ -1002,8 +1109,24 @@ static int line_get(lua_State *L)
 		LUA_PushUserdata(L, line->polyobj, META_POLYOBJ);
 		return 1;
 	case line_text:
-		lua_pushstring(L, line->text);
-		return 1;
+		{
+			if (udmf)
+			{
+				LUA_Deprecated(L, "(linedef_t).text", "(linedef_t).stringargs");
+				lua_pushnil(L);
+				return 1;
+			}
+
+			if (line->special == 331 || line->special == 443)
+			{
+				// See P_ProcessLinedefsAfterSidedefs, P_ConvertBinaryLinedefTypes
+				lua_pushstring(L, line->stringargs[0]);
+			}
+			else
+				lua_pushnil(L);
+
+			return 1;
+		}
 	case line_callcount:
 		lua_pushinteger(L, line->callcount);
 		return 1;
@@ -1057,7 +1180,7 @@ static int sidenum_get(lua_State *L)
 static int side_get(lua_State *L)
 {
 	side_t *side = *((side_t **)luaL_checkudata(L, 1, META_SIDE));
-	enum side_e field = luaL_checkoption(L, 2, side_opt[0], side_opt);
+	enum side_e field = Lua_optoption(L, 2, side_valid, side_fields_ref);
 
 	if (!side)
 	{
@@ -1079,6 +1202,24 @@ static int side_get(lua_State *L)
 	case side_rowoffset:
 		lua_pushfixed(L, side->rowoffset);
 		return 1;
+	case side_offsetx_top:
+		lua_pushfixed(L, side->offsetx_top);
+		return 1;
+	case side_offsety_top:
+		lua_pushfixed(L, side->offsety_top);
+		return 1;
+	case side_offsetx_mid:
+		lua_pushfixed(L, side->offsetx_mid);
+		return 1;
+	case side_offsety_mid:
+		lua_pushfixed(L, side->offsety_mid);
+		return 1;
+	case side_offsetx_bot:
+		lua_pushfixed(L, side->offsetx_bot);
+		return 1;
+	case side_offsety_bot:
+		lua_pushfixed(L, side->offsety_bot);
+		return 1;
 	case side_toptexture:
 		lua_pushinteger(L, side->toptexture);
 		return 1;
@@ -1101,8 +1242,19 @@ static int side_get(lua_State *L)
 		lua_pushinteger(L, side->repeatcnt);
 		return 1;
 	case side_text:
-		lua_pushstring(L, side->text);
-		return 1;
+		{
+			if (udmf)
+			{
+				LUA_Deprecated(L, "(sidedef_t).text", "(sidedef_t).line.stringargs");
+				lua_pushnil(L);
+				return 1;
+			}
+
+			boolean isfrontside = side->line->sidenum[0] == side-sides;
+
+			lua_pushstring(L, side->line->stringargs[isfrontside ? 0 : 1]);
+			return 1;
+		}
 	}
 	return 0;
 }
@@ -1110,7 +1262,7 @@ static int side_get(lua_State *L)
 static int side_set(lua_State *L)
 {
 	side_t *side = *((side_t **)luaL_checkudata(L, 1, META_SIDE));
-	enum side_e field = luaL_checkoption(L, 2, side_opt[0], side_opt);
+	enum side_e field = Lua_optoption(L, 2, side_valid, side_fields_ref);
 
 	if (!side)
 	{
@@ -1128,14 +1280,33 @@ 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;
 	case side_rowoffset:
 		side->rowoffset = luaL_checkfixed(L, 3);
 		break;
+	case side_offsetx_top:
+		side->offsetx_top = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_top:
+		side->offsety_top = luaL_checkfixed(L, 3);
+		break;
+	case side_offsetx_mid:
+		side->offsetx_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_mid:
+		side->offsety_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_offsetx_bot:
+		side->offsetx_bot = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_bot:
+		side->offsety_bot = luaL_checkfixed(L, 3);
+		break;
 	case side_toptexture:
 		side->toptexture = luaL_checkinteger(L, 3);
 		break;
@@ -1166,7 +1337,7 @@ static int side_num(lua_State *L)
 static int vertex_get(lua_State *L)
 {
 	vertex_t *vertex = *((vertex_t **)luaL_checkudata(L, 1, META_VERTEX));
-	enum vertex_e field = luaL_checkoption(L, 2, vertex_opt[0], vertex_opt);
+	enum vertex_e field = Lua_optoption(L, 2, vertex_valid, vertex_fields_ref);
 
 	if (!vertex)
 	{
@@ -1220,7 +1391,7 @@ static int vertex_num(lua_State *L)
 static int seg_get(lua_State *L)
 {
 	seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
-	enum seg_e field = luaL_checkoption(L, 2, seg_opt[0], seg_opt);
+	enum seg_e field = Lua_optoption(L, 2, seg_valid, seg_fields_ref);
 
 	if (!seg)
 	{
@@ -1284,7 +1455,7 @@ static int seg_num(lua_State *L)
 static int node_get(lua_State *L)
 {
 	node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
-	enum node_e field = luaL_checkoption(L, 2, node_opt[0], node_opt);
+	enum node_e field = Lua_optoption(L, 2, node_valid, node_fields_ref);
 
 	if (!node)
 	{
@@ -1920,7 +2091,7 @@ static INT32 P_GetOldFOFFlags(ffloor_t *fflr)
 static int ffloor_get(lua_State *L)
 {
 	ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
-	enum ffloor_e field = luaL_checkoption(L, 2, ffloor_opt[0], ffloor_opt);
+	enum ffloor_e field = Lua_optoption(L, 2, ffloor_valid, ffloor_fields_ref);
 	INT16 i;
 
 	if (!ffloor)
@@ -2102,7 +2273,7 @@ static void P_SetOldFOFFlags(ffloor_t *fflr, oldffloortype_e oldflags)
 static int ffloor_set(lua_State *L)
 {
 	ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
-	enum ffloor_e field = luaL_checkoption(L, 2, ffloor_opt[0], ffloor_opt);
+	enum ffloor_e field = Lua_optoption(L, 2, ffloor_valid, ffloor_fields_ref);
 
 	if (!ffloor)
 		return luaL_error(L, "accessed ffloor_t doesn't exist anymore.");
@@ -2122,8 +2293,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;
@@ -2197,7 +2369,7 @@ static int ffloor_set(lua_State *L)
 static int slope_get(lua_State *L)
 {
 	pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
-	enum slope_e field = luaL_checkoption(L, 2, slope_opt[0], slope_opt);
+	enum slope_e field = Lua_optoption(L, 2, slope_valid, slope_fields_ref);
 
 	if (!slope)
 	{
@@ -2241,7 +2413,7 @@ static int slope_get(lua_State *L)
 static int slope_set(lua_State *L)
 {
 	pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
-	enum slope_e field = luaL_checkoption(L, 2, slope_opt[0], slope_opt);
+	enum slope_e field = Lua_optoption(L, 2, slope_valid, slope_fields_ref);
 
 	if (!slope)
 		return luaL_error(L, "accessed pslope_t doesn't exist anymore.");
@@ -2257,8 +2429,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);
 
@@ -2405,116 +2578,266 @@ static int lib_nummapheaders(lua_State *L)
 // mapheader_t //
 /////////////////
 
+enum mapheaderinfo_e
+{
+	mapheaderinfo_lvlttl,
+	mapheaderinfo_subttl,
+	mapheaderinfo_actnum,
+	mapheaderinfo_typeoflevel,
+	mapheaderinfo_nextlevel,
+	mapheaderinfo_marathonnext,
+	mapheaderinfo_keywords,
+	mapheaderinfo_musname,
+	mapheaderinfo_mustrack,
+	mapheaderinfo_muspos,
+	mapheaderinfo_musinterfadeout,
+	mapheaderinfo_musintername,
+	mapheaderinfo_muspostbossname,
+	mapheaderinfo_muspostbosstrack,
+	mapheaderinfo_muspostbosspos,
+	mapheaderinfo_muspostbossfadein,
+	mapheaderinfo_musforcereset,
+	mapheaderinfo_forcecharacter,
+	mapheaderinfo_weather,
+	mapheaderinfo_skynum,
+	mapheaderinfo_skybox_scalex,
+	mapheaderinfo_skybox_scaley,
+	mapheaderinfo_skybox_scalez,
+	mapheaderinfo_interscreen,
+	mapheaderinfo_runsoc,
+	mapheaderinfo_scriptname,
+	mapheaderinfo_precutscenenum,
+	mapheaderinfo_cutscenenum,
+	mapheaderinfo_countdown,
+	mapheaderinfo_palette,
+	mapheaderinfo_numlaps,
+	mapheaderinfo_unlockrequired,
+	mapheaderinfo_levelselect,
+	mapheaderinfo_bonustype,
+	mapheaderinfo_ltzzpatch,
+	mapheaderinfo_ltzztext,
+	mapheaderinfo_ltactdiamond,
+	mapheaderinfo_maxbonuslives,
+	mapheaderinfo_levelflags,
+	mapheaderinfo_menuflags,
+	mapheaderinfo_selectheading,
+	mapheaderinfo_startrings,
+	mapheaderinfo_sstimer,
+	mapheaderinfo_ssspheres,
+	mapheaderinfo_gravity,
+};
+
+static const char *const mapheaderinfo_opt[] = {
+	"lvlttl",
+	"subttl",
+	"actnum",
+	"typeoflevel",
+	"nextlevel",
+	"marathonnext",
+	"keywords",
+	"musname",
+	"mustrack",
+	"muspos",
+	"musinterfadeout",
+	"musintername",
+	"muspostbossname",
+	"muspostbosstrack",
+	"muspostbosspos",
+	"muspostbossfadein",
+	"musforcereset",
+	"forcecharacter",
+	"weather",
+	"skynum",
+	"skybox_scalex",
+	"skybox_scaley",
+	"skybox_scalez",
+	"interscreen",
+	"runsoc",
+	"scriptname",
+	"precutscenenum",
+	"cutscenenum",
+	"countdown",
+	"palette",
+	"numlaps",
+	"unlockrequired",
+	"levelselect",
+	"bonustype",
+	"ltzzpatch",
+	"ltzztext",
+	"ltactdiamond",
+	"maxbonuslives",
+	"levelflags",
+	"menuflags",
+	"selectheading",
+	"startrings",
+	"sstimer",
+	"ssspheres",
+	"gravity",
+	NULL,
+};
+
+static int mapheaderinfo_fields_ref = LUA_NOREF;
+
 static int mapheaderinfo_get(lua_State *L)
 {
 	mapheader_t *header = *((mapheader_t **)luaL_checkudata(L, 1, META_MAPHEADER));
-	const char *field = luaL_checkstring(L, 2);
+	enum mapheaderinfo_e field = Lua_optoption(L, 2, -1, mapheaderinfo_fields_ref);
 	INT16 i;
-	if (fastcmp(field,"lvlttl"))
+
+	switch (field)
+	{
+	case mapheaderinfo_lvlttl:
 		lua_pushstring(L, header->lvlttl);
-	else if (fastcmp(field,"subttl"))
+		break;
+	case mapheaderinfo_subttl:
 		lua_pushstring(L, header->subttl);
-	else if (fastcmp(field,"actnum"))
+		break;
+	case mapheaderinfo_actnum:
 		lua_pushinteger(L, header->actnum);
-	else if (fastcmp(field,"typeoflevel"))
+		break;
+	case mapheaderinfo_typeoflevel:
 		lua_pushinteger(L, header->typeoflevel);
-	else if (fastcmp(field,"nextlevel"))
+		break;
+	case mapheaderinfo_nextlevel:
 		lua_pushinteger(L, header->nextlevel);
-	else if (fastcmp(field,"marathonnext"))
+		break;
+	case mapheaderinfo_marathonnext:
 		lua_pushinteger(L, header->marathonnext);
-	else if (fastcmp(field,"keywords"))
+		break;
+	case mapheaderinfo_keywords:
 		lua_pushstring(L, header->keywords);
-	else if (fastcmp(field,"musname"))
+		break;
+	case mapheaderinfo_musname:
 		lua_pushstring(L, header->musname);
-	else if (fastcmp(field,"mustrack"))
+		break;
+	case mapheaderinfo_mustrack:
 		lua_pushinteger(L, header->mustrack);
-	else if (fastcmp(field,"muspos"))
+		break;
+	case mapheaderinfo_muspos:
 		lua_pushinteger(L, header->muspos);
-	else if (fastcmp(field,"musinterfadeout"))
+		break;
+	case mapheaderinfo_musinterfadeout:
 		lua_pushinteger(L, header->musinterfadeout);
-	else if (fastcmp(field,"musintername"))
+		break;
+	case mapheaderinfo_musintername:
 		lua_pushstring(L, header->musintername);
-	else if (fastcmp(field,"muspostbossname"))
+		break;
+	case mapheaderinfo_muspostbossname:
 		lua_pushstring(L, header->muspostbossname);
-	else if (fastcmp(field,"muspostbosstrack"))
+		break;
+	case mapheaderinfo_muspostbosstrack:
 		lua_pushinteger(L, header->muspostbosstrack);
-	else if (fastcmp(field,"muspostbosspos"))
+		break;
+	case mapheaderinfo_muspostbosspos:
 		lua_pushinteger(L, header->muspostbosspos);
-	else if (fastcmp(field,"muspostbossfadein"))
+		break;
+	case mapheaderinfo_muspostbossfadein:
 		lua_pushinteger(L, header->muspostbossfadein);
-	else if (fastcmp(field,"musforcereset"))
+		break;
+	case mapheaderinfo_musforcereset:
 		lua_pushinteger(L, header->musforcereset);
-	else if (fastcmp(field,"forcecharacter"))
+		break;
+	case mapheaderinfo_forcecharacter:
 		lua_pushstring(L, header->forcecharacter);
-	else if (fastcmp(field,"weather"))
+		break;
+	case mapheaderinfo_weather:
 		lua_pushinteger(L, header->weather);
-	else if (fastcmp(field,"skynum"))
+		break;
+	case mapheaderinfo_skynum:
 		lua_pushinteger(L, header->skynum);
-	else if (fastcmp(field,"skybox_scalex"))
+		break;
+	case mapheaderinfo_skybox_scalex:
 		lua_pushinteger(L, header->skybox_scalex);
-	else if (fastcmp(field,"skybox_scaley"))
+		break;
+	case mapheaderinfo_skybox_scaley:
 		lua_pushinteger(L, header->skybox_scaley);
-	else if (fastcmp(field,"skybox_scalez"))
+		break;
+	case mapheaderinfo_skybox_scalez:
 		lua_pushinteger(L, header->skybox_scalez);
-	else if (fastcmp(field,"interscreen")) {
+		break;
+	case mapheaderinfo_interscreen:
 		for (i = 0; i < 8; i++)
 			if (!header->interscreen[i])
 				break;
 		lua_pushlstring(L, header->interscreen, i);
-	} else if (fastcmp(field,"runsoc"))
+		break;
+	case mapheaderinfo_runsoc:
 		lua_pushstring(L, header->runsoc);
-	else if (fastcmp(field,"scriptname"))
+		break;
+	case mapheaderinfo_scriptname:
 		lua_pushstring(L, header->scriptname);
-	else if (fastcmp(field,"precutscenenum"))
+		break;
+	case mapheaderinfo_precutscenenum:
 		lua_pushinteger(L, header->precutscenenum);
-	else if (fastcmp(field,"cutscenenum"))
+		break;
+	case mapheaderinfo_cutscenenum:
 		lua_pushinteger(L, header->cutscenenum);
-	else if (fastcmp(field,"countdown"))
+		break;
+	case mapheaderinfo_countdown:
 		lua_pushinteger(L, header->countdown);
-	else if (fastcmp(field,"palette"))
+		break;
+	case mapheaderinfo_palette:
 		lua_pushinteger(L, header->palette);
-	else if (fastcmp(field,"numlaps"))
+		break;
+	case mapheaderinfo_numlaps:
 		lua_pushinteger(L, header->numlaps);
-	else if (fastcmp(field,"unlockrequired"))
+		break;
+	case mapheaderinfo_unlockrequired:
 		lua_pushinteger(L, header->unlockrequired);
-	else if (fastcmp(field,"levelselect"))
+		break;
+	case mapheaderinfo_levelselect:
 		lua_pushinteger(L, header->levelselect);
-	else if (fastcmp(field,"bonustype"))
+		break;
+	case mapheaderinfo_bonustype:
 		lua_pushinteger(L, header->bonustype);
-	else if (fastcmp(field,"ltzzpatch"))
+		break;
+	case mapheaderinfo_ltzzpatch:
 		lua_pushstring(L, header->ltzzpatch);
-	else if (fastcmp(field,"ltzztext"))
+		break;
+	case mapheaderinfo_ltzztext:
 		lua_pushstring(L, header->ltzztext);
-	else if (fastcmp(field,"ltactdiamond"))
+		break;
+	case mapheaderinfo_ltactdiamond:
 		lua_pushstring(L, header->ltactdiamond);
-	else if (fastcmp(field,"maxbonuslives"))
+		break;
+	case mapheaderinfo_maxbonuslives:
 		lua_pushinteger(L, header->maxbonuslives);
-	else if (fastcmp(field,"levelflags"))
+		break;
+	case mapheaderinfo_levelflags:
 		lua_pushinteger(L, header->levelflags);
-	else if (fastcmp(field,"menuflags"))
+		break;
+	case mapheaderinfo_menuflags:
 		lua_pushinteger(L, header->menuflags);
-	else if (fastcmp(field,"selectheading"))
+		break;
+	case mapheaderinfo_selectheading:
 		lua_pushstring(L, header->selectheading);
-	else if (fastcmp(field,"startrings"))
+		break;
+	case mapheaderinfo_startrings:
 		lua_pushinteger(L, header->startrings);
-	else if (fastcmp(field, "sstimer"))
+		break;
+	case mapheaderinfo_sstimer:
 		lua_pushinteger(L, header->sstimer);
-	else if (fastcmp(field, "ssspheres"))
+		break;
+	case mapheaderinfo_ssspheres:
 		lua_pushinteger(L, header->ssspheres);
-	else if (fastcmp(field, "gravity"))
+		break;
+	case mapheaderinfo_gravity:
 		lua_pushfixed(L, header->gravity);
+		break;
 	// TODO add support for reading numGradedMares and grades
-	else {
+	default:
+	{
 		// Read custom vars now
 		// (note: don't include the "LUA." in your lua scripts!)
 		UINT8 j = 0;
-		for (;j < header->numCustomOptions && !fastcmp(field, header->customopts[j].option); ++j);
+		for (;j < header->numCustomOptions && !fastcmp(lua_tostring(L, 2), header->customopts[j].option); ++j);
 
 		if(j < header->numCustomOptions)
 			lua_pushstring(L, header->customopts[j].value);
 		else
 			lua_pushnil(L);
 	}
+	}
 	return 1;
 }
 
@@ -2539,6 +2862,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	sector_fields_ref = Lua_CreateFieldTable(L, sector_opt);
+
 	luaL_newmetatable(L, META_SUBSECTOR);
 		lua_pushcfunction(L, subsector_get);
 		lua_setfield(L, -2, "__index");
@@ -2547,6 +2872,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	subsector_fields_ref = Lua_CreateFieldTable(L, subsector_opt);
+
 	luaL_newmetatable(L, META_LINE);
 		lua_pushcfunction(L, line_get);
 		lua_setfield(L, -2, "__index");
@@ -2555,6 +2882,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	line_fields_ref = Lua_CreateFieldTable(L, line_opt);
+
 	luaL_newmetatable(L, META_LINEARGS);
 		lua_pushcfunction(L, lineargs_get);
 		lua_setfield(L, -2, "__index");
@@ -2587,6 +2916,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	side_fields_ref = Lua_CreateFieldTable(L, side_opt);
+
 	luaL_newmetatable(L, META_VERTEX);
 		lua_pushcfunction(L, vertex_get);
 		lua_setfield(L, -2, "__index");
@@ -2595,6 +2926,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	vertex_fields_ref = Lua_CreateFieldTable(L, vertex_opt);
+
 	luaL_newmetatable(L, META_FFLOOR);
 		lua_pushcfunction(L, ffloor_get);
 		lua_setfield(L, -2, "__index");
@@ -2603,6 +2936,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L, 1);
 
+	ffloor_fields_ref = Lua_CreateFieldTable(L, ffloor_opt);
+
 #ifdef HAVE_LUA_SEGS
 	luaL_newmetatable(L, META_SEG);
 		lua_pushcfunction(L, seg_get);
@@ -2612,6 +2947,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	seg_fields_ref = Lua_CreateFieldTable(L, seg_opt);
+
 	luaL_newmetatable(L, META_NODE);
 		lua_pushcfunction(L, node_get);
 		lua_setfield(L, -2, "__index");
@@ -2620,6 +2957,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	node_fields_ref = Lua_CreateFieldTable(L, node_opt);
+
 	luaL_newmetatable(L, META_NODEBBOX);
 		//lua_pushcfunction(L, nodebbox_get);
 		//lua_setfield(L, -2, "__index");
@@ -2646,6 +2985,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L, 1);
 
+	slope_fields_ref = Lua_CreateFieldTable(L, slope_opt);
+
 	luaL_newmetatable(L, META_VECTOR2);
 		lua_pushcfunction(L, vector2_get);
 		lua_setfield(L, -2, "__index");
@@ -2664,6 +3005,8 @@ int LUA_MapLib(lua_State *L)
 		//lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	mapheaderinfo_fields_ref = Lua_CreateFieldTable(L, mapheaderinfo_opt);
+
 	LUA_PushTaggableObjectArray(L, "sectors",
 			lib_iterateSectors,
 			lib_getSector,
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 7d2c775955ba9d2b40d5aedfe960d8e9f5bee13a..fddf958beb783e27671cb966c3afbc3e20b0c620 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -33,7 +33,8 @@ enum mobj_e {
 	mobj_angle,
 	mobj_pitch,
 	mobj_roll,
-	mobj_rollangle,
+	mobj_spriteroll,
+	mobj_rollangle, // backwards compat
 	mobj_sprite,
 	mobj_frame,
 	mobj_sprite2,
@@ -43,6 +44,8 @@ enum mobj_e {
 	mobj_spritexoffset,
 	mobj_spriteyoffset,
 	mobj_floorspriteslope,
+	mobj_drawonlyforplayer,
+	mobj_dontdrawforviewmobj,
 	mobj_touching_sectorlist,
 	mobj_subsector,
 	mobj_floorz,
@@ -96,7 +99,8 @@ enum mobj_e {
 	mobj_standingslope,
 	mobj_colorized,
 	mobj_mirrored,
-	mobj_shadowscale
+	mobj_shadowscale,
+	mobj_dispoffset
 };
 
 static const char *const mobj_opt[] = {
@@ -109,7 +113,8 @@ static const char *const mobj_opt[] = {
 	"angle",
 	"pitch",
 	"roll",
-	"rollangle",
+	"spriteroll",
+	"rollangle", // backwards compat
 	"sprite",
 	"frame",
 	"sprite2",
@@ -119,6 +124,8 @@ static const char *const mobj_opt[] = {
 	"spritexoffset",
 	"spriteyoffset",
 	"floorspriteslope",
+	"drawonlyforplayer",
+	"dontdrawforviewmobj",
 	"touching_sectorlist",
 	"subsector",
 	"floorz",
@@ -173,14 +180,17 @@ static const char *const mobj_opt[] = {
 	"colorized",
 	"mirrored",
 	"shadowscale",
+	"dispoffset",
 	NULL};
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field])
 
+static int mobj_fields_ref = LUA_NOREF;
+
 static int mobj_get(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-	enum mobj_e field = Lua_optoption(L, 2, NULL, mobj_opt);
+	enum mobj_e field = Lua_optoption(L, 2, -1, mobj_fields_ref);
 	lua_settop(L, 2);
 
 	if (!mo || !ISINLEVEL) {
@@ -225,8 +235,9 @@ static int mobj_get(lua_State *L)
 	case mobj_roll:
 		lua_pushangle(L, mo->roll);
 		break;
-	case mobj_rollangle:
-		lua_pushangle(L, mo->rollangle);
+	case mobj_spriteroll:
+	case mobj_rollangle: // backwards compat
+		lua_pushangle(L, mo->spriteroll);
 		break;
 	case mobj_sprite:
 		lua_pushinteger(L, mo->sprite);
@@ -255,6 +266,17 @@ static int mobj_get(lua_State *L)
 	case mobj_floorspriteslope:
 		LUA_PushUserdata(L, mo->floorspriteslope, META_SLOPE);
 		break;
+	case mobj_drawonlyforplayer:
+		LUA_PushUserdata(L, mo->drawonlyforplayer, META_PLAYER);
+		break;
+	case mobj_dontdrawforviewmobj:
+		if (mo->dontdrawforviewmobj && P_MobjWasRemoved(mo->dontdrawforviewmobj))
+		{ // don't put invalid mobj back into Lua.
+			P_SetTarget(&mo->dontdrawforviewmobj, NULL);
+			return 0;
+		}
+		LUA_PushUserdata(L, mo->dontdrawforviewmobj, META_MOBJ);
+		break;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -439,6 +461,9 @@ static int mobj_get(lua_State *L)
 	case mobj_shadowscale:
 		lua_pushfixed(L, mo->shadowscale);
 		break;
+	case mobj_dispoffset:
+		lua_pushinteger(L, mo->dispoffset);
+		break;
 	default: // extra custom variables in Lua memory
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -462,7 +487,7 @@ static int mobj_get(lua_State *L)
 static int mobj_set(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-	enum mobj_e field = Lua_optoption(L, 2, mobj_opt[0], mobj_opt);
+	enum mobj_e field = Lua_optoption(L, 2, mobj_valid, mobj_fields_ref);
 	lua_settop(L, 3);
 
 	INLEVEL
@@ -511,8 +536,9 @@ static int mobj_set(lua_State *L)
 	case mobj_roll:
 		mo->roll = luaL_checkangle(L, 3);
 		break;
-	case mobj_rollangle:
-		mo->rollangle = luaL_checkangle(L, 3);
+	case mobj_spriteroll:
+	case mobj_rollangle: // backwards compat
+		mo->spriteroll = luaL_checkangle(L, 3);
 		break;
 	case mobj_sprite:
 		mo->sprite = luaL_checkinteger(L, 3);
@@ -540,6 +566,24 @@ static int mobj_set(lua_State *L)
 		break;
 	case mobj_floorspriteslope:
 		return NOSET;
+	case mobj_drawonlyforplayer:
+		if (lua_isnil(L, 3))
+			mo->drawonlyforplayer = NULL;
+		else
+		{
+			player_t *drawonlyforplayer = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
+			mo->drawonlyforplayer = drawonlyforplayer;
+		}
+		break;
+	case mobj_dontdrawforviewmobj:
+		if (lua_isnil(L, 3))
+			P_SetTarget(&mo->dontdrawforviewmobj, NULL);
+		else
+		{
+			mobj_t *dontdrawforviewmobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			P_SetTarget(&mo->dontdrawforviewmobj, dontdrawforviewmobj);
+		}
+		break;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -804,6 +848,9 @@ static int mobj_set(lua_State *L)
 	case mobj_shadowscale:
 		mo->shadowscale = luaL_checkfixed(L, 3);
 		break;
+	case mobj_dispoffset:
+		mo->dispoffset = luaL_checkinteger(L, 3);
+		break;
 	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -868,14 +915,59 @@ static int thingstringargs_len(lua_State *L)
 	return 1;
 }
 
+enum mapthing_e {
+	mapthing_valid = 0,
+	mapthing_x,
+	mapthing_y,
+	mapthing_angle,
+	mapthing_pitch,
+	mapthing_roll,
+	mapthing_type,
+	mapthing_options,
+	mapthing_scale,
+	mapthing_spritexscale,
+	mapthing_spriteyscale,
+	mapthing_z,
+	mapthing_extrainfo,
+	mapthing_tag,
+	mapthing_taglist,
+	mapthing_args,
+	mapthing_stringargs,
+	mapthing_mobj,
+};
+
+const char *const mapthing_opt[] = {
+	"valid",
+	"x",
+	"y",
+	"angle",
+	"pitch",
+	"roll",
+	"type",
+	"options",
+	"scale",
+	"spritexscale",
+	"spriteyscale",
+	"z",
+	"extrainfo",
+	"tag",
+	"taglist",
+	"args",
+	"stringargs",
+	"mobj",
+	NULL,
+};
+
+static int mapthing_fields_ref = LUA_NOREF;
+
 static int mapthing_get(lua_State *L)
 {
 	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
-	const char *field = luaL_checkstring(L, 2);
-	lua_Integer number;
+	enum mapthing_e field = Lua_optoption(L, 2, -1, mapthing_fields_ref);
+	lua_settop(L, 2);
 
 	if (!mt) {
-		if (fastcmp(field,"valid")) {
+		if (field == mapthing_valid) {
 			lua_pushboolean(L, false);
 			return 1;
 		}
@@ -884,62 +976,77 @@ static int mapthing_get(lua_State *L)
 		return 0;
 	}
 
-	if (fastcmp(field,"valid")) {
-		lua_pushboolean(L, true);
-		return 1;
-	} else if(fastcmp(field,"x"))
-		number = mt->x;
-	else if(fastcmp(field,"y"))
-		number = mt->y;
-	else if(fastcmp(field,"angle"))
-		number = mt->angle;
-	else if(fastcmp(field,"pitch"))
-		number = mt->pitch;
-	else if(fastcmp(field,"roll"))
-		number = mt->roll;
-	else if(fastcmp(field,"type"))
-		number = mt->type;
-	else if(fastcmp(field,"options"))
-		number = mt->options;
-	else if(fastcmp(field,"scale"))
-		number = mt->scale;
-	else if(fastcmp(field,"z"))
-		number = mt->z;
-	else if(fastcmp(field,"extrainfo"))
-		number = mt->extrainfo;
-	else if(fastcmp(field,"tag"))
-		number = Tag_FGet(&mt->tags);
-	else if(fastcmp(field,"taglist"))
-	{
-		LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
-		return 1;
-	}
-	else if(fastcmp(field,"args"))
+	switch (field)
 	{
-		LUA_PushUserdata(L, mt->args, META_THINGARGS);
-		return 1;
-	}
-	else if(fastcmp(field,"stringargs"))
-	{
-		LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS);
-		return 1;
+		case mapthing_valid:
+			lua_pushboolean(L, true);
+			break;
+		case mapthing_x:
+			lua_pushinteger(L, mt->x);
+			break;
+		case mapthing_y:
+			lua_pushinteger(L, mt->y);
+			break;
+		case mapthing_angle:
+			lua_pushinteger(L, mt->angle);
+			break;
+		case mapthing_pitch:
+			lua_pushinteger(L, mt->pitch);
+			break;
+		case mapthing_roll:
+			lua_pushinteger(L, mt->roll);
+			break;
+		case mapthing_type:
+			lua_pushinteger(L, mt->type);
+			break;
+		case mapthing_options:
+			lua_pushinteger(L, mt->options);
+			break;
+		case mapthing_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);
+			break;
+		case mapthing_extrainfo:
+			lua_pushinteger(L, mt->extrainfo);
+			break;
+		case mapthing_tag:
+			lua_pushinteger(L, Tag_FGet(&mt->tags));
+			break;
+		case mapthing_taglist:
+			LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
+			break;
+		case mapthing_args:
+			LUA_PushUserdata(L, mt->args, META_THINGARGS);
+			break;
+		case mapthing_stringargs:
+			LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS);
+			break;
+		case mapthing_mobj:
+			LUA_PushUserdata(L, mt->mobj, META_MOBJ);
+			break;
+		default:
+			if (devparm)
+				return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
+			else
+				return 0;
 	}
-	else if(fastcmp(field,"mobj")) {
-		LUA_PushUserdata(L, mt->mobj, META_MOBJ);
-		return 1;
-	} else if (devparm)
-		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
-	else
-		return 0;
 
-	lua_pushinteger(L, number);
 	return 1;
 }
 
 static int mapthing_set(lua_State *L)
 {
 	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
-	const char *field = luaL_checkstring(L, 2);
+	enum mapthing_e field = Lua_optoption(L, 2, -1, mapthing_fields_ref);
+	lua_settop(L, 3);
 
 	if (!mt)
 		return luaL_error(L, "accessed mapthing_t doesn't exist anymore.");
@@ -949,39 +1056,60 @@ static int mapthing_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter mapthing_t in CMD building code!");
 
-	if(fastcmp(field,"x"))
-		mt->x = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"y"))
-		mt->y = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"angle"))
-		mt->angle = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"pitch"))
-		mt->pitch = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"roll"))
-		mt->roll = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"type"))
-		mt->type = (UINT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"options"))
-		mt->options = (UINT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"scale"))
-		mt->scale = luaL_checkfixed(L, 3);
-	else if(fastcmp(field,"z"))
-		mt->z = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"extrainfo"))
+	switch (field)
 	{
-		INT32 extrainfo = luaL_checkinteger(L, 3);
-		if (extrainfo & ~15)
-			return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
-		mt->extrainfo = (UINT8)extrainfo;
+		case mapthing_x:
+			mt->x = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_y:
+			mt->y = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_angle:
+			mt->angle = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_pitch:
+			mt->pitch = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_roll:
+			mt->roll = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_type:
+			mt->type = (UINT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_options:
+			mt->options = (UINT16)luaL_checkinteger(L, 3);
+			break;
+		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;
+		case mapthing_extrainfo:
+		{
+			INT32 extrainfo = luaL_checkinteger(L, 3);
+			if (extrainfo & ~15)
+				return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
+			mt->extrainfo = (UINT8)extrainfo;
+			break;
+		}
+		case mapthing_tag:
+			Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
+			break;
+		case mapthing_taglist:
+			return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
+		case mapthing_mobj:
+			mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			break;
+		default:
+			return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
 	}
-	else if (fastcmp(field,"tag"))
-		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
-	else if (fastcmp(field,"taglist"))
-		return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
-	else if(fastcmp(field,"mobj"))
-		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
-	else
-		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
 
 	return 0;
 }
@@ -1043,6 +1171,8 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	mobj_fields_ref = Lua_CreateFieldTable(L, mobj_opt);
+
 	luaL_newmetatable(L, META_THINGARGS);
 		lua_pushcfunction(L, thingargs_get);
 		lua_setfield(L, -2, "__index");
@@ -1070,6 +1200,8 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	mapthing_fields_ref = Lua_CreateFieldTable(L, mapthing_opt);
+
 	LUA_PushTaggableObjectArray(L, "mapthings",
 			lib_iterateMapthings,
 			lib_getMapthing,
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index f7e14e78f96a89e490069da4fcadfffdfc59dd7c..827e5a405fffb7fa1dda25f58aea049dc244bf38 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -80,332 +80,784 @@ static int lib_lenPlayer(lua_State *L)
 	return 1;
 }
 
+enum player_e
+{
+	player_valid,
+	player_name,
+	player_realmo,
+	player_mo,
+	player_cmd,
+	player_playerstate,
+	player_camerascale,
+	player_shieldscale,
+	player_viewz,
+	player_viewheight,
+	player_deltaviewheight,
+	player_bob,
+	player_viewrollangle,
+	player_aiming,
+	player_drawangle,
+	player_rings,
+	player_spheres,
+	player_pity,
+	player_currentweapon,
+	player_ringweapons,
+	player_ammoremoval,
+	player_ammoremovaltimer,
+	player_ammoremovalweapon,
+	player_powers,
+	player_pflags,
+	player_panim,
+	player_flashcount,
+	player_flashpal,
+	player_skincolor,
+	player_skin,
+	player_availabilities,
+	player_score,
+	player_recordscore,
+	player_dashspeed,
+	player_normalspeed,
+	player_runspeed,
+	player_thrustfactor,
+	player_accelstart,
+	player_acceleration,
+	player_charability,
+	player_charability2,
+	player_charflags,
+	player_thokitem,
+	player_spinitem,
+	player_revitem,
+	player_followitem,
+	player_followmobj,
+	player_actionspd,
+	player_mindash,
+	player_maxdash,
+	player_jumpfactor,
+	player_height,
+	player_spinheight,
+	player_lives,
+	player_continues,
+	player_xtralife,
+	player_gotcontinue,
+	player_speed,
+	player_secondjump,
+	player_fly1,
+	player_scoreadd,
+	player_glidetime,
+	player_climbing,
+	player_deadtimer,
+	player_exiting,
+	player_homing,
+	player_dashmode,
+	player_skidtime,
+	player_cmomx,
+	player_cmomy,
+	player_rmomx,
+	player_rmomy,
+	player_numboxes,
+	player_totalring,
+	player_realtime,
+	player_laps,
+	player_ctfteam,
+	player_gotflag,
+	player_weapondelay,
+	player_tossdelay,
+	player_starpostx,
+	player_starposty,
+	player_starpostz,
+	player_starpostnum,
+	player_starposttime,
+	player_starpostangle,
+	player_starpostscale,
+	player_angle_pos,
+	player_old_angle_pos,
+	player_axis1,
+	player_axis2,
+	player_bumpertime,
+	player_flyangle,
+	player_drilltimer,
+	player_linkcount,
+	player_linktimer,
+	player_anotherflyangle,
+	player_nightstime,
+	player_drillmeter,
+	player_drilldelay,
+	player_bonustime,
+	player_capsule,
+	player_drone,
+	player_oldscale,
+	player_mare,
+	player_marelap,
+	player_marebonuslap,
+	player_marebegunat,
+	player_startedtime,
+	player_finishedtime,
+	player_lapbegunat,
+	player_lapstartedtime,
+	player_finishedspheres,
+	player_finishedrings,
+	player_marescore,
+	player_lastmarescore,
+	player_totalmarescore,
+	player_lastmare,
+	player_lastmarelap,
+	player_lastmarebonuslap,
+	player_totalmarelap,
+	player_totalmarebonuslap,
+	player_maxlink,
+	player_texttimer,
+	player_textvar,
+	player_lastsidehit,
+	player_lastlinehit,
+	player_losstime,
+	player_timeshit,
+	player_onconveyor,
+	player_awayviewmobj,
+	player_awayviewtics,
+	player_awayviewaiming,
+	player_spectator,
+	player_outofcoop,
+	player_bot,
+	player_botleader,
+	player_lastbuttons,
+	player_blocked,
+	player_jointime,
+	player_quittime,
+	player_ping,
+#ifdef HWRENDER
+	player_fovadd,
+#endif
+};
+
+static const char *const player_opt[] = {
+	"valid",
+	"name",
+	"realmo",
+	"mo",
+	"cmd",
+	"playerstate",
+	"camerascale",
+	"shieldscale",
+	"viewz",
+	"viewheight",
+	"deltaviewheight",
+	"bob",
+	"viewrollangle",
+	"aiming",
+	"drawangle",
+	"rings",
+	"spheres",
+	"pity",
+	"currentweapon",
+	"ringweapons",
+	"ammoremoval",
+	"ammoremovaltimer",
+	"ammoremovalweapon",
+	"powers",
+	"pflags",
+	"panim",
+	"flashcount",
+	"flashpal",
+	"skincolor",
+	"skin",
+	"availabilities",
+	"score",
+	"recordscore",
+	"dashspeed",
+	"normalspeed",
+	"runspeed",
+	"thrustfactor",
+	"accelstart",
+	"acceleration",
+	"charability",
+	"charability2",
+	"charflags",
+	"thokitem",
+	"spinitem",
+	"revitem",
+	"followitem",
+	"followmobj",
+	"actionspd",
+	"mindash",
+	"maxdash",
+	"jumpfactor",
+	"height",
+	"spinheight",
+	"lives",
+	"continues",
+	"xtralife",
+	"gotcontinue",
+	"speed",
+	"secondjump",
+	"fly1",
+	"scoreadd",
+	"glidetime",
+	"climbing",
+	"deadtimer",
+	"exiting",
+	"homing",
+	"dashmode",
+	"skidtime",
+	"cmomx",
+	"cmomy",
+	"rmomx",
+	"rmomy",
+	"numboxes",
+	"totalring",
+	"realtime",
+	"laps",
+	"ctfteam",
+	"gotflag",
+	"weapondelay",
+	"tossdelay",
+	"starpostx",
+	"starposty",
+	"starpostz",
+	"starpostnum",
+	"starposttime",
+	"starpostangle",
+	"starpostscale",
+	"angle_pos",
+	"old_angle_pos",
+	"axis1",
+	"axis2",
+	"bumpertime",
+	"flyangle",
+	"drilltimer",
+	"linkcount",
+	"linktimer",
+	"anotherflyangle",
+	"nightstime",
+	"drillmeter",
+	"drilldelay",
+	"bonustime",
+	"capsule",
+	"drone",
+	"oldscale",
+	"mare",
+	"marelap",
+	"marebonuslap",
+	"marebegunat",
+	"startedtime",
+	"finishedtime",
+	"lapbegunat",
+	"lapstartedtime",
+	"finishedspheres",
+	"finishedrings",
+	"marescore",
+	"lastmarescore",
+	"totalmarescore",
+	"lastmare",
+	"lastmarelap",
+	"lastmarebonuslap",
+	"totalmarelap",
+	"totalmarebonuslap",
+	"maxlink",
+	"texttimer",
+	"textvar",
+	"lastsidehit",
+	"lastlinehit",
+	"losstime",
+	"timeshit",
+	"onconveyor",
+	"awayviewmobj",
+	"awayviewtics",
+	"awayviewaiming",
+	"spectator",
+	"outofcoop",
+	"bot",
+	"botleader",
+	"lastbuttons",
+	"blocked",
+	"jointime",
+	"quittime",
+	"ping",
+#ifdef HWRENDER
+	"fovadd",
+#endif
+	NULL,
+};
+
+static int player_fields_ref = LUA_NOREF;
+
 static int player_get(lua_State *L)
 {
 	player_t *plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	const char *field = luaL_checkstring(L, 2);
+	enum player_e field = Lua_optoption(L, 2, -1, player_fields_ref);
+	lua_settop(L, 2);
 
-	if (!plr) {
-		if (fastcmp(field,"valid")) {
+	if (!plr)
+	{
+		if (field == player_valid)
+		{
 			lua_pushboolean(L, false);
 			return 1;
 		}
 		return LUA_ErrInvalid(L, "player_t");
 	}
 
-	if (fastcmp(field,"valid"))
+	switch (field)
+	{
+	case player_valid:
 		lua_pushboolean(L, true);
-	else if (fastcmp(field,"name"))
+		break;
+	case player_name:
 		lua_pushstring(L, player_names[plr-players]);
-	else if (fastcmp(field,"realmo"))
+		break;
+	case player_realmo:
 		LUA_PushUserdata(L, plr->mo, META_MOBJ);
+		break;
 	// Kept for backward-compatibility
 	// Should be fixed to work like "realmo" later
-	else if (fastcmp(field,"mo"))
-	{
+	case player_mo:
 		if (plr->spectator)
 			lua_pushnil(L);
 		else
 			LUA_PushUserdata(L, plr->mo, META_MOBJ);
-	}
-	else if (fastcmp(field,"cmd"))
+		break;
+	case player_cmd:
 		LUA_PushUserdata(L, &plr->cmd, META_TICCMD);
-	else if (fastcmp(field,"playerstate"))
+		break;
+	case player_playerstate:
 		lua_pushinteger(L, plr->playerstate);
-	else if (fastcmp(field,"camerascale"))
+		break;
+	case player_camerascale:
 		lua_pushfixed(L, plr->camerascale);
-	else if (fastcmp(field,"shieldscale"))
+		break;
+	case player_shieldscale:
 		lua_pushfixed(L, plr->shieldscale);
-	else if (fastcmp(field,"viewz"))
+		break;
+	case player_viewz:
 		lua_pushfixed(L, plr->viewz);
-	else if (fastcmp(field,"viewheight"))
+		break;
+	case player_viewheight:
 		lua_pushfixed(L, plr->viewheight);
-	else if (fastcmp(field,"deltaviewheight"))
+		break;
+	case player_deltaviewheight:
 		lua_pushfixed(L, plr->deltaviewheight);
-	else if (fastcmp(field,"bob"))
+		break;
+	case player_bob:
 		lua_pushfixed(L, plr->bob);
-	else if (fastcmp(field,"viewrollangle"))
+		break;
+	case player_viewrollangle:
 		lua_pushangle(L, plr->viewrollangle);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case player_aiming:
 		lua_pushangle(L, plr->aiming);
-	else if (fastcmp(field,"drawangle"))
+		break;
+	case player_drawangle:
 		lua_pushangle(L, plr->drawangle);
-	else if (fastcmp(field,"rings"))
+		break;
+	case player_rings:
 		lua_pushinteger(L, plr->rings);
-	else if (fastcmp(field,"spheres"))
+		break;
+	case player_spheres:
 		lua_pushinteger(L, plr->spheres);
-	else if (fastcmp(field,"pity"))
+		break;
+	case player_pity:
 		lua_pushinteger(L, plr->pity);
-	else if (fastcmp(field,"currentweapon"))
+		break;
+	case player_currentweapon:
 		lua_pushinteger(L, plr->currentweapon);
-	else if (fastcmp(field,"ringweapons"))
+		break;
+	case player_ringweapons:
 		lua_pushinteger(L, plr->ringweapons);
-	else if (fastcmp(field,"ammoremoval"))
+		break;
+	case player_ammoremoval:
 		lua_pushinteger(L, plr->ammoremoval);
-	else if (fastcmp(field,"ammoremovaltimer"))
+		break;
+	case player_ammoremovaltimer:
 		lua_pushinteger(L, plr->ammoremovaltimer);
-	else if (fastcmp(field,"ammoremovalweapon"))
+		break;
+	case player_ammoremovalweapon:
 		lua_pushinteger(L, plr->ammoremovalweapon);
-	else if (fastcmp(field,"powers"))
+		break;
+	case player_powers:
 		LUA_PushUserdata(L, plr->powers, META_POWERS);
-	else if (fastcmp(field,"pflags"))
+		break;
+	case player_pflags:
 		lua_pushinteger(L, plr->pflags);
-	else if (fastcmp(field,"panim"))
+		break;
+	case player_panim:
 		lua_pushinteger(L, plr->panim);
-	else if (fastcmp(field,"flashcount"))
+		break;
+	case player_flashcount:
 		lua_pushinteger(L, plr->flashcount);
-	else if (fastcmp(field,"flashpal"))
+		break;
+	case player_flashpal:
 		lua_pushinteger(L, plr->flashpal);
-	else if (fastcmp(field,"skincolor"))
+		break;
+	case player_skincolor:
 		lua_pushinteger(L, plr->skincolor);
-	else if (fastcmp(field,"skin"))
+		break;
+	case player_skin:
 		lua_pushinteger(L, plr->skin);
-	else if (fastcmp(field,"availabilities"))
+		break;
+	case player_availabilities:
 		lua_pushinteger(L, plr->availabilities);
-	else if (fastcmp(field,"score"))
+		break;
+	case player_score:
 		lua_pushinteger(L, plr->score);
-	else if (fastcmp(field,"dashspeed"))
+		break;
+	case player_recordscore:
+		lua_pushinteger(L, plr->recordscore);
+		break;
+	case player_dashspeed:
 		lua_pushfixed(L, plr->dashspeed);
-	else if (fastcmp(field,"normalspeed"))
+		break;
+	case player_normalspeed:
 		lua_pushfixed(L, plr->normalspeed);
-	else if (fastcmp(field,"runspeed"))
+		break;
+	case player_runspeed:
 		lua_pushfixed(L, plr->runspeed);
-	else if (fastcmp(field,"thrustfactor"))
+		break;
+	case player_thrustfactor:
 		lua_pushinteger(L, plr->thrustfactor);
-	else if (fastcmp(field,"accelstart"))
+		break;
+	case player_accelstart:
 		lua_pushinteger(L, plr->accelstart);
-	else if (fastcmp(field,"acceleration"))
+		break;
+	case player_acceleration:
 		lua_pushinteger(L, plr->acceleration);
-	else if (fastcmp(field,"charability"))
+		break;
+	case player_charability:
 		lua_pushinteger(L, plr->charability);
-	else if (fastcmp(field,"charability2"))
+		break;
+	case player_charability2:
 		lua_pushinteger(L, plr->charability2);
-	else if (fastcmp(field,"charflags"))
+		break;
+	case player_charflags:
 		lua_pushinteger(L, plr->charflags);
-	else if (fastcmp(field,"thokitem"))
+		break;
+	case player_thokitem:
 		lua_pushinteger(L, plr->thokitem);
-	else if (fastcmp(field,"spinitem"))
+		break;
+	case player_spinitem:
 		lua_pushinteger(L, plr->spinitem);
-	else if (fastcmp(field,"revitem"))
+		break;
+	case player_revitem:
 		lua_pushinteger(L, plr->revitem);
-	else if (fastcmp(field,"followitem"))
+		break;
+	case player_followitem:
 		lua_pushinteger(L, plr->followitem);
-	else if (fastcmp(field,"followmobj"))
+		break;
+	case player_followmobj:
 		LUA_PushUserdata(L, plr->followmobj, META_MOBJ);
-	else if (fastcmp(field,"actionspd"))
+		break;
+	case player_actionspd:
 		lua_pushfixed(L, plr->actionspd);
-	else if (fastcmp(field,"mindash"))
+		break;
+	case player_mindash:
 		lua_pushfixed(L, plr->mindash);
-	else if (fastcmp(field,"maxdash"))
+		break;
+	case player_maxdash:
 		lua_pushfixed(L, plr->maxdash);
-	else if (fastcmp(field,"jumpfactor"))
+		break;
+	case player_jumpfactor:
 		lua_pushfixed(L, plr->jumpfactor);
-	else if (fastcmp(field,"height"))
+		break;
+	case player_height:
 		lua_pushfixed(L, plr->height);
-	else if (fastcmp(field,"spinheight"))
+		break;
+	case player_spinheight:
 		lua_pushfixed(L, plr->spinheight);
-	else if (fastcmp(field,"lives"))
+		break;
+	case player_lives:
 		lua_pushinteger(L, plr->lives);
-	else if (fastcmp(field,"continues"))
+		break;
+	case player_continues:
 		lua_pushinteger(L, plr->continues);
-	else if (fastcmp(field,"xtralife"))
+		break;
+	case player_xtralife:
 		lua_pushinteger(L, plr->xtralife);
-	else if (fastcmp(field,"gotcontinue"))
+		break;
+	case player_gotcontinue:
 		lua_pushinteger(L, plr->gotcontinue);
-	else if (fastcmp(field,"speed"))
+		break;
+	case player_speed:
 		lua_pushfixed(L, plr->speed);
-	else if (fastcmp(field,"secondjump"))
+		break;
+	case player_secondjump:
 		lua_pushinteger(L, plr->secondjump);
-	else if (fastcmp(field,"fly1"))
+		break;
+	case player_fly1:
 		lua_pushinteger(L, plr->fly1);
-	else if (fastcmp(field,"scoreadd"))
+		break;
+	case player_scoreadd:
 		lua_pushinteger(L, plr->scoreadd);
-	else if (fastcmp(field,"glidetime"))
+		break;
+	case player_glidetime:
 		lua_pushinteger(L, plr->glidetime);
-	else if (fastcmp(field,"climbing"))
+		break;
+	case player_climbing:
 		lua_pushinteger(L, plr->climbing);
-	else if (fastcmp(field,"deadtimer"))
+		break;
+	case player_deadtimer:
 		lua_pushinteger(L, plr->deadtimer);
-	else if (fastcmp(field,"exiting"))
+		break;
+	case player_exiting:
 		lua_pushinteger(L, plr->exiting);
-	else if (fastcmp(field,"homing"))
+		break;
+	case player_homing:
 		lua_pushinteger(L, plr->homing);
-	else if (fastcmp(field,"dashmode"))
+		break;
+	case player_dashmode:
 		lua_pushinteger(L, plr->dashmode);
-	else if (fastcmp(field,"skidtime"))
+		break;
+	case player_skidtime:
 		lua_pushinteger(L, plr->skidtime);
-	else if (fastcmp(field,"cmomx"))
+		break;
+	case player_cmomx:
 		lua_pushfixed(L, plr->cmomx);
-	else if (fastcmp(field,"cmomy"))
+		break;
+	case player_cmomy:
 		lua_pushfixed(L, plr->cmomy);
-	else if (fastcmp(field,"rmomx"))
+		break;
+	case player_rmomx:
 		lua_pushfixed(L, plr->rmomx);
-	else if (fastcmp(field,"rmomy"))
+		break;
+	case player_rmomy:
 		lua_pushfixed(L, plr->rmomy);
-	else if (fastcmp(field,"numboxes"))
+		break;
+	case player_numboxes:
 		lua_pushinteger(L, plr->numboxes);
-	else if (fastcmp(field,"totalring"))
+		break;
+	case player_totalring:
 		lua_pushinteger(L, plr->totalring);
-	else if (fastcmp(field,"realtime"))
+		break;
+	case player_realtime:
 		lua_pushinteger(L, plr->realtime);
-	else if (fastcmp(field,"laps"))
+		break;
+	case player_laps:
 		lua_pushinteger(L, plr->laps);
-	else if (fastcmp(field,"ctfteam"))
+		break;
+	case player_ctfteam:
 		lua_pushinteger(L, plr->ctfteam);
-	else if (fastcmp(field,"gotflag"))
+		break;
+	case player_gotflag:
 		lua_pushinteger(L, plr->gotflag);
-	else if (fastcmp(field,"weapondelay"))
+		break;
+	case player_weapondelay:
 		lua_pushinteger(L, plr->weapondelay);
-	else if (fastcmp(field,"tossdelay"))
+		break;
+	case player_tossdelay:
 		lua_pushinteger(L, plr->tossdelay);
-	else if (fastcmp(field,"starpostx"))
+		break;
+	case player_starpostx:
 		lua_pushinteger(L, plr->starpostx);
-	else if (fastcmp(field,"starposty"))
+		break;
+	case player_starposty:
 		lua_pushinteger(L, plr->starposty);
-	else if (fastcmp(field,"starpostz"))
+		break;
+	case player_starpostz:
 		lua_pushinteger(L, plr->starpostz);
-	else if (fastcmp(field,"starpostnum"))
+		break;
+	case player_starpostnum:
 		lua_pushinteger(L, plr->starpostnum);
-	else if (fastcmp(field,"starposttime"))
+		break;
+	case player_starposttime:
 		lua_pushinteger(L, plr->starposttime);
-	else if (fastcmp(field,"starpostangle"))
+		break;
+	case player_starpostangle:
 		lua_pushangle(L, plr->starpostangle);
-	else if (fastcmp(field,"starpostscale"))
+		break;
+	case player_starpostscale:
 		lua_pushfixed(L, plr->starpostscale);
-	else if (fastcmp(field,"angle_pos"))
+		break;
+	case player_angle_pos:
 		lua_pushangle(L, plr->angle_pos);
-	else if (fastcmp(field,"old_angle_pos"))
+		break;
+	case player_old_angle_pos:
 		lua_pushangle(L, plr->old_angle_pos);
-	else if (fastcmp(field,"axis1"))
+		break;
+	case player_axis1:
 		LUA_PushUserdata(L, plr->axis1, META_MOBJ);
-	else if (fastcmp(field,"axis2"))
+		break;
+	case player_axis2:
 		LUA_PushUserdata(L, plr->axis2, META_MOBJ);
-	else if (fastcmp(field,"bumpertime"))
+		break;
+	case player_bumpertime:
 		lua_pushinteger(L, plr->bumpertime);
-	else if (fastcmp(field,"flyangle"))
+		break;
+	case player_flyangle:
 		lua_pushinteger(L, plr->flyangle);
-	else if (fastcmp(field,"drilltimer"))
+		break;
+	case player_drilltimer:
 		lua_pushinteger(L, plr->drilltimer);
-	else if (fastcmp(field,"linkcount"))
+		break;
+	case player_linkcount:
 		lua_pushinteger(L, plr->linkcount);
-	else if (fastcmp(field,"linktimer"))
+		break;
+	case player_linktimer:
 		lua_pushinteger(L, plr->linktimer);
-	else if (fastcmp(field,"anotherflyangle"))
+		break;
+	case player_anotherflyangle:
 		lua_pushinteger(L, plr->anotherflyangle);
-	else if (fastcmp(field,"nightstime"))
+		break;
+	case player_nightstime:
 		lua_pushinteger(L, plr->nightstime);
-	else if (fastcmp(field,"drillmeter"))
+		break;
+	case player_drillmeter:
 		lua_pushinteger(L, plr->drillmeter);
-	else if (fastcmp(field,"drilldelay"))
+		break;
+	case player_drilldelay:
 		lua_pushinteger(L, plr->drilldelay);
-	else if (fastcmp(field,"bonustime"))
+		break;
+	case player_bonustime:
 		lua_pushboolean(L, plr->bonustime);
-	else if (fastcmp(field,"capsule"))
+		break;
+	case player_capsule:
 		LUA_PushUserdata(L, plr->capsule, META_MOBJ);
-	else if (fastcmp(field,"drone"))
+		break;
+	case player_drone:
 		LUA_PushUserdata(L, plr->drone, META_MOBJ);
-	else if (fastcmp(field,"oldscale"))
+		break;
+	case player_oldscale:
 		lua_pushfixed(L, plr->oldscale);
-	else if (fastcmp(field,"mare"))
+		break;
+	case player_mare:
 		lua_pushinteger(L, plr->mare);
-	else if (fastcmp(field,"marelap"))
+		break;
+	case player_marelap:
 		lua_pushinteger(L, plr->marelap);
-	else if (fastcmp(field,"marebonuslap"))
+		break;
+	case player_marebonuslap:
 		lua_pushinteger(L, plr->marebonuslap);
-	else if (fastcmp(field,"marebegunat"))
+		break;
+	case player_marebegunat:
 		lua_pushinteger(L, plr->marebegunat);
-	else if (fastcmp(field,"startedtime"))
+		break;
+	case player_startedtime:
 		lua_pushinteger(L, plr->startedtime);
-	else if (fastcmp(field,"finishedtime"))
+		break;
+	case player_finishedtime:
 		lua_pushinteger(L, plr->finishedtime);
-	else if (fastcmp(field,"lapbegunat"))
+		break;
+	case player_lapbegunat:
 		lua_pushinteger(L, plr->lapbegunat);
-	else if (fastcmp(field,"lapstartedtime"))
+		break;
+	case player_lapstartedtime:
 		lua_pushinteger(L, plr->lapstartedtime);
-	else if (fastcmp(field,"finishedspheres"))
+		break;
+	case player_finishedspheres:
 		lua_pushinteger(L, plr->finishedspheres);
-	else if (fastcmp(field,"finishedrings"))
+		break;
+	case player_finishedrings:
 		lua_pushinteger(L, plr->finishedrings);
-	else if (fastcmp(field,"marescore"))
+		break;
+	case player_marescore:
 		lua_pushinteger(L, plr->marescore);
-	else if (fastcmp(field,"lastmarescore"))
+		break;
+	case player_lastmarescore:
 		lua_pushinteger(L, plr->lastmarescore);
-	else if (fastcmp(field,"totalmarescore"))
+		break;
+	case player_totalmarescore:
 		lua_pushinteger(L, plr->totalmarescore);
-	else if (fastcmp(field,"lastmare"))
+		break;
+	case player_lastmare:
 		lua_pushinteger(L, plr->lastmare);
-	else if (fastcmp(field,"lastmarelap"))
+		break;
+	case player_lastmarelap:
 		lua_pushinteger(L, plr->lastmarelap);
-	else if (fastcmp(field,"lastmarebonuslap"))
+		break;
+	case player_lastmarebonuslap:
 		lua_pushinteger(L, plr->lastmarebonuslap);
-	else if (fastcmp(field,"totalmarelap"))
+		break;
+	case player_totalmarelap:
 		lua_pushinteger(L, plr->totalmarelap);
-	else if (fastcmp(field,"totalmarebonuslap"))
+		break;
+	case player_totalmarebonuslap:
 		lua_pushinteger(L, plr->totalmarebonuslap);
-	else if (fastcmp(field,"maxlink"))
+		break;
+	case player_maxlink:
 		lua_pushinteger(L, plr->maxlink);
-	else if (fastcmp(field,"texttimer"))
+		break;
+	case player_texttimer:
 		lua_pushinteger(L, plr->texttimer);
-	else if (fastcmp(field,"textvar"))
+		break;
+	case player_textvar:
 		lua_pushinteger(L, plr->textvar);
-	else if (fastcmp(field,"lastsidehit"))
+		break;
+	case player_lastsidehit:
 		lua_pushinteger(L, plr->lastsidehit);
-	else if (fastcmp(field,"lastlinehit"))
+		break;
+	case player_lastlinehit:
 		lua_pushinteger(L, plr->lastlinehit);
-	else if (fastcmp(field,"losstime"))
+		break;
+	case player_losstime:
 		lua_pushinteger(L, plr->losstime);
-	else if (fastcmp(field,"timeshit"))
+		break;
+	case player_timeshit:
 		lua_pushinteger(L, plr->timeshit);
-	else if (fastcmp(field,"onconveyor"))
+		break;
+	case player_onconveyor:
 		lua_pushinteger(L, plr->onconveyor);
-	else if (fastcmp(field,"awayviewmobj"))
+		break;
+	case player_awayviewmobj:
 		LUA_PushUserdata(L, plr->awayviewmobj, META_MOBJ);
-	else if (fastcmp(field,"awayviewtics"))
+		break;
+	case player_awayviewtics:
 		lua_pushinteger(L, plr->awayviewtics);
-	else if (fastcmp(field,"awayviewaiming"))
+		break;
+	case player_awayviewaiming:
 		lua_pushangle(L, plr->awayviewaiming);
-	else if (fastcmp(field,"spectator"))
+		break;
+	case player_spectator:
 		lua_pushboolean(L, plr->spectator);
-	else if (fastcmp(field,"outofcoop"))
+		break;
+	case player_outofcoop:
 		lua_pushboolean(L, plr->outofcoop);
-	else if (fastcmp(field,"bot"))
+		break;
+	case player_bot:
 		lua_pushinteger(L, plr->bot);
-	else if (fastcmp(field,"botleader"))
+		break;
+	case player_botleader:
 		LUA_PushUserdata(L, plr->botleader, META_PLAYER);
-	else if (fastcmp(field,"lastbuttons"))
+		break;
+	case player_lastbuttons:
 		lua_pushinteger(L, plr->lastbuttons);
-	else if (fastcmp(field,"blocked"))
+		break;
+	case player_blocked:
 		lua_pushboolean(L, plr->blocked);
-	else if (fastcmp(field,"jointime"))
+		break;
+	case player_jointime:
 		lua_pushinteger(L, plr->jointime);
-	else if (fastcmp(field,"quittime"))
+		break;
+	case player_quittime:
 		lua_pushinteger(L, plr->quittime);
+		break;
+	case player_ping:
+		lua_pushinteger(L, playerpingtable[plr - players]);
+		break;
 #ifdef HWRENDER
-	else if (fastcmp(field,"fovadd"))
+	case player_fovadd:
 		lua_pushfixed(L, plr->fovadd);
+		break;
 #endif
-	else {
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, plr);
 		lua_rawget(L, -2);
 		if (!lua_istable(L, -1)) { // no extra values table
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no extvars table or field named '%s'; returning nil.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no extvars table or field named '%s'; returning nil.\n"), "player_t", lua_tostring(L, 2));
 			return 0;
 		}
-		lua_getfield(L, -1, field);
+		lua_pushvalue(L, 2); // field name
+		lua_gettable(L, -2);
 		if (lua_isnil(L, -1)) // no value for this field
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "player_t", lua_tostring(L, 2));
+		break;
 	}
 
 	return 1;
 }
 
-#define NOSET luaL_error(L, LUA_QL("player_t") " field " LUA_QS " should not be set directly.", field)
+#define NOSET luaL_error(L, LUA_QL("player_t") " field " LUA_QS " should not be set directly.", player_opt[field])
 static int player_set(lua_State *L)
 {
 	player_t *plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	const char *field = luaL_checkstring(L, 2);
+	enum player_e field = Lua_optoption(L, 2, player_cmd, player_fields_ref);
 	if (!plr)
 		return LUA_ErrInvalid(L, "player_t");
 
@@ -414,337 +866,495 @@ static int player_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter player_t in CMD building code!");
 
-	if (fastcmp(field,"mo") || fastcmp(field,"realmo")) {
+	switch (field)
+	{
+	case player_mo:
+	case player_realmo:
+	{
 		mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		plr->mo->player = NULL; // remove player pointer from old mobj
 		(newmo->player = plr)->mo = newmo; // set player pointer for new mobj, and set new mobj as the player's mobj
+		break;
 	}
-	else if (fastcmp(field,"cmd"))
+	case player_cmd:
 		return NOSET;
-	else if (fastcmp(field,"playerstate"))
+	case player_playerstate:
 		plr->playerstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"camerascale"))
+		break;
+	case player_camerascale:
 		plr->camerascale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"shieldscale"))
+		break;
+	case player_shieldscale:
 		plr->shieldscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewz"))
+		break;
+	case player_viewz:
 		plr->viewz = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewheight"))
+		break;
+	case player_viewheight:
 		plr->viewheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"deltaviewheight"))
+		break;
+	case player_deltaviewheight:
 		plr->deltaviewheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"bob"))
+		break;
+	case player_bob:
 		plr->bob = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewrollangle"))
+		break;
+	case player_viewrollangle:
 		plr->viewrollangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"aiming")) {
+		break;
+	case player_aiming:
+	{
 		plr->aiming = luaL_checkangle(L, 3);
 		if (plr == &players[consoleplayer])
 			localaiming = plr->aiming;
 		else if (plr == &players[secondarydisplayplayer])
 			localaiming2 = plr->aiming;
+		break;
 	}
-	else if (fastcmp(field,"drawangle"))
+	case player_drawangle:
 		plr->drawangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"rings"))
+		break;
+	case player_rings:
 		plr->rings = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spheres"))
+		break;
+	case player_spheres:
 		plr->spheres = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"pity"))
+		break;
+	case player_pity:
 		plr->pity = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"currentweapon"))
+		break;
+	case player_currentweapon:
 		plr->currentweapon = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ringweapons"))
+		break;
+	case player_ringweapons:
 		plr->ringweapons = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremoval"))
+		break;
+	case player_ammoremoval:
 		plr->ammoremoval = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremovaltimer"))
+		break;
+	case player_ammoremovaltimer:
 		plr->ammoremovaltimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremovalweapon"))
+		break;
+	case player_ammoremovalweapon:
 		plr->ammoremovalweapon = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"powers"))
+		break;
+	case player_powers:
 		return NOSET;
-	else if (fastcmp(field,"pflags"))
+	case player_pflags:
 		plr->pflags = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"panim"))
+		break;
+	case player_panim:
 		plr->panim = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flashcount"))
+		break;
+	case player_flashcount:
 		plr->flashcount = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flashpal"))
+		break;
+	case player_flashpal:
 		plr->flashpal = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"skincolor"))
+		break;
+	case player_skincolor:
 	{
 		UINT16 newcolor = (UINT16)luaL_checkinteger(L,3);
 		if (newcolor >= numskincolors)
 			return luaL_error(L, "player.skincolor %d out of range (0 - %d).", newcolor, numskincolors-1);
 		plr->skincolor = newcolor;
+		break;
 	}
-	else if (fastcmp(field,"skin"))
+	case player_skin:
 		return NOSET;
-	else if (fastcmp(field,"availabilities"))
+	case player_availabilities:
 		return NOSET;
-	else if (fastcmp(field,"score"))
+	case player_score:
 		plr->score = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"dashspeed"))
+		break;
+	case player_recordscore:
+		plr->recordscore = (UINT32)luaL_checkinteger(L, 3);
+		break;
+	case player_dashspeed:
 		plr->dashspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"normalspeed"))
+		break;
+	case player_normalspeed:
 		plr->normalspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"runspeed"))
+		break;
+	case player_runspeed:
 		plr->runspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"thrustfactor"))
+		break;
+	case player_thrustfactor:
 		plr->thrustfactor = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"accelstart"))
+		break;
+	case player_accelstart:
 		plr->accelstart = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"acceleration"))
+		break;
+	case player_acceleration:
 		plr->acceleration = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charability"))
+		break;
+	case player_charability:
 		plr->charability = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charability2"))
+		break;
+	case player_charability2:
 		plr->charability2 = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charflags"))
+		break;
+	case player_charflags:
 		plr->charflags = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"thokitem"))
+		break;
+	case player_thokitem:
 		plr->thokitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spinitem"))
+		break;
+	case player_spinitem:
 		plr->spinitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"revitem"))
+		break;
+	case player_revitem:
 		plr->revitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"followitem"))
+		break;
+	case player_followitem:
 		plr->followitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"followmobj"))
+		break;
+	case player_followmobj:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->followmobj, mo);
+		break;
 	}
-	else if (fastcmp(field,"actionspd"))
+	case player_actionspd:
 		plr->actionspd = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"mindash"))
+		break;
+	case player_mindash:
 		plr->mindash = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"maxdash"))
+		break;
+	case player_maxdash:
 		plr->maxdash = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"jumpfactor"))
+		break;
+	case player_jumpfactor:
 		plr->jumpfactor = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"height"))
+		break;
+	case player_height:
 		plr->height = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"spinheight"))
+		break;
+	case player_spinheight:
 		plr->spinheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"lives"))
+		break;
+	case player_lives:
 		plr->lives = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"continues"))
+		break;
+	case player_continues:
 		plr->continues = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"xtralife"))
+		break;
+	case player_xtralife:
 		plr->xtralife = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"gotcontinue"))
+		break;
+	case player_gotcontinue:
 		plr->gotcontinue = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"speed"))
+		break;
+	case player_speed:
 		plr->speed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"secondjump"))
+		break;
+	case player_secondjump:
 		plr->secondjump = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"fly1"))
+		break;
+	case player_fly1:
 		plr->fly1 = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"scoreadd"))
+		break;
+	case player_scoreadd:
 		plr->scoreadd = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"glidetime"))
+		break;
+	case player_glidetime:
 		plr->glidetime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"climbing"))
+		break;
+	case player_climbing:
 		plr->climbing = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deadtimer"))
+		break;
+	case player_deadtimer:
 		plr->deadtimer = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"exiting"))
+		break;
+	case player_exiting:
 		plr->exiting = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"homing"))
+		break;
+	case player_homing:
 		plr->homing = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"dashmode"))
+		break;
+	case player_dashmode:
 		plr->dashmode = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"skidtime"))
+		break;
+	case player_skidtime:
 		plr->skidtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"cmomx"))
+		break;
+	case player_cmomx:
 		plr->cmomx = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"cmomy"))
+		break;
+	case player_cmomy:
 		plr->cmomy = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"rmomx"))
+		break;
+	case player_rmomx:
 		plr->rmomx = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"rmomy"))
+		break;
+	case player_rmomy:
 		plr->rmomy = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"numboxes"))
+		break;
+	case player_numboxes:
 		plr->numboxes = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalring"))
+		break;
+	case player_totalring:
 		plr->totalring = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"realtime"))
+		break;
+	case player_realtime:
 		plr->realtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"laps"))
+		break;
+	case player_laps:
 		plr->laps = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ctfteam"))
+		break;
+	case player_ctfteam:
 		plr->ctfteam = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"gotflag"))
+		break;
+	case player_gotflag:
 		plr->gotflag = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"weapondelay"))
+		break;
+	case player_weapondelay:
 		plr->weapondelay = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"tossdelay"))
+		break;
+	case player_tossdelay:
 		plr->tossdelay = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostx"))
+		break;
+	case player_starpostx:
 		plr->starpostx = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starposty"))
+		break;
+	case player_starposty:
 		plr->starposty = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostz"))
+		break;
+	case player_starpostz:
 		plr->starpostz = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostnum"))
+		break;
+	case player_starpostnum:
 		plr->starpostnum = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starposttime"))
+		break;
+	case player_starposttime:
 		plr->starposttime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostangle"))
+		break;
+	case player_starpostangle:
 		plr->starpostangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"starpostscale"))
+		break;
+	case player_starpostscale:
 		plr->starpostscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"angle_pos"))
+		break;
+	case player_angle_pos:
 		plr->angle_pos = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"old_angle_pos"))
+		break;
+	case player_old_angle_pos:
 		plr->old_angle_pos = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"axis1"))
+		break;
+	case player_axis1:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->axis1, mo);
+		break;
 	}
-	else if (fastcmp(field,"axis2"))
+	case player_axis2:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->axis2, mo);
+		break;
 	}
-	else if (fastcmp(field,"bumpertime"))
+	case player_bumpertime:
 		plr->bumpertime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flyangle"))
+		break;
+	case player_flyangle:
 		plr->flyangle = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drilltimer"))
+		break;
+	case player_drilltimer:
 		plr->drilltimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"linkcount"))
+		break;
+	case player_linkcount:
 		plr->linkcount = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"linktimer"))
+		break;
+	case player_linktimer:
 		plr->linktimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"anotherflyangle"))
+		break;
+	case player_anotherflyangle:
 		plr->anotherflyangle = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"nightstime"))
+		break;
+	case player_nightstime:
 		plr->nightstime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drillmeter"))
+		break;
+	case player_drillmeter:
 		plr->drillmeter = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drilldelay"))
+		break;
+	case player_drilldelay:
 		plr->drilldelay = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"bonustime"))
+		break;
+	case player_bonustime:
 		plr->bonustime = luaL_checkboolean(L, 3);
-	else if (fastcmp(field,"capsule"))
+		break;
+	case player_capsule:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->capsule, mo);
+		break;
 	}
-	else if (fastcmp(field,"drone"))
+	case player_drone:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->drone, mo);
+		break;
 	}
-	else if (fastcmp(field,"oldscale"))
+	case player_oldscale:
 		plr->oldscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"mare"))
+		break;
+	case player_mare:
 		plr->mare = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marelap"))
+		break;
+	case player_marelap:
 		plr->marelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marebonuslap"))
+		break;
+	case player_marebonuslap:
 		plr->marebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marebegunat"))
+		break;
+	case player_marebegunat:
 		plr->marebegunat = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"startedtime"))
+		break;
+	case player_startedtime:
 		plr->startedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedtime"))
+		break;
+	case player_finishedtime:
 		plr->finishedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lapbegunat"))
+		break;
+	case player_lapbegunat:
 		plr->lapbegunat = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lapstartedtime"))
+		break;
+	case player_lapstartedtime:
 		plr->lapstartedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedspheres"))
+		break;
+	case player_finishedspheres:
 		plr->finishedspheres = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedrings"))
+		break;
+	case player_finishedrings:
 		plr->finishedrings = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marescore"))
+		break;
+	case player_marescore:
 		plr->marescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarescore"))
+		break;
+	case player_lastmarescore:
 		plr->lastmarescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarescore"))
+		break;
+	case player_totalmarescore:
 		plr->totalmarescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmare"))
+		break;
+	case player_lastmare:
 		plr->lastmare = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarelap"))
+		break;
+	case player_lastmarelap:
 		plr->lastmarelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarebonuslap"))
+		break;
+	case player_lastmarebonuslap:
 		plr->lastmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarelap"))
+		break;
+	case player_totalmarelap:
 		plr->totalmarelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarebonuslap"))
+		break;
+	case player_totalmarebonuslap:
 		plr->totalmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"maxlink"))
+		break;
+	case player_maxlink:
 		plr->maxlink = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"texttimer"))
+		break;
+	case player_texttimer:
 		plr->texttimer = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"textvar"))
+		break;
+	case player_textvar:
 		plr->textvar = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastsidehit"))
+		break;
+	case player_lastsidehit:
 		plr->lastsidehit = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastlinehit"))
+		break;
+	case player_lastlinehit:
 		plr->lastlinehit = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"losstime"))
+		break;
+	case player_losstime:
 		plr->losstime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"timeshit"))
+		break;
+	case player_timeshit:
 		plr->timeshit = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"onconveyor"))
+		break;
+	case player_onconveyor:
 		plr->onconveyor = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"awayviewmobj"))
+		break;
+	case player_awayviewmobj:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
-		P_SetTarget(&plr->awayviewmobj, mo);
+		if (plr->awayviewmobj != mo) {
+			P_SetTarget(&plr->awayviewmobj, mo);
+			if (plr->awayviewtics) {
+				if (!plr->awayviewmobj)
+					plr->awayviewtics = 0; // can't have a NULL awayviewmobj with awayviewtics!
+				if (plr == &players[displayplayer])
+					P_ResetCamera(plr, &camera); // reset p1 camera on p1 getting an awayviewmobj
+				else if (splitscreen && plr == &players[secondarydisplayplayer])
+					P_ResetCamera(plr, &camera2);  // reset p2 camera on p2 getting an awayviewmobj
+			}
+		}
+		break;
 	}
-	else if (fastcmp(field,"awayviewtics"))
+	case player_awayviewtics:
 	{
-		plr->awayviewtics = (INT32)luaL_checkinteger(L, 3);
-		if (plr->awayviewtics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
+		INT32 tics = (INT32)luaL_checkinteger(L, 3);
+		if (tics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
 			P_SetTarget(&plr->awayviewmobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now.
+		if ((tics && !plr->awayviewtics) || (!tics && plr->awayviewtics)) {
+			if (plr == &players[displayplayer])
+				P_ResetCamera(plr, &camera); // reset p1 camera on p1 transitioning to/from zero awayviewtics
+			else if (splitscreen && plr == &players[secondarydisplayplayer])
+				P_ResetCamera(plr, &camera2);  // reset p2 camera on p2 transitioning to/from zero awayviewtics
+		}
+		plr->awayviewtics = tics;
+		break;
 	}
-	else if (fastcmp(field,"awayviewaiming"))
+	case player_awayviewaiming:
 		plr->awayviewaiming = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"spectator"))
+		break;
+	case player_spectator:
 		plr->spectator = lua_toboolean(L, 3);
-	else if (fastcmp(field,"outofcoop"))
+		break;
+	case player_outofcoop:
 		plr->outofcoop = lua_toboolean(L, 3);
-	else if (fastcmp(field,"bot"))
+		break;
+	case player_bot:
 		return NOSET;
-	else if (fastcmp(field,"botleader"))
+	case player_botleader:
 	{
 		player_t *player = NULL;
 		if (!lua_isnil(L, 3))
 			player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
 		plr->botleader = player;
+		break;
 	}
-	else if (fastcmp(field,"lastbuttons"))
+	case player_lastbuttons:
 		plr->lastbuttons = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"blocked"))
+		break;
+	case player_blocked:
 		plr->blocked = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"jointime"))
+		break;
+	case player_jointime:
 		plr->jointime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"quittime"))
+		break;
+	case player_quittime:
 		plr->quittime = (tic_t)luaL_checkinteger(L, 3);
+		break;
 #ifdef HWRENDER
-	else if (fastcmp(field,"fovadd"))
+	case player_fovadd:
 		plr->fovadd = luaL_checkfixed(L, 3);
+		break;
 #endif
-	else {
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, plr);
@@ -752,15 +1362,17 @@ static int player_set(lua_State *L)
 		if (lua_isnil(L, -1)) {
 			// This index doesn't have a table for extra values yet, let's make one.
 			lua_pop(L, 1);
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "player_t", lua_tostring(L, 2));
 			lua_newtable(L);
 			lua_pushlightuserdata(L, plr);
 			lua_pushvalue(L, -2); // ext value table
 			lua_rawset(L, -4); // LREG_EXTVARS table
 		}
+		lua_pushvalue(L, 2); // key
 		lua_pushvalue(L, 3); // value to store
-		lua_setfield(L, -2, field);
+		lua_settable(L, -3);
 		lua_pop(L, 2);
+		break;
 	}
 
 	return 0;
@@ -812,29 +1424,60 @@ static int power_len(lua_State *L)
 }
 
 #define NOFIELD luaL_error(L, LUA_QL("ticcmd_t") " has no field named " LUA_QS, field)
-#define NOSET luaL_error(L, LUA_QL("ticcmd_t") " field " LUA_QS " should not be set directly.", field)
+#define NOSET luaL_error(L, LUA_QL("ticcmd_t") " field " LUA_QS " should not be set directly.", ticcmd_opt[field])
+
+enum ticcmd_e
+{
+	ticcmd_forwardmove,
+	ticcmd_sidemove,
+	ticcmd_angleturn,
+	ticcmd_aiming,
+	ticcmd_buttons,
+	ticcmd_latency,
+};
+
+static const char *const ticcmd_opt[] = {
+	"forwardmove",
+	"sidemove",
+	"angleturn",
+	"aiming",
+	"buttons",
+	"latency",
+	NULL,
+};
+
+static int ticcmd_fields_ref = LUA_NOREF;
 
 static int ticcmd_get(lua_State *L)
 {
 	ticcmd_t *cmd = *((ticcmd_t **)luaL_checkudata(L, 1, META_TICCMD));
-	const char *field = luaL_checkstring(L, 2);
+	enum ticcmd_e field = Lua_optoption(L, 2, -1, ticcmd_fields_ref);
 	if (!cmd)
 		return LUA_ErrInvalid(L, "player_t");
 
-	if (fastcmp(field,"forwardmove"))
+	switch (field)
+	{
+	case ticcmd_forwardmove:
 		lua_pushinteger(L, cmd->forwardmove);
-	else if (fastcmp(field,"sidemove"))
+		break;
+	case ticcmd_sidemove:
 		lua_pushinteger(L, cmd->sidemove);
-	else if (fastcmp(field,"angleturn"))
+		break;
+	case ticcmd_angleturn:
 		lua_pushinteger(L, cmd->angleturn);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case ticcmd_aiming:
 		lua_pushinteger(L, cmd->aiming);
-	else if (fastcmp(field,"buttons"))
+		break;
+	case ticcmd_buttons:
 		lua_pushinteger(L, cmd->buttons);
-	else if (fastcmp(field,"latency"))
+		break;
+	case ticcmd_latency:
 		lua_pushinteger(L, cmd->latency);
-	else
+		break;
+	default:
 		return NOFIELD;
+	}
 
 	return 1;
 }
@@ -842,27 +1485,35 @@ static int ticcmd_get(lua_State *L)
 static int ticcmd_set(lua_State *L)
 {
 	ticcmd_t *cmd = *((ticcmd_t **)luaL_checkudata(L, 1, META_TICCMD));
-	const char *field = luaL_checkstring(L, 2);
+	enum ticcmd_e field = Lua_optoption(L, 2, -1, ticcmd_fields_ref);
 	if (!cmd)
 		return LUA_ErrInvalid(L, "ticcmd_t");
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
 
-	if (fastcmp(field,"forwardmove"))
+	switch (field)
+	{
+	case ticcmd_forwardmove:
 		cmd->forwardmove = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"sidemove"))
+		break;
+	case ticcmd_sidemove:
 		cmd->sidemove = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"angleturn"))
+		break;
+	case ticcmd_angleturn:
 		cmd->angleturn = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case ticcmd_aiming:
 		cmd->aiming = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"buttons"))
+		break;
+	case ticcmd_buttons:
 		cmd->buttons = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"latency"))
+		break;
+	case ticcmd_latency:
 		return NOSET;
-	else
+	default:
 		return NOFIELD;
+	}
 
 	return 0;
 }
@@ -883,6 +1534,8 @@ int LUA_PlayerLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	player_fields_ref = Lua_CreateFieldTable(L, player_opt);
+
 	luaL_newmetatable(L, META_POWERS);
 		lua_pushcfunction(L, power_get);
 		lua_setfield(L, -2, "__index");
@@ -902,6 +1555,8 @@ int LUA_PlayerLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	ticcmd_fields_ref = Lua_CreateFieldTable(L, ticcmd_opt);
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getPlayer);
diff --git a/src/lua_script.c b/src/lua_script.c
index 8f7b04430871de9f2c9d55089e016966acbc7e34..6a5982006379d3e32e9694009e73fe67111a7f40 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -204,6 +204,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"modifiedgame")) {
 		lua_pushboolean(L, modifiedgame && !savemoddata);
 		return 1;
+	} else if (fastcmp(word,"usedCheats")) {
+		lua_pushboolean(L, usedCheats);
+		return 1;
 	} else if (fastcmp(word,"menuactive")) {
 		lua_pushboolean(L, menuactive);
 		return 1;
@@ -222,6 +225,18 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"pointlimit")) {
 		lua_pushinteger(L, cv_pointlimit.value);
 		return 1;
+	} else if (fastcmp(word, "redflag")) {
+		LUA_PushUserdata(L, redflag, META_MOBJ);
+		return 1;
+	} else if (fastcmp(word, "blueflag")) {
+		LUA_PushUserdata(L, blueflag, META_MOBJ);
+		return 1;
+	} else if (fastcmp(word, "rflagpoint")) {
+		LUA_PushUserdata(L, rflagpoint, META_MAPTHING);
+		return 1;
+	} else if (fastcmp(word, "bflagpoint")) {
+		LUA_PushUserdata(L, bflagpoint, META_MAPTHING);
+		return 1;
 	// begin map vars
 	} else if (fastcmp(word,"spstage_start")) {
 		lua_pushinteger(L, spstage_start);
@@ -303,6 +318,18 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushinteger(L, ammoremovaltics);
 		return 1;
 	// end timers
+	} else if (fastcmp(word,"use1upSound")) {
+		lua_pushinteger(L, use1upSound);
+		return 1;
+	} else if (fastcmp(word,"maxXtraLife")) {
+		lua_pushinteger(L, maxXtraLife);
+		return 1;
+	} else if (fastcmp(word,"useContinues")) {
+		lua_pushinteger(L, useContinues);
+		return 1;
+	} else if (fastcmp(word,"shareEmblems")) {
+		lua_pushinteger(L, shareEmblems);
+		return 1;
 	} else if (fastcmp(word,"gametype")) {
 		lua_pushinteger(L, gametype);
 		return 1;
@@ -486,7 +513,19 @@ static int setglobals(lua_State *L)
 
 		actionnum = LUA_GetActionNumByName(name);
 		if (actionnum < NUMACTIONS)
-			actionsoverridden[actionnum] = true;
+		{
+			int i;
+
+			for (i = MAX_ACTION_RECURSION-1; i > 0; i--)
+			{
+				// Move other references deeper.
+				actionsoverridden[actionnum][i] = actionsoverridden[actionnum][i - 1];
+			}
+
+			// Add the new reference.
+			lua_pushvalue(L, 2);
+			actionsoverridden[actionnum][0] = luaL_ref(L, LUA_REGISTRYINDEX);
+		}
 
 		Z_Free(name);
 		return 0;
@@ -695,20 +734,23 @@ void LUA_DumpFile(const char *filename)
 
 fixed_t LUA_EvalMath(const char *word)
 {
-	lua_State *L = NULL;
+	static lua_State *L = NULL;
 	char buf[1024], *b;
 	const char *p;
 	fixed_t res = 0;
 
-	// make a new state so SOC can't interefere with scripts
-	// allocate state
-	L = lua_newstate(LUA_Alloc, NULL);
-	lua_atpanic(L, LUA_Panic);
-
-	// open only enum lib
-	lua_pushcfunction(L, LUA_EnumLib);
-	lua_pushboolean(L, true);
-	lua_call(L, 1, 0);
+	if (!L)
+	{
+		// make a new state so SOC can't interefere with scripts
+		// allocate state
+		L = lua_newstate(LUA_Alloc, NULL);
+		lua_atpanic(L, LUA_Panic);
+
+		// open only enum lib
+		lua_pushcfunction(L, LUA_EnumLib);
+		lua_pushboolean(L, true);
+		lua_call(L, 1, 0);
+	}
 
 	// change ^ into ^^ for Lua.
 	strcpy(buf, "return ");
@@ -733,8 +775,6 @@ fixed_t LUA_EvalMath(const char *word)
 	else
 		res = lua_tointeger(L, -1);
 
-	// clean up and return.
-	lua_close(L);
 	return res;
 }
 
@@ -949,6 +989,7 @@ enum
 	ARCH_MAPHEADER,
 	ARCH_SKINCOLOR,
 	ARCH_MOUSE,
+	ARCH_SKIN,
 
 	ARCH_TEND=0xFF,
 };
@@ -977,6 +1018,7 @@ static const struct {
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{META_SKINCOLOR,   ARCH_SKINCOLOR},
 	{META_MOUSE,    ARCH_MOUSE},
+	{META_SKIN,     ARCH_SKIN},
 	{NULL,          ARCH_NULL}
 };
 
@@ -1298,6 +1340,13 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			WRITEUINT8(save_p, m == &mouse ? 1 : 2);
 			break;
 		}
+		case ARCH_SKIN:
+		{
+			skin_t *skin = *((skin_t **)lua_touserdata(gL, myindex));
+			WRITEUINT8(save_p, ARCH_SKIN);
+			WRITEUINT8(save_p, skin - skins); // UINT8 because MAXSKINS is only 32
+			break;
+		}
 		default:
 			WRITEUINT8(save_p, ARCH_NULL);
 			return 2;
@@ -1544,6 +1593,9 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 	case ARCH_MOUSE:
 		LUA_PushUserdata(gL, READUINT16(save_p) == 1 ? &mouse : &mouse2, META_MOUSE);
 		break;
+	case ARCH_SKIN:
+		LUA_PushUserdata(gL, &skins[READUINT8(save_p)], META_SKIN);
+		break;
 	case ARCH_TEND:
 		return 1;
 	}
@@ -1713,17 +1765,39 @@ void LUA_UnArchive(void)
 }
 
 // For mobj_t, player_t, etc. to take custom variables.
-int Lua_optoption(lua_State *L, int narg,
-	const char *def, const char *const lst[])
+int Lua_optoption(lua_State *L, int narg, int def, int list_ref)
 {
-	const char *name = (def) ? luaL_optstring(L, narg, def) :  luaL_checkstring(L, narg);
-	int i;
-	for (i=0; lst[i]; i++)
-		if (fastcmp(lst[i], name))
-			return i;
+	if (lua_isnoneornil(L, narg))
+		return def;
+
+	I_Assert(lua_checkstack(L, 2));
+	luaL_checkstring(L, narg);
+
+	lua_rawgeti(L, LUA_REGISTRYINDEX, list_ref);
+	I_Assert(lua_istable(L, -1));
+	lua_pushvalue(L, narg);
+	lua_rawget(L, -2);
+
+	if (lua_isnumber(L, -1))
+		return lua_tointeger(L, -1);
 	return -1;
 }
 
+int Lua_CreateFieldTable(lua_State *L, const char *const lst[])
+{
+	int i;
+
+	lua_newtable(L);
+	for (i = 0; lst[i] != NULL; i++)
+	{
+		lua_pushstring(L, lst[i]);
+		lua_pushinteger(L, i);
+		lua_settable(L, -3);
+	}
+
+	return luaL_ref(L, LUA_REGISTRYINDEX);
+}
+
 void LUA_PushTaggableObjectArray
 (		lua_State *L,
 		const char *field,
diff --git a/src/lua_script.h b/src/lua_script.h
index fe04e5e608fdeca20690799e5df2aa1c3b145db6..d0b06a719e32a254ba56c5f860252aba15484da0 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -57,8 +57,8 @@ int LUA_PushGlobals(lua_State *L, const char *word);
 int LUA_CheckGlobals(lua_State *L, const char *word);
 void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c
 void LUA_CVarChanged(void *cvar); // lua_consolelib.c
-int Lua_optoption(lua_State *L, int narg,
-	const char *def, const char *const lst[]);
+int Lua_optoption(lua_State *L, int narg, int def, int list_ref);
+int Lua_CreateFieldTable(lua_State *L, const char *const lst[]);
 void LUA_HookNetArchive(lua_CFunction archFunc);
 
 void LUA_PushTaggableObjectArray
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 5c21b04c3a31ea4d29d0d73049dbd54d5ab1dc04..041c5d59851f3669b03d45c899c20b3440e193f0 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,
@@ -55,6 +56,7 @@ enum skin {
 	skin_soundsid,
 	skin_sprites
 };
+
 static const char *const skin_opt[] = {
 	"valid",
 	"name",
@@ -62,6 +64,7 @@ static const char *const skin_opt[] = {
 	"flags",
 	"realname",
 	"hudname",
+	"supername",
 	"ability",
 	"ability2",
 	"thokitem",
@@ -95,10 +98,12 @@ static const char *const skin_opt[] = {
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("skin_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", skin_opt[field])
 
+static int skin_fields_ref = LUA_NOREF;
+
 static int skin_get(lua_State *L)
 {
 	skin_t *skin = *((skin_t **)luaL_checkudata(L, 1, META_SKIN));
-	enum skin field = luaL_checkoption(L, 2, NULL, skin_opt);
+	enum skin field = Lua_optoption(L, 2, -1, skin_fields_ref);
 
 	// skins are always valid, only added, never removed
 	I_Assert(skin != NULL);
@@ -123,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;
@@ -331,13 +339,13 @@ static const char *const sprites_opt[] = {
 // skin.sprites[i] -> sprites[i]
 static int lib_getSkinSprite(lua_State *L)
 {
-	spritedef_t *sprites = *(spritedef_t **)luaL_checkudata(L, 1, META_SKINSPRITES);
+	spritedef_t *sksprites = *(spritedef_t **)luaL_checkudata(L, 1, META_SKINSPRITES);
 	playersprite_t i = luaL_checkinteger(L, 2);
 
 	if (i < 0 || i >= NUMPLAYERSPRITES*2)
 		return luaL_error(L, LUA_QL("skin_t") " field 'sprites' index %d out of range (0 - %d)", i, (NUMPLAYERSPRITES*2)-1);
 
-	LUA_PushUserdata(L, &sprites[i], META_SKINSPRITESLIST);
+	LUA_PushUserdata(L, &sksprites[i], META_SKINSPRITESLIST);
 	return 1;
 }
 
@@ -376,6 +384,8 @@ int LUA_SkinLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	skin_fields_ref = Lua_CreateFieldTable(L, skin_opt);
+
 	luaL_newmetatable(L, META_SOUNDSID);
 		lua_pushcfunction(L, soundsid_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 4ea8d13b41e83cba6f2af90bb75900a95a4cf59d..7ad86353ac86916b167d3e0768f9a8ad6f23444e 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -72,19 +72,16 @@ static UINT8 cheatf_ultimate(void)
 
 static UINT8 cheatf_warp(void)
 {
-	if (modifiedgame)
-		return 0;
-
 	if (menuactive && currentMenu != &MainDef)
 		return 0; // Only on the main menu!
 
 	S_StartSound(0, sfx_itemup);
 
 	// Temporarily unlock stuff.
-	G_SetGameModified(false);
-	unlockables[31].unlocked = true; // credits
-	unlockables[30].unlocked = true; // sound test
-	unlockables[28].unlocked = true; // level select
+	G_SetUsedCheats(false);
+	clientGamedata->unlocked[31] = true; // credits
+	clientGamedata->unlocked[30] = true; // sound test
+	clientGamedata->unlocked[28] = true; // level select
 
 	// Refresh secrets menu existing.
 	M_ClearMenus(true);
@@ -97,18 +94,15 @@ static UINT8 cheatf_devmode(void)
 {
 	UINT8 i;
 
-	if (modifiedgame)
-		return 0;
-
 	if (menuactive && currentMenu != &MainDef)
 		return 0; // Only on the main menu!
 
 	S_StartSound(0, sfx_itemup);
 
 	// Just unlock all the things and turn on -debug and console devmode.
-	G_SetGameModified(false);
+	G_SetUsedCheats(false);
 	for (i = 0; i < MAXUNLOCKABLES; i++)
-		unlockables[i].unlocked = true;
+		clientGamedata->unlocked[i] = true;
 	devparm = true;
 	cv_debug |= 0x8000;
 
@@ -244,7 +238,7 @@ boolean cht_Responder(event_t *ev)
 }
 
 // Console cheat commands rely on these a lot...
-#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA) && !cv_debug)\
+#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !cv_debug)\
 { CONS_Printf(M_GetText("You haven't earned this yet.\n")); return; }
 
 #define REQUIRE_DEVMODE if (!cv_debug)\
@@ -275,7 +269,7 @@ void Command_CheatNoClip_f(void)
 	plyr->pflags ^= PF_NOCLIP;
 	CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 void Command_CheatGod_f(void)
@@ -290,7 +284,7 @@ void Command_CheatGod_f(void)
 	plyr->pflags ^= PF_GODMODE;
 	CONS_Printf(M_GetText("Cheese Mode %s\n"), plyr->pflags & PF_GODMODE ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 void Command_CheatNoTarget_f(void)
@@ -305,7 +299,7 @@ void Command_CheatNoTarget_f(void)
 	plyr->pflags ^= PF_INVIS;
 	CONS_Printf(M_GetText("SEP Field %s\n"), plyr->pflags & PF_INVIS ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 void Command_Scale_f(void)
@@ -879,7 +873,7 @@ void Command_Devmode_f(void)
 		return;
 	}
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 void Command_Setrings_f(void)
@@ -905,7 +899,7 @@ void Command_Setrings_f(void)
 			// no totalsphere addition to revert
 		}
 
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 	}
 }
 
@@ -928,7 +922,7 @@ void Command_Setlives_f(void)
 			P_GivePlayerLives(&players[consoleplayer], atoi(COM_Argv(1)));
 		}
 
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 	}
 }
 
@@ -955,7 +949,7 @@ void Command_Setcontinues_f(void)
 
 		players[consoleplayer].continues = numcontinues;
 
-		G_SetGameModified(multiplayer);
+		G_SetUsedCheats(false);
 	}
 }
 
@@ -1108,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;
@@ -1446,7 +1442,7 @@ void Command_ObjectPlace_f(void)
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 
 	silent = COM_CheckParm("-silent");
 
diff --git a/src/m_cond.c b/src/m_cond.c
index 769ff90be3be5197985f415cab2a02f51d27c383..6c87ebf6e5d8fb30789be6b5675f10daa574a22e 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -21,6 +21,9 @@
 #include "r_skins.h" // numskins
 #include "r_draw.h" // R_GetColorByName
 
+gamedata_t *clientGamedata; // Our gamedata
+gamedata_t *serverGamedata; // Server's gamedata
+
 // Map triggers for linedef executors
 // 32 triggers, one bit each
 UINT32 unlocktriggers;
@@ -41,12 +44,76 @@ unlockable_t unlockables[MAXUNLOCKABLES];
 INT32 numemblems = 0;
 INT32 numextraemblems = 0;
 
+// Temporary holding place for nights data for the current map
+nightsdata_t ntemprecords[MAXPLAYERS];
+
+// Create a new gamedata_t, for start-up
+gamedata_t *M_NewGameDataStruct(void)
+{
+	gamedata_t *data = Z_Calloc(sizeof (*data), PU_STATIC, NULL);
+	M_ClearSecrets(data);
+	G_ClearRecords(data);
+	return data;
+}
+
+void M_CopyGameData(gamedata_t *dest, gamedata_t *src)
+{
+	INT32 i, j;
+
+	M_ClearSecrets(dest);
+	G_ClearRecords(dest);
+
+	dest->loaded = src->loaded;
+	dest->totalplaytime = src->totalplaytime;
+
+	dest->timesBeaten = src->timesBeaten;
+	dest->timesBeatenWithEmeralds = src->timesBeatenWithEmeralds;
+	dest->timesBeatenUltimate = src->timesBeatenUltimate;
+
+	memcpy(dest->achieved, src->achieved, sizeof(dest->achieved));
+	memcpy(dest->collected, src->collected, sizeof(dest->collected));
+	memcpy(dest->extraCollected, src->extraCollected, sizeof(dest->extraCollected));
+	memcpy(dest->unlocked, src->unlocked, sizeof(dest->unlocked));
+
+	memcpy(dest->mapvisited, src->mapvisited, sizeof(dest->mapvisited));
+
+	// Main records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if (!src->mainrecords[i])
+			continue;
+
+		G_AllocMainRecordData((INT16)i, dest);
+		dest->mainrecords[i]->score = src->mainrecords[i]->score;
+		dest->mainrecords[i]->time = src->mainrecords[i]->time;
+		dest->mainrecords[i]->rings = src->mainrecords[i]->rings;
+	}
+
+	// Nights records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if (!src->nightsrecords[i] || !src->nightsrecords[i]->nummares)
+			continue;
+
+		G_AllocNightsRecordData((INT16)i, dest);
+
+		for (j = 0; j < (src->nightsrecords[i]->nummares + 1); j++)
+		{
+			dest->nightsrecords[i]->score[j] = src->nightsrecords[i]->score[j];
+			dest->nightsrecords[i]->grade[j] = src->nightsrecords[i]->grade[j];
+			dest->nightsrecords[i]->time[j] = src->nightsrecords[i]->time[j];
+		}
+
+		dest->nightsrecords[i]->nummares = src->nightsrecords[i]->nummares;
+	}
+}
+
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
 {
 	condition_t *cond;
 	UINT32 num, wnum;
 
-	I_Assert(set && set <= MAXCONDITIONSETS);
+	I_Assert(set < MAXCONDITIONSETS);
 
 	wnum = conditionSets[set - 1].numconditions;
 	num = ++conditionSets[set - 1].numconditions;
@@ -70,89 +137,90 @@ void M_ClearConditionSet(UINT8 set)
 		conditionSets[set - 1].condition = NULL;
 		conditionSets[set - 1].numconditions = 0;
 	}
-	conditionSets[set - 1].achieved = false;
+	clientGamedata->achieved[set - 1] = serverGamedata->achieved[set - 1] = false;
 }
 
 // Clear ALL secrets.
-void M_ClearSecrets(void)
+void M_ClearSecrets(gamedata_t *data)
 {
 	INT32 i;
 
-	memset(mapvisited, 0, sizeof(mapvisited));
+	memset(data->mapvisited, 0, sizeof(data->mapvisited));
 
 	for (i = 0; i < MAXEMBLEMS; ++i)
-		emblemlocations[i].collected = false;
+		data->collected[i] = false;
 	for (i = 0; i < MAXEXTRAEMBLEMS; ++i)
-		extraemblems[i].collected = false;
+		data->extraCollected[i] = false;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
-		unlockables[i].unlocked = false;
+		data->unlocked[i] = false;
 	for (i = 0; i < MAXCONDITIONSETS; ++i)
-		conditionSets[i].achieved = false;
+		data->achieved[i] = false;
 
-	timesBeaten = timesBeatenWithEmeralds = timesBeatenUltimate = 0;
+	data->timesBeaten = data->timesBeatenWithEmeralds = data->timesBeatenUltimate = 0;
 
 	// Re-unlock any always unlocked things
-	M_SilentUpdateUnlockablesAndEmblems();
+	M_SilentUpdateUnlockablesAndEmblems(data);
+	M_SilentUpdateSkinAvailabilites();
 }
 
 // ----------------------
 // Condition set checking
 // ----------------------
-static UINT8 M_CheckCondition(condition_t *cn)
+static UINT8 M_CheckCondition(condition_t *cn, gamedata_t *data)
 {
 	switch (cn->type)
 	{
 		case UC_PLAYTIME: // Requires total playing time >= x
-			return (totalplaytime >= (unsigned)cn->requirement);
+			return (data->totalplaytime >= (unsigned)cn->requirement);
 		case UC_GAMECLEAR: // Requires game beaten >= x times
-			return (timesBeaten >= (unsigned)cn->requirement);
+			return (data->timesBeaten >= (unsigned)cn->requirement);
 		case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times
-			return (timesBeatenWithEmeralds >= (unsigned)cn->requirement);
+			return (data->timesBeatenWithEmeralds >= (unsigned)cn->requirement);
 		case UC_ULTIMATECLEAR: // Requires game beaten on ultimate >= x times (in other words, never)
-			return (timesBeatenUltimate >= (unsigned)cn->requirement);
+			return (data->timesBeatenUltimate >= (unsigned)cn->requirement);
 		case UC_OVERALLSCORE: // Requires overall score >= x
-			return (M_GotHighEnoughScore(cn->requirement));
+			return (M_GotHighEnoughScore(cn->requirement, data));
 		case UC_OVERALLTIME: // Requires overall time <= x
-			return (M_GotLowEnoughTime(cn->requirement));
+			return (M_GotLowEnoughTime(cn->requirement, data));
 		case UC_OVERALLRINGS: // Requires overall rings >= x
-			return (M_GotHighEnoughRings(cn->requirement));
+			return (M_GotHighEnoughRings(cn->requirement, data));
 		case UC_MAPVISITED: // Requires map x to be visited
-			return ((mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED);
+			return ((data->mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED);
 		case UC_MAPBEATEN: // Requires map x to be beaten
-			return ((mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN);
+			return ((data->mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN);
 		case UC_MAPALLEMERALDS: // Requires map x to be beaten with all emeralds in possession
-			return ((mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS);
+			return ((data->mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS);
 		case UC_MAPULTIMATE: // Requires map x to be beaten on ultimate
-			return ((mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE);
+			return ((data->mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE);
 		case UC_MAPPERFECT: // Requires map x to be beaten with a perfect bonus
-			return ((mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT);
+			return ((data->mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT);
 		case UC_MAPSCORE: // Requires score on map >= x
-			return (G_GetBestScore(cn->extrainfo1) >= (unsigned)cn->requirement);
+			return (G_GetBestScore(cn->extrainfo1, data) >= (unsigned)cn->requirement);
 		case UC_MAPTIME: // Requires time on map <= x
-			return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
+			return (G_GetBestTime(cn->extrainfo1, data) <= (unsigned)cn->requirement);
 		case UC_MAPRINGS: // Requires rings on map >= x
-			return (G_GetBestRings(cn->extrainfo1) >= cn->requirement);
+			return (G_GetBestRings(cn->extrainfo1, data) >= cn->requirement);
 		case UC_NIGHTSSCORE:
-			return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2) >= (unsigned)cn->requirement);
+			return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= (unsigned)cn->requirement);
 		case UC_NIGHTSTIME:
-			return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2) <= (unsigned)cn->requirement);
+			return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2, data) <= (unsigned)cn->requirement);
 		case UC_NIGHTSGRADE:
-			return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2) >= cn->requirement);
+			return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= cn->requirement);
 		case UC_TRIGGER: // requires map trigger set
 			return !!(unlocktriggers & (1 << cn->requirement));
 		case UC_TOTALEMBLEMS: // Requires number of emblems >= x
-			return (M_GotEnoughEmblems(cn->requirement));
+			return (M_GotEnoughEmblems(cn->requirement, data));
 		case UC_EMBLEM: // Requires emblem x to be obtained
-			return emblemlocations[cn->requirement-1].collected;
+			return data->collected[cn->requirement-1];
 		case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained
-			return extraemblems[cn->requirement-1].collected;
+			return data->extraCollected[cn->requirement-1];
 		case UC_CONDITIONSET: // requires condition set x to already be achieved
-			return M_Achieved(cn->requirement-1);
+			return M_Achieved(cn->requirement-1, data);
 	}
 	return false;
 }
 
-static UINT8 M_CheckConditionSet(conditionset_t *c)
+static UINT8 M_CheckConditionSet(conditionset_t *c, gamedata_t *data)
 {
 	UINT32 i;
 	UINT32 lastID = 0;
@@ -173,13 +241,13 @@ static UINT8 M_CheckConditionSet(conditionset_t *c)
 			continue;
 
 		lastID = cn->id;
-		achievedSoFar = M_CheckCondition(cn);
+		achievedSoFar = M_CheckCondition(cn, data);
 	}
 
 	return achievedSoFar;
 }
 
-void M_CheckUnlockConditions(void)
+void M_CheckUnlockConditions(gamedata_t *data)
 {
 	INT32 i;
 	conditionset_t *c;
@@ -187,30 +255,27 @@ void M_CheckUnlockConditions(void)
 	for (i = 0; i < MAXCONDITIONSETS; ++i)
 	{
 		c = &conditionSets[i];
-		if (!c->numconditions || c->achieved)
+		if (!c->numconditions || data->achieved[i])
 			continue;
 
-		c->achieved = (M_CheckConditionSet(c));
+		data->achieved[i] = (M_CheckConditionSet(c, data));
 	}
 }
 
-UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
+UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data)
 {
 	INT32 i;
 	char cechoText[992] = "";
 	UINT8 cechoLines = 0;
 
-	if (modifiedgame && !savemoddata)
-		return false;
-
-	M_CheckUnlockConditions();
+	M_CheckUnlockConditions(data);
 
 	// Go through extra emblems
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected || !extraemblems[i].conditionset)
+		if (data->extraCollected[i] || !extraemblems[i].conditionset)
 			continue;
-		if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
+		if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false)
 		{
 			strcat(cechoText, va(M_GetText("Got \"%s\" emblem!\\"), extraemblems[i].name));
 			++cechoLines;
@@ -220,14 +285,14 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 	// Fun part: if any of those unlocked we need to go through the
 	// unlock conditions AGAIN just in case an emblem reward was reached
 	if (cechoLines)
-		M_CheckUnlockConditions();
+		M_CheckUnlockConditions(data);
 
 	// Go through unlockables
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].unlocked || !unlockables[i].conditionset)
+		if (data->unlocked[i] || !unlockables[i].conditionset)
 			continue;
-		if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false)
+		if ((data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data)) != false)
 		{
 			if (unlockables[i].nocecho)
 				continue;
@@ -251,45 +316,50 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 		HU_DoCEcho(slashed);
 		return true;
 	}
+
 	return false;
 }
 
 // Used when loading gamedata to make sure all unlocks are synched with conditions
-void M_SilentUpdateUnlockablesAndEmblems(void)
+void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data)
 {
 	INT32 i;
 	boolean checkAgain = false;
 
 	// Just in case they aren't to sync
-	M_CheckUnlockConditions();
-	M_CheckLevelEmblems();
+	M_CheckUnlockConditions(data);
+	M_CheckLevelEmblems(data);
+	M_CompletionEmblems(data);
 
 	// Go through extra emblems
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected || !extraemblems[i].conditionset)
+		if (data->extraCollected[i] || !extraemblems[i].conditionset)
 			continue;
-		if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
+		if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false)
 			checkAgain = true;
 	}
 
 	// check again if extra emblems unlocked, blah blah, etc
 	if (checkAgain)
-		M_CheckUnlockConditions();
+		M_CheckUnlockConditions(data);
 
 	// Go through unlockables
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].unlocked || !unlockables[i].conditionset)
+		if (data->unlocked[i] || !unlockables[i].conditionset)
 			continue;
-		unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1);
+		data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data);
 	}
+}
 
+void M_SilentUpdateSkinAvailabilites(void)
+{
 	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
 }
 
 // Emblem unlocking shit
-UINT8 M_CheckLevelEmblems(void)
+UINT8 M_CheckLevelEmblems(gamedata_t *data)
 {
 	INT32 i;
 	INT32 valToReach;
@@ -300,7 +370,7 @@ UINT8 M_CheckLevelEmblems(void)
 	// Update Score, Time, Rings emblems
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected)
+		if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || data->collected[i])
 			continue;
 
 		levelnum = emblemlocations[i].level;
@@ -309,32 +379,32 @@ UINT8 M_CheckLevelEmblems(void)
 		switch (emblemlocations[i].type)
 		{
 			case ET_SCORE: // Requires score on map >= x
-				res = (G_GetBestScore(levelnum) >= (unsigned)valToReach);
+				res = (G_GetBestScore(levelnum, data) >= (unsigned)valToReach);
 				break;
 			case ET_TIME: // Requires time on map <= x
-				res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
+				res = (G_GetBestTime(levelnum, data) <= (unsigned)valToReach);
 				break;
 			case ET_RINGS: // Requires rings on map >= x
-				res = (G_GetBestRings(levelnum) >= valToReach);
+				res = (G_GetBestRings(levelnum, data) >= valToReach);
 				break;
 			case ET_NGRADE: // Requires NiGHTS grade on map >= x
-				res = (G_GetBestNightsGrade(levelnum, 0) >= valToReach);
+				res = (G_GetBestNightsGrade(levelnum, 0, data) >= valToReach);
 				break;
 			case ET_NTIME: // Requires NiGHTS time on map <= x
-				res = (G_GetBestNightsTime(levelnum, 0) <= (unsigned)valToReach);
+				res = (G_GetBestNightsTime(levelnum, 0, data) <= (unsigned)valToReach);
 				break;
 			default: // unreachable but shuts the compiler up.
 				continue;
 		}
 
-		emblemlocations[i].collected = res;
+		data->collected[i] = res;
 		if (res)
 			++somethingUnlocked;
 	}
 	return somethingUnlocked;
 }
 
-UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
+UINT8 M_CompletionEmblems(gamedata_t *data) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
 {
 	INT32 i;
 	INT32 embtype;
@@ -345,7 +415,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected)
+		if (emblemlocations[i].type != ET_MAP || data->collected[i])
 			continue;
 
 		levelnum = emblemlocations[i].level;
@@ -361,9 +431,9 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 		if (embtype & ME_PERFECT)
 			flags |= MV_PERFECT;
 
-		res = ((mapvisited[levelnum - 1] & flags) == flags);
+		res = ((data->mapvisited[levelnum - 1] & flags) == flags);
 
-		emblemlocations[i].collected = res;
+		data->collected[i] = res;
 		if (res)
 			++somethingUnlocked;
 	}
@@ -373,48 +443,105 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 // -------------------
 // Quick unlock checks
 // -------------------
-UINT8 M_AnySecretUnlocked(void)
+UINT8 M_AnySecretUnlocked(gamedata_t *data)
 {
 	INT32 i;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (!unlockables[i].nocecho && unlockables[i].unlocked)
+		if (!unlockables[i].nocecho && data->unlocked[i])
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_SecretUnlocked(INT32 type)
+UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data)
 {
 	INT32 i;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].type == type && unlockables[i].unlocked)
+		if (unlockables[i].type == type && data->unlocked[i])
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_MapLocked(INT32 mapnum)
+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;
-	if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked)
+	}
+
+	if (!data->unlocked[mapheaderinfo[mapnum-1]->unlockrequired])
+	{
 		return true;
+	}
+
 	return false;
 }
 
-INT32 M_CountEmblems(void)
+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]->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;
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].collected)
+		if (data->collected[i])
 			found++;
 	}
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected)
+		if (data->extraCollected[i])
 			found++;
 	}
 	return found;
@@ -426,23 +553,23 @@ INT32 M_CountEmblems(void)
 
 // Theoretically faster than using M_CountEmblems()
 // Stops when it reaches the target number of emblems.
-UINT8 M_GotEnoughEmblems(INT32 number)
+UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data)
 {
 	INT32 i, gottenemblems = 0;
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].collected)
+		if (data->collected[i])
 			if (++gottenemblems >= number) return true;
 	}
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected)
+		if (data->extraCollected[i])
 			if (++gottenemblems >= number) return true;
 	}
 	return false;
 }
 
-UINT8 M_GotHighEnoughScore(INT32 tscore)
+UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data)
 {
 	INT32 mscore = 0;
 	INT32 i;
@@ -451,16 +578,16 @@ UINT8 M_GotHighEnoughScore(INT32 tscore)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 			continue;
 
-		if ((mscore += mainrecords[i]->score) > tscore)
+		if ((mscore += data->mainrecords[i]->score) > tscore)
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_GotLowEnoughTime(INT32 tictime)
+UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data)
 {
 	INT32 curtics = 0;
 	INT32 i;
@@ -470,15 +597,15 @@ UINT8 M_GotLowEnoughTime(INT32 tictime)
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
 
-		if (!mainrecords[i] || !mainrecords[i]->time)
+		if (!data->mainrecords[i] || !data->mainrecords[i]->time)
 			return false;
-		else if ((curtics += mainrecords[i]->time) > tictime)
+		else if ((curtics += data->mainrecords[i]->time) > tictime)
 			return false;
 	}
 	return true;
 }
 
-UINT8 M_GotHighEnoughRings(INT32 trings)
+UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data)
 {
 	INT32 mrings = 0;
 	INT32 i;
@@ -487,10 +614,10 @@ UINT8 M_GotHighEnoughRings(INT32 trings)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 			continue;
 
-		if ((mrings += mainrecords[i]->rings) > trings)
+		if ((mrings += data->mainrecords[i]->rings) > trings)
 			return true;
 	}
 	return false;
diff --git a/src/m_cond.h b/src/m_cond.h
index d49dc920b30433b5d0ac416186ec9ba1cf9b7894..2491a384c02aa5f34ba47933eba689811ac599d0 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -10,7 +10,11 @@
 /// \file  m_cond.h
 /// \brief Unlockable condition system for SRB2 version 2.1
 
+#ifndef __M_COND__
+#define __M_COND__
+
 #include "doomdef.h"
+#include "doomdata.h"
 
 // --------
 // Typedefs
@@ -61,8 +65,6 @@ typedef struct
 {
 	UINT32 numconditions;   /// <- number of conditions.
 	condition_t *condition; /// <- All conditionals to be checked.
-	UINT8 achieved;         /// <- Whether this conditional has been achieved already or not.
-	                        ///    (Conditional checking is skipped if true -- it's assumed you can't relock an unlockable)
 } conditionset_t;
 
 // Emblem information
@@ -94,7 +96,6 @@ typedef struct
 	INT32 var;       ///< If needed, specifies information on the target amount to achieve (or target skin)
 	char *stringVar; ///< String version
 	char hint[110];  ///< Hint for emblem hints menu
-	UINT8 collected; ///< Do you have this emblem?
 } emblem_t;
 typedef struct
 {
@@ -104,7 +105,6 @@ typedef struct
 	UINT8 showconditionset; ///< Condition set that shows this emblem.
 	UINT8 sprite;           ///< emblem sprite to use, 0 - 25
 	UINT16 color;           ///< skincolor to use
-	UINT8 collected;        ///< Do you have this emblem?
 } extraemblem_t;
 
 // Unlockable information
@@ -120,7 +120,6 @@ typedef struct
 	char *stringVar;
 	UINT8 nocecho;
 	UINT8 nochecklist;
-	UINT8 unlocked;
 } unlockable_t;
 
 #define SECRET_NONE         -6 // Does nil.  Use with levels locked by UnlockRequired
@@ -140,8 +139,85 @@ typedef struct
 // you seriously need to get a life.
 #define MAXCONDITIONSETS 128
 #define MAXEMBLEMS       512
-#define MAXEXTRAEMBLEMS   16
-#define MAXUNLOCKABLES    32
+#define MAXEXTRAEMBLEMS   48
+#define MAXUNLOCKABLES    80
+
+/** Time attack information, currently a very small structure.
+  */
+typedef struct
+{
+	tic_t time;   ///< Time in which the level was finished.
+	UINT32 score; ///< Score when the level was finished.
+	UINT16 rings; ///< Rings when the level was finished.
+} recorddata_t;
+
+/** Setup for one NiGHTS map.
+  * These are dynamically allocated because I am insane
+  */
+#define GRADE_F 0
+#define GRADE_E 1
+#define GRADE_D 2
+#define GRADE_C 3
+#define GRADE_B 4
+#define GRADE_A 5
+#define GRADE_S 6
+
+typedef struct
+{
+	// 8 mares, 1 overall (0)
+	UINT8	nummares;
+	UINT32	score[9];
+	UINT8	grade[9];
+	tic_t	time[9];
+} nightsdata_t;
+
+// mapvisited is now a set of flags that says what we've done in the map.
+#define MV_VISITED      1
+#define MV_BEATEN       2
+#define MV_ALLEMERALDS  4
+#define MV_ULTIMATE     8
+#define MV_PERFECT     16
+#define MV_PERFECTRA   32
+#define MV_MAX         63 // used in gamedata check, update whenever MV's are added
+
+// Temporary holding place for nights data for the current map
+extern nightsdata_t ntemprecords[MAXPLAYERS];
+
+// GAMEDATA STRUCTURE
+// Everything that would get saved in gamedata.dat
+typedef struct
+{
+	// WHENEVER OR NOT WE'RE READY TO SAVE
+	boolean loaded;
+
+	// CONDITION SETS ACHIEVED
+	boolean achieved[MAXCONDITIONSETS];
+
+	// EMBLEMS COLLECTED
+	boolean collected[MAXEMBLEMS];
+
+	// EXTRA EMBLEMS COLLECTED
+	boolean extraCollected[MAXEXTRAEMBLEMS];
+
+	// UNLOCKABLES UNLOCKED
+	boolean unlocked[MAXUNLOCKABLES];
+
+	// TIME ATTACK DATA
+	recorddata_t *mainrecords[NUMMAPS];
+	nightsdata_t *nightsrecords[NUMMAPS];
+	UINT8 mapvisited[NUMMAPS];
+
+	// # OF TIMES THE GAME HAS BEEN BEATEN
+	UINT32 timesBeaten;
+	UINT32 timesBeatenWithEmeralds;
+	UINT32 timesBeatenUltimate;
+
+	// PLAY TIME
+	UINT32 totalplaytime;
+} gamedata_t;
+
+extern gamedata_t *clientGamedata;
+extern gamedata_t *serverGamedata;
 
 extern conditionset_t conditionSets[MAXCONDITIONSETS];
 extern emblem_t emblemlocations[MAXEMBLEMS];
@@ -153,25 +229,31 @@ extern INT32 numextraemblems;
 
 extern UINT32 unlocktriggers;
 
+gamedata_t *M_NewGameDataStruct(void);
+void M_CopyGameData(gamedata_t *dest, gamedata_t *src);
+
 // Condition set setup
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2);
 
 // Clearing secrets
 void M_ClearConditionSet(UINT8 set);
-void M_ClearSecrets(void);
+void M_ClearSecrets(gamedata_t *data);
 
 // Updating conditions and unlockables
-void M_CheckUnlockConditions(void);
-UINT8 M_UpdateUnlockablesAndExtraEmblems(void);
-void M_SilentUpdateUnlockablesAndEmblems(void);
-UINT8 M_CheckLevelEmblems(void);
-UINT8 M_CompletionEmblems(void);
+void M_CheckUnlockConditions(gamedata_t *data);
+UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data);
+void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data);
+UINT8 M_CheckLevelEmblems(gamedata_t *data);
+UINT8 M_CompletionEmblems(gamedata_t *data);
+
+void M_SilentUpdateSkinAvailabilites(void);
 
 // Checking unlockable status
-UINT8 M_AnySecretUnlocked(void);
-UINT8 M_SecretUnlocked(INT32 type);
-UINT8 M_MapLocked(INT32 mapnum);
-INT32 M_CountEmblems(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
 emblem_t *M_GetLevelEmblems(INT32 mapnum);
@@ -183,12 +265,14 @@ const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big);
 // If you're looking to compare stats for unlocks or what not, use these
 // They stop checking upon reaching the target number so they
 // should be (theoretically?) slightly faster.
-UINT8 M_GotEnoughEmblems(INT32 number);
-UINT8 M_GotHighEnoughScore(INT32 tscore);
-UINT8 M_GotLowEnoughTime(INT32 tictime);
-UINT8 M_GotHighEnoughRings(INT32 trings);
+UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data);
+UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data);
+UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data);
+UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data);
 
 INT32 M_UnlockableSkinNum(unlockable_t *unlock);
 INT32 M_EmblemSkinNum(emblem_t *emblem);
 
-#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved)
+#define M_Achieved(a, data) ((a) >= MAXCONDITIONSETS || data->achieved[a])
+
+#endif
diff --git a/src/m_fixed.c b/src/m_fixed.c
index ad283119646b75abf514360bcf64d96972e86214..b674e3b2c8e230524d57ea540dada83b8bf75eb6 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -21,50 +21,6 @@
 #include "doomdef.h"
 #include "m_fixed.h"
 
-#ifdef __USE_C_FIXEDMUL__
-
-/**	\brief	The FixedMul function
-
-	\param	a	fixed_t number
-	\param	b	fixed_t number
-
-	\return	a*b>>FRACBITS
-
-*/
-fixed_t FixedMul(fixed_t a, fixed_t b)
-{
-	// Need to cast to unsigned before shifting to avoid undefined behaviour
-	// for negative integers
-	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
-}
-
-#endif //__USE_C_FIXEDMUL__
-
-#ifdef __USE_C_FIXEDDIV__
-/**	\brief	The FixedDiv2 function
-
-	\param	a	fixed_t number
-	\param	b	fixed_t number
-
-	\return	a/b * FRACUNIT
-
-*/
-fixed_t FixedDiv2(fixed_t a, fixed_t b)
-{
-	INT64 ret;
-
-	if (b == 0)
-		I_Error("FixedDiv: divide by zero");
-
-	ret = (((INT64)a * FRACUNIT)) / b;
-
-	if ((ret > INT32_MAX) || (ret < INT32_MIN))
-		I_Error("FixedDiv: divide by zero");
-	return (fixed_t)ret;
-}
-
-#endif // __USE_C_FIXEDDIV__
-
 fixed_t FixedSqrt(fixed_t x)
 {
 #ifdef HAVE_SQRT
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 4a5b7ce2adf863571000b2134948d0c8e4e3e86f..94bd6a16bbf86e70c4f38c86a4758424da325db1 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -53,127 +53,35 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f)
 #define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT))
 #define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT))
 
+/**	\brief	The FixedMul function
 
-#if defined (__WATCOMC__) && FRACBITS == 16
-	#pragma aux FixedMul =  \
-		"imul ebx",         \
-		"shrd eax,edx,16"   \
-		parm    [eax] [ebx] \
-		value   [eax]       \
-		modify exact [eax edx]
-
-	#pragma aux FixedDiv2 = \
-		"cdq",              \
-		"shld edx,eax,16",  \
-		"sal eax,16",       \
-		"idiv ebx"          \
-		parm    [eax] [ebx] \
-		value   [eax]       \
-		modify exact [eax edx]
-#elif defined (__GNUC__) && defined (__i386__) && !defined (NOASM)
-	// i386 linux, cygwin or mingw
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret;
-		asm
-		(
-			 "imull %2;"           // a*b
-			 "shrdl %3,%%edx,%0;"  // shift logical right FRACBITS bits
-			:"=a" (ret)            // eax is always the result and the first operand (%0,%1)
-			:"0" (a), "r" (b)      // and %2 is what we use imull on with what in %1
-			, "I" (FRACBITS)       // %3 holds FRACBITS (normally 16)
-			:"cc", "%edx"         // edx and condition codes clobbered
-		);
-		return ret;
-	}
+	\param	a	fixed_t number
+	\param	b	fixed_t number
 
-	FUNCMATH FUNCINLINE static inline fixed_t FixedDiv2(fixed_t a, fixed_t b)
-	{
-		fixed_t ret;
-		asm
-		(
-			  "movl  %1,%%edx;"    // these two instructions allow the next two to pair, on the Pentium processor.
-			  "sarl  $31,%%edx;"   // shift arithmetic right 31 on EDX
-			  "shldl %3,%1,%%edx;" // DP shift logical left FRACBITS on EDX
-			  "sall  %3,%0;"       // shift arithmetic left FRACBITS on EAX
-			  "idivl %2;"          // EDX/b = EAX
-			: "=a" (ret)
-			: "0" (a), "r" (b)
-			, "I" (FRACBITS)
-			: "%edx"
-		);
-		return ret;
-	}
-#elif defined (__GNUC__) && defined (__arm__) && !defined(__thumb__) && !defined(NOASM) //ARMv4 ASM
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // let abuse smull
-	{
-		fixed_t ret;
-		asm
-		(
-			  "smull %[lo], r1, %[a], %[b];"
-			  "mov %[lo], %[lo], lsr %3;"
-			  "orr %[lo], %[lo], r1, lsl %3;"
-			: [lo] "=&r" (ret) // rhi, rlo and rm must be distinct registers
-			: [a] "r" (a), [b] "r" (b)
-			, "i" (FRACBITS)
-			: "r1"
-		);
-		return ret;
-	}
+	\return	a*b>>FRACBITS
 
-	#define __USE_C_FIXEDDIV__ // no double or asm div in ARM land
-#elif defined (__GNUC__) && defined (__ppc__) && !defined(NOASM) && 0 // WII: PPC CPU
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret, hi, lo;
-		asm
-		(
-			  "mullw %0, %2, %3;"
-			  "mulhw %1, %2, %3"
-			: "=r" (hi), "=r" (lo)
-			: "r" (a), "r" (b)
-			, "I" (FRACBITS)
-		);
-		ret = (INT64)((hi>>FRACBITS)+lo)<<FRACBITS;
-		return ret;
-	}
+*/
+FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedMul(fixed_t a, fixed_t b)
+{
+	// Need to cast to unsigned before shifting to avoid undefined behaviour
+	// for negative integers
+	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
+}
 
-	#define __USE_C_FIXEDDIV__// Alam: I am lazy
-#elif defined (__GNUC__) && defined (__mips__) && !defined(NOASM) && 0 // PSP: MIPS CPU
-	FUNCMATH FUNCINLINE static inline fixed_t FixedMul(fixed_t a, fixed_t b) // asm
-	{
-		fixed_t ret;
-		asm
-		(
-			  "mult %3, %4;"    // a*b=h<32+l
-			: "=r" (ret), "=l" (a), "=h" (b) //todo: abuse shr opcode
-			: "0" (a), "r" (b)
-			, "I" (FRACBITS)
-			//: "+l", "+h"
-		);
-		ret = (INT64)((a>>FRACBITS)+b)<<FRACBITS;
-		return ret;
-	}
+/**	\brief	The FixedDiv2 function
 
-	#define __USE_C_FIXEDDIV__ // no 64b asm div in MIPS land
-#elif defined (__GNUC__) && defined (__sh__) && 0 // DC: SH4 CPU
-#elif defined (__GNUC__) && defined (__m68k__) && 0 // DEAD: Motorola 6800 CPU
-#elif defined (_MSC_VER) && defined(USEASM) && FRACBITS == 16
-	// Microsoft Visual C++ (no asm inline)
-	fixed_t __cdecl FixedMul(fixed_t a, fixed_t b);
-	fixed_t __cdecl FixedDiv2(fixed_t a, fixed_t b);
-#else
-	#define __USE_C_FIXEDMUL__
-	#define __USE_C_FIXEDDIV__
-#endif
+	\param	a	fixed_t number
+	\param	b	fixed_t number
 
-#ifdef __USE_C_FIXEDMUL__
-FUNCMATH fixed_t FixedMul(fixed_t a, fixed_t b);
-#endif
+	\return	a/b * FRACUNIT
 
-#ifdef __USE_C_FIXEDDIV__
-FUNCMATH fixed_t FixedDiv2(fixed_t a, fixed_t b);
-#endif
+*/
+FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv2(fixed_t a, fixed_t b)
+{
+	// This does not check for division overflow or division by 0!
+	// That is the caller's responsibility.
+	return (fixed_t)(((INT64)a * FRACUNIT) / b);
+}
 
 /**	\brief	The FixedInt function
 
diff --git a/src/m_menu.c b/src/m_menu.c
index e879e9c14ddae6f73087cc01045b22b098d625c8..3946803b290e0240cac36accd363113256580933 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -241,6 +241,7 @@ static void M_EmblemHints(INT32 choice);
 static void M_HandleEmblemHints(INT32 choice);
 UINT32 hintpage = 1;
 static void M_HandleChecklist(INT32 choice);
+static void M_PauseLevelSelect(INT32 choice);
 menu_t SR_MainDef, SR_UnlockChecklistDef;
 
 static UINT8 check_on;
@@ -388,6 +389,7 @@ 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);
 
 // Handling functions
 static boolean M_ExitPandorasBox(void);
@@ -554,26 +556,30 @@ static menuitem_t MPauseMenu[] =
 {
 	{IT_STRING | IT_CALL,    NULL, "Add-ons...",                M_Addons,               8},
 	{IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...",         &MISC_ScrambleTeamDef, 16},
-	{IT_STRING | IT_CALL,    NULL, "Switch Gametype/Level...",  M_MapChange,           24},
+	{IT_STRING | IT_CALL,    NULL, "Emblem Hints...",           M_EmblemHints,         24},
+	{IT_STRING | IT_CALL,    NULL, "Switch Gametype/Level...",  M_MapChange,           32},
 
-	{IT_STRING | IT_CALL,    NULL, "Continue",                  M_SelectableClearMenus,40},
-	{IT_STRING | IT_CALL,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    48}, // splitscreen
-	{IT_STRING | IT_CALL,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   56}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Continue",                  M_SelectableClearMenus,48},
 
-	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     48},
-	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    48},
-	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   48},
-	{IT_STRING | IT_CALL,    NULL, "Player Setup",              M_SetupMultiPlayer,    56}, // alone
-	{IT_STRING | IT_CALL,    NULL, "Options",                   M_Options,             64},
+	{IT_STRING | IT_CALL,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    56}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   64},
 
-	{IT_STRING | IT_CALL,    NULL, "Return to Title",           M_EndGame,             80},
-	{IT_STRING | IT_CALL,    NULL, "Quit Game",                 M_QuitSRB2,            88},
+	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     56}, // alone
+	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    56},
+	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   56},
+	{IT_STRING | IT_CALL,    NULL, "Player Setup",              M_SetupMultiPlayer,    64},
+
+	{IT_STRING | IT_CALL,    NULL, "Options",                   M_Options,             72},
+
+	{IT_STRING | IT_CALL,    NULL, "Return to Title",           M_EndGame,             88},
+	{IT_STRING | IT_CALL,    NULL, "Quit Game",                 M_QuitSRB2,            96},
 };
 
 typedef enum
 {
 	mpause_addons = 0,
 	mpause_scramble,
+	mpause_hints,
 	mpause_switchmap,
 
 	mpause_continue,
@@ -597,7 +603,7 @@ static menuitem_t SPauseMenu[] =
 	// Pandora's Box will be shifted up if both options are available
 	{IT_CALL | IT_STRING,    NULL, "Pandora's Box...",     M_PandorasBox,         16},
 	{IT_CALL | IT_STRING,    NULL, "Emblem Hints...",      M_EmblemHints,         24},
-	{IT_CALL | IT_STRING,    NULL, "Level Select...",      M_LoadGameLevelSelect, 32},
+	{IT_CALL | IT_STRING,    NULL, "Level Select...",      M_PauseLevelSelect,    32},
 
 	{IT_CALL | IT_STRING,    NULL, "Continue",             M_SelectableClearMenus,48},
 	{IT_CALL | IT_STRING,    NULL, "Retry",                M_Retry,               56},
@@ -688,42 +694,10 @@ static menuitem_t SR_PandorasBox[] =
 };
 
 // Sky Room Custom Unlocks
-static menuitem_t SR_MainMenu[] =
+static menuitem_t SR_MainMenu[MAXUNLOCKABLES+1] =
 {
 	{IT_STRING|IT_SUBMENU,NULL, "Extras Checklist", &SR_UnlockChecklistDef, 0},
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom1
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom2
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom3
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom4
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom5
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom6
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom7
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom8
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom9
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom10
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom11
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom12
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom13
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom14
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom15
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom16
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom17
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom18
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom19
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom20
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom21
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom22
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom23
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom24
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom25
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom26
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom27
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom28
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom29
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom30
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom31
-	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom32
-
+	// The remaining (MAXUNLOCKABLES) items are now initialized in M_SecretsMenu
 };
 
 static menuitem_t SR_LevelSelectMenu[] =
@@ -757,12 +731,12 @@ static menuitem_t SR_EmblemHintMenu[] =
 static menuitem_t SP_MainMenu[] =
 {
 	// Note: If changing the positions here, also change them in M_SinglePlayerMenu()
-	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 76},
-	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,               84},
-	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,             92},
-	{IT_SECRET,                                 NULL, "Marathon Run",  M_Marathon,                100},
-	{IT_CALL | IT_STRING,                       NULL, "Tutorial",      M_StartTutorial,           108},
-	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116}
+	{IT_CALL | IT_STRING,	NULL, "Start Game",    M_LoadGame,                 76},
+	{IT_SECRET,				NULL, "Record Attack", M_TimeAttack,               84},
+	{IT_SECRET,				NULL, "NiGHTS Mode",   M_NightsAttack,             92},
+	{IT_SECRET,				NULL, "Marathon Run",  M_Marathon,                100},
+	{IT_CALL | IT_STRING,	NULL, "Tutorial",      M_StartTutorial,           108},
+	{IT_CALL | IT_STRING,	NULL, "Statistics",    M_Statistics,              116}
 };
 
 enum
@@ -970,7 +944,7 @@ static menuitem_t MP_MainMenu[] =
 {
 	{IT_HEADER, NULL, "Join a game", NULL, 0},
 	{IT_STRING|IT_CALL,       NULL, "Server browser...",     M_ConnectMenuModChecks,          12},
-	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP,      22},
+	{IT_STRING|IT_KEYHANDLER, NULL, "Specify server address:", M_HandleConnectIP,    22},
 	{IT_HEADER, NULL, "Host a game", NULL, 54},
 	{IT_STRING|IT_CALL,       NULL, "Internet/LAN...",       M_StartServerMenu,      66},
 	{IT_STRING|IT_CALL,       NULL, "Splitscreen...",        M_StartSplitServerMenu, 76},
@@ -1340,17 +1314,17 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CALL,  NULL, "Set Resolution...",       M_VideoModeMenu,          6},
 
 #if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	{IT_STRING|IT_CVAR,      NULL, "Fullscreen",             &cv_fullscreen,         11},
+	{IT_STRING|IT_CVAR,      NULL, "Fullscreen (F11)",          &cv_fullscreen,      11},
 #endif
 	{IT_STRING | IT_CVAR, NULL, "Vertical Sync",                &cv_vidwait,         16},
 #ifdef HWRENDER
-	{IT_STRING | IT_CVAR, NULL, "Renderer",                     &cv_renderer,        21},
+	{IT_STRING | IT_CVAR, NULL, "Renderer (F10)",               &cv_renderer,        21},
 #else
-	{IT_TRANSTEXT | IT_PAIR, "Renderer", "Software",            &cv_renderer,           21},
+	{IT_TRANSTEXT | IT_PAIR, "Renderer", "Software",            &cv_renderer,        21},
 #endif
 
 	{IT_HEADER, NULL, "Color Profile", NULL, 30},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,36},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness", &cv_globalgamma,36},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_globalsaturation, 41},
 	{IT_SUBMENU|IT_STRING, NULL, "Advanced Settings...",     &OP_ColorOptionsDef,  46},
 
@@ -1824,6 +1798,10 @@ menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(
 	MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
 	NULL, SP_LevelSelectMenu);
 
+menu_t SP_PauseLevelSelectDef = MAPPLATTERMENUSTYLE(
+	MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
+	NULL, SP_LevelSelectMenu);
+
 menu_t SP_LevelStatsDef =
 {
 	MTREE2(MN_SP_MAIN, MN_SP_LEVELSTATS),
@@ -2161,7 +2139,7 @@ static void M_VideoOptions(INT32 choice)
 	{
 		OP_VideoOptionsMenu[op_video_renderer].status = (IT_STRING | IT_CVAR);
 		OP_VideoOptionsMenu[op_video_renderer].patch = NULL;
-		OP_VideoOptionsMenu[op_video_renderer].text = "Renderer";
+		OP_VideoOptionsMenu[op_video_renderer].text = "Renderer (F10)";
 	}
 #endif
 
@@ -2284,6 +2262,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt);
 // Nextmap.  Used for Level select.
 void Nextmap_OnChange(void)
 {
+	gamedata_t *data = clientGamedata;
 	char *leveltitle;
 	char tabase[256];
 #ifdef OLDNREPLAYNAME
@@ -2301,7 +2280,7 @@ void Nextmap_OnChange(void)
 	{
 		CV_StealthSetValue(&cv_dummymares, 0);
 		// Hide the record changing CVAR if only one mare is available.
-		if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2)
+		if (!data->nightsrecords[cv_nextmap.value-1] || data->nightsrecords[cv_nextmap.value-1]->nummares < 2)
 			SP_NightsAttackMenu[narecords].status = IT_DISABLED;
 		else
 			SP_NightsAttackMenu[narecords].status = IT_STRING|IT_CVAR;
@@ -2432,14 +2411,15 @@ void Nextmap_OnChange(void)
 
 static void Dummymares_OnChange(void)
 {
-	if (!nightsrecords[cv_nextmap.value-1])
+	gamedata_t *data = clientGamedata;
+	if (!data->nightsrecords[cv_nextmap.value-1])
 	{
 		CV_StealthSetValue(&cv_dummymares, 0);
 		return;
 	}
 	else
 	{
-		UINT8 mares = nightsrecords[cv_nextmap.value-1]->nummares;
+		UINT8 mares = data->nightsrecords[cv_nextmap.value-1]->nummares;
 
 		if (cv_dummymares.value < 0)
 			CV_StealthSetValue(&cv_dummymares, mares);
@@ -2669,7 +2649,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;
@@ -2863,8 +2843,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;
@@ -3216,7 +3196,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)
@@ -3399,12 +3379,12 @@ boolean M_Responder(event_t *ev)
 			// Screenshots on F8 now handled elsewhere
 			// Same with Moviemode on F9
 
-			case KEY_F10: // Quit SRB2
-				M_QuitSRB2(0);
+			case KEY_F10: // Renderer toggle, also processed inside menus
+				CV_AddValue(&cv_renderer, 1);
 				return true;
 
-			case KEY_F11: // Gamma Level
-				CV_AddValue(&cv_globalgamma, 1);
+			case KEY_F11: // Fullscreen toggle, also processed inside menus
+				CV_SetValue(&cv_fullscreen, !cv_fullscreen.value);
 				return true;
 
 			// Spymode on F12 handled in game logic
@@ -3515,13 +3495,15 @@ boolean M_Responder(event_t *ev)
 			{
 				if (((currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_CALL
 				 || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU)
-                 && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
+				 && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
 				{
 #ifndef DEVELOP
-					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata)
+					// TODO: Replays are scary, so I left the remaining instances of this alone.
+					// It'd be nice to get rid of this once and for all though!
+					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && usedCheats)
 					{
 						S_StartSound(NULL, sfx_skid);
-						M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+						M_StartMessage(M_GetText("This cannot be done in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
 						return true;
 					}
 #endif
@@ -3583,6 +3565,14 @@ boolean M_Responder(event_t *ev)
 			//	M_SetupNextMenu(currentMenu->prevMenu);
 			return false;
 
+		case KEY_F10: // Renderer toggle, also processed outside menus
+			CV_AddValue(&cv_renderer, 1);
+			return true;
+
+		case KEY_F11: // Fullscreen toggle, also processed outside menus
+			CV_SetValue(&cv_fullscreen, !cv_fullscreen.value);
+			return true;
+
 		default:
 			CON_Responder(ev);
 			break;
@@ -3668,9 +3658,9 @@ void M_StartControlPanel(void)
 	if (!Playing())
 	{
 		// Secret menu!
-		MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked()) ? 76 : 84;
-		MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked()) ? 84 : 92;
-		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 76 : 84;
+		MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 84 : 92;
+		MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		currentMenu = &MainDef;
 		itemOn = singleplr;
@@ -3678,14 +3668,14 @@ void M_StartControlPanel(void)
 	else if (modeattacking)
 	{
 		currentMenu = &MAPauseDef;
-		MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 		itemOn = mapause_continue;
 	}
 	else if (!(netgame || multiplayer)) // Single Player
 	{
 		if (gamestate != GS_LEVEL || ultimatemode) // intermission, so gray out stuff.
 		{
-			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_GRAYEDOUT) : (IT_DISABLED);
+			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata)) ? (IT_GRAYEDOUT) : (IT_DISABLED);
 			SPauseMenu[spause_retry].status = IT_GRAYEDOUT;
 		}
 		else
@@ -3694,7 +3684,7 @@ void M_StartControlPanel(void)
 			if (players[consoleplayer].playerstate != PST_LIVE)
 				++numlives;
 
-			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 			// The list of things that can disable retrying is (was?) a little too complex
 			// for me to want to use the short if statement syntax
@@ -3705,10 +3695,10 @@ void M_StartControlPanel(void)
 		}
 
 		// We can always use level select though. :33
-		SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_levelselect].status = (maplistoption != 0) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// And emblem hints.
-		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// Shift up Pandora's Box if both pandora and levelselect are active
 		/*if (SPauseMenu[spause_pandora].status != (IT_DISABLED)
@@ -3743,12 +3733,10 @@ void M_StartControlPanel(void)
 		if (splitscreen)
 		{
 			MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL;
-			MPauseMenu[mpause_psetup].text = "Player 1 Setup";
 		}
 		else
 		{
 			MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL;
-			MPauseMenu[mpause_psetup].text = "Player Setup";
 
 			if (G_GametypeHasTeams())
 				MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU;
@@ -3758,6 +3746,8 @@ void M_StartControlPanel(void)
 				MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT;
 		}
 
+		MPauseMenu[mpause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && G_CoopGametype()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+
 		currentMenu = &MPauseDef;
 		itemOn = mpause_continue;
 	}
@@ -4213,15 +4203,6 @@ static void M_DrawSaveLoadBorder(INT32 x,INT32 y)
 }
 #endif
 
-// horizontally centered text
-static void M_CentreText(INT32 y, const char *string)
-{
-	INT32 x;
-	//added : 02-02-98 : centre on 320, because V_DrawString centers on vid.width...
-	x = (BASEVIDWIDTH - V_StringWidth(string, V_OLDSPACING))>>1;
-	V_DrawString(x,y,V_OLDSPACING,string);
-}
-
 //
 // M_DrawMapEmblems
 //
@@ -4257,7 +4238,7 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y, boolean norecordatt
 			x -= 4;
 		lasttype = curtype;
 
-		if (emblem->collected)
+		if (clientGamedata->collected[emblem - emblemlocations])
 			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 			                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		else
@@ -4690,7 +4671,9 @@ static void M_DrawGenericScrollMenu(void)
 
 static void M_DrawPauseMenu(void)
 {
-	if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
+	gamedata_t *data = clientGamedata;
+
+	if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
 	{
 		emblem_t *emblem_detail[3] = {NULL, NULL, NULL};
 		char emblem_text[3][20];
@@ -4718,7 +4701,7 @@ static void M_DrawPauseMenu(void)
 				{
 					case ET_SCORE:
 						snprintf(targettext, 9, "%d", emblem->var);
-						snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap));
+						snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4732,7 +4715,7 @@ static void M_DrawPauseMenu(void)
 							G_TicsToSeconds((tic_t)emblemslot),
 							G_TicsToCentiseconds((tic_t)emblemslot));
 
-						emblemslot = (INT32)G_GetBestTime(gamemap); // dumb hack pt ii
+						emblemslot = (INT32)G_GetBestTime(gamemap, data); // dumb hack pt ii
 						if ((tic_t)emblemslot == UINT32_MAX)
 							snprintf(currenttext, 9, "-:--.--");
 						else
@@ -4748,7 +4731,7 @@ static void M_DrawPauseMenu(void)
 						break;
 					case ET_RINGS:
 						snprintf(targettext, 9, "%d", emblem->var);
-						snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap));
+						snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4756,8 +4739,8 @@ static void M_DrawPauseMenu(void)
 						emblemslot = 2;
 						break;
 					case ET_NGRADE:
-						snprintf(targettext, 9, "%u", P_GetScoreForGradeOverall(gamemap, emblem->var));
-						snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0));
+						snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var));
+						snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4771,7 +4754,7 @@ static void M_DrawPauseMenu(void)
 							G_TicsToSeconds((tic_t)emblemslot),
 							G_TicsToCentiseconds((tic_t)emblemslot));
 
-						emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0); // dumb hack pt iv
+						emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0, data); // dumb hack pt iv
 						if ((tic_t)emblemslot == UINT32_MAX)
 							snprintf(currenttext, 9, "-:--.--");
 						else
@@ -4805,7 +4788,7 @@ static void M_DrawPauseMenu(void)
 			if (!emblem)
 				continue;
 
-			if (emblem->collected)
+			if (data->collected[emblem - emblemlocations])
 				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 			else
@@ -5017,7 +5000,9 @@ static void M_PatchSkinNameTable(void)
 //
 static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 {
-	if (M_MapLocked(mapnum+1))
+	gamedata_t *data = serverGamedata;
+
+	if (M_MapLocked(mapnum+1, data))
 		return false; // not unlocked
 
 	switch (levellistmode)
@@ -5030,7 +5015,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 				return true;
 
 #ifndef DEVELOP
-			if (mapvisited[mapnum]) // MV_MP
+			if (data->mapvisited[mapnum])
 #endif
 				return true;
 
@@ -5038,7 +5023,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 		case LLM_RECORDATTACK:
 		case LLM_NIGHTSATTACK:
 #ifndef DEVELOP
-			if (mapvisited[mapnum] & MV_MAX)
+			if (data->mapvisited[mapnum])
 				return true;
 
 			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
@@ -5069,7 +5054,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 	if (!mapheaderinfo[mapnum]->lvlttl[0])
 		return false;
 
-	/*if (M_MapLocked(mapnum+1))
+	/*if (M_MapLocked(mapnum+1, serverGamedata))
 		return false; // not unlocked*/
 
 	switch (levellistmode)
@@ -5964,7 +5949,7 @@ static void M_DrawLevelPlatterMenu(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 		else if (!curbghide || !titlemapinaction)
 		{
-			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+			F_SkyScroll(curbgname);
 			// Draw and animate foreground
 			if (!strncmp("RECATKBG", curbgname, 8))
 				M_DrawRecordAttackForeground();
@@ -6226,7 +6211,7 @@ static void M_DrawMessageMenu(void)
 			}
 			else
 			{
-				F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+				F_SkyScroll(curbgname);
 				if (!strncmp("RECATKBG", curbgname, 8))
 					M_DrawRecordAttackForeground();
 			}
@@ -6710,7 +6695,7 @@ static void M_DrawAddons(void)
 
 	// draw save icon
 	x = BASEVIDWIDTH - x - 16;
-	V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
+	V_DrawSmallScaledPatch(x, y + 4, (!usedCheats ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
 
 	if (modifiedgame)
 		V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]);
@@ -6902,7 +6887,7 @@ static void M_HandleAddons(INT32 choice)
 		closefilemenu(true);
 
 		// secrets disabled by addfile...
-		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu(currentMenu->prevMenu);
@@ -7081,7 +7066,7 @@ static void M_AllowSuper(INT32 choice)
 	M_StartMessage(M_GetText("You are now capable of turning super.\nRemember to get all the emeralds!\n"),NULL,MM_NOTHING);
 	SR_PandorasBox[6].status = IT_GRAYEDOUT;
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 static void M_GetAllEmeralds(INT32 choice)
@@ -7092,7 +7077,7 @@ static void M_GetAllEmeralds(INT32 choice)
 	M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING);
 	SR_PandorasBox[7].status = IT_GRAYEDOUT;
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 static void M_DestroyRobotsResponse(INT32 ch)
@@ -7103,7 +7088,7 @@ static void M_DestroyRobotsResponse(INT32 ch)
 	// Destroy all robots
 	P_DestroyRobots();
 
-	G_SetGameModified(multiplayer);
+	G_SetUsedCheats(false);
 }
 
 static void M_DestroyRobots(INT32 choice)
@@ -7115,8 +7100,6 @@ static void M_DestroyRobots(INT32 choice)
 
 static void M_LevelSelectWarp(INT32 choice)
 {
-	boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
-
 	(void)choice;
 
 	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
@@ -7126,12 +7109,25 @@ static void M_LevelSelectWarp(INT32 choice)
 	}
 
 	startmap = (INT16)(cv_nextmap.value);
-
 	fromlevelselect = true;
 
-	if (fromloadgame)
-		G_LoadGame((UINT32)cursaveslot, startmap);
-	else
+	if (currentMenu == &SP_LevelSelectDef || currentMenu == &SP_PauseLevelSelectDef)
+	{
+		if (cursaveslot > 0) // do we have a save slot to load?
+			G_LoadGame((UINT32)cursaveslot, startmap); // reload from SP save data: this is needed to keep score/lives/continues from reverting to defaults
+		else // no save slot, start new game but keep the current skin
+		{
+			M_ClearMenus(true);
+
+			G_DeferedInitNew(false, G_BuildMapName(startmap), cv_skin.value, false, fromlevelselect); // Not sure about using cv_skin here, but it seems fine in testing.
+			COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
+
+			if (levelselect.rows)
+				Z_Free(levelselect.rows);
+			levelselect.rows = NULL;
+		}
+	}
+	else // start new game
 	{
 		cursaveslot = 0;
 		M_SetupChoosePlayer(0);
@@ -7148,7 +7144,9 @@ static boolean checklist_cangodown; // uuuueeerggghhhh HACK
 
 static void M_HandleChecklist(INT32 choice)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 j;
+
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
@@ -7165,7 +7163,7 @@ static void M_HandleChecklist(INT32 choice)
 						continue;
 					if (unlockables[j].conditionset > MAXCONDITIONSETS)
 						continue;
-					if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
+					if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data))
 						continue;
 					if (unlockables[j].conditionset == unlockables[check_on].conditionset)
 						continue;
@@ -7190,7 +7188,7 @@ static void M_HandleChecklist(INT32 choice)
 						continue;
 					if (unlockables[j].conditionset > MAXCONDITIONSETS)
 						continue;
-					if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
+					if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data))
 						continue;
 					if (j && unlockables[j].conditionset == unlockables[j-1].conditionset)
 						continue;
@@ -7216,6 +7214,9 @@ static void M_HandleChecklist(INT32 choice)
 
 static void M_DrawChecklist(void)
 {
+	gamedata_t *data = clientGamedata;
+	INT32 emblemCount = M_CountEmblems(data);
+
 	INT32 i = check_on, j = 0, y = currentMenu->y, emblems = numemblems+numextraemblems;
 	UINT32 condnum, previd, maxcond;
 	condition_t *cond;
@@ -7226,7 +7227,7 @@ static void M_DrawChecklist(void)
 	// draw emblem counter
 	if (emblems > 0)
 	{
-		V_DrawString(42, 20, (emblems == M_CountEmblems()) ? V_GREENMAP : 0, va("%d/%d", M_CountEmblems(), emblems));
+		V_DrawString(42, 20, (emblems == emblemCount) ? V_GREENMAP : 0, va("%d/%d", emblemCount, emblems));
 		V_DrawSmallScaledPatch(28, 20, 0, W_CachePatchName("EMBLICON", PU_PATCH));
 	}
 
@@ -7237,13 +7238,13 @@ static void M_DrawChecklist(void)
 	{
 		if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist
 		|| !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS
-		|| (!unlockables[i].unlocked && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset)))
+		|| (!data->unlocked[i] && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset, data)))
 		{
 			i += 1;
 			continue;
 		}
 
-		V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
+		V_DrawString(currentMenu->x, y, ((data->unlocked[i]) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((data->unlocked[i] || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
 
 		for (j = i+1; j < MAXUNLOCKABLES; j++)
 		{
@@ -7321,7 +7322,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].requirement, data) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7354,7 +7355,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7423,7 +7424,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7486,7 +7487,7 @@ static void M_DrawChecklist(void)
 
 		/*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
 
-		if (unlockables[i].unlocked)
+		if (data->unlocked[i])
 			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y");
 		else
 			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/
@@ -7515,7 +7516,7 @@ static void M_EmblemHints(INT32 choice)
 
 	(void)choice;
 	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED);
-	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
+	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
 	hintpage = 1;
 	SR_EmblemHintDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SR_EmblemHintDef);
@@ -7575,7 +7576,7 @@ static void M_DrawEmblemHints(void)
 
 		if (totalemblems >= ((hintpage-1)*(NUMHINTS*2) + 1) && totalemblems < (hintpage*NUMHINTS*2)+1){
 
-			if (emblem->collected)
+			if (clientGamedata->collected[i])
 			{
 				collected = V_GREENMAP;
 				V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
@@ -7645,6 +7646,26 @@ static void M_HandleEmblemHints(INT32 choice)
 
 }
 
+static void M_PauseLevelSelect(INT32 choice)
+{
+	(void)choice;
+
+	SP_PauseLevelSelectDef.prevMenu = currentMenu;
+	levellistmode = LLM_LEVELSELECT;
+
+	// maplistoption is NOT specified, so that this
+	// transfers the level select list from the menu
+	// used to enter the game to the pause menu.
+
+	if (!M_PrepareLevelPlatter(-1, true))
+	{
+		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
+		return;
+	}
+
+	M_SetupNextMenu(&SP_PauseLevelSelectDef);
+}
+
 /*static void M_DrawSkyRoom(void)
 {
 	INT32 i, y = 0;
@@ -8074,14 +8095,15 @@ static void M_SecretsMenu(INT32 choice)
 
 	(void)choice;
 
-	// Clear all before starting
-	for (i = 1; i < MAXUNLOCKABLES+1; ++i)
-		SR_MainMenu[i].status = IT_DISABLED;
+	// Initialize array with placeholder entries
+	menuitem_t placeholder = {IT_DISABLED, NULL, "", NULL, 0};
+	for (i = 1; i <= MAXUNLOCKABLES; ++i)
+		SR_MainMenu[i] = placeholder;
 
 	memset(skyRoomMenuTranslations, 0, sizeof(skyRoomMenuTranslations));
 	memset(done, 0, sizeof(done));
 
-	for (i = 1; i < MAXUNLOCKABLES+1; ++i)
+	for (i = 1; i <= MAXUNLOCKABLES; ++i)
 	{
 		curheight = UINT16_MAX;
 		ul = -1;
@@ -8115,7 +8137,7 @@ static void M_SecretsMenu(INT32 choice)
 
 		SR_MainMenu[i].status = IT_SECRET;
 
-		if (unlockables[ul].unlocked)
+		if (clientGamedata->unlocked[ul])
 		{
 			switch (unlockables[ul].type)
 			{
@@ -8152,6 +8174,7 @@ INT32 ultimate_selectable = false;
 static void M_NewGame(void)
 {
 	fromlevelselect = false;
+	maplistoption = 0;
 
 	startmap = spstage_start;
 	CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
@@ -8163,6 +8186,7 @@ static void M_CustomWarp(INT32 choice)
 {
 	INT32 ul = skyRoomMenuTranslations[choice-1];
 
+	maplistoption = 0;
 	startmap = (INT16)(unlockables[ul].variable);
 
 	M_SetupChoosePlayer(0);
@@ -8214,7 +8238,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 
 	levellistmode = LLM_RECORDATTACK;
 	if (M_GametypeHasLevels(-1))
-		SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
+		SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET;
 	else // If Record Attack is nonexistent in the current add-on...
 	{
 		SP_MainMenu[sprecordattack].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the Record Attack option...
@@ -8224,7 +8248,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 
 	levellistmode = LLM_NIGHTSATTACK;
 	if (M_GametypeHasLevels(-1))
-		SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET;
+		SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET;
 	else // If NiGHTS Mode is nonexistent in the current add-on...
 	{
 		SP_MainMenu[spnightsmode].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the NiGHTS Mode option...
@@ -8247,7 +8271,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 		SP_MainMenu[spnightsmode]  .alphaKey += 8;
 	}
 	else // Otherwise, if Marathon Run is allowed and Record Attack is unlocked, unlock Marathon Run!
-		SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET;
+		SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET;
 
 
 	if (tutorialmap) // If there's a tutorial available in the current add-on...
@@ -8355,6 +8379,7 @@ static void M_StartTutorial(INT32 choice)
 	M_ClearMenus(true);
 	gamecomplete = 0;
 	cursaveslot = 0;
+	maplistoption = 0;
 	G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
 }
 
@@ -8462,7 +8487,7 @@ static void M_DrawLoadGameData(void)
 					if (charskin->prefoppositecolor)
 					{
 						col = charskin->prefoppositecolor;
-						col = skincolors[col].ramp[skincolors[skincolors[col].invcolor].invshade];
+						col = skincolors[col].ramp[skincolors[charskin->prefcolor].invshade];
 					}
 					else
 					{
@@ -8703,9 +8728,9 @@ static void M_DrawLoad(void)
 
 	M_DrawLoadGameData();
 
-	if (modifiedgame && !savemoddata)
+	if (usedCheats)
 	{
-		V_DrawCenteredThinString(BASEVIDWIDTH/2, 184, 0, "\x85WARNING: \x80The game is modified.");
+		V_DrawCenteredThinString(BASEVIDWIDTH/2, 184, 0, "\x85WARNING:\x80 Cheats have been activated.");
 		V_DrawCenteredThinString(BASEVIDWIDTH/2, 192, 0, "Progress will not be saved.");
 	}
 }
@@ -8717,6 +8742,10 @@ static void M_LoadSelect(INT32 choice)
 {
 	(void)choice;
 
+	// Reset here, if we want a level select
+	// M_LoadGameLevelSelect will set it for us.
+	maplistoption = 0;
+
 	if (saveSlotSelected == NOSAVESLOT) //last slot is play without saving
 	{
 		M_NewGame();
@@ -9009,17 +9038,17 @@ static void M_HandleLoadSave(INT32 choice)
 			break;
 
 		case KEY_ENTER:
-			if (ultimate_selectable && saveSlotSelected == NOSAVESLOT && !savemoddata && !modifiedgame)
+			if (ultimate_selectable && saveSlotSelected == NOSAVESLOT && !savemoddata)
 			{
 				loadgamescroll = 0;
 				S_StartSound(NULL, sfx_skid);
 				M_StartMessage("Are you sure you want to play\n\x85ultimate mode\x80? It isn't remotely fair,\nand you don't even get an emblem for it.\n\n(Press 'Y' to confirm)\n",M_SaveGameUltimateResponse,MM_YESNO);
 			}
-			else if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives == -42 && !(!modifiedgame || savemoddata))
+			else if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives == -42 && usedCheats)
 			{
 				loadgamescroll = 0;
 				S_StartSound(NULL, sfx_skid);
-				M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				M_StartMessage(M_GetText("This cannot be done in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
 			}
 			else if (saveSlotSelected == NOSAVESLOT || savegameinfo[saveSlotSelected-1].lives != -666) // don't allow loading of "bad saves"
 			{
@@ -9624,7 +9653,7 @@ static void M_Statistics(INT32 choice)
 		if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS))
 			continue;
 
-		if (!(mapvisited[i] & MV_MAX))
+		if (!(clientGamedata->mapvisited[i] & MV_MAX))
 			continue;
 
 		statsMapList[j++] = i;
@@ -9641,6 +9670,7 @@ static void M_Statistics(INT32 choice)
 
 static void M_DrawStatsMaps(int location)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 y = 80, i = -1;
 	INT16 mnum;
 	extraemblem_t *exemblem;
@@ -9708,14 +9738,14 @@ static void M_DrawStatsMaps(int location)
 		{
 			exemblem = &extraemblems[i];
 
-			if (exemblem->collected)
+			if (data->extraCollected[i])
 				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE));
 			else
 				V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 			V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE,
-				(!exemblem->collected && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset))
+				(!data->extraCollected[i] && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset, data))
 				? M_CreateSecretMenuOption(exemblem->description)
 				: exemblem->description);
 		}
@@ -9732,6 +9762,7 @@ bottomarrow:
 
 static void M_DrawLevelStats(void)
 {
+	gamedata_t *data = clientGamedata;
 	char beststr[40];
 
 	tic_t besttime = 0;
@@ -9746,9 +9777,9 @@ static void M_DrawLevelStats(void)
 
 	V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:");
 	V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds",
-	                         G_TicsToHours(totalplaytime),
-	                         G_TicsToMinutes(totalplaytime, false),
-	                         G_TicsToSeconds(totalplaytime)));
+	                         G_TicsToHours(data->totalplaytime),
+	                         G_TicsToMinutes(data->totalplaytime, false),
+	                         G_TicsToSeconds(data->totalplaytime)));
 
 	for (i = 0; i < NUMMAPS; i++)
 	{
@@ -9757,25 +9788,25 @@ static void M_DrawLevelStats(void)
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
 
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 		{
 			mapsunfinished++;
 			bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true;
 			continue;
 		}
 
-		if (mainrecords[i]->score > 0)
-			bestscore += mainrecords[i]->score;
+		if (data->mainrecords[i]->score > 0)
+			bestscore += data->mainrecords[i]->score;
 		else
 			mapunfinished = bestunfinished[0] = true;
 
-		if (mainrecords[i]->time > 0)
-			besttime += mainrecords[i]->time;
+		if (data->mainrecords[i]->time > 0)
+			besttime += data->mainrecords[i]->time;
 		else
 			mapunfinished = bestunfinished[1] = true;
 
-		if (mainrecords[i]->rings > 0)
-			bestrings += mainrecords[i]->rings;
+		if (data->mainrecords[i]->rings > 0)
+			bestrings += data->mainrecords[i]->rings;
 		else
 			mapunfinished = bestunfinished[2] = true;
 
@@ -9790,7 +9821,7 @@ static void M_DrawLevelStats(void)
 	else
 		V_DrawString(20, 56, V_GREENMAP, "(complete)");
 
-	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(data), numemblems+numextraemblems));
 	V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_PATCH));
 
 	sprintf(beststr, "%u", bestscore);
@@ -9857,6 +9888,7 @@ static void M_HandleLevelStats(INT32 choice)
 // Drawing function for Time Attack
 void M_DrawTimeAttackMenu(void)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 i, x, y, empatx, empaty, cursory = 0;
 	UINT16 dispstatus;
 	patch_t *PictureOfUrFace;	// my WHAT
@@ -9873,7 +9905,7 @@ void M_DrawTimeAttackMenu(void)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 	else if (!curbghide || !titlemapinaction)
 	{
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		F_SkyScroll(curbgname);
 		// Draw and animate foreground
 		if (!strncmp("RECATKBG", curbgname, 8))
 			M_DrawRecordAttackForeground();
@@ -10015,7 +10047,7 @@ void M_DrawTimeAttackMenu(void)
 			empatx = empatch->leftoffset / 2;
 			empaty = empatch->topoffset / 2;
 
-			if (em->collected)
+			if (data->collected[em - emblemlocations])
 				V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 			else
@@ -10028,34 +10060,34 @@ void M_DrawTimeAttackMenu(void)
 		// Draw in-level emblems.
 		M_DrawMapEmblems(cv_nextmap.value, 288, 28, true);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->score)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
+			sprintf(beststr, "%u", data->mainrecords[cv_nextmap.value-1]->score);
 
 		V_DrawString(104-72, 33+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
 		V_DrawRightAlignedString(104+64, 33+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 		V_DrawRightAlignedString(104+72, 43+lsheadingheight/2, V_ALLOWLOWERCASE, reqscore);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->time)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->time, true),
-			                                 G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
-			                                 G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
+			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(data->mainrecords[cv_nextmap.value-1]->time, true),
+			                                 G_TicsToSeconds(data->mainrecords[cv_nextmap.value-1]->time),
+			                                 G_TicsToCentiseconds(data->mainrecords[cv_nextmap.value-1]->time));
 
 		V_DrawString(104-72, 53+lsheadingheight/2, V_YELLOWMAP, "TIME:");
 		V_DrawRightAlignedString(104+64, 53+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 		V_DrawRightAlignedString(104+72, 63+lsheadingheight/2, V_ALLOWLOWERCASE, reqtime);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->rings)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
+			sprintf(beststr, "%hu", data->mainrecords[cv_nextmap.value-1]->rings);
 
 		V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
 
-		V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr);
+		V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((data->mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr);
 
 		V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings);
 	}
@@ -10149,6 +10181,7 @@ static void M_TimeAttack(INT32 choice)
 // Drawing function for Nights Attack
 void M_DrawNightsAttackMenu(void)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
 
@@ -10215,10 +10248,10 @@ void M_DrawNightsAttackMenu(void)
 		lumpnum_t lumpnum;
 		char beststr[40];
 
-		//UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0);
-		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
-		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
-		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
+		//UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0, data);
+		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value, data);
+		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value, data);
+		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value, data);
 
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
@@ -10299,7 +10332,7 @@ void M_DrawNightsAttackMenu(void)
 						goto skipThisOne;
 				}
 
-				if (em->collected)
+				if (data->collected[em - emblemlocations])
 					V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_PATCH),
 																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 				else
@@ -10414,13 +10447,23 @@ static void M_ChooseTimeAttack(INT32 choice)
 	G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), false, false);
 }
 
+static char ra_demoname[1024];
+
+static void M_StartTimeAttackReplay(INT32 choice)
+{
+	if (choice == 'y' || choice == KEY_ENTER)
+	{
+		M_ClearMenus(true);
+		modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
+		G_DoPlayDemo(ra_demoname);
+	}
+}
+
 // Player has selected the "REPLAY" from the time attack screen
 static void M_ReplayTimeAttack(INT32 choice)
 {
 	const char *which;
-	char *demoname;
-	M_ClearMenus(true);
-	modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
+	UINT8 error = DFILE_ERROR_NONE;
 
 	if (currentMenu == &SP_ReplayDef)
 	{
@@ -10440,11 +10483,15 @@ static void M_ReplayTimeAttack(INT32 choice)
 			break;
 		case 4: // guest
 			// srb2/replay/main/map01-guest.lmp
-			G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
-			return;
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
+			break;
+		}
+
+		if (choice != 4)
+		{
+			// srb2/replay/main/map01-sonic-time-best.lmp
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
 		}
-		// srb2/replay/main/map01-sonic-time-best.lmp
-		G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which));
 	}
 	else if (currentMenu == &SP_NightsReplayDef)
 	{
@@ -10460,19 +10507,78 @@ static void M_ReplayTimeAttack(INT32 choice)
 			which = "last";
 			break;
 		case 3: // guest
-			G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
-			return;
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
+			break;
 		}
 
-		demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
+		if (choice != 3)
+		{
+			snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), skins[cv_chooseskin.value-1].name, which);
 
 #ifdef OLDNREPLAYNAME // Check for old style named NiGHTS replay if a new style replay doesn't exist.
-		if (!FIL_FileExists(demoname))
-			demoname = va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which);
+			if (!FIL_FileExists(ra_demoname))
+			{
+				snprintf(ra_demoname, 1024, "%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which);
+			}
 #endif
+		}
+	}
 
-		G_DoPlayDemo(demoname);
+	demofileoverride = DFILE_OVERRIDE_NONE;
+	error = G_CheckDemoForError(ra_demoname);
+
+	if (error)
+	{
+		S_StartSound(NULL, sfx_skid);
+
+		switch (error)
+		{
+			case DFILE_ERROR_NOTDEMO:
+				M_StartMessage(M_GetText("An error occurred loading this replay.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_NOTLOADED:
+				demofileoverride = DFILE_OVERRIDE_LOAD;
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded.\n\nAttempt to load files?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				break;
+
+			case DFILE_ERROR_OUTOFORDER:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("Add-ons for this replay\nwere loaded out of order.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\nwere loaded out of order.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_INCOMPLETEOUTOFORDER:
+				/*
+				demofileoverride = DFILE_OVERRIDE_LOAD;
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded,\nand some are in the wrong order.\n\nAttempt to load files?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\nhave not been loaded,\nand some are in the wrong order.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_CANNOTLOAD:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("Add-ons for this replay\ncould not be loaded.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("Add-ons for this replay\ncould not be loaded.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+
+			case DFILE_ERROR_EXTRAFILES:
+				/*
+				demofileoverride = DFILE_OVERRIDE_SKIP;
+				M_StartMessage(M_GetText("You have more files loaded\nthan the replay does.\n\nAttempt to playback anyway?\n\n(Press 'Y' to confirm)\n"), M_StartTimeAttackReplay, MM_YESNO);
+				*/
+				M_StartMessage(M_GetText("You have more files loaded\nthan the replay does.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+				break;
+		}
+
+		return;
 	}
+
+	M_StartTimeAttackReplay(KEY_ENTER);
 }
 
 static void M_EraseGuest(INT32 choice)
@@ -10653,6 +10759,7 @@ static void M_Marathon(INT32 choice)
 	}
 
 	fromlevelselect = false;
+	maplistoption = 0;
 
 	startmap = spmarathon_start;
 	CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
@@ -11671,35 +11778,19 @@ static void M_StartServerMenu(INT32 choice)
 
 #define CONNIP_LEN 128
 static char setupm_ip[CONNIP_LEN];
-
 #define DOTS "... "
 
-// Draw the funky Connect IP menu. Tails 11-19-2002
-// So much work for such a little thing!
-static void M_DrawMPMainMenu(void)
+static void M_DrawConnectIP(void)
 {
 	INT32 x = currentMenu->x;
-	INT32 y = currentMenu->y;
+	INT32 y = currentMenu->y + 22;
+
 	const INT32 boxwidth = /*16*8 + 6*/ (BASEVIDWIDTH - 2*(x+5));
 	const INT32 maxstrwidth = boxwidth - 5;
 	char *drawnstr = malloc(sizeof(setupm_ip));
 	char *drawnstr_orig = drawnstr;
 	boolean drawthin, shorten = false;
 
-	// use generic drawer for cursor, items and title
-	M_DrawGenericMenu();
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
-		((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
-		((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
-		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
-
-	y += 22;
-
 	V_DrawFill(x+5, y+4+5, boxwidth, 8+6, 159);
 
 	strcpy(drawnstr, setupm_ip);
@@ -11747,6 +11838,28 @@ static void M_DrawMPMainMenu(void)
 	free(drawnstr_orig);
 }
 
+// Draw the funky Connect IP menu. Tails 11-19-2002
+// So much work for such a little thing!
+static void M_DrawMPMainMenu(void)
+{
+	INT32 x = currentMenu->x;
+	INT32 y = currentMenu->y;
+
+	// use generic drawer for cursor, items and title
+	M_DrawGenericMenu();
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
+		((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
+		((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
+		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
+
+	M_DrawConnectIP();
+}
+
 #undef DOTS
 
 // Tails 11-19-2002
@@ -11884,7 +11997,11 @@ static void M_HandleConnectIP(INT32 choice)
 				break;
 
 			// Rudimentary number and period enforcing - also allows letters so hostnames can be used instead
-			if ((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z'))
+			// and square brackets for RFC 2732 IPv6 addresses
+			if ((choice >= '-' && choice <= ':') ||
+					(choice == '[' || choice == ']') ||
+					(choice >= 'A' && choice <= 'Z') ||
+					(choice >= 'a' && choice <= 'z'))
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_ip[l] = (char)choice;
@@ -11921,6 +12038,8 @@ static void M_HandleConnectIP(INT32 choice)
 static fixed_t    multi_tics;
 static UINT8      multi_frame;
 static UINT8      multi_spr2;
+static boolean    multi_paused;
+static boolean    multi_invcolor;
 
 // this is set before entering the MultiPlayer setup menu,
 // for either player 1 or 2
@@ -11933,6 +12052,137 @@ static consvar_t   *setupm_cvdefaultskin;
 static consvar_t   *setupm_cvdefaultcolor;
 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)
 {
@@ -11982,12 +12232,12 @@ static void M_DrawSetupMultiPlayerMenu(void)
 			'\x1D' | V_YELLOWMAP, false);
 	}
 
-	x = BASEVIDWIDTH/2;
+	x = colorgrid ? 92 : BASEVIDWIDTH/2;
 	y += 11;
 
 	// anim the player in the box
 	multi_tics -= renderdeltatics;
-	while (multi_tics <= 0)
+	while (!multi_paused && multi_tics <= 0)
 	{
 		multi_frame++;
 		multi_tics += 4*FRACUNIT;
@@ -11996,7 +12246,8 @@ static void M_DrawSetupMultiPlayerMenu(void)
 #define charw 74
 
 	// draw box around character
-	V_DrawFill(x-(charw/2), y, charw, 84, 159);
+	V_DrawFill(x-(charw/2), y, charw, 84,
+		multi_invcolor ?skincolors[skincolors[setupm_fakecolor->color].invcolor].ramp[skincolors[setupm_fakecolor->color].invshade] : 159);
 
 	sprdef = &skins[setupm_fakeskin].sprites[multi_spr2];
 
@@ -12039,61 +12290,143 @@ faildraw:
 #undef chary
 
 colordraw:
-	x = MP_PlayerSetupDef.x;
-	y += 75;
-
-	M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Color", true, false);
-	if (itemOn == 2)
-		cursory = y;
 
-	// draw color string
-	V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
-	             ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 2 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
-	             skincolors[setupm_fakecolor->color].name);
+#define indexwidth 8
 
-	if (itemOn == 2 && (MP_PlayerSetupMenu[2].status & IT_TYPE) != IT_SPACE)
+	if (colorgrid) // Draw color grid & skip the later options
 	{
-		V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skincolors[setupm_fakecolor->color].name, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
-			'\x1C' | V_YELLOWMAP, false);
-		V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
-			'\x1D' | V_YELLOWMAP, false);
-	}
+		UINT16 pos;
+		INT16 cx = 96, cy = 66;
+		INT16 i, j;
+		INT32 w = indexwidth; // Width of a singular color block
+		boolean stoprow = false;
+		menucolor_t *mc; // Last accessed color
 
-	y += 11;
+		const UINT16 grid_len = M_ColorGridLen();
+		const UINT16 grid_end_y = M_GridIndexToY(grid_len - 1);
 
-#define indexwidth 8
+		INT32 grid_select = M_GetColorGridIndex(setupm_fakecolor->color);
+		INT32 grid_select_y = M_GridIndexToY(grid_select);
+
+		x = 132;
+		y = 66;
+
+		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 < COLOR_GRID_ROW_SIZE; i++)
+			{
+				if (skincolors[mc->color].accessible)
+				{
+					M_DrawColorRamp(x + i*w, y + j*16, w, 1, skincolors[mc->color]);
+
+					if (mc == setupm_fakecolor) // store current color position
+					{
+						cx = x + i*w;
+						cy = y + j*16;
+					}
+				}
+
+				if (stoprow)
+				{
+					break;
+				}
+
+				// Find accessible color after this one
+				do
+				{
+					mc = mc->next;
+					if (mc == menucolortail)
+					{
+						stoprow = true;
+					}
+				} while (!skincolors[mc->color].accessible && !stoprow);
+			}
+
+			if (stoprow)
+			{
+				break;
+			}
+		}
+
+		// Draw arrows, if needed
+		if (pos > 0)
+			V_DrawCharacter(264, y - (skullAnimCounter/5), '\x1A' | V_YELLOWMAP, false);
+		if (!stoprow)
+			V_DrawCharacter(264, y+120 + (skullAnimCounter/5), '\x1B' | V_YELLOWMAP, false);
+
+		// Draw cursor & current skincolor
+		V_DrawFill(cx - 2, cy - 2, 12, 20, 0);
+		V_DrawFill(cx - 1, cy - 1, 11, 19, 31);
+		V_DrawFill(    cx,     cy,  9, 17, 0);
+		M_DrawColorRamp(cx, cy, w, 1, skincolors[setupm_fakecolor->color]);
+
+		// Draw color string (with background)
+		V_DrawFill(55, 148,  74, 1, 73);
+		V_DrawFill(55, 149,  74, 1, 26);
+		M_DrawColorRamp(55, 150, 74, 1, skincolors[setupm_fakecolor->color]);
+		V_DrawRightAlignedString(x-2,166,
+								 ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 2 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
+								 skincolors[setupm_fakecolor->color].name);
+
+		return; // Don't draw anything after this
+	}
+	else // Draw color strip & the rest of the menu options
 	{
 		const INT32 numcolors = (282-charw)/(2*indexwidth); // Number of colors per side
 		INT32 w = indexwidth; // Width of a singular color block
 		menucolor_t *mc = setupm_fakecolor->prev; // Last accessed color
-		UINT8 h;
 		INT16 i;
 
+		x = MP_PlayerSetupDef.x;
+		y += 75;
+
+		// Draw color header & string
+		M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Color...", true, false);
+		V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+								 ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 2 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
+								 skincolors[setupm_fakecolor->color].name);
+
+		// Draw horizontal arrows
+		if (itemOn == 2)
+		{
+			cursory = y;
+			if ((MP_PlayerSetupMenu[2].status & IT_TYPE) != IT_SPACE)
+			{
+				V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skincolors[setupm_fakecolor->color].name, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
+					'\x1C' | V_YELLOWMAP, false);
+				V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+					'\x1D' | V_YELLOWMAP, false);
+			}
+		}
+
 		// Draw color in the middle
 		x += numcolors*w;
-		for (h = 0; h < 16; h++)
-			V_DrawFill(x, y+h, charw, 1, skincolors[setupm_fakecolor->color].ramp[h]);
+		y += 11;
+		M_DrawColorRamp(x, y, charw, 1, skincolors[setupm_fakecolor->color]);
 
-		//Draw colors from middle to left
-		for (i=0; i<numcolors; i++) {
+		// Draw colors from middle to left
+		for (i = 0; i < numcolors; i++)
+		{
 			x -= w;
-			// Find accessible color before this one
-			while (!skincolors[mc->color].accessible)
+			while (!skincolors[mc->color].accessible) // Find accessible color before this one
 				mc = mc->prev;
-			for (h = 0; h < 16; h++)
-				V_DrawFill(x, y+h, w, 1, skincolors[mc->color].ramp[h]);
+			M_DrawColorRamp(x, y, w, 1, skincolors[mc->color]);
 			mc = mc->prev;
 		}
 
 		// Draw colors from middle to right
 		mc = setupm_fakecolor->next;
 		x += numcolors*w + charw;
-		for (i=0; i<numcolors; i++) {
-			// Find accessible color after this one
-			while (!skincolors[mc->color].accessible)
+		for (i = 0; i < numcolors; i++)
+		{
+			while (!skincolors[mc->color].accessible) // Find accessible color after this one
 				mc = mc->next;
-			for (h = 0; h < 16; h++)
-				V_DrawFill(x, y+h, w, 1, skincolors[mc->color].ramp[h]);
+			M_DrawColorRamp(x, y, w, 1, skincolors[mc->color]);
 			x += w;
 			mc = mc->next;
 		}
@@ -12118,6 +12451,13 @@ colordraw:
 		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
+static void M_DrawColorRamp(INT32 x, INT32 y, INT32 w, INT32 h, skincolor_t color)
+{
+	UINT8 i;
+	for (i = 0; i < 16; i++)
+		V_DrawFill(x, y+(i*h), w, h, color.ramp[i]);
+}
+
 // Handle 1P/2P MP Setup
 static void M_HandleSetupMultiPlayer(INT32 choice)
 {
@@ -12128,13 +12468,26 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
-			M_NextOpt();
-			S_StartSound(NULL,sfx_menu1); // Tails
-			break;
-
 		case KEY_UPARROW:
-			M_PrevOpt();
-			S_StartSound(NULL,sfx_menu1); // Tails
+			{
+				if (itemOn == 2 && colorgrid)
+				{
+					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();
+				else
+					M_NextOpt();
+
+				S_StartSound(NULL,sfx_menu1);
+			}
 			break;
 
 		case KEY_LEFTARROW:
@@ -12153,8 +12506,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;
 
@@ -12169,6 +12522,13 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				COM_BufAddText (va("%s %d\n",setupm_cvdefaultcolor->name,setupm_fakecolor->color));
 				break;
 			}
+			else if (itemOn == 2)
+			{
+				if (!colorgrid)
+					S_StartSound(NULL,sfx_menu1);
+				colorgrid = !colorgrid;
+				break;
+			}
 			/* FALLTHRU */
 		case KEY_RIGHTARROW:
 			if (itemOn == 1)       //player skin
@@ -12186,13 +12546,48 @@ 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;
+
+		case KEY_PGUP:
+		case KEY_PGDN:
+			{
+				UINT8 i;
+				if (itemOn == 2) // player color
+				{
+					if (colorgrid)
+					{
+						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;
 
 		case KEY_ESCAPE:
-			exitmenu = true;
+			if (itemOn == 2 && colorgrid)
+				colorgrid = false;
+			else
+				exitmenu = true;
 			break;
 
 		case KEY_BACKSPACE:
@@ -12213,7 +12608,6 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				}
 			}
 			break;
-			break;
 
 		case KEY_DEL:
 			if (itemOn == 0 && (l = strlen(setupm_name))!=0)
@@ -12223,6 +12617,14 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			}
 			break;
 
+		case KEY_PAUSE:
+			multi_paused = !multi_paused;
+			break;
+
+		case KEY_INS:
+			multi_invcolor = !multi_invcolor;
+			break;
+
 		default:
 			if (itemOn != 0 || choice < 32 || choice > 127)
 				break;
@@ -12262,6 +12664,7 @@ static void M_SetupMultiPlayer(INT32 choice)
 
 	multi_frame = 0;
 	multi_tics = 4*FRACUNIT;
+
 	strcpy(setupm_name, cv_playername.string);
 
 	// set for player 1
@@ -12306,6 +12709,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
@@ -12502,6 +12906,42 @@ UINT16 M_GetColorAfter(UINT16 color) {
 	}
 }
 
+UINT16 M_GetColorIndex(UINT16 color) {
+	menucolor_t *look;
+	UINT16 i = 0;
+
+	if (color >= numskincolors) {
+		CONS_Printf("M_GetColorIndex: color %d does not exist.\n",color);
+		return 0;
+	}
+
+	for (look=menucolorhead;;look=look->next) {
+		if (look->color == color)
+			return i;
+		if (look==menucolortail)
+			return 0;
+		i++;
+	}
+}
+
+menucolor_t* M_GetColorFromIndex(UINT16 index) {
+	menucolor_t *look = menucolorhead;
+	UINT16 i = 0;
+
+	if (index >= numskincolors) {
+		CONS_Printf("M_GetColorIndex: index %d does not exist.\n",index);
+		return 0;
+	}
+
+	for (i = 0; i <= index; i++) {
+		if (look==menucolortail)
+			return menucolorhead;
+		look=look->next;
+	}
+
+	return look;
+}
+
 void M_InitPlayerSetupColors(void) {
 	UINT8 i;
 	numskincolors = SKINCOLOR_FIRSTFREESLOT;
@@ -12542,12 +12982,12 @@ static void M_EraseDataResponse(INT32 ch)
 
 	// Delete the data
 	if (erasecontext != 1)
-		G_ClearRecords();
+		G_ClearRecords(clientGamedata);
 	if (erasecontext != 0)
-		M_ClearSecrets();
+		M_ClearSecrets(clientGamedata);
 	if (erasecontext == 2)
 	{
-		totalplaytime = 0;
+		clientGamedata->totalplaytime = 0;
 		F_StartIntro();
 	}
 	BwehHehHe();
@@ -12903,12 +13343,12 @@ static void M_DrawControl(void)
 	if (tutorialmode && tutorialgcs)
 	{
 		if ((gametic / TICRATE) % 2)
-			M_CentreText(30, "\202EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
+			V_DrawCenteredString(BASEVIDWIDTH/2, 30, 0, "\202EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
 		else
-			M_CentreText(30, "EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
+			V_DrawCenteredString(BASEVIDWIDTH/2, 30, 0, "EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
 	}
 	else
-		M_CentreText(30,
+		V_DrawCenteredString(BASEVIDWIDTH/2, 30, 0,
 		    (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" :
 		                                     "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR"));
 
@@ -13273,11 +13713,11 @@ static void M_DrawVideoMode(void)
 	// draw title
 	M_DrawMenuTitle();
 
-	V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y,
-		V_YELLOWMAP, "Choose mode, reselect to change default");
+	V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y, V_YELLOWMAP, "Choose mode, reselect to change default");
+	V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y+8, V_YELLOWMAP, "Press F11 to toggle fullscreen");
 
 	row = 41;
-	col = OP_VideoModeDef.y + 14;
+	col = OP_VideoModeDef.y + 24;
 	for (i = 0; i < vidm_nummodes; i++)
 	{
 		if (i == vidm_selected)
@@ -13290,7 +13730,7 @@ static void M_DrawVideoMode(void)
 		if ((i % vidm_column_size) == (vidm_column_size-1))
 		{
 			row += 7*13;
-			col = OP_VideoModeDef.y + 14;
+			col = OP_VideoModeDef.y + 24;
 		}
 	}
 
@@ -13298,28 +13738,34 @@ static void M_DrawVideoMode(void)
 	{
 		INT32 testtime = (vidm_testingmode/TICRATE) + 1;
 
-		M_CentreText(OP_VideoModeDef.y + 116,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 116, 0,
 			va("Previewing mode %c%dx%d",
 				(SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
 				vid.width, vid.height));
-		M_CentreText(OP_VideoModeDef.y + 138,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 138, 0,
 			"Press ENTER again to keep this mode");
-		M_CentreText(OP_VideoModeDef.y + 150,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 150, 0,
 			va("Wait %d second%s", testtime, (testtime > 1) ? "s" : ""));
-		M_CentreText(OP_VideoModeDef.y + 158,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 158, 0,
 			"or press ESC to return");
-
 	}
 	else
 	{
-		M_CentreText(OP_VideoModeDef.y + 116,
+		V_DrawFill(60, OP_VideoModeDef.y + 98, 200, 12, 159);
+		V_DrawFill(60, OP_VideoModeDef.y + 114, 200, 20, 159);
+
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 100, 0,
 			va("Current mode is %c%dx%d",
 				(SCR_IsAspectCorrect(vid.width, vid.height)) ? 0x83 : 0x80,
 				vid.width, vid.height));
-		M_CentreText(OP_VideoModeDef.y + 124,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 116, (cv_fullscreen.value ? 0 : V_TRANSLUCENT),
 			va("Default mode is %c%dx%d",
-				(SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : 0x80,
+				(SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : (!(VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value)+1) ? 0x85 : 0x80),
 				cv_scr_width.value, cv_scr_height.value));
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 124, (cv_fullscreen.value ? V_TRANSLUCENT : 0),
+			va("Windowed mode is %c%dx%d",
+				(SCR_IsAspectCorrect(cv_scr_width_w.value, cv_scr_height_w.value)) ? 0x83 : (!(VID_GetModeForSize(cv_scr_width_w.value, cv_scr_height_w.value)+1) ? 0x85 : 0x80),
+				cv_scr_width_w.value, cv_scr_height_w.value));
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 138,
 			V_GREENMAP, "Green modes are recommended.");
@@ -13331,7 +13777,7 @@ static void M_DrawVideoMode(void)
 
 	// Draw the cursor for the VidMode menu
 	i = 41 - 10 + ((vidm_selected / vidm_column_size)*7*13);
-	j = OP_VideoModeDef.y + 14 + ((vidm_selected % vidm_column_size)*8);
+	j = OP_VideoModeDef.y + 24 + ((vidm_selected % vidm_column_size)*8);
 
 	V_DrawScaledPatch(i - 8, j, 0,
 		W_CachePatchName("M_CURSOR", PU_PATCH));
@@ -13514,11 +13960,14 @@ static void M_HandleVideoMode(INT32 ch)
 			break;
 
 		case KEY_ENTER:
-			S_StartSound(NULL, sfx_menu1);
 			if (vid.modenum == modedescs[vidm_selected].modenum)
+			{
+				S_StartSound(NULL, sfx_strpst);
 				SCR_SetDefaultMode();
+			}
 			else
 			{
+				S_StartSound(NULL, sfx_menu1);
 				vidm_testingmode = 15*TICRATE;
 				vidm_previousmode = vid.modenum;
 				if (!setmodeneeded) // in case the previous setmode was not finished
@@ -13533,6 +13982,27 @@ static void M_HandleVideoMode(INT32 ch)
 				M_ClearMenus(true);
 			break;
 
+		case KEY_BACKSPACE:
+			S_StartSound(NULL, sfx_menu1);
+			CV_Set(&cv_scr_width, cv_scr_width.defaultvalue);
+			CV_Set(&cv_scr_height, cv_scr_height.defaultvalue);
+			CV_Set(&cv_scr_width_w, cv_scr_width_w.defaultvalue);
+			CV_Set(&cv_scr_height_w, cv_scr_height_w.defaultvalue);
+			if (cv_fullscreen.value)
+				setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value)+1;
+			else
+				setmodeneeded = VID_GetModeForSize(cv_scr_width_w.value, cv_scr_height_w.value)+1;
+			break;
+
+		case KEY_F10: // Renderer toggle, also processed inside menus
+			CV_AddValue(&cv_renderer, 1);
+			break;
+
+		case KEY_F11:
+			S_StartSound(NULL, sfx_menu1);
+			CV_SetValue(&cv_fullscreen, !cv_fullscreen.value);
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/m_menu.h b/src/m_menu.h
index 35c77cc438a8c295c3d86d5caa5b127548cc0df8..c925c7f49c775d4a6e0becd7e2c5eb03d60784e4 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -482,6 +482,8 @@ void M_MoveColorBefore(UINT16 color, UINT16 targ);
 void M_MoveColorAfter(UINT16 color, UINT16 targ);
 UINT16 M_GetColorBefore(UINT16 color);
 UINT16 M_GetColorAfter(UINT16 color);
+UINT16 M_GetColorIndex(UINT16 color);
+menucolor_t* M_GetColorFromIndex(UINT16 index);
 void M_InitPlayerSetupColors(void);
 void M_FreePlayerSetupColors(void);
 
diff --git a/src/m_misc.c b/src/m_misc.c
index 49eb7f1efc02157e9826cf5f4ee3f04a0dc9c7c5..f547f5c41ac8fb0fea405f8ed760ffe4070fdeb2 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -63,7 +63,7 @@ typedef off_t off64_t;
 
 #if defined(__MINGW32__) && ((__GNUC__ > 7) || (__GNUC__ == 6 && __GNUC_MINOR__ >= 3)) && (__GNUC__ < 8)
 #define PRIdS "u"
-#elif defined (_WIN32)
+#elif defined(_WIN32) && !defined(__MINGW64__)
 #define PRIdS "Iu"
 #else
 #define PRIdS "zu"
@@ -274,8 +274,8 @@ size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag)
 	size_t count, length;
 	UINT8 *buf;
 
-	if (FIL_ReadFileOK(name))
-		handle = fopen(name, "rb");
+	//if (FIL_ReadFileOK(name))
+		handle = fopenfile(name, "rb");
 
 	if (!handle)
 		return 0;
@@ -468,6 +468,7 @@ void Command_SaveConfig_f(void)
 		CONS_Printf(M_GetText("saveconfig <filename[.cfg]> [-silent] : save config to a file\n"));
 		return;
 	}
+
 	strcpy(tmpstr, COM_Argv(1));
 	FIL_ForceExtension(tmpstr, ".cfg");
 
diff --git a/src/m_random.c b/src/m_random.c
index 3d0774a60b6c73895a31cfcb2f5d69d6fbfda02f..536fbfbbd1077abf6ae400bd4d8773a7574e4b08 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -3,6 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
+// Copyright (C) 2022-2023 by tertu marybig.
 // Copyright (C) 1999-2023 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -14,12 +15,122 @@
 
 #include "doomdef.h"
 #include "doomtype.h"
-#include "doomstat.h" // totalplaytime
+#include "i_system.h" // I_GetRandomBytes
 
 #include "m_random.h"
 #include "m_fixed.h"
 
+// SFC32 random number generator implementation
 
+typedef struct rnstate_s {
+	UINT32 data[3];
+	UINT32 counter;
+} rnstate_t;
+
+/** Generate a raw uniform random number using a particular state.
+  *
+  * \param state The RNG state to use.
+  * \return A random UINT32.
+  */
+static inline UINT32 RandomState_Get32(rnstate_t *state) {
+	UINT32 result, b, c;
+
+	b = state->data[1];
+	c = state->data[2];
+	result = state->data[0] + b + state->counter++;
+
+	state->data[0] = b ^ (b >> 9);
+	state->data[1] = c * 9;
+	state->data[2] = ((c << 21) | (c >> 11)) + result;
+
+	return result;
+}
+
+/** Seed an SFC32 RNG state with up to 96 bits of seed data.
+  *
+  * \param state The RNG state to seed.
+  * \param seeds A pointer to up to 3 UINT32s to use as seed data.
+  * \param seed_count The number of seed words.
+  */
+static inline void RandomState_Seed(rnstate_t *state, UINT32 *seeds, size_t seed_count)
+{
+	size_t i;
+
+	state->counter = 1;
+
+	for(i = 0; i < 3; i++)
+	{
+		UINT32 seed_word;
+
+		if(i < seed_count)
+			seed_word = seeds[i];
+		else
+			seed_word = 0;
+
+		// For SFC32, seed data should be stored in the state in reverse order.
+		state->data[2-i] = seed_word;
+	}
+
+	for(i = 0; i < 16; i++)
+		RandomState_Get32(state);
+}
+
+/** Gets a uniform number in the range [0, limit).
+  * Technique is based on a combination of scaling and rejection sampling
+  * and is adapted from Daniel Lemire.
+  *
+  * \note Any UINT32 is a valid argument for limit.
+  *
+  * \param state The RNG state to use.
+  * \param limit The upper limit of the range.
+  * \return A UINT32 in the range [0, limit).
+  */
+static inline UINT32 RandomState_GetKey32(rnstate_t *state, const UINT32 limit)
+{
+	UINT32 raw_random, scaled_lower_word;
+	UINT64 scaled_random;
+
+	// This algorithm won't work correctly if passed a 0.
+	if (limit == 0) return 0;
+
+	raw_random = RandomState_Get32(state);
+	scaled_random = (UINT64)raw_random * (UINT64)limit;
+
+	/*The high bits of scaled_random now contain the number we want, but it is
+	possible, depending on the number we generated and the value of limit,
+	that there is bias in the result. The rest of this code is for ensuring
+	that does not happen.
+	*/
+	scaled_lower_word = (UINT32)scaled_random;
+
+	// If we're lucky, we can bail out now and avoid the division
+	if (scaled_lower_word < limit)
+	{
+		// Scale the limit to improve the chance of success.
+		// After this, the first result might turn out to be good enough.
+		UINT32 scaled_limit;
+		// An explanation for this trick: scaled_limit should be
+		// (UINT32_MAX+1)%range, but if that was computed directly the result
+		// would need to be computed as a UINT64. This trick allows it to be
+		// computed using 32-bit arithmetic.
+		scaled_limit = (-limit) % limit;
+
+		while (scaled_lower_word < scaled_limit)
+		{
+			raw_random = RandomState_Get32(state);
+			scaled_random = (UINT64)raw_random * (UINT64)limit;
+			scaled_lower_word = (UINT32)scaled_random;
+		}
+	}
+
+	return scaled_random >> 32;
+}
+
+// The default seed is the hexadecimal digits of pi, though it will be overwritten.
+static rnstate_t m_randomstate = {
+	.data = {0x4A3B6035U, 0x99555606U, 0x6F603421U},
+	.counter = 16
+};
 
 // ---------------------------
 // RNG functions (not synched)
@@ -32,13 +143,7 @@
   */
 fixed_t M_RandomFixed(void)
 {
-#if RAND_MAX < 65535
-	// Compensate for insufficient randomness.
-	fixed_t rndv = (rand()&1)<<15;
-	return rand()^rndv;
-#else
-	return (rand() & 0xFFFF);
-#endif
+	return RandomState_Get32(&m_randomstate) >> (32-FRACBITS);
 }
 
 /** Provides a random byte. Distribution is uniform.
@@ -48,7 +153,7 @@ fixed_t M_RandomFixed(void)
   */
 UINT8 M_RandomByte(void)
 {
-	return (rand() & 0xFF);
+	return RandomState_Get32(&m_randomstate) >> 24;
 }
 
 /** Provides a random integer for picking random elements from an array.
@@ -60,7 +165,22 @@ UINT8 M_RandomByte(void)
   */
 INT32 M_RandomKey(INT32 a)
 {
-	return (INT32)((rand()/((float)RAND_MAX+1.0f))*a);
+	boolean range_is_negative;
+	INT64 range;
+	INT32 random_result;
+
+	range = a;
+	range_is_negative = range < 0;
+
+	if(range_is_negative)
+		range = -range;
+
+	random_result = RandomState_GetKey32(&m_randomstate, (UINT32)range);
+
+	if(range_is_negative)
+		random_result = -random_result;
+
+	return random_result;
 }
 
 /** Provides a random integer in a given range.
@@ -73,7 +193,46 @@ INT32 M_RandomKey(INT32 a)
   */
 INT32 M_RandomRange(INT32 a, INT32 b)
 {
-	return (INT32)((rand()/((float)RAND_MAX+1.0f))*(b-a+1))+a;
+  	if (b < a)
+	{
+    	INT32 temp;
+
+		temp = a;
+		a = b;
+		b = temp;
+	}
+
+	const UINT32 spread = b-a+1;
+	return (INT32)((INT64)RandomState_GetKey32(&m_randomstate, spread) + a);
+}
+
+/** Attempts to seed the unsynched RNG from a good random number source
+  * provided by the operating system.
+  * \return true on success, false on failure.
+  */
+boolean M_RandomSeedFromOS(void)
+{
+	UINT32 complete_word_count;
+
+	union {
+		UINT32 words[3];
+		char bytes[sizeof(UINT32[3])];
+	} seed_data;
+
+	complete_word_count = I_GetRandomBytes((char *)&seed_data.bytes, sizeof(seed_data)) / sizeof(UINT32);
+
+	// If we get even 1 word of seed, it's fine, but any less probably is not fine.
+	if (complete_word_count == 0)
+		return false;
+
+	RandomState_Seed(&m_randomstate, (UINT32 *)&seed_data.words, complete_word_count);
+
+	return true;
+}
+
+void M_RandomSeed(UINT32 seed)
+{
+	RandomState_Seed(&m_randomstate, &seed, 1);
 }
 
 
@@ -247,10 +406,18 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed)
 }
 
 /** Gets a randomized seed for setting the random seed.
+  * This function will never return 0, as the current P_Random implementation
+  * cannot handle a zero seed. Any other seed is equally likely.
   *
   * \sa P_GetRandSeed
   */
 UINT32 M_RandomizedSeed(void)
 {
-	return ((totalplaytime & 0xFFFF) << 16)|M_RandomFixed();
+	UINT32 seed;
+
+	do {
+		seed = RandomState_Get32(&m_randomstate);
+	} while(seed == 0);
+
+	return seed;
 }
diff --git a/src/m_random.h b/src/m_random.h
index 824287e27d486906c60d976e60817cb5ac9ffb73..a7c07a46b5e2c951548d67f409287e80345c20dd 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -3,6 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
+// Copyright (C) 2022-2023 by tertu marybig.
 // Copyright (C) 1999-2023 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -29,6 +30,8 @@ fixed_t M_RandomFixed(void);
 UINT8   M_RandomByte(void);
 INT32   M_RandomKey(INT32 a);
 INT32   M_RandomRange(INT32 a, INT32 b);
+boolean M_RandomSeedFromOS(void);
+void    M_RandomSeed(UINT32 a);
 
 // PRNG functions
 #ifdef DEBUGRANDOM
diff --git a/src/mserv.h b/src/mserv.h
index 1c8d742d818915a61cc90389a067ca44a1752f2a..07253da8562906cb51c59e6a27f2ec289d62c9a8 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -33,7 +33,7 @@ typedef union
 typedef struct
 {
 	msg_header_t header;
-	char ip[16];
+	char ip[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"];
 	char port[8];
 	char name[32];
 	INT32 room;
diff --git a/src/p5prof.h b/src/p5prof.h
deleted file mode 100644
index a9ed3965e9691f9931cd360da7cc41efc2ee5c55..0000000000000000000000000000000000000000
--- a/src/p5prof.h
+++ /dev/null
@@ -1,278 +0,0 @@
-/*********************************************************
- *
- * File:  p5prof.h
- * By:    Kevin Baca
- *
- * MODIFIED BY Fab SO THAT RDMSR(...) WRITES EDX : EAX TO A LONG LONG
- * (WHICH MEANS WRITE THE LOW DWORD FIRST)
- *
- * Now in yer code do:
- *   INT64 count,total;
- *
- *   ...
- *   RDMSR(0x10,&count);        //inner loop count
- *   total += count;
- *   ...
- *
- *   printf("0x%x %x", (INT32)total, *((INT32 *)&total+1));
- *   //                  HIGH        LOW
- *
- *********************************************************/
-/**\file
-	\brief  This file provides macros to profile your code.
-
- Here's how they work...
-
- As you may or may not know, the Pentium class of
- processors provides extremely fine grained profiling
- capabilities through the use of what are called
- Machine Specific Registers (MSRs). These registers
- can provide information about almost any aspect of
- CPU performance down to a single cycle.
-
- The MSRs of interest for profiling are specified by
- indices 0x10, 0x11, 0x12, and 0x13.  Here is a brief
- description of each of these registers:
-
- MSR 0x10
-    This register is simple a cycle counter.
-
- MSR 0x11
-    This register controls what type of profiling data
- will be gathered.
-
- MSRs 0x12 and 0x13
-    These registers gather the profiling data specified in
- MSR 0x11.
-
- Each MSR is 64 bits wide.  For the Pentium processor,
- only the lower 32 bits of MSR 0x11 are valid.  Bits 0-15
- specify what data will be gathered in MSR 0x12.  Bits 16-31
- specify what data will be gathered in MSR 0x13.  Both sets
- of bits have the same format:
-
- Bits 0-5 specify which hardware event will be tracked.
- Bit 6, if set, indicates events will be tracked in
- rings 0-2.
- Bit 7, if set, indicates events will be tracked in
- ring 3.
- Bit 8, if set, indicates cycles should be counted for
- the specified event.  If clear, it indicates the
- number of events should be counted.
-
- Two instructions are provided for manupulating the MSRs.
- RDMSR (Read Machine Specific Register) and WRMSR
- (Write Machine Specific Register).  These opcodes were
- originally undocumented and therefore most assemblers don't
- recognize them.  Their byte codes are provided in the
- macros below.
-
- RDMSR takes the MSR index in ecx and the profiling criteria
- in edx : eax.
-
- WRMSR takes the MSR index in ecx and returns the profile data
- in edx : eax.
-
- Two profiling registers limits profiling capability to
- gathering only two types of information.  The register
- usage can, however, be combined in interesting ways.
- For example, you can set one register to gather the
- number of a specific type of event while the other gathers
- the number of cycles for the same event.  Or you can
- gather the number of two separate events while using
- MSR 0x10 to gather the number of cycles.
-
- The enumerated list provides somewhat readable labels for
- the types of events that can be tracked.
-
- For more information, get ahold of appendix H from the
- Intel Pentium programmer's manual (I don't remember the
- order number) or go to
- http://green.kaist.ac.kr/jwhahn/art3.htm.
- That's an article by Terje Mathisen where I got most of
- my information.
-
- You may use this code however you wish.  I hope it's
- useful and I hope I got everything right.
-
- -Kevin
-
- kbaca@skygames.com
-
-*/
-
-#ifdef __GNUC__
-
-#define RDTSC(_dst) \
-__asm__("
-     .byte 0x0F,0x31
-     movl %%edx,(%%edi)
-     movl %%eax,4(%%edi)"\
-: : "D" (_dst) : "eax", "edx", "edi")
-
-// the old code... swapped it
-//     movl %%edx,(%%edi)
-//     movl %%eax,4(%%edi)"
-#define RDMSR(_msri, _msrd) \
-__asm__("
-     .byte 0x0F,0x32
-     movl %%eax,(%%edi)
-     movl %%edx,4(%%edi)"\
-: : "c" (_msri), "D" (_msrd) : "eax", "ecx", "edx", "edi")
-
-#define WRMSR(_msri, _msrd) \
-__asm__("
-     xorl %%edx,%%edx
-     .byte 0x0F,0x30"\
-: : "c" (_msri), "a" (_msrd) : "eax", "ecx", "edx")
-
-#define RDMSR_0x12_0x13(_msr12, _msr13) \
-__asm__("
-     movl $0x12,%%ecx
-     .byte 0x0F,0x32
-     movl %%edx,(%%edi)
-     movl %%eax,4(%%edi)
-     movl $0x13,%%ecx
-     .byte 0x0F,0x32
-     movl %%edx,(%%esi)
-     movl %%eax,4(%%esi)"\
-: : "D" (_msr12), "S" (_msr13) : "eax", "ecx", "edx", "edi")
-
-#define ZERO_MSR_0x12_0x13() \
-__asm__("
-     xorl %%edx,%%edx
-     xorl %%eax,%%eax
-     movl $0x12,%%ecx
-     .byte 0x0F,0x30
-     movl $0x13,%%ecx
-     .byte 0x0F,0x30"\
-: : : "eax", "ecx", "edx")
-
-#elif defined (__WATCOMC__)
-
-extern void RDTSC(UINT32 *dst);
-#pragma aux RDTSC =\
-   "db 0x0F,0x31"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   parm [edi]\
-   modify [eax edx edi];
-
-extern void RDMSR(UINT32 msri, UINT32 *msrd);
-#pragma aux RDMSR =\
-   "db 0x0F,0x32"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   parm [ecx] [edi]\
-   modify [eax ecx edx edi];
-
-extern void WRMSR(UINT32 msri, UINT32 msrd);
-#pragma aux WRMSR =\
-   "xor edx,edx"\
-   "db 0x0F,0x30"\
-   parm [ecx] [eax]\
-   modify [eax ecx edx];
-
-extern void RDMSR_0x12_0x13(UINT32 *msr12, UINT32 *msr13);
-#pragma aux RDMSR_0x12_0x13 =\
-   "mov ecx,0x12"\
-   "db 0x0F,0x32"\
-   "mov [edi],edx"\
-   "mov [4+edi],eax"\
-   "mov ecx,0x13"\
-   "db 0x0F,0x32"\
-   "mov [esi],edx"\
-   "mov [4+esi],eax"\
-   parm [edi] [esi]\
-   modify [eax ecx edx edi esi];
-
-extern void ZERO_MSR_0x12_0x13(void);
-#pragma aux ZERO_MSR_0x12_0x13 =\
-   "xor edx,edx"\
-   "xor eax,eax"\
-   "mov ecx,0x12"\
-   "db 0x0F,0x30"\
-   "mov ecx,0x13"\
-   "db 0x0F,0x30"\
-   modify [eax ecx edx];
-
-#endif
-
-typedef enum
-{
-   DataRead,
-     DataWrite,
-     DataTLBMiss,
-     DataReadMiss,
-     DataWriteMiss,
-     WriteHitEM,
-     DataCacheLinesWritten,
-     DataCacheSnoops,
-     DataCacheSnoopHit,
-     MemAccessBothPipes,
-     BankConflict,
-     MisalignedDataRef,
-     CodeRead,
-     CodeTLBMiss,
-     CodeCacheMiss,
-     SegRegLoad,
-     RESERVED0,
-     RESERVED1,
-     Branch,
-     BTBHit,
-     TakenBranchOrBTBHit,
-     PipelineFlush,
-     InstructionsExeced,
-     InstructionsExecedVPipe,
-     BusUtilizationClocks,
-     PipelineStalledWriteBackup,
-     PipelineStalledDateMemRead,
-     PipeLineStalledWriteEM,
-     LockedBusCycle,
-     IOReadOrWriteCycle,
-     NonCacheableMemRef,
-     AGI,
-     RESERVED2,
-     RESERVED3,
-     FPOperation,
-     Breakpoint0Match,
-     Breakpoint1Match,
-     Breakpoint2Match,
-     Breakpoint3Match,
-     HWInterrupt,
-     DataReadOrWrite,
-     DataReadOrWriteMiss
-};
-
-#define PROF_CYCLES (0x100)
-#define PROF_EVENTS (0x000)
-#define RING_012    (0x40)
-#define RING_3      (0x80)
-#define RING_0123   (RING_012 | RING_3)
-
-/*void ProfSetProfiles(UINT32 msr12, UINT32 msr13);*/
-#define ProfSetProfiles(_msr12, _msr13)\
-{\
-   UINT32 prof;\
-\
-   prof = (_msr12) | ((_msr13) << 16);\
-   WRMSR(0x11, prof);\
-}
-
-/*void ProfBeginProfiles(void);*/
-#define ProfBeginProfiles()\
-   ZERO_MSR_0x12_0x13();
-
-/*void ProfGetProfiles(UINT32 msr12[2], UINT32 msr13[2]);*/
-#define ProfGetProfiles(_msr12, _msr13)\
-   RDMSR_0x12_0x13(_msr12, _msr13);
-
-/*void ProfZeroTimer(void);*/
-#define ProfZeroTimer()\
-   WRMSR(0x10, 0);
-
-/*void ProfReadTimer(UINT32 timer[2]);*/
-#define ProfReadTimer(timer)\
-   RDMSR(0x10, timer);
-
-/*EOF*/
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 63d430eb68f9dccc291beb02c899e78e8cdb2359..eebb65f3cb146f229183b014acdc89e0042f8da2 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -174,6 +174,7 @@ void A_Boss1Spikeballs(mobj_t *actor);
 void A_Boss3TakeDamage(mobj_t *actor);
 void A_Boss3Path(mobj_t *actor);
 void A_Boss3ShockThink(mobj_t *actor);
+void A_Shockwave(mobj_t *actor);
 void A_LinedefExecute(mobj_t *actor);
 void A_LinedefExecuteFromArg(mobj_t *actor);
 void A_PlaySeeSound(mobj_t *actor);
@@ -828,7 +829,7 @@ static boolean P_LookForShield(mobj_t *actor)
 			continue;
 
 		if ((player->powers[pw_shield] & SH_PROTECTELECTRIC)
-			&& (P_AproxDistance(P_AproxDistance(actor->x-player->mo->x, actor->y-player->mo->y), actor->z-player->mo->z) < FixedMul(RING_DIST, player->mo->scale)))
+			&& (R_PointToDist2(0, 0, R_PointToDist2(0, 0, actor->x-player->mo->x, actor->y-player->mo->y), actor->z-player->mo->z) < FixedMul(RING_DIST, player->mo->scale)))
 		{
 			P_SetTarget(&actor->tracer, player->mo);
 
@@ -1571,6 +1572,8 @@ void A_PointyThink(mobj_t *actor)
 	// Okay, we found the closest player. Let's move based on his movement.
 	P_SetTarget(&actor->target, player->mo);
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y) < P_AproxDistance(player->mo->x + player->mo->momx - actor->x, player->mo->y + player->mo->momy - actor->y))
 		sign = -1; // Player is moving away
@@ -1589,6 +1592,10 @@ void A_PointyThink(mobj_t *actor)
 	if (!actor->tracer) // For some reason we do not have spike balls...
 		return;
 
+	// Catch case where actor lastlook is -1 (which segfaults the following blocks)
+	if (actor->lastlook < 0)
+		return;
+
 	// Position spike balls relative to the value of 'lastlook'.
 	ball = actor->tracer;
 
@@ -1686,6 +1693,8 @@ void A_HoodFire(mobj_t *actor)
 	}
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (!(arrow = P_SpawnMissile(actor, actor->target, (mobjtype_t)locvar1)))
 		return;
@@ -2227,7 +2236,7 @@ void A_CrushclawLaunch(mobj_t *actor)
 		}
 	}
 
-	if (!actor->target)
+	if (P_MobjWasRemoved(actor->target))
 		return;
 
 	{
@@ -2266,6 +2275,8 @@ void A_VultureVtol(mobj_t *actor)
 	actor->flags |= MF_FLOAT;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	S_StopSound(actor);
 
@@ -2364,6 +2375,9 @@ void A_VultureHover(mobj_t *actor)
 	P_VultureHoverParticle(actor);
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	targetz = actor->target->z + actor->target->height / 2;
 	for (i = -1; i <= 1; i++)
 	{
@@ -2680,6 +2694,8 @@ void A_LobShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
 	{
@@ -2775,6 +2791,8 @@ void A_FireShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
 		z = actor->z + actor->height - FixedMul(48*FRACUNIT + locvar2*FRACUNIT, actor->scale);
@@ -2813,6 +2831,8 @@ void A_SuperFireShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
 		z = actor->z + actor->height - FixedMul(48*FRACUNIT + locvar2*FRACUNIT, actor->scale);
@@ -2860,6 +2880,8 @@ void A_BossFireShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	switch (locvar2)
 	{
@@ -2947,6 +2969,8 @@ void A_Boss7FireMissiles(mobj_t *actor)
 	}
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	S_StartSound(NULL, locvar2);
 
@@ -3331,6 +3355,8 @@ void A_SkullAttack(mobj_t *actor)
 	if (actor->info->activesound)
 		S_StartSound(actor, actor->info->activesound);
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
 
@@ -3442,6 +3468,9 @@ void A_BossZoom(mobj_t *actor)
 	if (actor->info->attacksound)
 		S_StartAttackSound(actor, actor->info->attacksound);
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	an = actor->angle >> ANGLETOFINESHIFT;
 	actor->momx = FixedMul(FixedMul(actor->info->speed*5*FRACUNIT, actor->scale), FINECOSINE(an));
 	actor->momy = FixedMul(FixedMul(actor->info->speed*5*FRACUNIT, actor->scale), FINESINE(an));
@@ -3910,7 +3939,7 @@ static void P_DoBossVictory(mobj_t *mo)
 	}
 
 	// victory!
-	if (mo->spawnpoint)
+	if (mo->spawnpoint && mo->spawnpoint->args[3])
 		P_LinedefExecute(mo->spawnpoint->args[3], mo, NULL);
 
 	if (stoppedclock && modeattacking) // if you're just time attacking, skip making the capsule appear since you don't need to step on it anyways.
@@ -4129,7 +4158,7 @@ void A_BossDeath(mobj_t *mo)
 	if (LUA_CallAction(A_BOSSDEATH, mo))
 		return;
 
-	if (mo->spawnpoint)
+	if (mo->spawnpoint && mo->spawnpoint->args[2])
 		P_LinedefExecute(mo->spawnpoint->args[2], mo, NULL);
 	mo->health = 0;
 
@@ -4858,7 +4887,9 @@ void A_FishJump(mobj_t *actor)
 			jumpval = locvar1;
 		else
 		{
-			if (actor->spawnpoint && actor->spawnpoint->args[0])
+			if (!udmf && actor->angle)
+				jumpval = AngleFixed(actor->angle)>>2;
+			else if (actor->spawnpoint && actor->spawnpoint->args[0])
 				jumpval = actor->spawnpoint->args[0] << (FRACBITS - 2);
 			else
 				jumpval = 44 << (FRACBITS - 2);
@@ -5293,7 +5324,7 @@ void A_SignPlayer(mobj_t *actor)
 
 	actor->tracer->color = signcolor;
 	if (signcolor && signcolor < numskincolors)
-		signframe += (15 - skincolors[skincolors[signcolor].invcolor].invshade);
+		signframe += (15 - skincolors[facecolor].invshade);
 	actor->tracer->frame = signframe;
 }
 
@@ -5539,6 +5570,9 @@ void A_JetgShoot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	P_SpawnMissile(actor, actor->target, (mobjtype_t)actor->info->raisestate);
 
 	if (ultimatemode)
@@ -5573,6 +5607,9 @@ void A_ShootBullet(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	P_SpawnMissile(actor, actor->target, (mobjtype_t)actor->info->raisestate);
 
 	if (actor->info->attacksound)
@@ -7102,7 +7139,7 @@ void A_Boss1Chase(mobj_t *actor)
 		}
 		else
 		{
-			if (actor->spawnpoint)
+			if (actor->spawnpoint && actor->spawnpoint->args[4])
 				P_LinedefExecute(actor->spawnpoint->args[4], actor, NULL);
 			P_SetMobjState(actor, actor->info->raisestate);
 		}
@@ -7431,6 +7468,8 @@ void A_Boss7Chase(mobj_t *actor)
 		&& (actor->target->player->powers[pw_carry] == CR_GENERIC))
 	{
 		A_FaceTarget(actor);
+		if (P_MobjWasRemoved(actor))
+			return;
 		P_SetMobjState(actor, S_BLACKEGG_SHOOT1);
 		actor->movecount = TICRATE + P_RandomByte()/2;
 		return;
@@ -7448,6 +7487,8 @@ void A_Boss7Chase(mobj_t *actor)
 				if (actor->z < 1056*FRACUNIT)
 				{
 					A_FaceTarget(actor);
+					if (P_MobjWasRemoved(actor))
+						return;
 					P_SetMobjState(actor, actor->info->xdeathstate);
 					actor->movecount = 7*TICRATE + P_RandomByte();
 					break;
@@ -7456,6 +7497,8 @@ void A_Boss7Chase(mobj_t *actor)
 				/* FALLTHRU */
 			case 1: // Chaingun Goop
 				A_FaceTarget(actor);
+				if (P_MobjWasRemoved(actor))
+					return;
 				P_SetMobjState(actor, S_BLACKEGG_SHOOT1);
 
 				if (actor->health > actor->info->damage)
@@ -7465,6 +7508,8 @@ void A_Boss7Chase(mobj_t *actor)
 				break;
 			case 2: // Homing Missile
 				A_FaceTarget(actor);
+				if (P_MobjWasRemoved(actor))
+					return;
 				P_SetMobjState(actor, actor->info->missilestate);
 				S_StartSound(0, sfx_beflap);
 				break;
@@ -8182,6 +8227,9 @@ void A_Boss3Path(mobj_t *actor)
 		P_SetTarget(&actor->target, actor->tracer->target);
 		var1 = 0, var2 = 0;
 		A_FaceTarget(actor);
+		if (P_MobjWasRemoved(actor))
+			return;
+
 		if (actor->tracer->state == &states[actor->tracer->info->missilestate])
 			P_SetMobjState(actor, actor->info->missilestate);
 		return;
@@ -8332,6 +8380,56 @@ void A_Boss3ShockThink(mobj_t *actor)
 	}
 }
 
+// Function: A_Shockwave
+//
+// Description: Spawns a shockwave of objects. Best used to spawn objects that call A_Boss3ShockThink.
+//
+// var1 = object spawned
+// var2 = amount of objects spawned
+//
+void A_Shockwave(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT32 i;
+
+	angle_t ang = 0, interval;
+	mobj_t *shock = NULL, *sfirst = NULL, *sprev = NULL;
+
+	if (LUA_CallAction(A_SHOCKWAVE, actor))
+		return;
+
+	if (locvar2 == 0)
+		locvar2 = 24; // a sensible default, just in case
+
+	interval = FixedAngle((360 << FRACBITS) / locvar2);
+
+	for (i = 0; i < locvar2; i++)
+	{
+		shock = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1);
+		P_SetTarget(&shock->target, actor);
+		shock->fuse = shock->info->painchance;
+
+		if (i % 2 == 0)
+			P_SetMobjState(shock, shock->state->nextstate);
+
+		if (!sprev)
+			sfirst = shock;
+		else
+		{
+			if (i == locvar2 - 1)
+				P_SetTarget(&shock->hnext, sfirst);
+			P_SetTarget(&sprev->hnext, shock);
+		}
+
+		P_Thrust(shock, ang, shock->info->speed);
+		ang += interval;
+		sprev = shock;
+	}
+	
+	S_StartSound(actor, shock->info->seesound);
+}
+
 // Function: A_LinedefExecute
 //
 // Description: Object's location is used to set the calling sector. The tag used is var1. Optionally, if var2 is set, the actor's angle (multiplied by var2) is added to the tag number as well.
@@ -8640,10 +8738,10 @@ void A_RollAngle(mobj_t *actor)
 
 	// relative (default)
 	if (!locvar2)
-		actor->rollangle += angle;
+		actor->spriteroll += angle;
 	// absolute
 	else
-		actor->rollangle = angle;
+		actor->spriteroll = angle;
 }
 
 // Function: A_ChangeRollAngleRelative
@@ -8668,7 +8766,7 @@ void A_ChangeRollAngleRelative(mobj_t *actor)
 		I_Error("A_ChangeRollAngleRelative: var1 is greater than var2");
 #endif
 
-	actor->rollangle += FixedAngle(P_RandomRange(amin, amax));
+	actor->spriteroll += FixedAngle(P_RandomRange(amin, amax));
 }
 
 // Function: A_ChangeRollAngleAbsolute
@@ -8693,7 +8791,7 @@ void A_ChangeRollAngleAbsolute(mobj_t *actor)
 		I_Error("A_ChangeRollAngleAbsolute: var1 is greater than var2");
 #endif
 
-	actor->rollangle = FixedAngle(P_RandomRange(amin, amax));
+	actor->spriteroll = FixedAngle(P_RandomRange(amin, amax));
 }
 
 // Function: A_PlaySound
@@ -9856,6 +9954,8 @@ void A_SplitShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 	{
 		const angle_t an = (actor->angle + ANGLE_90) >> ANGLETOFINESHIFT;
 		const fixed_t fasin = FINESINE(an);
@@ -9920,6 +10020,9 @@ void A_MultiShot(mobj_t *actor)
 	if (actor->target)
 		A_FaceTarget(actor);
 
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	if(loc1lw > 90)
 		ad = FixedMul(90*FRACUNIT, actor->scale);
 	else
@@ -11064,6 +11167,8 @@ void A_VileTarget(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	// Determine object to spawn
 	if (locvar1 <= 0 || locvar1 >= NUMMOBJTYPES)
@@ -11151,6 +11256,8 @@ void A_VileAttack(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (locvar1 <= 0 || locvar1 >= NUMSFX)
 		soundtoplay = sfx_brakrx;
@@ -11469,6 +11576,8 @@ void A_BrakFireShot(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	x = actor->x
 		+ P_ReturnThrustX(actor, actor->angle, FixedMul(64*FRACUNIT, actor->scale))
@@ -11586,6 +11695,9 @@ void A_BrakLobShot(mobj_t *actor)
 
 	// Okay, complicated math done. Let's fire our object already, sheesh.
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	if (locvar1 <= 0 || locvar1 >= NUMMOBJTYPES)
 		typeOfShot = MT_CANNONBALL;
 	else typeOfShot = (mobjtype_t)locvar1;
@@ -12524,8 +12636,7 @@ void A_MineRange(mobj_t *actor)
 void A_ConnectToGround(mobj_t *actor)
 {
 	mobj_t *work;
-	fixed_t workz;
-	fixed_t workh;
+	fixed_t endz;
 	angle_t ang;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
@@ -12536,38 +12647,42 @@ void A_ConnectToGround(mobj_t *actor)
 	if (actor->subsector->sector->ffloors)
 		P_AdjustMobjFloorZ_FFloors(actor, actor->subsector->sector, 2);
 
+	endz = actor->z;
 	if (actor->flags2 & MF2_OBJECTFLIP)
-		workz = (actor->z + actor->height) - actor->ceilingz;
+		actor->z = actor->ceilingz - actor->height; // Ensures perfect ceiling connection
 	else
-		workz = actor->floorz - actor->z;
+		actor->z = actor->floorz; // Ensures perfect floor connection
 
 	if (locvar2)
 	{
-		workh = FixedMul(mobjinfo[locvar2].height, actor->scale);
-		if (actor->flags2 & MF2_OBJECTFLIP)
-			workz += workh;
-		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar2);
-		workz += workh;
-	}
+		work = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar2);
+		if (work)
+			work->old_z = work->z; // Don't copy old_z from the actor
 
-	if (!locvar1)
-		return;
+		actor->z += P_MobjFlip(actor) * FixedMul(mobjinfo[locvar2].height, actor->scale);
+	}
 
-	if (!(workh = FixedMul(mobjinfo[locvar1].height, actor->scale)))
+	if (!locvar1 || !mobjinfo[locvar1].height) // Can't tile the middle object?
+	{
+		actor->z = endz;
 		return;
+	}
 
 	ang = actor->angle + ANGLE_45;
-	while (workz < 0)
+	while ((actor->flags2 & MF2_OBJECTFLIP) ? (actor->z > endz) : (actor->z < endz))
 	{
-		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar1);
+		work = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
 		if (work)
-			work->angle = ang;
+		{
+			work->angle = work->old_angle = ang;
+			work->old_z = work->z; // Don't copy old_z from the actor
+		}
+
 		ang += ANGLE_90;
-		workz += workh;
+		actor->z += P_MobjFlip(actor) * FixedMul(mobjinfo[locvar1].height, actor->scale);
 	}
 
-	if (workz != 0)
-		actor->z += P_MobjFlip(actor)*workz;
+	actor->old_z = actor->z; // Reset Z interpolation - the spawned objects intentionally don't have any Z interpolation either, after all
 }
 
 // Function: A_SpawnParticleRelative
@@ -12670,6 +12785,8 @@ void A_WhoCaresIfYourSonIsABee(mobj_t *actor)
 		return;
 
 	A_FaceTarget(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	if (actor->extravalue1)
 		actor->extravalue1--;
diff --git a/src/p_floor.c b/src/p_floor.c
index 9c24f585141f3223c223c902f80fbba21896c4f3..38f0c5a0fbc496564e7b69c96f7cacd8a93287f6 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -155,7 +155,7 @@ result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, boolean crus
 		}
 	}
 
-	return ok;
+	return planeok;
 }
 
 //
@@ -1128,7 +1128,7 @@ void T_ThwompSector(thwomp_t *thwomp)
 				thwomp->direction         // direction
 			);
 
-			if (res == ok || res == pastdest)
+			if (res == planeok || res == pastdest)
 				T_MovePlane
 				(
 					thwomp->sector,             // sector
@@ -1160,7 +1160,7 @@ void T_ThwompSector(thwomp_t *thwomp)
 				thwomp->direction // direction
 			);
 
-			if (res == ok || res == pastdest)
+			if (res == planeok || res == pastdest)
 				T_MovePlane
 				(
 					thwomp->sector,   // sector
@@ -1465,7 +1465,7 @@ void T_RaiseSector(raise_t *raise)
 		direction           // direction
 	);
 
-	if (res == ok || res == pastdest)
+	if (res == planeok || res == pastdest)
 		T_MovePlane
 		(
 			raise->sector,    // sector
diff --git a/src/p_inter.c b/src/p_inter.c
index 8bc5c95e42a720ed8824c5a76de091b98badaa56..271b6ebc45a5bf42d048b39ace8db61d212741c8 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -164,6 +164,62 @@ boolean P_CanPickupItem(player_t *player, boolean weapon)
 	return true;
 }
 
+boolean P_CanPickupEmblem(player_t *player, INT32 emblemID)
+{
+	emblem_t *emblem = NULL;
+
+	if (emblemID < 0 || emblemID >= numemblems)
+	{
+		// Invalid emblem ID, can't pickup.
+		return false;
+	}
+
+	emblem = &emblemlocations[emblemID];
+
+	if (demoplayback)
+	{
+		// Never collect emblems in replays.
+		return false;
+	}
+
+	if (player->bot && player->bot != BOT_MPAI)
+	{
+		// Your little lap-dog can't grab these for you.
+		return false;
+	}
+
+	if (emblem->type == ET_SKIN)
+	{
+		INT32 skinnum = M_EmblemSkinNum(emblem);
+
+		if (player->skin != skinnum)
+		{
+			// Incorrect skin to pick up this emblem.
+			return false;
+		}
+	}
+
+	return true;
+}
+
+boolean P_EmblemWasCollected(INT32 emblemID)
+{
+	if (emblemID < 0 || emblemID >= numemblems)
+	{
+		// Invalid emblem ID, can't pickup.
+		return true;
+	}
+
+	if (shareEmblems && !serverGamedata->collected[emblemID])
+	{
+		// It can be worth collecting again if we're sharing emblems
+		// and the server doesn't have it.
+		return false;
+	}
+
+	return clientGamedata->collected[emblemID];
+}
+
 //
 // P_DoNightsScore
 //
@@ -199,42 +255,19 @@ void P_DoNightsScore(player_t *player)
 		player->linktimer = nightslinktics;
 	}
 
-	if (player->linkcount < 10)
-	{
-		if (player->bonustime)
-		{
-			P_AddPlayerScore(player, player->linkcount*20);
-			P_SetMobjState(dummymo, dummymo->info->xdeathstate+player->linkcount-1);
-		}
-		else
-		{
-			P_AddPlayerScore(player, player->linkcount*10);
-			P_SetMobjState(dummymo, dummymo->info->spawnstate+player->linkcount-1);
-		}
-	}
-	else
-	{
-		if (player->bonustime)
-		{
-			P_AddPlayerScore(player, 200);
-			P_SetMobjState(dummymo, dummymo->info->xdeathstate+9);
-		}
-		else
-		{
-			P_AddPlayerScore(player, 100);
-			P_SetMobjState(dummymo, dummymo->info->spawnstate+9);
-		}
-	}
+	// Award 10-100 score, doubled if bonus time is active
+	P_AddPlayerScore(player, min(player->linkcount,10)*(player->bonustime ? 20 : 10));
+	P_SetMobjState(dummymo, (player->bonustime ? dummymo->info->xdeathstate : dummymo->info->spawnstate) + min(player->linkcount,10)-1);
 
-	// Hoops are the only things that should add to your drill meter
-	//player->drillmeter += TICRATE;
+	// Make objects slowly rise & scale up
 	dummymo->momz = FRACUNIT;
 	dummymo->fuse = 3*TICRATE;
-
-	// What?! NO, don't use the camera! Scale up instead!
-	//P_InstaThrust(dummymo, R_PointToAngle2(dummymo->x, dummymo->y, camera.x, camera.y), 3*FRACUNIT);
 	dummymo->scalespeed = FRACUNIT/25;
 	dummymo->destscale = 2*FRACUNIT;
+
+	// Add extra values used for color variety
+	dummymo->extravalue1 = player->linkcount-1;
+	dummymo->extravalue2 = ((player->linkcount-1 >= 300) ? (player->linkcount-1 >= 600) ? 2 : 1 : 0);
 }
 
 //
@@ -465,23 +498,20 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (special->type == MT_PTERABYTE && special->target == player->mo && special->extravalue1 == 1)
 				return; // Can't hurt a Pterabyte if it's trying to pick you up
 
-			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
+			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1) && (!(player->powers[pw_strong] & STR_HEAVY)))
 			{
-				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+				fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+				
+				if (elementalpierce == 2) // Reset bubblewrap, part 1
+					P_DoBubbleBounce(player);
+				toucher->momz = setmomz;
+				if (elementalpierce == 2) // Reset bubblewrap, part 2
 				{
-					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
-
-					if (elementalpierce == 2) // Reset bubblewrap, part 1
-						P_DoBubbleBounce(player);
-					toucher->momz = setmomz;
-					if (elementalpierce == 2) // Reset bubblewrap, part 2
-					{
-						boolean underwater = toucher->eflags & MFE_UNDERWATER;
-
-						if (underwater)
-							toucher->momz /= 2;
-						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
-					}
+					boolean underwater = toucher->eflags & MFE_UNDERWATER;
+							
+					if (underwater)
+						toucher->momz /= 2;
+					toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
 				}
 			}
 			if (player->pflags & PF_BOUNCING)
@@ -500,8 +530,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					toucher->momx = 7*toucher->momx>>3;
 					toucher->momy = 7*toucher->momy>>3;
 				}
-				else if (player->dashmode >= DASHMODE_THRESHOLD && (player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
-					&& player->panim == PA_DASH)
+				else if ((player->powers[pw_strong] & STR_DASH) && player->panim == PA_DASH)
 					P_DoPlayerPain(player, special, special);
 			}
 			P_DamageMobj(special, toucher, toucher, 1, 0);
@@ -563,7 +592,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			special->momx = special->momy = special->momz = 0;
 			P_GivePlayerSpheres(player, 1);
 
-			if (special->type == MT_BLUESPHERE)
+			if (special->type == MT_BLUESPHERE || special->type == MT_FLINGBLUESPHERE)
 			{
 				special->destscale = ((player->powers[pw_carry] == CR_NIGHTSMODE) ? 4 : 2)*special->scale;
 				if (states[special->info->deathstate].tics > 0)
@@ -738,13 +767,70 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		// Secret emblem thingy
 		case MT_EMBLEM:
 			{
-				if (demoplayback || (player->bot && player->bot != BOT_MPAI) || special->health <= 0 || special->health > MAXEMBLEMS)
+				const boolean toucherIsServer = ((player - players) == serverplayer);
+				const boolean consoleIsServer = (consoleplayer == serverplayer);
+				boolean prevCollected = false;
+
+				if ((special->flags2 & MF2_NIGHTSPULL)
+					&& (toucher == special->tracer))
+				{
+					// Since collecting may not remove the object,
+					// we need to manually stop it from chasing.
+					P_SetTarget(&special->tracer, NULL);
+					special->flags2 &= ~MF2_NIGHTSPULL;
+					special->movefactor = 0;
+					special->momx = special->momy = special->momz = 0;
+				}
+
+				if (!P_CanPickupEmblem(player, special->health - 1))
+				{
 					return;
-				emblemlocations[special->health-1].collected = true;
+				}
 
-				M_UpdateUnlockablesAndExtraEmblems();
-				G_SaveGameData();
-				break;
+				prevCollected = P_EmblemWasCollected(special->health - 1);
+
+				if (toucherIsServer || shareEmblems)
+				{
+					serverGamedata->collected[special->health-1] = true;
+					M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+				}
+
+				if (P_IsLocalPlayer(player) || (consoleIsServer && shareEmblems))
+				{
+					clientGamedata->collected[special->health-1] = true;
+					M_UpdateUnlockablesAndExtraEmblems(clientGamedata);
+					G_SaveGameData(clientGamedata);
+				}
+
+				if (netgame)
+				{
+					// This always spawns the object to prevent mobjnum issues,
+					// but makes the effect invisible to whoever it doesn't matter to.
+					mobj_t *spark = P_SpawnMobjFromMobj(special, 0, 0, 0, MT_SPARK);
+
+					if (prevCollected == false && P_EmblemWasCollected(special->health - 1) == true)
+					{
+						// Play the sound if it was collected.
+						S_StartSound((shareEmblems ? NULL : special), special->info->deathsound);
+					}
+					else
+					{
+						// We didn't collect it, make it invisible to us.
+						spark->flags2 |= MF2_DONTDRAW;
+					}
+
+					return;
+				}
+				else
+				{
+					if (prevCollected == false && P_EmblemWasCollected(special->health - 1) == true)
+					{
+						// Disappear when collecting for local games.
+						break;
+					}
+
+					return;
+				}
 			}
 
 		// CTF Flags
@@ -1058,7 +1144,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!
@@ -2149,7 +2235,7 @@ void P_CheckTimeLimit(void)
 		}
 
 		if (server)
-			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+			D_SendExitLevel(false);
 	}
 
 	//Optional tie-breaker for Match/CTF
@@ -2212,11 +2298,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.
@@ -2245,7 +2331,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
@@ -2258,7 +2344,7 @@ void P_CheckPointLimit(void)
 			if ((UINT32)cv_pointlimit.value <= players[i].score)
 			{
 				if (server)
-					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+					D_SendExitLevel(false);
 				return;
 			}
 		}
@@ -2302,7 +2388,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;
 		}
@@ -2322,7 +2408,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;
@@ -2334,7 +2420,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;
@@ -2346,7 +2432,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);
 	}
 }
 
@@ -2583,7 +2669,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
 				{
 					numgameovers++;
-					if ((!modifiedgame || savemoddata) && cursaveslot > 0)
+					if (!usedCheats && cursaveslot > 0)
 						G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0));
 				}
 			}
@@ -3031,7 +3117,7 @@ static void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN);
 		S_StartSound(target, sfx_nghurt);
 
-		player->mo->rollangle = 0;
+		player->mo->spriteroll = 0;
 
 		if (oldnightstime > 10*TICRATE
 			&& player->nightstime < 10*TICRATE)
@@ -3213,7 +3299,7 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 		return false;
 
 	// Add pity.
-	if (!player->powers[pw_flashing] && !player->powers[pw_invulnerability] && !player->powers[pw_super]
+	if (!player->powers[pw_flashing] && !player->powers[pw_invulnerability] && !player->powers[pw_super] && !(player->powers[pw_strong] & STR_GUARD)
 	&& source->player->score > player->score)
 		player->pity++;
 
@@ -3546,6 +3632,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		UINT8 shouldForce = LUA_HookShouldDamage(target, inflictor, source, damage, damagetype);
 		if (P_MobjWasRemoved(target))
 			return (shouldForce == 1); // mobj was removed
+		if (P_MobjWasRemoved(source))
+			source = NULL;
 		if (shouldForce == 1)
 			force = true;
 		else if (shouldForce == 2)
@@ -3683,7 +3771,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			}
 			return false;
 		}
-		else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super]) // ignore bouncing & such in invulnerability
+		else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super] || (player->powers[pw_strong] & STR_GUARD)) // ignore bouncing & such in invulnerability
 		{
 			if (force
 			|| (inflictor && inflictor->flags & MF_MISSILE && inflictor->flags2 & MF2_SUPERFIRE)) // Super Sonic is stunned!
@@ -3763,6 +3851,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 	else
 		P_SetMobjState(target, target->info->painstate);
 
+	if (P_MobjWasRemoved(target))
+		return false;
+
 	if (target->type == MT_HIVEELEMENTAL)
 		target->extravalue1 += 3;
 
diff --git a/src/p_lights.c b/src/p_lights.c
index 75455da73681fa0587dbdd538401720fa38a439d..4b6a3673b51b6a1720d237e4b85ae8f7c7839c4b 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -353,8 +353,8 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 	else
 	{
 		// Speed means increment per tic (literally speed).
-		ll->timer = FixedDiv((destvalue<<FRACBITS) - ll->fixedcurlevel, speed<<FRACBITS)>>FRACBITS;
-		ll->fixedpertic = speed<<FRACBITS;
+		ll->timer = abs(FixedDiv((destvalue<<FRACBITS) - ll->fixedcurlevel, speed<<FRACBITS)>>FRACBITS);
+		ll->fixedpertic = ll->destlevel < ll->sourcelevel ? -speed<<FRACBITS : speed<<FRACBITS;
 	}
 }
 
diff --git a/src/p_local.h b/src/p_local.h
index cc060e4eee3c9b45f5b0da4e1c9c43a61f5c8ccf..563e257d8f1e67e7e5d45d0a8f7122659c66b435 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -510,6 +510,8 @@ void P_ClearStarPost(INT32 postnum);
 void P_ResetStarposts(void);
 
 boolean P_CanPickupItem(player_t *player, boolean weapon);
+boolean P_CanPickupEmblem(player_t *player, INT32 emblemID);
+boolean P_EmblemWasCollected(INT32 emblemID);
 void P_DoNightsScore(player_t *player);
 void P_DoMatchSuper(player_t *player);
 
@@ -535,5 +537,6 @@ void P_Thrust(mobj_t *mo, angle_t angle, fixed_t move);
 void P_DoSuperTransformation(player_t *player, boolean giverings);
 void P_ExplodeMissile(mobj_t *mo);
 void P_CheckGravity(mobj_t *mo, boolean affect);
+void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope);
 
 #endif // __P_LOCAL__
diff --git a/src/p_map.c b/src/p_map.c
index a7d1f4abdd8f37300b1e97be041971a045be19ea..251837876caab9f979b92ef1ece83a1750f24768 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -177,10 +177,8 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	{
 		if (spring->info->painchance == 3)
 			;
-		else if (object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
+		else if (object->player->powers[pw_strong] & STR_SPRING)
 			strong = 1;
-		else if (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)
-			strong = 2;
 	}
 
 	if (spring->info->painchance == -1) // Pinball bumper mode.
@@ -420,7 +418,20 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				P_SetPlayerMobjState(object, S_PLAY_ROLL);
 		}
 		else
-			pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING); // I still need these.
+		{
+			boolean wasSpindashing = object->player->dashspeed > 0 && (object->player->charability2 == CA2_SPINDASH);
+
+			pflags = object->player->pflags & (PF_STARTJUMP | PF_JUMPED | PF_NOJUMPDAMAGE | PF_SPINNING | PF_THOKKED | PF_BOUNCING); // I still need these.
+
+			if (wasSpindashing) // Ensure we're in the rolling state, and not spindash.
+				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+
+			if (object->player->charability == CA_GLIDEANDCLIMB && object->player->skidtime && (pflags & PF_JUMPED))
+			{
+				object->player->skidtime = 0; // No skidding should be happening, either.
+				pflags &= ~PF_JUMPED;
+			}
+		}
 		secondjump = object->player->secondjump;
 		washoming = object->player->homing;
 		P_ResetPlayer(object->player);
@@ -482,7 +493,8 @@ springstate:
 
 		if (strong)
 		{
-			P_TwinSpinRejuvenate(object->player, (strong == 1 ? object->player->thokitem : object->player->revitem));
+			if (object->player->charability == CA_TWINSPIN || object->player->charability2 == CA2_MELEE)
+				P_TwinSpinRejuvenate(object->player, (object->player->charability == CA_TWINSPIN ? object->player->thokitem : object->player->revitem));
 			S_StartSound(object, sfx_sprong); // strong spring. sprong.
 		}
 	}
@@ -823,43 +835,25 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	// SF_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users and CA2_MELEE users destroy spikes.
-	if ((tmthing->player)
-		&& ((((tmthing->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)) && (tmthing->player->dashmode >= DASHMODE_THRESHOLD)
-		&& (thing->flags & (MF_MONITOR)
-		|| (thing->type == MT_SPIKE
-		|| thing->type == MT_WALLSPIKE)))
-	|| ((((tmthing->player->charability == CA_TWINSPIN) && (tmthing->player->panim == PA_ABILITY))
-	|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2))
-		&& (thing->type == MT_SPIKE
-		|| thing->type == MT_WALLSPIKE))))
-	{
-		if ((thing->flags & (MF_MONITOR)) && (thing->health <= 0 || !(thing->flags & MF_SHOOTABLE)))
-			return true;
-		blockdist = thing->radius + tmthing->radius;
-		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
-			return true; // didn't hit it
-		// see if it went over / under
-		if (tmthing->z > thing->z + thing->height)
-			return true; // overhead
-		if (tmthing->z + tmthing->height < thing->z)
-			return true; // underneath
-		if (thing->type == MT_SPIKE
-		|| thing->type == MT_WALLSPIKE)
-		{
-			mobj_t *iter;
-			if (thing->flags & MF_SOLID)
-				S_StartSound(tmthing, thing->info->deathsound);
-			for (iter = thing->subsector->sector->thinglist; iter; iter = iter->snext)
-				if (iter->type == thing->type && iter->health > 0 && iter->flags & MF_SOLID && (iter == thing || P_AproxDistance(P_AproxDistance(thing->x - iter->x, thing->y - iter->y), thing->z - iter->z) < 56*thing->scale))//FixedMul(56*FRACUNIT, thing->scale))
-					P_KillMobj(iter, tmthing, tmthing, 0);
-			return true;
-		}
-		else
-		{
-			if (P_DamageMobj(thing, tmthing, tmthing, 1, 0))
-				return true;
-		}
+	// STR_SPIKE users destroy spikes
+	if ((tmthing->player) && ((tmthing->player->powers[pw_strong] & STR_SPIKE) && (thing->type == MT_SPIKE || thing->type == MT_WALLSPIKE)))
+	{
+		mobj_t *iter;
+        	blockdist = thing->radius + tmthing->radius;
+        	if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
+            		return true; // didn't hit it
+        	// see if it went over / under
+        	if (tmthing->z > thing->z + thing->height)
+            		return true; // overhead
+        	if (tmthing->z + tmthing->height < thing->z)
+            		return true; // underneath
+
+		if (thing->flags & MF_SOLID)
+			S_StartSound(tmthing, thing->info->deathsound);
+		for (iter = thing->subsector->sector->thinglist; iter; iter = iter->snext)
+			if (iter->type == thing->type && iter->health > 0 && iter->flags & MF_SOLID && (iter == thing || P_AproxDistance(P_AproxDistance(thing->x - iter->x, thing->y - iter->y), thing->z - iter->z) < 56*thing->scale))//FixedMul(56*FRACUNIT, thing->scale))
+				P_KillMobj(iter, tmthing, tmthing, 0);
+		return true;
 	}
 
 	// vectorise metal - done in a special case as at this point neither has the right flags for touching
@@ -1172,7 +1166,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	}
 
 	// When solid spikes move, assume they just popped up and teleport things on top of them to hurt.
-	if (tmthing->type == MT_SPIKE && tmthing->flags & MF_SOLID)
+	if (tmthing->type == MT_SPIKE && (thing->flags & MF_SOLID) && (tmthing->flags & MF_SOLID))
 	{
 		if (thing->z > tmthing->z + tmthing->height)
 			return true; // overhead
@@ -1714,25 +1708,22 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				// Going down? Then bounce back up.
 				if (P_DamageMobj(thing, tmthing, tmthing, 1, 0) // break the monitor
 				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
-				&& (elementalpierce != 1)) // you're not piercing through the monitor...
+				&& (elementalpierce != 1) && (!(player->powers[pw_strong] & STR_HEAVY))) // you're not piercing through the monitor...
 				{
-					if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+					fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+
+					if (elementalpierce == 2) // Reset bubblewrap, part 1
+						P_DoBubbleBounce(player);
+					*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
+					if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+						P_TwinSpinRejuvenate(player, player->thokitem);
+					if (elementalpierce == 2) // Reset bubblewrap, part 2
 					{
-						fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
-
-						if (elementalpierce == 2) // Reset bubblewrap, part 1
-							P_DoBubbleBounce(player);
-						*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
-						if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
-							P_TwinSpinRejuvenate(player, player->thokitem);
-						if (elementalpierce == 2) // Reset bubblewrap, part 2
-						{
-							boolean underwater = tmthing->eflags & MFE_UNDERWATER;
+						boolean underwater = tmthing->eflags & MFE_UNDERWATER;
 
-							if (underwater)
-								*momz /= 2;
-							*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
-						}
+						if (underwater)
+							*momz /= 2;
+						*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
 					}
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
@@ -2533,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;
@@ -2548,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;
@@ -2617,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;
 }
 
 //
@@ -2715,8 +2705,22 @@ increment_move
 	fixed_t thingtop;
 	floatok = false;
 
-	if (radius < MAXRADIUS/2)
-		radius = MAXRADIUS/2;
+	// This makes sure that there are no freezes from computing extremely small movements.
+	// Originally was MAXRADIUS/2, but that can cause some bad inconsistencies for small players.
+	radius = max(radius, thing->scale);
+
+	// And we also have to prevent Big Large (tm) movements, as those can skip too far
+	// across slopes and cause us to fail step up checks on them when we otherwise shouldn't.
+	radius = min(radius, 16 * thing->scale);
+
+	// (This whole "step" system is flawed; it was OK before, but the addition of slopes has
+	// exposed the problems with doing it like this. The right thing to do would be to use
+	// raycasting for physics to fix colliding in weird order, double-checking collisions,
+	// randomly colliding with slopes instead of going up them, etc. I don't feel like porting
+	// that from RR, as its both a huge sweeping change and still incomplete at the time of
+	// writing. Clamping radius to make our steps more precise will work just fine as long
+	// as you keep all of your crazy intentions to poke any of the other deep-rooted movement
+	// code to yourself. -- Sal 6/5/2023)
 
 	do {
 		if (thing->flags & MF_NOCLIP) {
@@ -2923,6 +2927,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			if (thing->momz <= 0)
 			{
 				thing->standingslope = tmfloorslope;
+				P_SetPitchRollFromSlope(thing, thing->standingslope);
+
 				if (thing->momz == 0 && thing->player && !startingonground)
 					P_PlayerHitFloor(thing->player, true);
 			}
@@ -2934,6 +2940,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			if (thing->momz >= 0)
 			{
 				thing->standingslope = tmceilingslope;
+				P_SetPitchRollFromSlope(thing, thing->standingslope);
+
 				if (thing->momz == 0 && thing->player && !startingonground)
 					P_PlayerHitFloor(thing->player, true);
 			}
@@ -3508,11 +3516,9 @@ static void PTR_GlideClimbTraverse(line_t *li)
 			if (fofline)
 				whichside = 0;
 
-			if (!whichside)
-			{
-				slidemo->player->lastsidehit = checkline->sidenum[whichside];
-				slidemo->player->lastlinehit = (INT16)(checkline - lines);
-			}
+                        // Even if you attach to the second side of a linedef, we want to know the last hit.
+			slidemo->player->lastsidehit = checkline->sidenum[whichside];
+			slidemo->player->lastlinehit = (INT16)(checkline - lines);
 
 			P_Thrust(slidemo, slidemo->angle, FixedMul(5*FRACUNIT, slidemo->scale));
 		}
@@ -3724,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)
 	{
@@ -4002,131 +4010,135 @@ void P_BounceMove(mobj_t *mo)
 	slidemo = mo;
 	hitcount = 0;
 
-retry:
-	if (++hitcount == 3)
-		goto bounceback; // don't loop forever
-
-	if (mo->player)
-	{
-		mmomx = mo->player->rmomx;
-		mmomy = mo->player->rmomy;
-	}
-	else
+	do
 	{
-		mmomx = mo->momx;
-		mmomy = mo->momy;
-	}
+		if (++hitcount == 3)
+			goto bounceback; // don't loop forever
 
-	// trace along the three leading corners
-	if (mo->momx > 0)
-	{
-		leadx = mo->x + mo->radius;
-		trailx = mo->x - mo->radius;
-	}
-	else
-	{
-		leadx = mo->x - mo->radius;
-		trailx = mo->x + mo->radius;
-	}
+		if (mo->player)
+		{
+			mmomx = mo->player->rmomx;
+			mmomy = mo->player->rmomy;
+		}
+		else
+		{
+			mmomx = mo->momx;
+			mmomy = mo->momy;
+		}
 
-	if (mo->momy > 0)
-	{
-		leady = mo->y + mo->radius;
-		traily = mo->y - mo->radius;
-	}
-	else
-	{
-		leady = mo->y - mo->radius;
-		traily = mo->y + mo->radius;
-	}
+		// trace along the three leading corners
+		if (mo->momx > 0)
+		{
+			leadx = mo->x + mo->radius;
+			trailx = mo->x - mo->radius;
+		}
+		else
+		{
+			leadx = mo->x - mo->radius;
+			trailx = mo->x + mo->radius;
+		}
 
-	bestslidefrac = FRACUNIT + 1;
+		if (mo->momy > 0)
+		{
+			leady = mo->y + mo->radius;
+			traily = mo->y - mo->radius;
+		}
+		else
+		{
+			leady = mo->y - mo->radius;
+			traily = mo->y + mo->radius;
+		}
 
-	P_PathTraverse(leadx, leady, leadx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
-	P_PathTraverse(trailx, leady, trailx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
-	P_PathTraverse(leadx, traily, leadx + mmomx, traily + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		bestslidefrac = FRACUNIT + 1;
 
-	// move up to the wall
-	if (bestslidefrac == FRACUNIT + 1)
-	{
-		// the move must have hit the middle, so bounce straight back
-bounceback:
-		if (P_TryMove(mo, mo->x - mmomx, mo->y - mmomy, true))
-		{
-			mo->momx *= -1;
-			mo->momy *= -1;
-			mo->momx = FixedMul(mo->momx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-			mo->momy = FixedMul(mo->momy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+		P_PathTraverse(leadx, leady, leadx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		P_PathTraverse(trailx, leady, trailx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
+		P_PathTraverse(leadx, traily, leadx + mmomx, traily + mmomy, PT_ADDLINES, PTR_SlideTraverse);
 
-			if (mo->player)
+		// move up to the wall
+		if (bestslidefrac == FRACUNIT + 1)
+		{
+			// the move must have hit the middle, so bounce straight back
+bounceback:
+			if (P_TryMove(mo, mo->x - mmomx, mo->y - mmomy, true))
 			{
-				mo->player->cmomx *= -1;
-				mo->player->cmomy *= -1;
-				mo->player->cmomx = FixedMul(mo->player->cmomx,
-					(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-				mo->player->cmomy = FixedMul(mo->player->cmomy,
-					(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				mo->momx *= -1;
+				mo->momy *= -1;
+				mo->momx = FixedMul(mo->momx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				mo->momy = FixedMul(mo->momy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+
+				if (mo->player)
+				{
+					mo->player->cmomx *= -1;
+					mo->player->cmomy *= -1;
+					mo->player->cmomx = FixedMul(mo->player->cmomx,
+						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+					mo->player->cmomy = FixedMul(mo->player->cmomy,
+						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+				}
 			}
+			return;
 		}
-		return;
-	}
 
-	// fudge a bit to make sure it doesn't hit
-	bestslidefrac -= 0x800;
-	if (bestslidefrac > 0)
-	{
-		newx = FixedMul(mmomx, bestslidefrac);
-		newy = FixedMul(mmomy, bestslidefrac);
+		// fudge a bit to make sure it doesn't hit
+		bestslidefrac -= 0x800;
+		if (bestslidefrac > 0)
+		{
+			newx = FixedMul(mmomx, bestslidefrac);
+			newy = FixedMul(mmomy, bestslidefrac);
 
-		if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
-			goto bounceback;
-	}
+			if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
+			{
+				if (P_MobjWasRemoved(mo))
+					return;
+				goto bounceback;
+			}
+		}
 
-	// Now continue along the wall.
-	// First calculate remainder.
-	bestslidefrac = FRACUNIT - bestslidefrac;
+		// Now continue along the wall.
+		// First calculate remainder.
+		bestslidefrac = FRACUNIT - bestslidefrac;
 
-	if (bestslidefrac > FRACUNIT)
-		bestslidefrac = FRACUNIT;
+		if (bestslidefrac > FRACUNIT)
+			bestslidefrac = FRACUNIT;
 
-	if (bestslidefrac <= 0)
-		return;
+		if (bestslidefrac <= 0)
+			return;
 
-	if (mo->type == MT_SHELL)
-	{
-		tmxmove = mmomx;
-		tmymove = mmomy;
-	}
-	else if (mo->type == MT_THROWNBOUNCE)
-	{
-		tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
-		tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
-	}
-	else if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
-	{
-		// Quickly decay speed as it bounces
-		tmxmove = FixedDiv(mmomx, 2*FRACUNIT);
-		tmymove = FixedDiv(mmomy, 2*FRACUNIT);
-	}
-	else
-	{
-		tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-		tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
-	}
+		if (mo->type == MT_SHELL)
+		{
+			tmxmove = mmomx;
+			tmymove = mmomy;
+		}
+		else if (mo->type == MT_THROWNBOUNCE)
+		{
+			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
+			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
+		}
+		else if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
+		{
+			// Quickly decay speed as it bounces
+			tmxmove = FixedDiv(mmomx, 2*FRACUNIT);
+			tmymove = FixedDiv(mmomy, 2*FRACUNIT);
+		}
+		else
+		{
+			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
+		}
 
-	P_HitBounceLine(bestslideline); // clip the moves
+		P_HitBounceLine(bestslideline); // clip the moves
 
-	mo->momx = tmxmove;
-	mo->momy = tmymove;
+		mo->momx = tmxmove;
+		mo->momy = tmymove;
 
-	if (mo->player)
-	{
-		mo->player->cmomx = tmxmove;
-		mo->player->cmomy = tmymove;
+		if (mo->player)
+		{
+			mo->player->cmomx = tmxmove;
+			mo->player->cmomy = tmymove;
+		}
 	}
-
-	if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true))
-		goto retry;
+	while (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true) && !P_MobjWasRemoved(mo));
 }
 
 //
@@ -4231,13 +4243,11 @@ void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 dama
 //  the way it was and call P_CheckSector (? was P_ChangeSector - Graue) again
 //  to undo the changes.
 //
-static boolean crushchange;
-static boolean nofit;
 
 //
 // PIT_ChangeSector
 //
-static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
+static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush, boolean crunch)
 {
 	mobj_t *killer = NULL;
 	//If a thing is both pushable and vulnerable, it doesn't block the crusher because it gets killed.
@@ -4261,11 +4271,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 	if (thing->z + thing->height > thing->ceilingz && thing->z <= thing->ceilingz)
 	{
 		if (immunepushable && thing->z + thing->height > thing->subsector->sector->ceilingheight)
-		{
-			//Thing is a pushable and blocks the moving ceiling
-			nofit = true;
-			return false;
-		}
+			return false; //Thing is a pushable and blocks the moving ceiling
 
 		//Check FOFs in the sector
 		if (thing->subsector->sector->ffloors && (realcrush || immunepushable))
@@ -4277,47 +4283,54 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 
 			for (rover = thing->subsector->sector->ffloors; rover; rover = rover->next)
 			{
-				if (!(((rover->fofflags & FOF_BLOCKPLAYER) && thing->player)
-				|| ((rover->fofflags & FOF_BLOCKOTHERS) && !thing->player)) || !(rover->fofflags & FOF_EXISTS))
+				thinker_t *think;
+
+				if (!(rover->fofflags & FOF_EXISTS))
+					continue;
+				if (thing->player && !(rover->fofflags & FOF_BLOCKPLAYER))
+					continue;
+				if (!thing->player && !(rover->fofflags & FOF_BLOCKOTHERS))
 					continue;
 
 				topheight = *rover->topheight;
 				bottomheight = *rover->bottomheight;
-				//topheight    = P_GetFFloorTopZAt   (rover, thing->x, thing->y);
-				//bottomheight = P_GetFFloorBottomZAt(rover, thing->x, thing->y);
+
+				if (bottomheight > thing->ceilingz)
+					continue;
 
 				delta1 = thing->z - (bottomheight + topheight)/2;
 				delta2 = thingtop - (bottomheight + topheight)/2;
-				if (bottomheight <= thing->ceilingz && abs(delta1) >= abs(delta2))
+				if (abs(delta1) < abs(delta2))
+					continue;
+
+				if (immunepushable)
+					return false; //FOF is blocked by pushable
+
+				if (!realcrush)
+					continue;
+
+				//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
+				for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 				{
-					if (immunepushable)
-					{
-						//FOF is blocked by pushable
-						nofit = true;
-						return false;
-					}
-					else
-					{
-						//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
-						thinker_t *think;
-						crumble_t *crumbler;
+					crumble_t *crumbler;
 
-						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
-						{
-							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
-								continue;
-
-							crumbler = (crumble_t *)think;
-
-							if (crumbler->player && crumbler->player->mo
-								&& crumbler->player->mo != thing
-								&& crumbler->actionsector == thing->subsector->sector
-								&& crumbler->sector == rover->master->frontsector)
-							{
-								killer = crumbler->player->mo;
-							}
-						}
-					}
+					if (think->function.acp1 != (actionf_p1)T_StartCrumble)
+						continue;
+
+					crumbler = (crumble_t *)think;
+
+					if (!crumbler->player)
+						continue;
+					if (!crumbler->player->mo)
+						continue;
+					if (crumbler->player->mo == thing)
+						continue;
+					if (crumbler->actionsector != thing->subsector->sector)
+						continue;
+					if (crumbler->sector != rover->master->frontsector)
+						continue;
+
+					killer = crumbler->player->mo;
 				}
 			}
 		}
@@ -4333,121 +4346,68 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 		}
 	}
 
-	if (realcrush && crushchange)
+	if (realcrush && crunch)
 		P_DamageMobj(thing, NULL, NULL, 1, 0);
 
 	// keep checking (crush other things)
 	return true;
 }
 
-//
-// P_CheckSector
-//
-boolean P_CheckSector(sector_t *sector, boolean crunch)
+static boolean P_CheckSectorPolyObjects(sector_t *sector, boolean realcrush, boolean crunch)
 {
-	msecnode_t *n;
 	size_t i;
 
-	nofit = false;
-	crushchange = crunch;
-
-	// killough 4/4/98: scan list front-to-back until empty or exhausted,
-	// restarting from beginning after each thing is processed. Avoids
-	// crashes, and is sure to examine all things in the sector, and only
-	// the things which are in the sector, until a steady-state is reached.
-	// Things can arbitrarily be inserted and removed and it won't mess up.
-	//
-	// killough 4/7/98: simplified to avoid using complicated counter
-
-
-	// First, let's see if anything will keep it from crushing.
-
 	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
 	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
 	validcount++;
 
 	for (i = 0; i < sector->linecount; i++)
 	{
-		if (sector->lines[i]->polyobj)
+		INT32 x, y;
+		polyobj_t *po = sector->lines[i]->polyobj;
+
+		if (!po)
+			continue;
+		if (po->validcount == validcount)
+			continue; // skip if already checked
+		if (!(po->flags & POF_SOLID))
+			continue;
+		if (po->lines[0]->backsector != sector) // Make sure you're currently checking the control sector
+			continue;
+
+		po->validcount = validcount;
+
+		for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
 		{
-			polyobj_t *po = sector->lines[i]->polyobj;
-			if (po->validcount == validcount)
-				continue; // skip if already checked
-			if (!(po->flags & POF_SOLID))
-				continue;
-			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
+			for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
 			{
-				INT32 x, y;
-				po->validcount = validcount;
-
-				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
-				{
-					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
-					{
-						mobj_t *mo;
+				mobj_t *mo;
 
-						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
-							continue;
+				if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+					continue;
 
-						mo = blocklinks[y * bmapwidth + x];
+				mo = blocklinks[y * bmapwidth + x];
 
-						for (; mo; mo = mo->bnext)
-						{
-							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+				for (; mo; mo = mo->bnext)
+				{
+					// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
 
-							if (!P_MobjInsidePolyobj(po, mo))
-								continue;
+					if (!P_MobjInsidePolyobj(po, mo))
+						continue;
 
-							if (!PIT_ChangeSector(mo, false))
-							{
-								nofit = true;
-								return nofit;
-							}
-						}
-					}
+					if (!PIT_ChangeSector(mo, realcrush, crunch) && !realcrush)
+						return false;
 				}
 			}
 		}
 	}
 
-	if (sector->numattached)
-	{
-		sector_t *sec;
-		for (i = 0; i < sector->numattached; i++)
-		{
-			sec = &sectors[sector->attached[i]];
-			for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				n->visited = false;
-
-			sec->moved = true;
-
-			P_RecalcPrecipInSector(sec);
-
-			if (!sector->attachedsolid[i])
-				continue;
-
-			do
-			{
-				for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				if (!n->visited)
-				{
-					n->visited = true;
-					if (!(n->m_thing->flags & MF_NOBLOCKMAP))
-					{
-						if (!PIT_ChangeSector(n->m_thing, false))
-						{
-							nofit = true;
-							return nofit;
-						}
-					}
-					break;
-				}
-			} while (n);
-		}
-	}
+	return true;
+}
 
-	// Mark all things invalid
-	sector->moved = true;
+static boolean P_CheckTouchingThinglist(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	msecnode_t *n;
 
 	for (n = sector->touching_thinglist; n; n = n->m_thinglist_next)
 		n->visited = false;
@@ -4455,122 +4415,86 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 	do
 	{
 		for (n = sector->touching_thinglist; n; n = n->m_thinglist_next) // go through list
-			if (!n->visited) // unprocessed thing found
-			{
-				n->visited = true; // mark thing as processed
-				if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
-				{
-					if (!PIT_ChangeSector(n->m_thing, false)) // process it
-					{
-						nofit = true;
-						return nofit;
-					}
-				}
-				break; // exit and start over
-			}
-	} while (n); // repeat from scratch until all things left are marked valid
-
-	// Nothing blocked us, so lets crush for real!
+		{
+			if (n->visited)
+				continue;
 
-	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
-	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
-	validcount++;
+			n->visited = true; // mark thing as processed
 
-	for (i = 0; i < sector->linecount; i++)
-	{
-		if (sector->lines[i]->polyobj)
-		{
-			polyobj_t *po = sector->lines[i]->polyobj;
-			if (po->validcount == validcount)
-				continue; // skip if already checked
-			if (!(po->flags & POF_SOLID))
+			if (n->m_thing->flags & MF_NOBLOCKMAP) //jff 4/7/98 don't do these
 				continue;
-			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
-			{
-				INT32 x, y;
-				po->validcount = validcount;
 
-				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
-				{
-					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
-					{
-						mobj_t *mo;
+			if (!PIT_ChangeSector(n->m_thing, realcrush, crunch) && !realcrush) // process it
+				return false;
 
-						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
-							continue;
+			break; // exit and start over
+		}
+	} while (n); // repeat from scratch until all things left are marked valid
 
-						mo = blocklinks[y * bmapwidth + x];
+	return true;
+}
 
-						for (; mo; mo = mo->bnext)
-						{
-							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+static boolean P_CheckSectorFFloors(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	sector_t *sec;
+	size_t i;
 
-							if (!P_MobjInsidePolyobj(po, mo))
-								continue;
+	if (!sector->numattached)
+		return true;
 
-							PIT_ChangeSector(mo, true);
-							return nofit;
-						}
-					}
-				}
-			}
-		}
-	}
-	if (sector->numattached)
+	for (i = 0; i < sector->numattached; i++)
 	{
-		sector_t *sec;
-		for (i = 0; i < sector->numattached; i++)
-		{
-			sec = &sectors[sector->attached[i]];
-			for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				n->visited = false;
+		sec = &sectors[sector->attached[i]];
 
-			sec->moved = true;
+		sec->moved = true;
 
-			P_RecalcPrecipInSector(sec);
+		P_RecalcPrecipInSector(sec);
 
-			if (!sector->attachedsolid[i])
-				continue;
+		if (!sector->attachedsolid[i])
+			continue;
 
-			do
-			{
-				for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				if (!n->visited)
-				{
-					n->visited = true;
-					if (!(n->m_thing->flags & MF_NOBLOCKMAP))
-					{
-						PIT_ChangeSector(n->m_thing, true);
-						return nofit;
-					}
-					break;
-				}
-			} while (n);
-		}
+		if (!P_CheckTouchingThinglist(sec, realcrush, crunch))
+			return false;
 	}
 
+	return true;
+}
+
+static boolean P_CheckSectorHelper(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	if (!P_CheckSectorPolyObjects(sector, realcrush, crunch))
+		return false;
+
+	if (!P_CheckSectorFFloors(sector, realcrush, crunch))
+		return false;
+
 	// Mark all things invalid
 	sector->moved = true;
 
-	for (n = sector->touching_thinglist; n; n = n->m_thinglist_next)
-		n->visited = false;
+	return P_CheckTouchingThinglist(sector, realcrush, crunch);
+}
 
-	do
-	{
-		for (n = sector->touching_thinglist; n; n = n->m_thinglist_next) // go through list
-			if (!n->visited) // unprocessed thing found
-			{
-				n->visited = true; // mark thing as processed
-				if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
-				{
-					PIT_ChangeSector(n->m_thing, true); // process it
-					return nofit;
-				}
-				break; // exit and start over
-			}
-	} while (n); // repeat from scratch until all things left are marked valid
+//
+// P_CheckSector
+//
+boolean P_CheckSector(sector_t *sector, boolean crunch)
+{
+	// killough 4/4/98: scan list front-to-back until empty or exhausted,
+	// restarting from beginning after each thing is processed. Avoids
+	// crashes, and is sure to examine all things in the sector, and only
+	// the things which are in the sector, until a steady-state is reached.
+	// Things can arbitrarily be inserted and removed and it won't mess up.
+	//
+	// killough 4/7/98: simplified to avoid using complicated counter
 
-	return nofit;
+	// First, let's see if anything will keep it from crushing.
+	if (!P_CheckSectorHelper(sector, false, crunch))
+		return true;
+
+	// Nothing blocked us, so lets crush for real!
+	P_CheckSectorHelper(sector, true, crunch);
+
+	return false;
 }
 
 /*
diff --git a/src/p_maputl.c b/src/p_maputl.c
index b6a3207308f3c5c7af8b434e50c2920d09b8f859..e36d5fd724fd152346b34b8ec047277e25735ecb 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -509,26 +509,26 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				// on non-solid polyobjects should NEVER happen in the future
 				if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT)) {
 					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
-						texbottom = back->floorheight + side->rowoffset;
-						textop = back->ceilingheight + side->rowoffset;
+						texbottom = back->floorheight + side->rowoffset + side->offsety_mid;
+						textop = back->ceilingheight + side->rowoffset + side->offsety_mid;
 					} else if (linedef->flags & ML_MIDTEX) {
-						texbottom = back->floorheight + side->rowoffset;
+						texbottom = back->floorheight + side->rowoffset + side->offsety_mid;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
-						textop = back->ceilingheight + side->rowoffset;
+						textop = back->ceilingheight + side->rowoffset + side->offsety_mid;
 						texbottom = textop - texheight*(side->repeatcnt+1);
 					}
 				} else
 #endif
 				{
 					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
-						texbottom = openbottom + side->rowoffset;
-						textop = opentop + side->rowoffset;
+						texbottom = openbottom + side->rowoffset + side->offsety_mid;
+						textop = opentop + side->rowoffset + side->offsety_mid;
 					} else if (linedef->flags & ML_MIDPEG) {
-						texbottom = openbottom + side->rowoffset;
+						texbottom = openbottom + side->rowoffset + side->offsety_mid;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
-						textop = opentop + side->rowoffset;
+						textop = opentop + side->rowoffset + side->offsety_mid;
 						texbottom = textop - texheight*(side->repeatcnt+1);
 					}
 				}
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9e7110de3f79ab15dc4866f531a216380c3b0549..686f08478e8d7006e3d01c7a69ea3f38b4e1534a 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1551,6 +1551,31 @@ void P_CheckGravity(mobj_t *mo, boolean affect)
 	}
 }
 
+//
+// P_SetPitchRollFromSlope
+//
+void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope)
+{
+#if 0
+	if (slope)
+	{
+		fixed_t tempz = slope->normal.z;
+		fixed_t tempy = slope->normal.y;
+		fixed_t tempx = slope->normal.x;
+
+		mo->pitch = R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx);
+		mo->roll = R_PointToAngle2(0, 0, tempz, tempy);
+	}
+	else
+	{
+		mo->pitch = mo->roll = 0;
+	}
+#else
+	(void)mo;
+	(void)slope;
+#endif
+}
+
 #define STOPSPEED (FRACUNIT)
 
 //
@@ -1985,6 +2010,7 @@ void P_XYMovement(mobj_t *mo)
 			// Now compare the Zs of the different quantizations
 			if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) { // Allow for a bit of sticking - this value can be adjusted later
 				mo->standingslope = oldslope;
+				P_SetPitchRollFromSlope(mo, mo->standingslope);
 				P_SlopeLaunch(mo);
 
 				//CONS_Printf("launched off of slope - ");
@@ -2557,6 +2583,7 @@ boolean P_ZMovement(mobj_t *mo)
 		if (((mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope) && (mo->type != MT_STEAM))
 		{
 			mo->standingslope = (mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope;
+			P_SetPitchRollFromSlope(mo, mo->standingslope);
 			P_ReverseQuantizeMomentumToSlope(&mom, mo->standingslope);
 		}
 
@@ -3139,7 +3166,8 @@ boolean P_SceneryZMovement(mobj_t *mo)
 
 	if (P_CheckDeathPitCollide(mo))
 	{
-		P_RemoveMobj(mo);
+		if (mo->type != MT_GHOST)  // ghosts play death animations instead, so don't remove them
+			P_RemoveMobj(mo);
 		return false;
 	}
 
@@ -3660,7 +3688,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
 			{
@@ -3691,7 +3719,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
@@ -6765,7 +6793,8 @@ void P_RunShields(void)
 	// run shields
 	for (i = 0; i < numshields; i++)
 	{
-		P_ShieldLook(shields[i], shields[i]->threshold);
+		if (!P_MobjWasRemoved(shields[i]))
+			P_ShieldLook(shields[i], shields[i]->threshold);
 		P_SetTarget(&shields[i], NULL);
 	}
 	numshields = 0;
@@ -6904,6 +6933,13 @@ static void P_AddOverlay(mobj_t *thing)
 static void P_RemoveOverlay(mobj_t *thing)
 {
 	mobj_t *mo;
+	if (overlaycap == thing)
+	{
+		P_SetTarget(&overlaycap, thing->hnext);
+		P_SetTarget(&thing->hnext, NULL);
+		return;
+	}
+
 	for (mo = overlaycap; mo; mo = mo->hnext)
 	{
 		if (mo->hnext != thing)
@@ -7915,11 +7951,6 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 			return;
 		}
 
-		if (!camera.chase)
-			mobj->flags2 |= MF2_DONTDRAW;
-		else
-			mobj->flags2 &= ~MF2_DONTDRAW;
-
 		P_UnsetThingPosition(mobj);
 		{
 			fixed_t radius = FixedMul(10*mobj->info->speed, mobj->target->scale);
@@ -9196,7 +9227,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;
@@ -9594,7 +9625,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 	case MT_BOSSFLYPOINT:
 		return false;
 	case MT_NIGHTSCORE:
-		mobj->color = (UINT16)(leveltime % SKINCOLOR_WHITE);
+		mobj->color = linkColor[mobj->extravalue2][(leveltime + mobj->extravalue1) % NUMLINKCOLORS];
 		break;
 	case MT_JETFUME1:
 		if (!P_JetFume1Think(mobj))
@@ -9690,7 +9721,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		P_RingThinker(mobj);
 		if (mobj->flags2 & MF2_NIGHTSPULL)
 			P_NightsItemChase(mobj);
-		else
+		else if (mobj->type != MT_BOMBSPHERE) // prevent shields from attracting bomb spheres
 			A_AttractChase(mobj);
 		return false;
 		// Flung items
@@ -9708,6 +9739,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			A_AttractChase(mobj);
 		break;
 	case MT_EMBLEM:
+		if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1))
+			mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
+		else
+			mobj->frame &= ~FF_TRANSMASK;
+
 		if (mobj->flags2 & MF2_NIGHTSPULL)
 			P_NightsItemChase(mobj);
 		break;
@@ -9832,9 +9868,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		break;
 	case MT_MINUS:
 		if (P_IsObjectOnGround(mobj))
-			mobj->rollangle = 0;
+			mobj->spriteroll = 0;
 		else
-			mobj->rollangle = R_PointToAngle2(0, 0, P_MobjFlip(mobj)*mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
+			mobj->spriteroll = R_PointToAngle2(0, 0, P_MobjFlip(mobj)*mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
 		break;
 	case MT_PUSH:
 		P_PointPushThink(mobj);
@@ -10527,6 +10563,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:
@@ -10615,6 +10674,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 
 	mobj->reactiontime = info->reactiontime;
 
+	mobj->dispoffset = info->dispoffset;
+
 	mobj->lastlook = -1; // stuff moved in P_enemy.P_LookForPlayer
 
 	// do not set the state with P_SetMobjState,
@@ -11142,12 +11203,6 @@ void P_RemoveMobj(mobj_t *mobj)
 
 	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
 
-	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
-#ifdef SCRAMBLE_REMOVED
-	// Invalidate mobj_t data to cause crashes if accessed!
-	memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
-#endif
-
 	R_RemoveMobjInterpolator(mobj);
 
 	// free block
@@ -11166,6 +11221,17 @@ void P_RemoveMobj(mobj_t *mobj)
 	}
 
 	P_RemoveThinker((thinker_t *)mobj);
+
+#ifdef PARANOIA
+	// Saved to avoid being scrambled like below...
+	mobj->thinker.debug_mobjtype = mobj->type;
+#endif
+
+	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
+#ifdef SCRAMBLE_REMOVED
+	// Invalidate mobj_t data to cause crashes if accessed!
+	memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
+#endif
 }
 
 // This does not need to be added to Lua.
@@ -11797,7 +11863,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);
 
@@ -11807,9 +11873,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)
@@ -11817,6 +11883,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)
 	{
@@ -11872,7 +11939,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;
@@ -11880,7 +11947,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)
@@ -11995,12 +12062,8 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 
 		break;
 	case MT_EMBLEM:
-		if (netgame || multiplayer)
-			return false; // Single player
-
-		if (modifiedgame && !savemoddata)
-			return false; // No cheating!!
-
+		if (!G_CoopGametype())
+			return false; // Gametype's not right
 		break;
 	default:
 		break;
@@ -12144,7 +12207,6 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 	INT32 j;
 	emblem_t* emblem = M_GetLevelEmblems(gamemap);
 	skincolornum_t emcolor;
-	boolean validEmblem = true;
 
 	while (emblem)
 	{
@@ -12169,42 +12231,19 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 	emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
 	mobj->color = (UINT16)emcolor;
 
-	validEmblem = !emblemlocations[j].collected;
+	mobj->frame &= ~FF_TRANSMASK;
 
-	if (emblemlocations[j].type == ET_SKIN)
+	if (emblemlocations[j].type == ET_GLOBAL)
 	{
-		INT32 skinnum = M_EmblemSkinNum(&emblemlocations[j]);
-
-		if (players[0].skin != skinnum)
+		mobj->reactiontime = emblemlocations[j].var;
+		if (emblemlocations[j].var & GE_NIGHTSITEM)
 		{
-			validEmblem = false;
+			mobj->flags |= MF_NIGHTSITEM;
+			mobj->flags &= ~MF_SPECIAL;
+			mobj->flags2 |= MF2_DONTDRAW;
 		}
 	}
 
-	if (validEmblem == false)
-	{
-		P_UnsetThingPosition(mobj);
-		mobj->flags |= MF_NOCLIP;
-		mobj->flags &= ~MF_SPECIAL;
-		mobj->flags |= MF_NOBLOCKMAP;
-		mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
-		P_SetThingPosition(mobj);
-	}
-	else
-	{
-		mobj->frame &= ~FF_TRANSMASK;
-
-		if (emblemlocations[j].type == ET_GLOBAL)
-		{
-			mobj->reactiontime = emblemlocations[j].var;
-			if (emblemlocations[j].var & GE_NIGHTSITEM)
-			{
-				mobj->flags |= MF_NIGHTSITEM;
-				mobj->flags &= ~MF_SPECIAL;
-				mobj->flags2 |= MF2_DONTDRAW;
-			}
-		}
-	}
 	return true;
 }
 
@@ -12577,7 +12616,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)
 	{
@@ -13316,6 +13355,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;
 
@@ -13402,7 +13444,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;
@@ -13529,7 +13571,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++)
 	{
@@ -13588,7 +13630,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++)
 	{
@@ -13700,7 +13742,6 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
 		UINT8 numitemtypes;
 		if (!udmf)
 			return;
-		CONS_Printf("Itemstring: %s\n", mthing->stringargs[0]);
 		P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
 		P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, bonustime);
 		return;
@@ -14158,6 +14199,13 @@ mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zo
 		newmobj->old_angle = mobj->old_angle;
 	}
 
+	newmobj->old_pitch2 = mobj->old_pitch2;
+	newmobj->old_pitch = mobj->old_pitch;
+	newmobj->old_roll2 = mobj->old_roll2;
+	newmobj->old_roll = mobj->old_roll;
+	newmobj->old_spriteroll2 = mobj->old_spriteroll2;
+	newmobj->old_spriteroll = mobj->old_spriteroll;
+
 	newmobj->old_scale2 = mobj->old_scale2;
 	newmobj->old_scale = mobj->old_scale;
 	newmobj->old_spritexscale = mobj->old_spritexscale;
diff --git a/src/p_mobj.h b/src/p_mobj.h
index a839eaba6784a4b325e9df0889ba88a87a712639..a980691beb8b296b4fd16415141e4f9e5f484af8 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -122,7 +122,7 @@ typedef enum
 	MF_AMBIENT          = 1<<10,
 	// Slide this object when it hits a wall.
 	MF_SLIDEME          = 1<<11,
-	// Player cheat.
+	// Don't collide with walls or solid objects. Two MF_NOCLIP objects can't touch each other at all!
 	MF_NOCLIP           = 1<<12,
 	// Allow moves to any height, no gravity. For active floaters.
 	MF_FLOAT            = 1<<13,
@@ -292,7 +292,7 @@ typedef struct mobj_s
 	angle_t angle, pitch, roll; // orientation
 	angle_t old_angle, old_pitch, old_roll; // orientation interpolation
 	angle_t old_angle2, old_pitch2, old_roll2;
-	angle_t rollangle;
+	angle_t spriteroll, old_spriteroll, old_spriteroll2;
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
@@ -334,6 +334,8 @@ typedef struct mobj_s
 	// Player and mobj sprites in multiplayer modes are modified
 	//  using an internal color lookup table for re-indexing.
 	UINT16 color; // This replaces MF_TRANSLATION. Use 0 for default (no translation).
+	struct player_s *drawonlyforplayer; // If set, hides the mobj for everyone except this player and their spectators
+	struct mobj_s *dontdrawforviewmobj; // If set, hides the mobj if dontdrawforviewmobj is the current camera (first-person player or awayviewmobj)
 
 	// Interaction info, by BLOCKMAP.
 	// Links in blocks (if needed).
@@ -399,6 +401,7 @@ typedef struct mobj_s
 	boolean colorized; // Whether the mobj uses the rainbow colormap
 	boolean mirrored; // The object's rotations will be mirrored left to right, e.g., see frame AL from the right and AR from the left
 	fixed_t shadowscale; // If this object casts a shadow, and the size relative to radius
+	INT32 dispoffset; // copy of info->dispoffset, so mobjs can be sorted independently of their type
 
 	// WARNING: New fields must be added separately to savegame and Lua.
 } mobj_t;
@@ -428,7 +431,7 @@ typedef struct precipmobj_s
 	angle_t angle, pitch, roll; // orientation
 	angle_t old_angle, old_pitch, old_roll; // orientation interpolation
 	angle_t old_angle2, old_pitch2, old_roll2;
-	angle_t rollangle;
+	angle_t spriteroll, old_spriteroll, old_spriteroll2;
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
@@ -488,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_saveg.c b/src/p_saveg.c
index 880466d3726c96febec4d5e06511369946f1d3f6..faecd13770b3d81b992017af51b4b663487aa50d 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -47,6 +47,7 @@ UINT8 *save_p;
 #define ARCHIVEBLOCK_POBJS    0x7F928546
 #define ARCHIVEBLOCK_THINKERS 0x7F37037C
 #define ARCHIVEBLOCK_SPECIALS 0x7F228378
+#define ARCHIVEBLOCK_EMBLEMS  0x7F4A5445
 
 // Note: This cannot be bigger
 // than an UINT16
@@ -168,6 +169,7 @@ static void P_NetArchivePlayers(void)
 		WRITEUINT8(save_p, players[i].playerstate);
 		WRITEUINT32(save_p, players[i].pflags);
 		WRITEUINT8(save_p, players[i].panim);
+		WRITEUINT8(save_p, players[i].stronganim);
 		WRITEUINT8(save_p, players[i].spectator);
 
 		WRITEUINT16(save_p, players[i].flashpal);
@@ -177,6 +179,7 @@ static void P_NetArchivePlayers(void)
 		WRITEINT32(save_p, players[i].skin);
 		WRITEUINT32(save_p, players[i].availabilities);
 		WRITEUINT32(save_p, players[i].score);
+		WRITEUINT32(save_p, players[i].recordscore);
 		WRITEFIXED(save_p, players[i].dashspeed);
 		WRITESINT8(save_p, players[i].lives);
 		WRITESINT8(save_p, players[i].continues);
@@ -395,6 +398,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].playerstate = READUINT8(save_p);
 		players[i].pflags = READUINT32(save_p);
 		players[i].panim = READUINT8(save_p);
+		players[i].stronganim = READUINT8(save_p);
 		players[i].spectator = READUINT8(save_p);
 
 		players[i].flashpal = READUINT16(save_p);
@@ -404,6 +408,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].skin = READINT32(save_p);
 		players[i].availabilities = READUINT32(save_p);
 		players[i].score = READUINT32(save_p);
+		players[i].recordscore = READUINT32(save_p);
 		players[i].dashspeed = READFIXED(save_p); // dashing speed
 		players[i].lives = READSINT8(save_p);
 		players[i].continues = READSINT8(save_p); // continues that player has acquired
@@ -1022,17 +1027,17 @@ static void ArchiveSectors(void)
 		if (ss->special != spawnss->special)
 			diff |= SD_SPECIAL;
 
-		if (ss->floor_xoffs != spawnss->floor_xoffs)
+		if (ss->floorxoffset != spawnss->floorxoffset)
 			diff2 |= SD_FXOFFS;
-		if (ss->floor_yoffs != spawnss->floor_yoffs)
+		if (ss->flooryoffset != spawnss->flooryoffset)
 			diff2 |= SD_FYOFFS;
-		if (ss->ceiling_xoffs != spawnss->ceiling_xoffs)
+		if (ss->ceilingxoffset != spawnss->ceilingxoffset)
 			diff2 |= SD_CXOFFS;
-		if (ss->ceiling_yoffs != spawnss->ceiling_yoffs)
+		if (ss->ceilingyoffset != spawnss->ceilingyoffset)
 			diff2 |= SD_CYOFFS;
-		if (ss->floorpic_angle != spawnss->floorpic_angle)
+		if (ss->floorangle != spawnss->floorangle)
 			diff2 |= SD_FLOORANG;
-		if (ss->ceilingpic_angle != spawnss->ceilingpic_angle)
+		if (ss->ceilingangle != spawnss->ceilingangle)
 			diff2 |= SD_CEILANG;
 
 		if (!Tag_Compare(&ss->tags, &spawnss->tags))
@@ -1095,17 +1100,17 @@ static void ArchiveSectors(void)
 			if (diff & SD_SPECIAL)
 				WRITEINT16(save_p, ss->special);
 			if (diff2 & SD_FXOFFS)
-				WRITEFIXED(save_p, ss->floor_xoffs);
+				WRITEFIXED(save_p, ss->floorxoffset);
 			if (diff2 & SD_FYOFFS)
-				WRITEFIXED(save_p, ss->floor_yoffs);
+				WRITEFIXED(save_p, ss->flooryoffset);
 			if (diff2 & SD_CXOFFS)
-				WRITEFIXED(save_p, ss->ceiling_xoffs);
+				WRITEFIXED(save_p, ss->ceilingxoffset);
 			if (diff2 & SD_CYOFFS)
-				WRITEFIXED(save_p, ss->ceiling_yoffs);
+				WRITEFIXED(save_p, ss->ceilingyoffset);
 			if (diff2 & SD_FLOORANG)
-				WRITEANGLE(save_p, ss->floorpic_angle);
+				WRITEANGLE(save_p, ss->floorangle);
 			if (diff2 & SD_CEILANG)
-				WRITEANGLE(save_p, ss->ceilingpic_angle);
+				WRITEANGLE(save_p, ss->ceilingangle);
 			if (diff2 & SD_TAG)
 			{
 				WRITEUINT32(save_p, ss->tags.count);
@@ -1196,17 +1201,17 @@ static void UnArchiveSectors(void)
 			sectors[i].special = READINT16(save_p);
 
 		if (diff2 & SD_FXOFFS)
-			sectors[i].floor_xoffs = READFIXED(save_p);
+			sectors[i].floorxoffset = READFIXED(save_p);
 		if (diff2 & SD_FYOFFS)
-			sectors[i].floor_yoffs = READFIXED(save_p);
+			sectors[i].flooryoffset = READFIXED(save_p);
 		if (diff2 & SD_CXOFFS)
-			sectors[i].ceiling_xoffs = READFIXED(save_p);
+			sectors[i].ceilingxoffset = READFIXED(save_p);
 		if (diff2 & SD_CYOFFS)
-			sectors[i].ceiling_yoffs = READFIXED(save_p);
+			sectors[i].ceilingyoffset = READFIXED(save_p);
 		if (diff2 & SD_FLOORANG)
-			sectors[i].floorpic_angle  = READANGLE(save_p);
+			sectors[i].floorangle  = READANGLE(save_p);
 		if (diff2 & SD_CEILANG)
-			sectors[i].ceilingpic_angle = READANGLE(save_p);
+			sectors[i].ceilingangle = READANGLE(save_p);
 		if (diff2 & SD_TAG)
 		{
 			size_t ncount = READUINT32(save_p);
@@ -1548,29 +1553,32 @@ typedef enum
 
 typedef enum
 {
-	MD2_CUSVAL      = 1,
-	MD2_CVMEM       = 1<<1,
-	MD2_SKIN        = 1<<2,
-	MD2_COLOR       = 1<<3,
-	MD2_SCALESPEED  = 1<<4,
-	MD2_EXTVAL1     = 1<<5,
-	MD2_EXTVAL2     = 1<<6,
-	MD2_HNEXT       = 1<<7,
-	MD2_HPREV       = 1<<8,
-	MD2_FLOORROVER  = 1<<9,
-	MD2_CEILINGROVER = 1<<10,
-	MD2_SLOPE        = 1<<11,
-	MD2_COLORIZED    = 1<<12,
-	MD2_MIRRORED     = 1<<13,
-	MD2_ROLLANGLE    = 1<<14,
-	MD2_SHADOWSCALE  = 1<<15,
-	MD2_RENDERFLAGS  = 1<<16,
-	MD2_BLENDMODE    = 1<<17,
-	MD2_SPRITEXSCALE = 1<<18,
-	MD2_SPRITEYSCALE = 1<<19,
-	MD2_SPRITEXOFFSET = 1<<20,
-	MD2_SPRITEYOFFSET = 1<<21,
-	MD2_FLOORSPRITESLOPE = 1<<22,
+	MD2_CUSVAL              = 1,
+	MD2_CVMEM               = 1<<1,
+	MD2_SKIN                = 1<<2,
+	MD2_COLOR               = 1<<3,
+	MD2_SCALESPEED          = 1<<4,
+	MD2_EXTVAL1             = 1<<5,
+	MD2_EXTVAL2             = 1<<6,
+	MD2_HNEXT               = 1<<7,
+	MD2_HPREV               = 1<<8,
+	MD2_FLOORROVER          = 1<<9,
+	MD2_CEILINGROVER        = 1<<10,
+	MD2_SLOPE               = 1<<11,
+	MD2_COLORIZED           = 1<<12,
+	MD2_MIRRORED            = 1<<13,
+	MD2_SPRITEROLL          = 1<<14,
+	MD2_SHADOWSCALE         = 1<<15,
+	MD2_RENDERFLAGS         = 1<<16,
+	MD2_BLENDMODE           = 1<<17,
+	MD2_SPRITEXSCALE        = 1<<18,
+	MD2_SPRITEYSCALE        = 1<<19,
+	MD2_SPRITEXOFFSET       = 1<<20,
+	MD2_SPRITEYOFFSET       = 1<<21,
+	MD2_FLOORSPRITESLOPE    = 1<<22,
+	MD2_DISPOFFSET          = 1<<23,
+	MD2_DRAWONLYFORPLAYER   = 1<<24,
+	MD2_DONTDRAWFORVIEWMOBJ = 1<<25
 } mobj_diff2_t;
 
 typedef enum
@@ -1779,8 +1787,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_COLORIZED;
 	if (mobj->mirrored)
 		diff2 |= MD2_MIRRORED;
-	if (mobj->rollangle)
-		diff2 |= MD2_ROLLANGLE;
+	if (mobj->spriteroll)
+		diff2 |= MD2_SPRITEROLL;
 	if (mobj->shadowscale)
 		diff2 |= MD2_SHADOWSCALE;
 	if (mobj->renderflags)
@@ -1805,6 +1813,12 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		|| (slope->normal.z != FRACUNIT))
 			diff2 |= MD2_FLOORSPRITESLOPE;
 	}
+	if (mobj->drawonlyforplayer)
+		diff2 |= MD2_DRAWONLYFORPLAYER;
+	if (mobj->dontdrawforviewmobj)
+		diff2 |= MD2_DONTDRAWFORVIEWMOBJ;
+	if (mobj->dispoffset != mobj->info->dispoffset)
+		diff2 |= MD2_DISPOFFSET;
 
 	if (diff2 != 0)
 		diff |= MD_MORE;
@@ -1945,8 +1959,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEUINT8(save_p, mobj->colorized);
 	if (diff2 & MD2_MIRRORED)
 		WRITEUINT8(save_p, mobj->mirrored);
-	if (diff2 & MD2_ROLLANGLE)
-		WRITEANGLE(save_p, mobj->rollangle);
+	if (diff2 & MD2_SPRITEROLL)
+		WRITEANGLE(save_p, mobj->spriteroll);
 	if (diff2 & MD2_SHADOWSCALE)
 		WRITEFIXED(save_p, mobj->shadowscale);
 	if (diff2 & MD2_RENDERFLAGS)
@@ -1980,6 +1994,12 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEFIXED(save_p, slope->normal.y);
 		WRITEFIXED(save_p, slope->normal.z);
 	}
+	if (diff2 & MD2_DRAWONLYFORPLAYER)
+		WRITEUINT8(save_p, mobj->drawonlyforplayer-players);
+	if (diff2 & MD2_DONTDRAWFORVIEWMOBJ)
+		WRITEUINT32(save_p, mobj->dontdrawforviewmobj->mobjnum);
+	if (diff2 & MD2_DISPOFFSET)
+		WRITEINT32(save_p, mobj->dispoffset);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -2696,8 +2716,8 @@ static void P_NetArchiveThinkers(void)
 				continue;
 			}
 #ifdef PARANOIA
-			else if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed) // wait garbage collection
-				I_Error("unknown thinker type %p", th->function.acp1);
+			else
+				I_Assert(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed); // wait garbage collection
 #endif
 		}
 
@@ -2993,8 +3013,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->colorized = READUINT8(save_p);
 	if (diff2 & MD2_MIRRORED)
 		mobj->mirrored = READUINT8(save_p);
-	if (diff2 & MD2_ROLLANGLE)
-		mobj->rollangle = READANGLE(save_p);
+	if (diff2 & MD2_SPRITEROLL)
+		mobj->spriteroll = READANGLE(save_p);
 	if (diff2 & MD2_SHADOWSCALE)
 		mobj->shadowscale = READFIXED(save_p);
 	if (diff2 & MD2_RENDERFLAGS)
@@ -3034,6 +3054,14 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		slope->normal.y = READFIXED(save_p);
 		slope->normal.z = READFIXED(save_p);
 	}
+	if (diff2 & MD2_DRAWONLYFORPLAYER)
+		mobj->drawonlyforplayer = &players[READUINT8(save_p)];
+	if (diff2 & MD2_DONTDRAWFORVIEWMOBJ)
+		mobj->dontdrawforviewmobj = (mobj_t *)(size_t)READUINT32(save_p);
+	if (diff2 & MD2_DISPOFFSET)
+		mobj->dispoffset = READINT32(save_p);
+	else
+		mobj->dispoffset = mobj->info->dispoffset;
 
 	if (diff & MD_REDFLAG)
 	{
@@ -4049,6 +4077,13 @@ static void P_RelinkPointers(void)
 		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
 			continue;
 
+		if (mobj->dontdrawforviewmobj)
+		{
+			temp = (UINT32)(size_t)mobj->dontdrawforviewmobj;
+			mobj->dontdrawforviewmobj = NULL;
+			if (!P_SetTarget(&mobj->dontdrawforviewmobj, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "dontdrawforviewmobj not found on %d\n", mobj->type);
+		}
 		if (mobj->tracer)
 		{
 			temp = (UINT32)(size_t)mobj->tracer;
@@ -4267,7 +4302,11 @@ static void P_NetArchiveMisc(boolean resending)
 	if (resending)
 		WRITEUINT32(save_p, gametic);
 	WRITEINT16(save_p, gamemap);
-	WRITEINT16(save_p, gamestate);
+
+	if (gamestate != GS_LEVEL)
+		WRITEINT16(save_p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers
+	else
+		WRITEINT16(save_p, gamestate);
 	WRITEINT16(save_p, gametype);
 
 	{
@@ -4435,6 +4474,267 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 	return true;
 }
 
+static inline void P_NetArchiveEmblems(void)
+{
+	gamedata_t *data = serverGamedata;
+	INT32 i, j;
+	UINT8 btemp;
+	INT32 curmare;
+
+	WRITEUINT32(save_p, ARCHIVEBLOCK_EMBLEMS);
+
+	// These should be synchronized before savegame loading by the wad files being the same anyway,
+	// but just in case, for now, we'll leave them here for testing. It would be very bad if they mismatch.
+	WRITEUINT8(save_p, (UINT8)savemoddata);
+	WRITEINT32(save_p, numemblems);
+	WRITEINT32(save_p, numextraemblems);
+
+	// The rest of this is lifted straight from G_SaveGameData in g_game.c
+	// TODO: Optimize this to only send information about emblems, unlocks, etc. which actually exist
+	//       There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same.
+
+	WRITEUINT32(save_p, data->totalplaytime);
+
+	// TODO put another cipher on these things? meh, I don't care...
+	for (i = 0; i < NUMMAPS; i++)
+		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
+
+	// To save space, use one bit per collected/achieved/unlocked flag
+	for (i = 0; i < MAXEMBLEMS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
+			btemp |= (data->collected[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXEXTRAEMBLEMS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
+			btemp |= (data->extraCollected[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXUNLOCKABLES;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
+			btemp |= (data->unlocked[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXCONDITIONSETS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
+			btemp |= (data->achieved[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+
+	WRITEUINT32(save_p, data->timesBeaten);
+	WRITEUINT32(save_p, data->timesBeatenWithEmeralds);
+	WRITEUINT32(save_p, data->timesBeatenUltimate);
+
+	// Main records
+	for (i = 0; i < NUMMAPS; i++)
+	{
+		if (data->mainrecords[i])
+		{
+			WRITEUINT32(save_p, data->mainrecords[i]->score);
+			WRITEUINT32(save_p, data->mainrecords[i]->time);
+			WRITEUINT16(save_p, data->mainrecords[i]->rings);
+		}
+		else
+		{
+			WRITEUINT32(save_p, 0);
+			WRITEUINT32(save_p, 0);
+			WRITEUINT16(save_p, 0);
+		}
+	}
+
+	// NiGHTS records
+	for (i = 0; i < NUMMAPS; i++)
+	{
+		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
+		{
+			WRITEUINT8(save_p, 0);
+			continue;
+		}
+
+		WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
+
+		for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
+		{
+			WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
+			WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
+		}
+	}
+
+	// Mid-map stuff
+	WRITEUINT32(save_p, unlocktriggers);
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!ntemprecords[i].nummares)
+		{
+			WRITEUINT8(save_p, 0);
+			continue;
+		}
+
+		WRITEUINT8(save_p, ntemprecords[i].nummares);
+
+		for (curmare = 0; curmare < (ntemprecords[i].nummares + 1); ++curmare)
+		{
+			WRITEUINT32(save_p, ntemprecords[i].score[curmare]);
+			WRITEUINT8(save_p, ntemprecords[i].grade[curmare]);
+			WRITEUINT32(save_p, ntemprecords[i].time[curmare]);
+		}
+	}
+}
+
+static inline void P_NetUnArchiveEmblems(void)
+{
+	gamedata_t *data = serverGamedata;
+	INT32 i, j;
+	UINT8 rtemp;
+	UINT32 recscore;
+	tic_t rectime;
+	UINT16 recrings;
+	UINT8 recmares;
+	INT32 curmare;
+
+	if (READUINT32(save_p) != ARCHIVEBLOCK_EMBLEMS)
+		I_Error("Bad $$$.sav at archive block Emblems");
+
+	savemoddata = (boolean)READUINT8(save_p); // this one is actually necessary because savemoddata stays false otherwise for some reason.
+
+	if (numemblems != READINT32(save_p))
+		I_Error("Bad $$$.sav dearchiving Emblems (numemblems mismatch)");
+	if (numextraemblems != READINT32(save_p))
+		I_Error("Bad $$$.sav dearchiving Emblems (numextraemblems mismatch)");
+
+	// This shouldn't happen, but if something really fucked up happens and you transfer
+	// the SERVER player's gamedata over your own CLIENT gamedata,
+	// then this prevents it from being saved over yours.
+	data->loaded = false;
+
+	M_ClearSecrets(data);
+	G_ClearRecords(data);
+
+	// The rest of this is lifted straight from G_LoadGameData in g_game.c
+	// TODO: Optimize this to only read information about emblems, unlocks, etc. which actually exist
+	//       There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same.
+
+	data->totalplaytime = READUINT32(save_p);
+
+	// TODO put another cipher on these things? meh, I don't care...
+	for (i = 0; i < NUMMAPS; i++)
+		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+			I_Error("Bad $$$.sav dearchiving Emblems (invalid visit flags)");
+
+	// To save space, use one bit per collected/achieved/unlocked flag
+	for (i = 0; i < MAXEMBLEMS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
+			data->collected[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXEXTRAEMBLEMS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
+			data->extraCollected[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXUNLOCKABLES;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
+			data->unlocked[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXCONDITIONSETS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
+			data->achieved[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+
+	data->timesBeaten = READUINT32(save_p);
+	data->timesBeatenWithEmeralds = READUINT32(save_p);
+	data->timesBeatenUltimate = READUINT32(save_p);
+
+	// Main records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		recscore = READUINT32(save_p);
+		rectime  = (tic_t)READUINT32(save_p);
+		recrings = READUINT16(save_p);
+
+		if (recrings > 10000 || recscore > MAXSCORE)
+			I_Error("Bad $$$.sav dearchiving Emblems (invalid score)");
+
+		if (recscore || rectime || recrings)
+		{
+			G_AllocMainRecordData((INT16)i, data);
+			data->mainrecords[i]->score = recscore;
+			data->mainrecords[i]->time = rectime;
+			data->mainrecords[i]->rings = recrings;
+		}
+	}
+
+	// Nights records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if ((recmares = READUINT8(save_p)) == 0)
+			continue;
+
+		G_AllocNightsRecordData((INT16)i, data);
+
+		for (curmare = 0; curmare < (recmares+1); ++curmare)
+		{
+			data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
+			data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
+			data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+
+			if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
+			{
+				I_Error("Bad $$$.sav dearchiving Emblems (invalid grade)");
+			}
+		}
+
+		data->nightsrecords[i]->nummares = recmares;
+	}
+
+	// Mid-map stuff
+	unlocktriggers = READUINT32(save_p);
+
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		if ((recmares = READUINT8(save_p)) == 0)
+			continue;
+
+		for (curmare = 0; curmare < (recmares+1); ++curmare)
+		{
+			ntemprecords[i].score[curmare] = READUINT32(save_p);
+			ntemprecords[i].grade[curmare] = READUINT8(save_p);
+			ntemprecords[i].time[curmare] = (tic_t)READUINT32(save_p);
+
+			if (ntemprecords[i].grade[curmare] > GRADE_S)
+			{
+				I_Error("Bad $$$.sav dearchiving Emblems (invalid temp grade)");
+			}
+		}
+
+		ntemprecords[i].nummares = recmares;
+	}
+}
+
 static inline void P_ArchiveLuabanksAndConsistency(void)
 {
 	UINT8 i, banksinuse = NUM_LUABANKS;
@@ -4498,6 +4798,7 @@ void P_SaveNetGame(boolean resending)
 
 	CV_SaveNetVars(&save_p);
 	P_NetArchiveMisc(resending);
+	P_NetArchiveEmblems();
 
 	// Assign the mobjnumber for pointer tracking
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -4550,6 +4851,7 @@ boolean P_LoadNetGame(boolean reloading)
 	CV_LoadNetVars(&save_p);
 	if (!P_NetUnArchiveMisc(reloading))
 		return false;
+	P_NetUnArchiveEmblems();
 	P_NetUnArchivePlayers();
 	if (gamestate == GS_LEVEL)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index 38a2f082e3d457e753022fcb3a45440cb25c1d20..e289b834699dd957aabc3ec554c25c720b6d5b2a 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
 
@@ -876,7 +877,7 @@ static void P_SpawnMapThings(boolean spawnemblems)
 	size_t i;
 	mapthing_t *mt;
 
-        // Spawn axis points first so they are at the front of the list for fast searching.
+	// Spawn axis points first so they are at the front of the list for fast searching.
 	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
 		switch (mt->type)
@@ -1049,10 +1050,10 @@ static void P_LoadSectors(UINT8 *data)
 		ss->special = SHORT(ms->special);
 		Tag_FSet(&ss->tags, SHORT(ms->tag));
 
-		ss->floor_xoffs = ss->floor_yoffs = 0;
-		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
+		ss->floorxoffset = ss->flooryoffset = 0;
+		ss->ceilingxoffset = ss->ceilingyoffset = 0;
 
-		ss->floorpic_angle = ss->ceilingpic_angle = 0;
+		ss->floorangle = ss->ceilingangle = 0;
 
 		ss->floorlightlevel = ss->ceilinglightlevel = 0;
 		ss->floorlightabsolute = ss->ceilinglightabsolute = false;
@@ -1103,7 +1104,6 @@ static void P_InitializeLinedef(line_t *ld)
 	ld->validcount = 0;
 	ld->polyobj = NULL;
 
-	ld->text = NULL;
 	ld->callcount = 0;
 
 	// cph 2006/09/30 - fix sidedef errors right away.
@@ -1207,10 +1207,115 @@ static void P_InitializeSidedef(side_t *sd)
 		sd->special = sd->line->special;
 	}
 
-	sd->text = NULL;
 	sd->colormap_data = NULL;
 }
 
+static boolean contextdrift = false;
+
+// In Ring Racers, this is just the reference implementation.
+// But in SRB2, backwards compatibility is a design goal!?
+// So we let the map function on load, but report that there's
+// an issue which could prevent saving it as a UDMF map.
+//
+// ... "context drift" is a term I learned from the story "Lena",
+// styled after a wikipedia article on a man whose brain scan
+// spawns an industry of digital brain emulation. It is a term
+// to describe that the understanding of the world MMAcevedo has
+// is rooted in a specific moment and time and will lose relevance
+// as societal norms, languages, and events change and are changed.
+// It is on the real wikipedia, but under the name "concept drift".
+// I am connecting the idea of the rooted-in-time brainstate with
+// the numerical evaluation of get_number(), the ground truth
+// CONTEXT for all these silly integers, that will trip up future
+// builds of SRB2 that have modified states and object lists.
+// Golden statues rotating in place of carousel rides is perhaps
+// not quite what qntm meant, but is a TEXTMAP, a human-readable
+// document for imparting a sense of place made/read by machines,
+// not its own language providing a vulnerable context?
+// ~toast 200723, see https://qntm.org/mmacevedo
+//
+static void P_WriteConstant(INT32 constant, char **target, const char *desc, UINT32 id)
+{
+	char buffer[12];
+	size_t len;
+
+	CONS_Alert(
+		CONS_WARNING, M_GetText(
+		"P_WriteConstant has been called for %s %d, "
+		"this level is vulnerable to context drift "
+		"if -writetextmap is used.\n"),
+		desc, id
+	);
+
+	contextdrift = true;
+
+	sprintf(buffer, "%d", constant);
+	len = strlen(buffer) + 1;
+	*target = Z_Malloc(len, PU_LEVEL, NULL);
+	M_Memcpy(*target, buffer, len);
+}
+
+static void P_WriteDuplicateText(const char *text, char **target)
+{
+	if (text == NULL || text[0] == '\0')
+		return;
+
+	size_t len = strlen(text) + 1;
+	*target = Z_Malloc(len, PU_LEVEL, NULL);
+	M_Memcpy(*target, text, len);
+}
+
+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", color_name),
+		target
+	);
+}
+
+static void P_WriteSfx(INT32 constant, char **target)
+{
+	if (constant <= sfx_None
+	|| constant >= (INT32)sfxfree)
+		return;
+
+	P_WriteDuplicateText(
+		va("SFX_%s", S_sfx[constant].name),
+		target
+	);
+}
+
+static void P_WriteTics(INT32 tics, char **target)
+{
+	if (!tics)
+		return;
+
+	INT32 seconds = (tics / TICRATE);
+	tics %= TICRATE;
+
+	const char *text;
+
+	if (tics && !seconds)
+		text = va("%d", tics);
+	else if (seconds && !tics)
+		text = va("(%d*TICRATE)", seconds);
+	else
+		text = va("(%d*TICRATE)%s%d", seconds, (tics > 0) ? "+" : "", tics);
+
+	P_WriteDuplicateText(text, target);
+}
+
 static void P_LoadSidedefs(UINT8 *data)
 {
 	mapsidedef_t *msd = (mapsidedef_t*)data;
@@ -1240,6 +1345,9 @@ static void P_LoadSidedefs(UINT8 *data)
 		}
 		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
+
 		P_SetSidedefSector(i, SHORT(msd->sector));
 
 		// Special info stored in texture fields!
@@ -1256,9 +1364,13 @@ static void P_LoadSidedefs(UINT8 *data)
 
 			case 413: // Change music
 			{
+				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+
+				if (!isfrontside)
+					break;
+
 				char process[8+1];
 
-				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
 				if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
 				{
 					M_Memcpy(process,msd->bottomtexture,8);
@@ -1273,48 +1385,39 @@ static void P_LoadSidedefs(UINT8 *data)
 					sd->midtexture = get_number(process);
 				}
 
-				sd->text = Z_Malloc(7, PU_LEVEL, NULL);
-				if (isfrontside && !(msd->toptexture[0] == '-' && msd->toptexture[1] == '\0'))
+				if (msd->toptexture[0] != '-' && msd->toptexture[1] != '\0')
 				{
+					sd->line->stringargs[0] = Z_Malloc(7, PU_LEVEL, NULL);
+
 					M_Memcpy(process,msd->toptexture,8);
 					process[8] = '\0';
 
 					// If they type in O_ or D_ and their music name, just shrug,
 					// then copy the rest instead.
 					if ((process[0] == 'O' || process[0] == 'D') && process[7])
-						M_Memcpy(sd->text, process+2, 6);
+						M_Memcpy(sd->line->stringargs[0], process+2, 6);
 					else // Assume it's a proper music name.
-						M_Memcpy(sd->text, process, 6);
-					sd->text[6] = 0;
+						M_Memcpy(sd->line->stringargs[0], process, 6);
+					sd->line->stringargs[0][6] = '\0';
 				}
-				else
-					sd->text[0] = 0;
-				break;
-			}
 
-			case 4: // Speed pad parameters
-			{
-				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
-				if (msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
-				{
-					char process[8+1];
-					M_Memcpy(process,msd->toptexture,8);
-					process[8] = '\0';
-					sd->toptexture = get_number(process);
-				}
 				break;
 			}
 
+			case 4: // Speed pad parameters
 			case 414: // Play SFX
 			{
 				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+
+				if (!isfrontside)
+					break;
+
 				if (msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
 				{
 					char process[8 + 1];
 					M_Memcpy(process, msd->toptexture, 8);
 					process[8] = '\0';
-					sd->text = Z_Malloc(strlen(process) + 1, PU_LEVEL, NULL);
-					M_Memcpy(sd->text, process, strlen(process) + 1);
+					P_WriteDuplicateText(process, &sd->line->stringargs[0]);
 				}
 				break;
 			}
@@ -1323,21 +1426,13 @@ static void P_LoadSidedefs(UINT8 *data)
 			case 14: // Bustable block parameters
 			case 15: // Fan particle spawner parameters
 			{
-				char process[8*3+1];
-				memset(process,0,8*3+1);
-				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
-				if (msd->toptexture[0] == '-' && msd->toptexture[1] == '\0')
+				if (msd->toptexture[7] == '\0' && strcasecmp(msd->toptexture, "MT_NULL") == 0)
+				{
+					// Don't bulk the conversion with irrelevant types
 					break;
-				else
-					M_Memcpy(process,msd->toptexture,8);
-				if (msd->midtexture[0] != '-' || msd->midtexture[1] != '\0')
-					M_Memcpy(process+strlen(process), msd->midtexture, 8);
-				if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
-					M_Memcpy(process+strlen(process), msd->bottomtexture, 8);
-				sd->toptexture = get_number(process);
-				break;
+				}
 			}
-
+			// FALLTHRU
 			case 331: // Trigger linedef executor: Skin - Continuous
 			case 332: // Trigger linedef executor: Skin - Each time
 			case 333: // Trigger linedef executor: Skin - Once
@@ -1363,8 +1458,11 @@ static void P_LoadSidedefs(UINT8 *data)
 					M_Memcpy(process+strlen(process), msd->midtexture, 8);
 				if (msd->bottomtexture[0] != '-' || msd->bottomtexture[1] != '\0')
 					M_Memcpy(process+strlen(process), msd->bottomtexture, 8);
-				sd->text = Z_Malloc(strlen(process)+1, PU_LEVEL, NULL);
-				M_Memcpy(sd->text, process, strlen(process)+1);
+
+				P_WriteDuplicateText(
+					process,
+					&sd->line->stringargs[(isfrontside) ? 0 : 1]
+				);
 				break;
 			}
 
@@ -1420,6 +1518,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;
@@ -1578,19 +1677,19 @@ static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char
 			if ((id = strchr(id, ' ')))
 				id++;
 		}
-	}
+	}	
 	else if (fastcmp(param, "xpanningfloor"))
-		sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
+		sectors[i].floorxoffset = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningfloor"))
-		sectors[i].floor_yoffs = FLOAT_TO_FIXED(atof(val));
+		sectors[i].flooryoffset = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "xpanningceiling"))
-		sectors[i].ceiling_xoffs = FLOAT_TO_FIXED(atof(val));
+		sectors[i].ceilingxoffset = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningceiling"))
-		sectors[i].ceiling_yoffs = FLOAT_TO_FIXED(atof(val));
+		sectors[i].ceilingyoffset = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "rotationfloor"))
-		sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+		sectors[i].floorangle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
 	else if (fastcmp(param, "rotationceiling"))
-		sectors[i].ceilingpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+		sectors[i].ceilingangle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
 	else if (fastcmp(param, "floorplane_a"))
 	{
 		textmap_planefloor.defined |= PD_A;
@@ -1777,6 +1876,18 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char
 		sides[i].textureoffset = atol(val)<<FRACBITS;
 	else if (fastcmp(param, "offsety"))
 		sides[i].rowoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "offsetx_top"))
+		sides[i].offsetx_top = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsetx_mid"))
+		sides[i].offsetx_mid = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsetx_bottom"))
+		sides[i].offsetx_bot = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_top"))
+		sides[i].offsety_top = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_mid"))
+		sides[i].offsety_mid = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_bottom"))
+		sides[i].offsety_bot = atol(val) << FRACBITS;
 	else if (fastcmp(param, "texturetop"))
 		sides[i].toptexture = R_TextureNumForName(val);
 	else if (fastcmp(param, "texturebottom"))
@@ -1911,11 +2022,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)
 	{
@@ -1966,47 +2085,47 @@ static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, cons
  */
 static void TextmapFixFlatOffsets(sector_t *sec)
 {
-	if (sec->floorpic_angle)
+	if (sec->floorangle)
 	{
-		fixed_t pc = FINECOSINE(sec->floorpic_angle>>ANGLETOFINESHIFT);
-		fixed_t ps = FINESINE  (sec->floorpic_angle>>ANGLETOFINESHIFT);
-		fixed_t xoffs = sec->floor_xoffs;
-		fixed_t yoffs = sec->floor_yoffs;
-		sec->floor_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
-		sec->floor_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		fixed_t pc = FINECOSINE(sec->floorangle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->floorangle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->floorxoffset;
+		fixed_t yoffs = sec->flooryoffset;
+		sec->floorxoffset = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->flooryoffset = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
 	}
 
-	if (sec->ceilingpic_angle)
+	if (sec->ceilingangle)
 	{
-		fixed_t pc = FINECOSINE(sec->ceilingpic_angle>>ANGLETOFINESHIFT);
-		fixed_t ps = FINESINE  (sec->ceilingpic_angle>>ANGLETOFINESHIFT);
-		fixed_t xoffs = sec->ceiling_xoffs;
-		fixed_t yoffs = sec->ceiling_yoffs;
-		sec->ceiling_xoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
-		sec->ceiling_yoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		fixed_t pc = FINECOSINE(sec->ceilingangle>>ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE  (sec->ceilingangle>>ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->ceilingxoffset;
+		fixed_t yoffs = sec->ceilingyoffset;
+		sec->ceilingxoffset = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		sec->ceilingyoffset = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
 	}
 }
 
 static void TextmapUnfixFlatOffsets(sector_t *sec)
 {
-	if (sec->floorpic_angle)
+	if (sec->floorangle)
 	{
-		fixed_t pc = FINECOSINE(sec->floorpic_angle >> ANGLETOFINESHIFT);
-		fixed_t ps = FINESINE(sec->floorpic_angle >> ANGLETOFINESHIFT);
-		fixed_t xoffs = sec->floor_xoffs;
-		fixed_t yoffs = sec->floor_yoffs;
-		sec->floor_xoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
-		sec->floor_yoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		fixed_t pc = FINECOSINE(sec->floorangle >> ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE(sec->floorangle >> ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->floorxoffset;
+		fixed_t yoffs = sec->flooryoffset;
+		sec->floorxoffset = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		sec->flooryoffset = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
 	}
 
-	if (sec->ceilingpic_angle)
+	if (sec->ceilingangle)
 	{
-		fixed_t pc = FINECOSINE(sec->ceilingpic_angle >> ANGLETOFINESHIFT);
-		fixed_t ps = FINESINE(sec->ceilingpic_angle >> ANGLETOFINESHIFT);
-		fixed_t xoffs = sec->ceiling_xoffs;
-		fixed_t yoffs = sec->ceiling_yoffs;
-		sec->ceiling_xoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
-		sec->ceiling_yoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+		fixed_t pc = FINECOSINE(sec->ceilingangle >> ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE(sec->ceilingangle >> ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->ceilingxoffset;
+		fixed_t yoffs = sec->ceilingyoffset;
+		sec->ceilingxoffset = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		sec->ceilingyoffset = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
 	}
 }
 
@@ -2329,8 +2448,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++)
@@ -2461,6 +2584,18 @@ static void P_WriteTextmap(void)
 			fprintf(f, "offsetx = %d;\n", wsides[i].textureoffset >> FRACBITS);
 		if (wsides[i].rowoffset != 0)
 			fprintf(f, "offsety = %d;\n", wsides[i].rowoffset >> FRACBITS);
+		if (wsides[i].offsetx_top != 0)
+			fprintf(f, "offsetx_top = %d;\n", wsides[i].offsetx_top >> FRACBITS);
+		if (wsides[i].offsety_top != 0)
+			fprintf(f, "offsety_top = %d;\n", wsides[i].offsety_top >> FRACBITS);
+		if (wsides[i].offsetx_mid != 0)
+			fprintf(f, "offsetx_mid = %d;\n", wsides[i].offsetx_mid >> FRACBITS);
+		if (wsides[i].offsety_mid != 0)
+			fprintf(f, "offsety_mid = %d;\n", wsides[i].offsety_mid >> FRACBITS);
+		if (wsides[i].offsetx_bot != 0)
+			fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bot >> FRACBITS);
+		if (wsides[i].offsety_bot != 0)
+			fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bot >> FRACBITS);
 		if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures)
 			fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name);
 		if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures)
@@ -2508,18 +2643,18 @@ static void P_WriteTextmap(void)
 		}
 		sector_t tempsec = wsectors[i];
 		TextmapUnfixFlatOffsets(&tempsec);
-		if (tempsec.floor_xoffs != 0)
-			fprintf(f, "xpanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.floor_xoffs));
-		if (tempsec.floor_yoffs != 0)
-			fprintf(f, "ypanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.floor_yoffs));
-		if (tempsec.ceiling_xoffs != 0)
-			fprintf(f, "xpanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceiling_xoffs));
-		if (tempsec.ceiling_yoffs != 0)
-			fprintf(f, "ypanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceiling_yoffs));
-		if (wsectors[i].floorpic_angle != 0)
-			fprintf(f, "rotationfloor = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].floorpic_angle)));
-		if (wsectors[i].ceilingpic_angle != 0)
-			fprintf(f, "rotationceiling = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].ceilingpic_angle)));
+		if (tempsec.floorxoffset != 0)
+			fprintf(f, "xpanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.floorxoffset));
+		if (tempsec.flooryoffset != 0)
+			fprintf(f, "ypanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.flooryoffset));
+		if (tempsec.ceilingxoffset != 0)
+			fprintf(f, "xpanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingxoffset));
+		if (tempsec.ceilingyoffset != 0)
+			fprintf(f, "ypanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingyoffset));
+		if (wsectors[i].floorangle != 0)
+			fprintf(f, "rotationfloor = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].floorangle)));
+		if (wsectors[i].ceilingangle != 0)
+			fprintf(f, "rotationceiling = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].ceilingangle)));
         if (wsectors[i].extra_colormap)
 		{
 			INT32 lightcolor = P_RGBAToColor(wsectors[i].extra_colormap->rgba);
@@ -2689,6 +2824,15 @@ static void P_WriteTextmap(void)
 	Z_Free(wlines);
 	Z_Free(wsides);
 	Z_Free(specialthings);
+
+	if (contextdrift)
+	{
+		CONS_Alert(
+			CONS_WARNING, M_GetText(
+			"Some elements of this level are vulnerable to context drift."
+			"Please check the console log for more information.\n")
+		);
+	}
 }
 
 /** Loads the textmap data, after obtaining the elements count and allocating their respective space.
@@ -2739,10 +2883,10 @@ static void P_LoadTextmap(void)
 		sc->special = 0;
 		Tag_FSet(&sc->tags, 0);
 
-		sc->floor_xoffs = sc->floor_yoffs = 0;
-		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
+		sc->floorxoffset = sc->flooryoffset = 0;
+		sc->ceilingxoffset = sc->ceilingyoffset = 0;
 
-		sc->floorpic_angle = sc->ceilingpic_angle = 0;
+		sc->floorangle = sc->ceilingangle = 0;
 
 		sc->floorlightlevel = sc->ceilinglightlevel = 0;
 		sc->floorlightabsolute = sc->ceilinglightabsolute = false;
@@ -2828,6 +2972,8 @@ static void P_LoadTextmap(void)
 		// Defaults.
 		sd->textureoffset = 0;
 		sd->rowoffset = 0;
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
 		sd->toptexture = R_TextureNumForName("-");
 		sd->midtexture = R_TextureNumForName("-");
 		sd->bottomtexture = R_TextureNumForName("-");
@@ -2853,6 +2999,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;
@@ -2880,15 +3027,20 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 		case 332: // Trigger linedef executor: Skin - Each time
 		case 333: // Trigger linedef executor: Skin - Once
 		case 443: // Calls a named Lua function
-			if (sides[ld->sidenum[0]].text)
+			if (ld->stringargs[0] && ld->stringargs[1])
 			{
-				size_t len = strlen(sides[ld->sidenum[0]].text) + 1;
-				if (ld->sidenum[1] != 0xffff && sides[ld->sidenum[1]].text)
-					len += strlen(sides[ld->sidenum[1]].text);
-				ld->text = Z_Malloc(len, PU_LEVEL, NULL);
-				M_Memcpy(ld->text, sides[ld->sidenum[0]].text, strlen(sides[ld->sidenum[0]].text) + 1);
-				if (ld->sidenum[1] != 0xffff && sides[ld->sidenum[1]].text)
-					M_Memcpy(ld->text + strlen(ld->text) + 1, sides[ld->sidenum[1]].text, strlen(sides[ld->sidenum[1]].text) + 1);
+				size_t len[2];
+				len[0] = strlen(ld->stringargs[0]);
+				len[1] = strlen(ld->stringargs[1]);
+
+				if (len[1])
+				{
+					ld->stringargs[0] = Z_Realloc(ld->stringargs[0], len[0] + len[1] + 1, PU_LEVEL, NULL);
+					M_Memcpy(ld->stringargs[0] + len[0] + 1, ld->stringargs[1], len[1] + 1);
+				}
+
+				Z_Free(ld->stringargs[1]);
+				ld->stringargs[1] = NULL;
 			}
 			break;
 		case 447: // Change colormap
@@ -3176,10 +3328,17 @@ static nodetype_t P_GetNodetype(const virtres_t *virt, UINT8 **nodedata)
 	nodetype_t nodetype = NT_UNSUPPORTED;
 	char signature[4 + 1];
 
+	*nodedata = NULL;
+
 	if (udmf)
 	{
-		*nodedata = vres_Find(virt, "ZNODES")->data;
-		supported[NT_XGLN] = supported[NT_XGL3] = true;
+		virtlump_t *virtznodes = vres_Find(virt, "ZNODES");
+
+		if (virtznodes && virtznodes->size)
+		{
+			*nodedata = virtznodes->data;
+			supported[NT_XGLN] = supported[NT_XGL3] = true;
+		}
 	}
 	else
 	{
@@ -3188,24 +3347,39 @@ static nodetype_t P_GetNodetype(const virtres_t *virt, UINT8 **nodedata)
 
 		if (virtsegs && virtsegs->size)
 		{
-			*nodedata = vres_Find(virt, "NODES")->data;
-			return NT_DOOM; // Traditional map format BSP tree.
-		}
-
-		virtssectors = vres_Find(virt, "SSECTORS");
-
-		if (virtssectors && virtssectors->size)
-		{ // Possibly GL nodes: NODES ignored, SSECTORS takes precedence as nodes lump, (It is confusing yeah) and has a signature.
-			*nodedata = virtssectors->data;
-			supported[NT_XGLN] = supported[NT_ZGLN] = supported[NT_XGL3] = true;
+			virtlump_t *virtnodes = vres_Find(virt, "NODES");
+			if (virtnodes && virtnodes->size)
+			{
+				*nodedata = virtnodes->data;
+				return NT_DOOM; // Traditional map format BSP tree.
+			}
 		}
 		else
-		{ // Possibly ZDoom extended nodes: SSECTORS is empty, NODES has a signature.
-			*nodedata = vres_Find(virt, "NODES")->data;
-			supported[NT_XNOD] = supported[NT_ZNOD] = true;
+		{
+			virtssectors = vres_Find(virt, "SSECTORS");
+
+			if (virtssectors && virtssectors->size)
+			{ // Possibly GL nodes: NODES ignored, SSECTORS takes precedence as nodes lump, (It is confusing yeah) and has a signature.
+				*nodedata = virtssectors->data;
+				supported[NT_XGLN] = supported[NT_ZGLN] = supported[NT_XGL3] = true;
+			}
+			else
+			{ // Possibly ZDoom extended nodes: SSECTORS is empty, NODES has a signature.
+				virtlump_t *virtnodes = vres_Find(virt, "NODES");
+				if (virtnodes && virtnodes->size)
+				{
+					*nodedata = virtnodes->data;
+					supported[NT_XNOD] = supported[NT_ZNOD] = true;
+				}
+			}
 		}
 	}
 
+	if (*nodedata == NULL)
+	{
+		I_Error("Level has no nodes (does your map have at least 2 sectors?)");
+	}
+
 	M_Memcpy(signature, *nodedata, 4);
 	signature[4] = '\0';
 	(*nodedata) += 4;
@@ -3982,14 +4156,6 @@ static void P_AddBinaryMapTags(void)
 	}
 }
 
-static void P_WriteConstant(INT32 constant, char **target)
-{
-	char buffer[12];
-	sprintf(buffer, "%d", constant);
-	*target = Z_Malloc(strlen(buffer) + 1, PU_LEVEL, NULL);
-	M_Memcpy(*target, buffer, strlen(buffer) + 1);
-}
-
 static line_t *P_FindPointPushLine(taglist_t *list)
 {
 	INT32 i, l;
@@ -4132,7 +4298,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 				lines[i].args[1] |= TMSP_NOTELEPORT;
 			if (lines[i].flags & ML_WRAPMIDTEX)
 				lines[i].args[1] |= TMSP_FORCESPIN;
-			P_WriteConstant(sides[lines[i].sidenum[0]].toptexture ? sides[lines[i].sidenum[0]].toptexture : sfx_spdpad, &lines[i].stringargs[0]);
 			break;
 		case 7: //Sector flat alignment
 			lines[i].args[0] = tag;
@@ -4218,8 +4383,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
 			lines[i].args[2] = !!(lines[i].flags & ML_SKEWTD);
-			if (sides[lines[i].sidenum[0]].toptexture)
-				P_WriteConstant(sides[lines[i].sidenum[0]].toptexture, &lines[i].stringargs[0]);
 			break;
 		case 16: //Minecart parameters
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
@@ -4682,7 +4845,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 				lines[i].args[2] = 16;
 			}
 			if (lines[i].flags & ML_MIDSOLID)
-				P_WriteConstant(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, &lines[i].stringargs[0]);
+				P_WriteSfx(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, &lines[i].stringargs[0]);
 			break;
 		case 252: //FOF: Shatter block
 		case 253: //FOF: Shatter block, translucent
@@ -4949,11 +5112,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			else
 				lines[i].args[0] = TMT_CONTINUOUS;
 			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
-			if (lines[i].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
-			}
 			lines[i].special = 331;
 			break;
 		case 334: // Object dye - continuous
@@ -4966,11 +5124,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			else
 				lines[i].args[0] = TMT_CONTINUOUS;
 			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
 			lines[i].special = 334;
 			break;
 		case 337: //Emerald check - continuous
@@ -5117,11 +5270,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[4] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
 			lines[i].args[5] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1;
 			lines[i].args[6] = sides[lines[i].sidenum[0]].bottomtexture;
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
 			break;
 		case 414: //Play sound effect
 			lines[i].args[2] = tag;
@@ -5161,11 +5309,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 					lines[i].args[1] = TMSL_EVERYONE;
 				}
 			}
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
 			break;
 		case 415: //Run script
 		{
@@ -5256,13 +5399,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
 			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
 			break;
-		case 425: //Change object state
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
-			break;
 		case 426: //Stop object
 			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
 			break;
@@ -5302,18 +5438,18 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[2] = !!(lines[i].flags & ML_BLOCKMONSTERS);
 			break;
 		case 434: //Award power-up
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
-			if (lines[i].sidenum[1] != 0xffff && lines[i].flags & ML_BLOCKMONSTERS) // read power from back sidedef
+			if ((lines[i].flags & ML_BLOCKMONSTERS) == 0)
 			{
-				lines[i].stringargs[1] = Z_Malloc(strlen(sides[lines[i].sidenum[1]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[1], sides[lines[i].sidenum[1]].text, strlen(sides[lines[i].sidenum[1]].text) + 1);
+				// do NOT read power from back sidedef if this flag is not present
+				if (lines[i].stringargs[1] != NULL)
+				{
+					Z_Free(lines[i].stringargs[1]);
+					lines[i].stringargs[1] = NULL;
+				}
+
+				// Instead... write the desired time!
+				P_WriteTics((lines[i].flags & ML_NOCLIMB) ? -1 : (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS), &lines[i].stringargs[1]);
 			}
-			else
-				P_WriteConstant((lines[i].flags & ML_NOCLIMB) ? -1 : (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS), &lines[i].stringargs[1]);
 			break;
 		case 435: //Change plane scroller direction
 			lines[i].args[0] = tag;
@@ -5341,30 +5477,10 @@ static void P_ConvertBinaryLinedefTypes(void)
 			break;
 		case 442: //Change object type state
 			lines[i].args[0] = tag;
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
-			if (lines[i].sidenum[1] == 0xffff)
-				lines[i].args[1] = 1;
-			else
-			{
-				lines[i].args[1] = 0;
-				if (sides[lines[i].sidenum[1]].text)
-				{
-					lines[i].stringargs[1] = Z_Malloc(strlen(sides[lines[i].sidenum[1]].text) + 1, PU_LEVEL, NULL);
-					M_Memcpy(lines[i].stringargs[1], sides[lines[i].sidenum[1]].text, strlen(sides[lines[i].sidenum[1]].text) + 1);
-				}
-			}
+			lines[i].args[1] = (lines[i].sidenum[1] == 0xffff) ? 1 : 0;
 			break;
 		case 443: //Call Lua function
-			if (lines[i].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
-			}
-			else
+			if (lines[i].stringargs[0] == NULL)
 				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
 			break;
 		case 444: //Earthquake
@@ -5517,11 +5633,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			if (lines[i].flags & ML_MIDSOLID)
 				lines[i].args[2] |= TMP_FREEZETHINKERS;*/
 			lines[i].args[3] = (lines[i].sidenum[1] != 0xFFFF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag;
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
 			break;
 		case 460: //Award rings
 			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
@@ -5549,18 +5660,6 @@ static void P_ConvertBinaryLinedefTypes(void)
 			}
 			else
 				lines[i].args[4] = 0;
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
-			break;
-		case 463: //Dye object
-			if (sides[lines[i].sidenum[0]].text)
-			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
-			}
 			break;
 		case 464: //Trigger egg capsule
 			lines[i].args[0] = tag;
@@ -5731,22 +5830,31 @@ static void P_ConvertBinaryLinedefTypes(void)
 		case 513: //Scroll ceiling texture
 		case 514: //Scroll ceiling texture (accelerative)
 		case 515: //Scroll ceiling texture (displacement)
+		case 516: //Scroll floor and ceiling texture
+		case 517: //Scroll floor and ceiling texture (accelerative)
+		case 518: //Scroll floor and ceiling texture (displacement)
 		case 520: //Carry objects on floor
 		case 521: //Carry objects on floor (accelerative)
 		case 522: //Carry objects on floor (displacement)
 		case 523: //Carry objects on ceiling
 		case 524: //Carry objects on ceiling (accelerative)
 		case 525: //Carry objects on ceiling (displacement)
+		case 526: //Carry objects on floor and ceiling
+		case 527: //Carry objects on floor and ceiling (accelerative)
+		case 528: //Carry objects on floor and ceiling (displacement)
 		case 530: //Scroll floor texture and carry objects
 		case 531: //Scroll floor texture and carry objects (accelerative)
 		case 532: //Scroll floor texture and carry objects (displacement)
 		case 533: //Scroll ceiling texture and carry objects
 		case 534: //Scroll ceiling texture and carry objects (accelerative)
 		case 535: //Scroll ceiling texture and carry objects (displacement)
+		case 536: //Scroll floor and ceiling texture and carry objects
+		case 537: //Scroll floor and ceiling texture and carry objects (accelerative)
+		case 538: //Scroll floor and ceiling texture and carry objects (displacement)
 			lines[i].args[0] = tag;
-			lines[i].args[1] = ((lines[i].special % 10) < 3) ? TMP_FLOOR : TMP_CEILING;
+			lines[i].args[1] = ((lines[i].special % 10) < 6) ? (((lines[i].special % 10) < 3) ? TMP_FLOOR : TMP_CEILING) : TMP_BOTH;
 			lines[i].args[2] = ((lines[i].special - 510)/10 + 1) % 3;
-			lines[i].args[3] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+			lines[i].args[3] = ((lines[i].flags & ML_EFFECT6) ? sides[lines[i].sidenum[0]].textureoffset : R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y)) >> FRACBITS;
 			lines[i].args[4] = (lines[i].special % 10) % 3;
 			if (lines[i].args[2] != TMS_SCROLLONLY && !(lines[i].flags & ML_NOCLIMB))
 				lines[i].args[4] |= TMST_NONEXCLUSIVE;
@@ -5777,17 +5885,19 @@ static void P_ConvertBinaryLinedefTypes(void)
 		case 544: //Current
 		case 545: //Upwards current
 		case 546: //Downwards current
+		{
+			fixed_t strength = (lines[i].flags & ML_EFFECT6) ? sides[lines[i].sidenum[0]].textureoffset : R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y);
 			lines[i].args[0] = tag;
 			switch ((lines[i].special - 541) % 3)
 			{
 				case 0:
-					lines[i].args[1] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					lines[i].args[1] = strength >> FRACBITS;
 					break;
 				case 1:
-					lines[i].args[2] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					lines[i].args[2] = strength >> FRACBITS;
 					break;
 				case 2:
-					lines[i].args[2] = -R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					lines[i].args[2] = -strength >> FRACBITS;
 					break;
 			}
 			lines[i].args[3] = (lines[i].special >= 544) ? p_current : p_wind;
@@ -5797,6 +5907,7 @@ static void P_ConvertBinaryLinedefTypes(void)
 				lines[i].args[4] |= TMPF_NONEXCLUSIVE;
 			lines[i].special = 541;
 			break;
+		}
 		case 600: //Floor lighting
 		case 601: //Ceiling lighting
 			lines[i].args[0] = tag;
@@ -6377,7 +6488,7 @@ static void P_ConvertBinaryThingTypes(void)
 			break;
 		case 543: //Balloon
 			if (mapthings[i].angle > 0)
-				P_WriteConstant(((mapthings[i].angle - 1) % (numskincolors - 1)) + 1, &mapthings[i].stringargs[0]);
+				P_WriteSkincolor(((mapthings[i].angle - 1) % (numskincolors - 1)) + 1, &mapthings[i].stringargs[0]);
 			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
 			break;
 		case 555: //Diagonal yellow spring
@@ -6402,22 +6513,22 @@ static void P_ConvertBinaryThingTypes(void)
 		case 706: //Water ambience G
 		case 707: //Water ambience H
 			mapthings[i].args[0] = 35;
-			P_WriteConstant(sfx_amwtr1 + mapthings[i].type - 700, &mapthings[i].stringargs[0]);
+			P_WriteSfx(sfx_amwtr1 + mapthings[i].type - 700, &mapthings[i].stringargs[0]);
 			mapthings[i].type = 700;
 			break;
 		case 708: //Disco ambience
 			mapthings[i].args[0] = 512;
-			P_WriteConstant(sfx_ambint, &mapthings[i].stringargs[0]);
+			P_WriteSfx(sfx_ambint, &mapthings[i].stringargs[0]);
 			mapthings[i].type = 700;
 			break;
 		case 709: //Volcano ambience
 			mapthings[i].args[0] = 220;
-			P_WriteConstant(sfx_ambin2, &mapthings[i].stringargs[0]);
+			P_WriteSfx(sfx_ambin2, &mapthings[i].stringargs[0]);
 			mapthings[i].type = 700;
 			break;
 		case 710: //Machine ambience
 			mapthings[i].args[0] = 24;
-			P_WriteConstant(sfx_ambmac, &mapthings[i].stringargs[0]);
+			P_WriteSfx(sfx_ambmac, &mapthings[i].stringargs[0]);
 			mapthings[i].type = 700;
 			break;
 		case 750: //Slope vertex
@@ -6450,7 +6561,7 @@ static void P_ConvertBinaryThingTypes(void)
 			}
 
 			mapthings[i].args[0] = mapthings[i].angle;
-			mapthings[i].args[1] = P_AproxDistance(line->dx >> FRACBITS, line->dy >> FRACBITS);
+			mapthings[i].args[1] = (line->flags & ML_EFFECT6) ? sides[line->sidenum[0]].textureoffset >> FRACBITS : P_AproxDistance(line->dx >> FRACBITS, line->dy >> FRACBITS);
 			if (mapthings[i].type == 755)
 				mapthings[i].args[1] *= -1;
 			if (mapthings[i].options & MTF_OBJECTSPECIAL)
@@ -6480,8 +6591,7 @@ static void P_ConvertBinaryThingTypes(void)
 			mapthings[i].args[3] = sides[lines[j].sidenum[0]].rowoffset >> FRACBITS;
 			mapthings[i].args[4] = lines[j].backsector ? sides[lines[j].sidenum[1]].textureoffset >> FRACBITS : 0;
 			mapthings[i].args[6] = mapthings[i].angle;
-			if (sides[lines[j].sidenum[0]].toptexture)
-				P_WriteConstant(sides[lines[j].sidenum[0]].toptexture, &mapthings[i].stringargs[0]);
+			P_WriteDuplicateText(lines[j].stringargs[0], &mapthings[i].stringargs[0]);
 			break;
 		}
 		case 762: //PolyObject spawn point (crush)
@@ -6570,8 +6680,8 @@ static void P_ConvertBinaryThingTypes(void)
 				mapthings[i].args[8] |= TMM_ALWAYSTHINK;
 			if (mapthings[i].type == 1110)
 			{
-				P_WriteConstant(sides[lines[j].sidenum[0]].toptexture, &mapthings[i].stringargs[0]);
-				P_WriteConstant(lines[j].backsector ? sides[lines[j].sidenum[1]].toptexture : MT_NULL, &mapthings[i].stringargs[1]);
+				P_WriteDuplicateText(lines[j].stringargs[0], &mapthings[i].stringargs[0]);
+				P_WriteDuplicateText(lines[j].stringargs[1], &mapthings[i].stringargs[1]);
 			}
 			break;
 		}
@@ -6612,7 +6722,13 @@ static void P_ConvertBinaryThingTypes(void)
 			mapthings[i].args[0] = P_AproxDistance(lines[j].dx, lines[j].dy) >> FRACBITS;
 			mapthings[i].args[1] = sides[lines[j].sidenum[0]].textureoffset >> FRACBITS;
 			mapthings[i].args[2] = !!(lines[j].flags & ML_NOCLIMB);
-			P_WriteConstant(MT_ROCKCRUMBLE1 + (sides[lines[j].sidenum[0]].rowoffset >> FRACBITS), &mapthings[i].stringargs[0]);
+			INT32 id = (sides[lines[j].sidenum[0]].rowoffset >> FRACBITS);
+			// Rather than introduce deh_tables.h as a dependency for literally one
+			// conversion, we just... recreate the string expected to be produced.
+			if (id > 0 && id < 16)
+				P_WriteDuplicateText(va("MT_ROCKCRUMBLE%d", id+1), &mapthings[i].stringargs[0]);
+			else
+				P_WriteConstant(MT_ROCKCRUMBLE1 + id, &mapthings[i].stringargs[0], "Mapthing", i);
 			break;
 		}
 		case 1221: //Minecart saloon door
@@ -6704,6 +6820,9 @@ static void P_ConvertBinaryThingTypes(void)
 		default:
 			break;
 		}
+		
+		// Clear binary thing height hacks, to prevent interfering with UDMF-only flags
+		mapthings[i].options &= 0xF;
 	}
 }
 
@@ -6745,6 +6864,7 @@ static void P_ConvertBinaryLinedefFlags(void)
 //For maps in binary format, converts setup of specials to UDMF format.
 static void P_ConvertBinaryMap(void)
 {
+	contextdrift = false;
 	P_ConvertBinaryLinedefTypes();
 	P_ConvertBinarySectorTypes();
 	P_ConvertBinaryThingTypes();
@@ -6944,7 +7064,7 @@ static void P_InitLevelSettings(void)
 	stagefailed = G_IsSpecialStage(gamemap);
 
 	// Reset temporary record data
-	memset(&ntemprecords, 0, sizeof(nightsdata_t));
+	memset(&ntemprecords, 0, sizeof(ntemprecords));
 
 	// earthquake camera
 	memset(&quake,0,sizeof(struct quake));
@@ -7395,6 +7515,7 @@ static void P_RunSpecialStageWipe(void)
 		lastwipetic = nowtime;
 		if (moviemode) // make sure we save frames for the white hold too
 			M_SaveFrame();
+		NetKeepAlive(); // Prevent timeout
 	}
 }
 
@@ -7448,7 +7569,7 @@ static void P_WriteLetter(void)
 {
 	char *buf, *b;
 
-	if (!unlockables[28].unlocked) // pandora's box
+	if (!serverGamedata->unlocked[28]) // pandora's box
 		return;
 
 	if (modeattacking)
@@ -7699,18 +7820,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	R_InitMobjInterpolators();
 	P_InitCachedActions();
 
-	if (!fromnetsave && savedata.lives > 0)
-	{
-		numgameovers = savedata.numgameovers;
-		players[consoleplayer].continues = savedata.continues;
-		players[consoleplayer].lives = savedata.lives;
-		players[consoleplayer].score = savedata.score;
-		if ((botingame = ((botskin = savedata.botskin) != 0)))
-			botcolor = skins[botskin-1].prefcolor;
-		emeralds = savedata.emeralds;
-		savedata.lives = 0;
-	}
-
 	// internal game map
 	maplumpname = G_BuildMapName(gamemap);
 	lastloadedmaplumpnum = W_CheckNumForMap(maplumpname);
@@ -7792,10 +7901,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	nextmapoverride = 0;
 	skipstats = 0;
 
-	if (!(netgame || multiplayer || demoplayback) && (!modifiedgame || savemoddata))
-		mapvisited[gamemap-1] |= MV_VISITED;
-	else if (netgame || multiplayer)
-		mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently
+	if (!demoplayback)
+	{
+		clientGamedata->mapvisited[gamemap-1] |= MV_VISITED;
+		serverGamedata->mapvisited[gamemap-1] |= MV_VISITED;
+	}
 
 	levelloading = false;
 
@@ -7810,8 +7920,10 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 		{
 			// I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020
 			if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || marathonmode)
-			&& (!modifiedgame || savemoddata) && cursaveslot > 0)
+				&& !usedCheats && cursaveslot > 0)
+			{
 				G_SaveGame((UINT32)cursaveslot, gamemap);
+			}
 			// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
 		}
 		lastmaploaded = gamemap; // HAS to be set after saving!!
@@ -7946,8 +8058,10 @@ static lumpinfo_t* FindFolder(const char *folName, UINT16 *start, UINT16 *end, l
 // Add a wadfile to the active wad files,
 // replace sounds, musics, patches, textures, sprites and maps
 //
-static boolean P_LoadAddon(UINT16 wadnum, UINT16 numlumps)
+static boolean P_LoadAddon(UINT16 numlumps)
 {
+	const UINT16 wadnum = (UINT16)(numwadfiles-1);
+
 	size_t i, j, sreplaces = 0, mreplaces = 0, digmreplaces = 0;
 	char *name;
 	lumpinfo_t *lumpinfo;
@@ -7969,6 +8083,12 @@ static boolean P_LoadAddon(UINT16 wadnum, UINT16 numlumps)
 //	UINT16 flaPos, flaNum = 0;
 //	UINT16 mapPos, mapNum = 0;
 
+	if (numlumps == INT16_MAX)
+	{
+		refreshdirmenu |= REFRESHDIR_NOTLOADED;
+		return false;
+	}
+
 	switch(wadfiles[wadnum]->type)
 	{
 	case RET_PK3:
@@ -8124,14 +8244,14 @@ static boolean P_LoadAddon(UINT16 wadnum, UINT16 numlumps)
 		ST_Start();
 
 	// Prevent savefile cheating
-	if (cursaveslot > 0)
+	if (modifiedgame && (cursaveslot > 0))
 		cursaveslot = 0;
 
 	if (replacedcurrentmap && gamestate == GS_LEVEL && (netgame || multiplayer))
 	{
 		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;
@@ -8139,32 +8259,12 @@ static boolean P_LoadAddon(UINT16 wadnum, UINT16 numlumps)
 
 boolean P_AddWadFile(const char *wadfilename)
 {
-	UINT16 numlumps, wadnum;
-
-	// Init file.
-	if ((numlumps = W_InitFile(wadfilename, false, false)) == INT16_MAX)
-	{
-		refreshdirmenu |= REFRESHDIR_NOTLOADED;
-		return false;
-	}
-	else
-		wadnum = (UINT16)(numwadfiles-1);
-
-	return P_LoadAddon(wadnum, numlumps);
+	return D_CheckPathAllowed(wadfilename, "tried to add file") &&
+		P_LoadAddon(W_InitFile(wadfilename, false, false));
 }
 
 boolean P_AddFolder(const char *folderpath)
 {
-	UINT16 numlumps, wadnum;
-
-	// Init file.
-	if ((numlumps = W_InitFolder(folderpath, false, false)) == INT16_MAX)
-	{
-		refreshdirmenu |= REFRESHDIR_NOTLOADED;
-		return false;
-	}
-	else
-		wadnum = (UINT16)(numwadfiles-1);
-
-	return P_LoadAddon(wadnum, numlumps);
+	return D_CheckPathAllowed(folderpath, "tried to add folder") &&
+		P_LoadAddon(W_InitFolder(folderpath, false, false));
 }
diff --git a/src/p_slopes.c b/src/p_slopes.c
index cf7807d4e50b9d0adb065202ca09e998e587e8f5..1c0ee81a7e9453d204049d2f0ab5986a07849edb 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -328,7 +328,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 
 	if(!frontfloor && !backfloor && !frontceil && !backceil)
 	{
-		CONS_Printf("line_SpawnViaLine: Slope special with nothing to do.\n");
+		CONS_Printf("line_SpawnViaLine: Unused slope special with nothing to do on line number %i\n", linenum);
 		return;
 	}
 
@@ -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;
 	}
@@ -869,7 +870,7 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope)
 	vector3_t slopemom, axis;
 	angle_t ang;
 
-	if (mo->standingslope->flags & SL_NOPHYSICS)
+	if (slope->flags & SL_NOPHYSICS)
 		return 0;
 
 	// If there's physics, time for launching.
@@ -899,6 +900,8 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope)
 		if (P_MobjFlip(thing)*(thing->momz) < 0) // falling, land on slope
 		{
 			thing->standingslope = slope;
+			P_SetPitchRollFromSlope(thing, slope);
+
 			if (!thing->player || !(thing->player->pflags & PF_BOUNCING))
 				thing->momz = -P_MobjFlip(thing);
 		}
@@ -915,6 +918,7 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope)
 		thing->momx = mom.x;
 		thing->momy = mom.y;
 		thing->standingslope = slope;
+		P_SetPitchRollFromSlope(thing, slope);
 		if (!thing->player || !(thing->player->pflags & PF_BOUNCING))
 			thing->momz = -P_MobjFlip(thing);
 	}
diff --git a/src/p_spec.c b/src/p_spec.c
index 4a91b8b8e84d4b15bd25bc75f9f0d56879cef9a8..28ecc60f4dedb5f67f9aa33e092232eb0b3782f6 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1741,14 +1741,13 @@ static boolean P_ActivateLinedefExecutorsInSector(line_t *triggerline, mobj_t *a
 
 /** Used by P_LinedefExecute to check a trigger linedef's conditions
   * The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
-  * Return false cancels P_LinedefExecute, this happens if a condition is not met.
   *
   * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
   * \param actor Object initiating the action; should not be NULL.
   * \param caller Sector in which the action was started. May be NULL.
   * \sa P_ProcessLineSpecial, P_LinedefExecute
   */
-boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
+void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
 {
 	INT16 specialtype = triggerline->special;
 
@@ -1761,12 +1760,12 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		if (caller->triggerer == TO_PLAYEREMERALDS)
 		{
 			if (!(ALL7EMERALDS(emeralds)))
-				return false;
+				return;
 		}
 		else if (caller->triggerer == TO_PLAYERNIGHTS)
 		{
 			if (!P_CheckPlayerMareOld(triggerline))
-				return false;
+				return;
 		}
 	}
 
@@ -1774,51 +1773,47 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	{
 		case 303:
 			if (!P_CheckPlayerRings(triggerline, actor))
-				return false;
+				return;
 			break;
 		case 305:
 			if (!(actor && actor->player && actor->player->charability == triggerline->args[1]))
-				return false;
+				return;
 			break;
 		case 309:
 			// Only red/blue team members can activate this.
 			if (!(actor && actor->player))
-				return false;
+				return;
 			if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? 1 : 2))
-				return false;
+				return;
 			break;
 		case 314:
 			if (!P_CheckPushables(triggerline, caller))
-				return false;
+				return;
 			break;
 		case 317:
 			{ // Unlockable triggers required
 				INT32 trigid = triggerline->args[1];
 
-				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
-					return false;
-				else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
+				if (trigid < 0 || trigid > 31) // limited by 32 bit variable
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
-					return false;
+					return;
 				}
 				else if (!(unlocktriggers & (1 << trigid)))
-					return false;
+					return;
 			}
 			break;
 		case 319:
 			{ // An unlockable itself must be unlocked!
 				INT32 unlockid = triggerline->args[1];
 
-				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
-					return false;
-				else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
+				if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
-					return false;
+					return;
 				}
-				else if (!(unlockables[unlockid-1].unlocked))
-					return false;
+				else if (!(serverGamedata->unlocked[unlockid-1]))
+					return;
 			}
 			break;
 		case 321:
@@ -1826,7 +1821,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (triggerline->callcount > 0)
 			{
 				if (--triggerline->callcount > 0)
-					return false;
+					return;
 			}
 			break;
 		case 323: // nightserize
@@ -1834,15 +1829,15 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		case 327: // nights lap
 		case 329: // nights egg capsule touch
 			if (!P_CheckNightsTriggerLine(triggerline, actor))
-				return false;
+				return;
 			break;
 		case 331:
 			if (!(actor && actor->player))
-				return false;
+				return;
 			if (!triggerline->stringargs[0])
-				return false;
+				return;
 			if (!(stricmp(triggerline->stringargs[0], skins[actor->player->skin].name) == 0) ^ !!(triggerline->args[1]))
-				return false;
+				return;
 			break;
 		case 334: // object dye
 			{
@@ -1850,22 +1845,22 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 				UINT16 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
 
 				if (!!(triggerline->args[1]) ^ (triggercolor != color))
-					return false;
+					return;
 			}
 			break;
 		case 337: // emerald check
 			if (!P_CheckEmeralds(triggerline->args[2], (UINT16)triggerline->args[1]))
-				return false;
+				return;
 			break;
 		case 340: // NiGHTS mare
 			if (!P_CheckPlayerMare(triggerline))
-				return false;
+				return;
 			break;
 		case 343: // gravity check
 			if (triggerline->args[1] == TMG_TEMPREVERSE && (!(actor->flags2 & MF2_OBJECTFLIP) != !(actor->player->powers[pw_gravityboots])))
-				return false;
+				return;
 			if ((triggerline->args[1] == TMG_NORMAL) != !(actor->eflags & MFE_VERTICALFLIP))
-				return false;
+				return;
 			break;
 		default:
 			break;
@@ -1876,7 +1871,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	/////////////////////////////////
 
 	if (!P_ActivateLinedefExecutorsInSector(triggerline, actor, caller))
-		return false;
+		return;
 
 	// "Trigger on X calls" linedefs reset if args[2] is set
 	if (specialtype == 321 && triggerline->args[2])
@@ -1909,8 +1904,6 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			&& triggerline->args[0] == TMT_ONCE)
 			triggerline->special = 0;
 	}
-
-	return true;
 }
 
 /** Runs a linedef executor.
@@ -1963,8 +1956,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		if (lines[masterline].special == 321 && lines[masterline].args[0] > TMXT_EACHTIMEMASK) // Trigger after X calls
 			continue;
 
-		if (!P_RunTriggerLinedef(&lines[masterline], actor, caller))
-			return; // cancel P_LinedefExecute if function returns false
+		P_RunTriggerLinedef(&lines[masterline], actor, caller); // Even if it fails, there might be more linedefs to trigger
 	}
 }
 
@@ -2569,11 +2561,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				// Change the music and apply position/fade operations
 				else
 				{
-					if (!line->stringargs[0])
-						break;
-
-					strncpy(mapmusname, line->stringargs[0], 7);
-					mapmusname[6] = 0;
+					if (!line->stringargs[0] || !strcmp(line->stringargs[0], "-"))
+						strcpy(mapmusname, "");
+					else
+					{
+						strncpy(mapmusname, line->stringargs[0], 7);
+						mapmusname[6] = 0;
+					}
 
 					mapmusflags = tracknum & MUSIC_TRACKMASK;
 					if (!(line->args[0] & TMM_NORELOAD))
@@ -2665,10 +2659,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				// This is not revoked until overwritten; awayviewtics is ignored
 				if (titlemapinaction)
 					titlemapcameraref = altview;
-				else
-				{
+				else if (!mo->player->awayviewtics || mo->player->awayviewmobj != altview) {
 					P_SetTarget(&mo->player->awayviewmobj, altview);
-					mo->player->awayviewtics = line->args[1];
+					
+					if (mo->player == &players[displayplayer])
+						P_ResetCamera(mo->player, &camera); // reset p1 camera on p1 getting an awayviewmobj
+					else if (splitscreen && mo->player == &players[secondarydisplayplayer])
+						P_ResetCamera(mo->player, &camera2);  // reset p2 camera on p2 getting an awayviewmobj
 				}
 
 				aim = udmf ? altview->spawnpoint->pitch : line->args[2];
@@ -2678,8 +2675,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				aim <<= 8;
 				if (titlemapinaction)
 					titlemapcameraref->cusval = (angle_t)aim;
-				else
+				else {
 					mo->player->awayviewaiming = (angle_t)aim;
+					mo->player->awayviewtics = line->args[1];
+				}
 			}
 			break;
 
@@ -2937,7 +2936,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 441: // Trigger unlockable
-			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
 			{
 				INT32 trigid = line->args[0];
 
@@ -2948,10 +2946,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					unlocktriggers |= 1 << trigid;
 
 					// Unlocked something?
-					if (M_UpdateUnlockablesAndExtraEmblems())
+					M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+					if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					{
 						S_StartSound(NULL, sfx_s3k68);
-						G_SaveGameData(); // only save if unlocked something
+						G_SaveGameData(clientGamedata); // only save if unlocked something
 					}
 				}
 			}
@@ -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
@@ -5596,17 +5594,17 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I
 	fflr->target = sec;
 	fflr->bottomheight = &sec2->floorheight;
 	fflr->bottompic = &sec2->floorpic;
-	fflr->bottomxoffs = &sec2->floor_xoffs;
-	fflr->bottomyoffs = &sec2->floor_yoffs;
-	fflr->bottomangle = &sec2->floorpic_angle;
+	fflr->bottomxoffs = &sec2->floorxoffset;
+	fflr->bottomyoffs = &sec2->flooryoffset;
+	fflr->bottomangle = &sec2->floorangle;
 
 	// Add the ceiling
 	fflr->topheight = &sec2->ceilingheight;
 	fflr->toppic = &sec2->ceilingpic;
 	fflr->toplightlevel = &sec2->lightlevel;
-	fflr->topxoffs = &sec2->ceiling_xoffs;
-	fflr->topyoffs = &sec2->ceiling_yoffs;
-	fflr->topangle = &sec2->ceilingpic_angle;
+	fflr->topxoffs = &sec2->ceilingxoffset;
+	fflr->topyoffs = &sec2->ceilingyoffset;
+	fflr->topangle = &sec2->ceilingangle;
 
 	// Add slopes
 	fflr->t_slope = &sec2->c_slope;
@@ -6122,16 +6120,16 @@ void P_ApplyFlatAlignment(sector_t *sector, angle_t flatangle, fixed_t xoffs, fi
 {
 	if (floor)
 	{
-		sector->floorpic_angle = flatangle;
-		sector->floor_xoffs += xoffs;
-		sector->floor_yoffs += yoffs;
+		sector->floorangle = flatangle;
+		sector->floorxoffset += xoffs;
+		sector->flooryoffset += yoffs;
 	}
 
 	if (ceiling)
 	{
-		sector->ceilingpic_angle = flatangle;
-		sector->ceiling_xoffs += xoffs;
-		sector->ceiling_yoffs += yoffs;
+		sector->ceilingangle = flatangle;
+		sector->ceilingxoffset += xoffs;
+		sector->ceilingyoffset += yoffs;
 	}
 
 }
@@ -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;
 				}
 
@@ -6883,71 +6880,6 @@ void P_SpawnSpecials(boolean fromnetsave)
 				}
 				break;
 
-			case 260: // GZDoom-like 3D Floor.
-				{
-					UINT8 dtype = lines[i].args[1] & 3;
-					UINT8 dflags1 = lines[i].args[1] - dtype;
-					UINT8 dflags2 = lines[i].args[2];
-					UINT8 dopacity = lines[i].args[3];
-					boolean isfog = false;
-
-					if (dtype == 0)
-						dtype = 1;
-
-					ffloorflags = FOF_EXISTS;
-
-					if (dflags2 & 1) ffloorflags |= FOF_NOSHADE; // Disable light effects (Means no shadowcast)
-					if (dflags2 & 2) ffloorflags |= FOF_DOUBLESHADOW; // Restrict light inside (Means doubleshadow)
-					if (dflags2 & 4) isfog = true; // Fog effect (Explicitly render like a fog block)
-
-					if (dflags1 & 4) ffloorflags |= FOF_BOTHPLANES|FOF_ALLSIDES; // Render-inside
-					if (dflags1 & 16) ffloorflags |= FOF_INVERTSIDES|FOF_INVERTPLANES; // Invert visibility rules
-
-					// Fog block
-					if (isfog)
-						ffloorflags |= FOF_RENDERALL|FOF_CUTEXTRA|FOF_CUTSPRITES|FOF_BOTHPLANES|FOF_EXTRA|FOF_FOG|FOF_INVERTPLANES|FOF_ALLSIDES|FOF_INVERTSIDES;
-					else
-					{
-						ffloorflags |= FOF_RENDERALL;
-
-						// Solid
-						if (dtype == 1)
-							ffloorflags |= FOF_SOLID|FOF_CUTLEVEL;
-						// Water
-						else if (dtype == 2)
-							ffloorflags |= FOF_SWIMMABLE|FOF_CUTEXTRA|FOF_CUTSPRITES|FOF_EXTRA|FOF_RIPPLE;
-						// Intangible
-						else if (dtype == 3)
-							ffloorflags |= FOF_CUTEXTRA|FOF_CUTSPRITES|FOF_EXTRA;
-					}
-
-					// Non-opaque
-					if (dopacity < 255)
-					{
-						// Invisible
-						if (dopacity == 0)
-						{
-							// True invisible
-							if (ffloorflags & FOF_NOSHADE)
-								ffloorflags &= ~(FOF_RENDERALL|FOF_CUTEXTRA|FOF_CUTSPRITES|FOF_EXTRA|FOF_BOTHPLANES|FOF_ALLSIDES|FOF_CUTLEVEL);
-							// Shadow block
-							else
-							{
-								ffloorflags |= FOF_CUTSPRITES;
-								ffloorflags &= ~(FOF_RENDERALL|FOF_CUTEXTRA|FOF_EXTRA|FOF_BOTHPLANES|FOF_ALLSIDES|FOF_CUTLEVEL);
-							}
-						}
-						else
-						{
-							ffloorflags |= FOF_TRANSLUCENT|FOF_CUTEXTRA|FOF_EXTRA;
-							ffloorflags &= ~FOF_CUTLEVEL;
-						}
-					}
-
-					P_AddFakeFloorsByLine(i, dopacity, TMB_TRANSLUCENT, ffloorflags, secthinkers);
-				}
-				break;
-
 			case 300: // Trigger linedef executor
 			case 303: // Count rings
 			case 305: // Character ability
@@ -7366,14 +7298,14 @@ void T_Scroll(scroll_t *s)
 
 		case sc_floor: // scroll floor texture
 			sec = sectors + s->affectee;
-			sec->floor_xoffs += dx;
-			sec->floor_yoffs += dy;
+			sec->floorxoffset += dx;
+			sec->flooryoffset += dy;
 			break;
 
 		case sc_ceiling: // scroll ceiling texture
 			sec = sectors + s->affectee;
-			sec->ceiling_xoffs += dx;
-			sec->ceiling_yoffs += dy;
+			sec->ceilingxoffset += dx;
+			sec->ceilingyoffset += dy;
 			break;
 
 		case sc_carry:
@@ -7821,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;
@@ -7915,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;
@@ -7927,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))
@@ -8027,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)
@@ -8047,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;
 		}
 	}
 
@@ -8077,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);
@@ -8098,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_spec.h b/src/p_spec.h
index 91dfccb70fabb355f1111f5e38ce9cec35876fca..50ab6410f145b0d2ad180b492525cec51d786365 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -527,7 +527,7 @@ boolean P_IsMobjTouchingPolyobj(mobj_t *mo, polyobj_t *po, sector_t *polysec);
 
 void P_SwitchWeather(INT32 weathernum);
 
-boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
+void P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller);
 void P_RunNightserizeExecutors(mobj_t *actor);
 void P_RunDeNightserizeExecutors(mobj_t *actor);
@@ -895,7 +895,7 @@ typedef struct
 
 typedef enum
 {
-	ok,
+	planeok,
 	crushed,
 	pastdest
 } result_e;
diff --git a/src/p_tick.c b/src/p_tick.c
index 0357258e812a57d05668f8221371098a0fa52373..ec5d8a2da0a487a6e4743e39dedc04c2fb27069d 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -30,6 +30,10 @@
 // Object place
 #include "m_cheat.h"
 
+#ifdef PARANOIA
+#include "deh_tables.h" // MOBJTYPE_LIST
+#endif
+
 tic_t leveltime;
 
 //
@@ -211,8 +215,49 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 	thlist[n].prev = thinker;
 
 	thinker->references = 0;    // killough 11/98: init reference counter to 0
+
+#ifdef PARANOIA
+	thinker->debug_mobjtype = MT_NULL;
+#endif
 }
 
+#ifdef PARANOIA
+static const char *MobjTypeName(const mobj_t *mobj)
+{
+	actionf_p1 p1 = mobj->thinker.function.acp1;
+
+	if (p1 == (actionf_p1)P_MobjThinker)
+	{
+		return MOBJTYPE_LIST[mobj->type];
+	}
+	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed)
+	{
+		if (mobj->thinker.debug_mobjtype != MT_NULL)
+		{
+			return MOBJTYPE_LIST[mobj->thinker.debug_mobjtype];
+		}
+	}
+
+	return "<Not a mobj>";
+}
+
+static const char *MobjThinkerName(const mobj_t *mobj)
+{
+	actionf_p1 p1 = mobj->thinker.function.acp1;
+
+	if (p1 == (actionf_p1)P_MobjThinker)
+	{
+		return "P_MobjThinker";
+	}
+	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed)
+	{
+		return "P_RemoveThinkerDelayed";
+	}
+
+	return "<Unknown Thinker>";
+}
+#endif
+
 //
 // killough 11/98:
 //
@@ -234,20 +279,34 @@ static thinker_t *currentthinker;
 void P_RemoveThinkerDelayed(thinker_t *thinker)
 {
 	thinker_t *next;
-#ifdef PARANOIA
-#define BEENAROUNDBIT (0x40000000) // has to be sufficiently high that it's unlikely to happen in regular gameplay. If you change this, pay attention to the bit pattern of INT32_MIN.
-	if (thinker->references & ~BEENAROUNDBIT)
+
+	if (thinker->references != 0)
 	{
-		if (thinker->references & BEENAROUNDBIT) // Usually gets cleared up in one frame; what's going on here, then?
-			CONS_Printf("Number of potentially faulty references: %d\n", (thinker->references & ~BEENAROUNDBIT));
-		thinker->references |= BEENAROUNDBIT;
+#ifdef PARANOIA
+		if (thinker->debug_time > leveltime)
+		{
+			thinker->debug_time = leveltime + 2; // do not print errors again
+		}
+		// Removed mobjs can be the target of another mobj. In
+		// that case, the other mobj will manage its reference
+		// to the removed mobj in P_MobjThinker. However, if
+		// the removed mobj is removed after the other object
+		// thinks, the reference management is delayed by one
+		// tic.
+		else if (thinker->debug_time < leveltime)
+		{
+			CONS_Printf(
+					"PARANOIA/P_RemoveThinkerDelayed: %p %s references=%d\n",
+					(void*)thinker,
+					MobjTypeName((mobj_t*)thinker),
+					thinker->references
+			);
+
+			thinker->debug_time = leveltime + 2; // do not print this error again
+		}
+#endif
 		return;
 	}
-#undef BEENAROUNDBIT
-#else
-	if (thinker->references)
-		return;
-#endif
 
 	/* Remove from main thinker list */
 	next = thinker->next;
@@ -291,12 +350,45 @@ void P_RemoveThinker(thinker_t *thinker)
  * references, and delay removal until the count is 0.
  */
 
-mobj_t *P_SetTarget(mobj_t **mop, mobj_t *targ)
+mobj_t *P_SetTarget2(mobj_t **mop, mobj_t *targ
+#ifdef PARANOIA
+		, const char *source_file, int source_line
+#endif
+)
 {
-	if (*mop)              // If there was a target already, decrease its refcount
+	if (*mop) // If there was a target already, decrease its refcount
+	{
 		(*mop)->thinker.references--;
-if ((*mop = targ) != NULL) // Set new target and if non-NULL, increase its counter
+
+#ifdef PARANOIA
+		if ((*mop)->thinker.references < 0)
+		{
+			CONS_Printf(
+					"PARANOIA/P_SetTarget: %p %s %s references=%d, references go negative! (%s:%d)\n",
+					(void*)*mop,
+					MobjTypeName(*mop),
+					MobjThinkerName(*mop),
+					(*mop)->thinker.references,
+					source_file,
+					source_line
+			);
+		}
+
+		(*mop)->thinker.debug_time = leveltime;
+#endif
+	}
+
+	if (targ != NULL) // Set new target and if non-NULL, increase its counter
+	{
 		targ->thinker.references++;
+
+#ifdef PARANOIA
+		targ->thinker.debug_time = leveltime;
+#endif
+	}
+
+	*mop = targ;
+
 	return targ;
 }
 
@@ -675,7 +767,10 @@ void P_Ticker(boolean run)
 
 	// Keep track of how long they've been playing!
 	if (!demoplayback) // Don't increment if a demo is playing.
-		totalplaytime++;
+	{
+		clientGamedata->totalplaytime++;
+		serverGamedata->totalplaytime++;
+	}
 
 	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
diff --git a/src/p_tick.h b/src/p_tick.h
index 594bbc7afb608ce88a54b8f63ae7e5494f96af4f..bbc227e081433652a9a3d79b8ab0513c531677bc 100644
--- a/src/p_tick.h
+++ b/src/p_tick.h
@@ -14,6 +14,8 @@
 #ifndef __P_TICK__
 #define __P_TICK__
 
+#include "doomdef.h"
+
 #ifdef __GNUG__
 #pragma interface
 #endif
@@ -28,6 +30,17 @@ void P_Ticker(boolean run);
 void P_PreTicker(INT32 frames);
 void P_DoTeamscrambling(void);
 void P_RemoveThinkerDelayed(thinker_t *thinker); //killed
-mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target);   // killough 11/98
+
+mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target
+#ifdef PARANOIA
+		, const char *source_file, int source_line
+#endif
+);
+
+#ifdef PARANOIA
+#define P_SetTarget(...) P_SetTarget2(__VA_ARGS__, __FILE__, __LINE__)
+#else
+#define P_SetTarget P_SetTarget2
+#endif
 
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index d441a7e81d8bdee512d62dc1cbdff4abc9e7e188..3b2c60e3a6d744d5da157e0a5952efd5ad0cf3d4 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -398,6 +398,7 @@ void P_GiveFinishFlags(player_t *player)
 		angle += FixedAngle(120*FRACUNIT);
 
 		P_SetTarget(&flag->target, player->mo);
+		P_SetTarget(&flag->dontdrawforviewmobj, player->mo); // Hide the flag in first-person
 	}
 }
 
@@ -676,7 +677,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->marebonuslap = 0;
 	player->flyangle = 0;
 	player->anotherflyangle = 0;
-	player->mo->rollangle = 0;
+	player->mo->spriteroll = 0;
 
 	P_SetTarget(&player->mo->target, NULL);
 	P_SetTarget(&player->axis1, P_SetTarget(&player->axis2, NULL));
@@ -802,7 +803,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->secondjump = 0;
 	player->flyangle = 0;
 	player->anotherflyangle = 0;
-	player->mo->rollangle = 0;
+	player->mo->spriteroll = 0;
 
 	player->powers[pw_shield] = SH_NONE;
 	player->powers[pw_super] = 0;
@@ -882,8 +883,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			}
 
 			// Add score to leaderboards now
-			if (!(netgame||multiplayer) && P_IsLocalPlayer(&players[i]))
-				G_AddTempNightsRecords(players[i].marescore, leveltime - player->marebegunat, players[i].mare + 1);
+			G_AddTempNightsRecords(player, players[i].marescore, leveltime - player->marebegunat, players[i].mare + 1);
 
 			// transfer scores anyway
 			players[i].totalmarescore += players[i].marescore;
@@ -909,8 +909,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		player->finishedrings = (INT16)(player->rings);
 
 		// Add score to temp leaderboards
-		if (!(netgame||multiplayer) && P_IsLocalPlayer(player))
-			G_AddTempNightsRecords(player->marescore, leveltime - player->marebegunat, (UINT8)(oldmare + 1));
+		G_AddTempNightsRecords(player, player->marescore, leveltime - player->marebegunat, (UINT8)(oldmare + 1));
 
 		// Starting a new mare, transfer scores
 		player->totalmarescore += player->marescore;
@@ -989,6 +988,8 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 	if (player->powers[pw_carry] == CR_ROPEHANG)
 		P_SetTarget(&player->mo->tracer, NULL);
 
+	player->powers[pw_strong] = STR_NONE;
+
 	{
 		angle_t ang;
 		fixed_t fallbackspeed;
@@ -1106,6 +1107,7 @@ void P_ResetPlayer(player_t *player)
 boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 {
 	fixed_t bottomheight, topheight;
+	boolean allatk = ((player->powers[pw_strong] & STR_PUNCH) && (player->powers[pw_strong] & STR_TAIL) && (player->powers[pw_strong] & STR_STOMP) && (player->powers[pw_strong] & STR_UPPER));
 
 	if (!player->mo || player->spectator || !thing || P_MobjWasRemoved(thing))
 		return false;
@@ -1130,22 +1132,33 @@ boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 
 	// Jumping.
 	if ((player->pflags & PF_JUMPED)
-	&& (!(player->pflags & PF_NOJUMPDAMAGE)
-		|| (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+	&& (!(player->pflags & PF_NOJUMPDAMAGE)))
 		return true;
 
 	// Spinning.
 	if (player->pflags & PF_SPINNING)
 		return true;
 
-	if (player->dashmode >= DASHMODE_THRESHOLD && (player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE))
+	// Shield stomp.
+	if (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY))
+		return true;
+
+	// pw_strong checks below here
+
+	// Omnidirectional attacks.
+	if (allatk || (player->powers[pw_strong] & STR_DASH))
 		return true;
 
 	// From the front.
-	if (((player->pflags & PF_GLIDING) || (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+	if ((player->powers[pw_strong] & STR_PUNCH)
 	&& (player->drawangle - R_PointToAngle2(player->mo->x - player->mo->momx, player->mo->y - player->mo->momy, thing->x, thing->y) +  + ANGLE_90) < ANGLE_180)
 		return true;
 
+	// From the back.
+	if ((player->powers[pw_strong] & STR_TAIL)
+	&& (player->drawangle - R_PointToAngle2(player->mo->x - player->mo->momx, player->mo->y - player->mo->momy, thing->x, thing->y) +  + ANGLE_90) >= ANGLE_180)
+		return true;
+
 	// From the top/bottom.
 	bottomheight = player->mo->z;
 	topheight = player->mo->z + player->mo->height;
@@ -1159,19 +1172,15 @@ boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 
 	if (P_MobjFlip(player->mo)*(bottomheight - (thing->z + thing->height/2)) > 0)
 	{
-		if ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) < 0))
+		if ((player->charflags & SF_STOMPDAMAGE || player->powers[pw_strong] & STR_STOMP) && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) < 0))
 			return true;
 	}
 	else if (P_MobjFlip(player->mo)*(topheight - (thing->z + thing->height/2)) < 0)
 	{
-		if (player->charability == CA_FLY && player->panim == PA_ABILITY && !(player->mo->eflags & MFE_UNDERWATER) && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) > 0))
+		if ((player->powers[pw_strong] & STR_UPPER) && (player->mo->sprite2 != SPR2_SWIM) && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) > 0))
 			return true;
 	}
 
-	// Shield stomp.
-	if (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY))
-		return true;
-
 	return false;
 }
 
@@ -1430,6 +1439,10 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 	if (player->score > MAXSCORE)
 		player->score = MAXSCORE;
 
+	player->recordscore += amount;
+	if (player->recordscore > MAXSCORE)
+		player->recordscore = MAXSCORE;
+
 	// check for extra lives every 50000 pts
 	if (!ultimatemode && !modeattacking && player->score > oldscore && player->score % 50000 < amount && (gametyperules & GTR_LIVES))
 	{
@@ -1828,6 +1841,7 @@ void P_SpawnShieldOrb(player_t *player)
 	shieldobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, orbtype);
 	shieldobj->flags2 |= MF2_SHIELD;
 	P_SetTarget(&shieldobj->target, player->mo);
+	P_SetTarget(&shieldobj->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
 	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK)
 	{
 		shieldobj->color = SKINCOLOR_PINK;
@@ -1841,6 +1855,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);
 	}
@@ -1848,12 +1863,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)
@@ -1952,6 +1969,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	mobj_t *ghost = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_GHOST);
 
 	P_SetTarget(&ghost->target, mobj);
+	P_SetTarget(&ghost->dontdrawforviewmobj, mobj); // Hide the ghost in first-person
 
 	P_SetScale(ghost, mobj->scale);
 	ghost->destscale = mobj->scale;
@@ -1966,7 +1984,9 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	ghost->colorized = mobj->colorized; // alternatively, "true" for sonic advance style colourisation
 
 	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
-	ghost->rollangle = mobj->rollangle;
+	ghost->roll = mobj->roll;
+	ghost->pitch = mobj->pitch;
+	ghost->spriteroll = mobj->spriteroll;
 
 	ghost->sprite = mobj->sprite;
 	ghost->sprite2 = mobj->sprite2;
@@ -1985,15 +2005,17 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 
 	ghost->fuse = ghost->info->damage;
 	ghost->skin = mobj->skin;
+	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 target
 		ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
 	}
 
@@ -2004,6 +2026,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	ghost->old_angle = (mobj->player ? mobj->player->old_drawangle2 : mobj->old_angle2);
 	ghost->old_pitch = mobj->old_pitch2;
 	ghost->old_roll = mobj->old_roll2;
+	ghost->old_spriteroll = mobj->old_spriteroll2;
 
 	return ghost;
 }
@@ -2333,6 +2356,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 					player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
 					S_StartSound(player->mo, sfx_s3k8b);
 					player->pflags |= PF_FULLSTASIS;
+					player->powers[pw_strong] = STR_MELEE;
 
 					// hearticles
 					if (type)
@@ -2557,17 +2581,13 @@ static boolean P_PlayerCanBust(player_t *player, ffloor_t *rover)
 			return true;
 
 		// Passive wall breaking
-		if (player->charflags & SF_CANBUSTWALLS)
+		if (player->charflags & SF_CANBUSTWALLS || player->powers[pw_strong] & (STR_WALL|STR_FLOOR|STR_CEILING|STR_DASH))
 			return true;
 
 		// Super
 		if (player->powers[pw_super])
 			return true;
 
-		// Dashmode
-		if ((player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE) && player->dashmode >= DASHMODE_THRESHOLD)
-			return true;
-
 		// NiGHTS drill
 		if (player->pflags & PF_DRILLING)
 			return true;
@@ -2578,21 +2598,11 @@ static boolean P_PlayerCanBust(player_t *player, ffloor_t *rover)
 
 		/* FALLTHRU */
 	case BT_STRONG: // Requires a "strong ability"
-		if (player->charflags & SF_CANBUSTWALLS)
-			return true;
-
-		if (player->pflags & PF_BOUNCING)
-			return true;
-
-		if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
-			return true;
-
-		if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+		if (player->charflags & SF_CANBUSTWALLS || player->powers[pw_strong] & (STR_WALL|STR_FLOOR|STR_CEILING))
 			return true;
 
 		break;
 	}
-
 	return false;
 }
 
@@ -2608,7 +2618,7 @@ static void P_CheckBustableBlocks(player_t *player)
 	oldx = player->mo->x;
 	oldy = player->mo->y;
 
-	if (!(player->pflags & PF_BOUNCING)) // Bouncers only get to break downwards, not sideways
+	if (!((player->powers[pw_strong] & (STR_FLOOR|STR_CEILING)) && (!(player->powers[pw_strong] & STR_WALL)) && (!(player->charflags & SF_CANBUSTWALLS)))) // Don't break sideways without wall powers
 	{
 		P_UnsetThingPosition(player->mo);
 		player->mo->x += player->mo->momx;
@@ -2635,8 +2645,24 @@ static void P_CheckBustableBlocks(player_t *player)
 			topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 			bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
-			if (((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
-			|| ((P_MobjFlip(player->mo)*player->mo->momz < 0) && (player->pflags & PF_BOUNCING || ((player->charability2 == CA2_MELEE) && (player->panim == PA_ABILITY2)))))
+			// Height checks
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+			{
+				if ((player->powers[pw_strong] & STR_FLOOR) && (!(player->powers[pw_strong] & STR_CEILING)) && player->mo->z > topheight)
+					continue;
+
+				if ((player->powers[pw_strong] & STR_CEILING) && (!(player->powers[pw_strong] & STR_FLOOR)) && player->mo->z + player->mo->height < bottomheight)
+					continue;
+			}
+			else
+			{
+				if ((player->powers[pw_strong] & STR_FLOOR) && (!(player->powers[pw_strong] & STR_CEILING)) && player->mo->z < bottomheight)
+					continue;
+
+				if ((player->powers[pw_strong] & STR_CEILING) && (!(player->powers[pw_strong] & STR_FLOOR)) && player->mo->z + player->mo->height > topheight)
+					continue;
+			}
+			if (player->powers[pw_strong] & (STR_FLOOR|STR_CEILING))
 			{
 				topheight -= player->mo->momz;
 				bottomheight -= player->mo->momz;
@@ -2704,7 +2730,7 @@ static void P_CheckBustableBlocks(player_t *player)
 		}
 	}
 bustupdone:
-	if (!(player->pflags & PF_BOUNCING))
+	if (!((player->powers[pw_strong] & (STR_FLOOR|STR_CEILING)) && (!(player->powers[pw_strong] & STR_WALL)) && (!(player->charflags & SF_CANBUSTWALLS))))
 	{
 		P_UnsetThingPosition(player->mo);
 		player->mo->x = oldx;
@@ -2984,7 +3010,7 @@ static void P_CheckInvincibilityTimer(player_t *player)
 		return;
 
 	if (mariomode && !player->powers[pw_super])
-		player->mo->color = (UINT16)(SKINCOLOR_RUBY + (leveltime % (numskincolors - SKINCOLOR_RUBY))); // Passes through all saturated colours
+		player->mo->color = (UINT16)(SKINCOLOR_RUBY + (leveltime % (FIRSTSUPERCOLOR - SKINCOLOR_RUBY))); // Passes through all saturated colours
 	else if (leveltime % (TICRATE/7) == 0)
 	{
 		mobj_t *sparkle = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_IVSP);
@@ -3104,39 +3130,41 @@ static void P_DoBubbleBreath(player_t *player)
 //
 static void P_DoPlayerHeadSigns(player_t *player)
 {
+	mobj_t *sign = NULL;
+
 	if (G_TagGametype())
 	{
-		// If you're "IT", show a big "IT" over your head for others to see.
-		if (player->pflags & PF_TAGIT && !P_IsLocalPlayer(player))
+		// If you're "IT", show a big "IT" over your head for others to see, including spectators
+		// (and even yourself if you spectate someone else).
+		if (player->pflags & PF_TAGIT && (!P_IsLocalPlayer(player) || consoleplayer != displayplayer || splitscreen))
 		{
-			mobj_t* it = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TAG);
-			it->x = player->mo->x;
-			it->y = player->mo->y;
-			it->z = player->mo->z;
-			it->old_x = player->mo->old_x;
-			it->old_y = player->mo->old_y;
-			it->old_z = player->mo->old_z;
+			sign = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TAG);
+			sign->x = player->mo->x;
+			sign->y = player->mo->y;
+			sign->z = player->mo->z;
+			sign->old_x = player->mo->old_x;
+			sign->old_y = player->mo->old_y;
+			sign->old_z = player->mo->old_z;
 
 			if (!(player->mo->eflags & MFE_VERTICALFLIP))
 			{
-				it->z += player->mo->height;
-				it->old_z += player->mo->height;
+				sign->z += player->mo->height;
+				sign->old_z += player->mo->height;
 			}
 			else
 			{
-				it->z -= mobjinfo[MT_TAG].height;
-				it->old_z -= mobjinfo[MT_TAG].height;
+				sign->z -= mobjinfo[MT_TAG].height;
+				sign->old_z -= mobjinfo[MT_TAG].height;
 			}
 		}
 	}
 	else if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) // If you have the flag (duh).
 	{
-		// Spawn a got-flag message over the head of the player that
-		// has it (but not on your own screen if you have the flag).
-		if (splitscreen || player != &players[consoleplayer])
+		// Spawn a got-flag message over the head of the player that has it
+		// (but not on your own screen if you have the flag, unless you're spectating).
+		if (!P_IsLocalPlayer(player) || consoleplayer != displayplayer || splitscreen)
 		{
 			fixed_t zofs;
-			mobj_t *sign;
 			boolean player_is_flipped = (player->mo->eflags & MFE_VERTICALFLIP) > 0;
 
 			zofs = player->mo->momz;
@@ -3168,6 +3196,43 @@ static void P_DoPlayerHeadSigns(player_t *player)
 				sign->frame = 2|FF_FULLBRIGHT;
 		}
 	}
+
+	if (!P_MobjWasRemoved(sign) && splitscreen) // Hide the sign from yourself in splitscreen - In single-screen, it wouldn't get spawned if it shouldn't be visible
+	{
+		if (player == &players[displayplayer])
+			sign->drawonlyforplayer = &players[secondarydisplayplayer];
+		else
+			sign->drawonlyforplayer = &players[displayplayer];
+
+#ifdef QUADS
+		if (splitscreen > 1) // Can be seen by at least two local views, so we need an extra copy of the sign
+		{
+			UINT32 signframe = sign->frame; // Copy the flag frame
+			sign = P_SpawnMobjFromMobj(sign, 0, 0, 0, MT_TAG);
+			if (P_MobjWasRemoved(sign))
+				return;
+
+			sign->frame = signframe;
+			if (player == &players[displayplayer] || player == &players[secondarydisplayplayer])
+				sign->drawonlyforplayer = &players[thirddisplayplayer];
+			else
+				sign->drawonlyforplayer = &players[secondarydisplayplayer];
+
+			if (splitscreen > 2) // Can be seen by three local views
+			{
+				sign = P_SpawnMobjFromMobj(sign, 0, 0, 0, MT_TAG);
+				if (P_MobjWasRemoved(sign))
+					return;
+
+				sign->frame = signframe;
+				if (player != &players[fourthdisplayplayer])
+					sign->drawonlyforplayer = &players[fourthdisplayplayer];
+				else
+					sign->drawonlyforplayer = &players[thirddisplayplayer];
+			}
+		}
+#endif
+	}
 }
 
 //
@@ -4688,10 +4753,11 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						mobj_t *lockon = P_LookForEnemies(player, false, true);
 						if (lockon)
 						{
-							if (P_IsLocalPlayer(player)) // Only display it on your own view.
+							if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 							{
 								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 								P_SetTarget(&visual->target, lockon);
+								visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 							}
 						}
 						if ((cmd->buttons & BT_SPIN) && !(player->pflags & PF_SPINDOWN))
@@ -4769,6 +4835,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						player->mo->momx += player->cmomx;
 						player->mo->momy += player->cmomy;
 						P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
+						player->powers[pw_strong] = STR_MELEE;
 						S_StartSound(player->mo, sfx_s3k42);
 					}
 					player->pflags |= PF_SPINDOWN;
@@ -5016,6 +5083,7 @@ static void P_DoTwinSpin(player_t *player)
 	S_StartSound(player->mo, sfx_s3k42);
 	player->mo->frame = 0;
 	P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+	player->powers[pw_strong] = STR_TWINSPIN;
 }
 
 //
@@ -5033,7 +5101,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 		{
 			if ((lockonshield = P_LookForEnemies(player, false, false)))
 			{
-				if (P_IsLocalPlayer(player)) // Only display it on your own view.
+				if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 				{
 					boolean dovis = true;
 					if (lockonshield == lockonthok)
@@ -5047,6 +5115,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 					{
 						visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 						P_SetTarget(&visual->target, lockonshield);
+						visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 						P_SetMobjStateNF(visual, visual->info->spawnstate+1);
 					}
 				}
@@ -5150,10 +5219,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY)) && (lockonthok = P_LookForEnemies(player, true, false)))
 	{
-		if (P_IsLocalPlayer(player)) // Only display it on your own view.
+		if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 		{
 			visual = P_SpawnMobj(lockonthok->x, lockonthok->y, lockonthok->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 			P_SetTarget(&visual->target, lockonthok);
+			visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 		}
 	}
 
@@ -5383,6 +5453,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							player->pflags |= PF_THOKKED;
 						else
 							player->pflags |= (PF_THOKKED|PF_CANCARRY);
+						player->powers[pw_strong] = STR_FLY;
 					}
 					break;
 				case CA_GLIDEANDCLIMB:
@@ -5409,7 +5480,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
 						if (playerspeed < glidespeed)
 							P_Thrust(player->mo, player->mo->angle, glidespeed - playerspeed);
-						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
+						player->pflags &= ~(PF_JUMPED|PF_SPINNING|PF_STARTDASH);
+						player->powers[pw_strong] = STR_GLIDE;
 					}
 					break;
 				case CA_DOUBLEJUMP: // Double-Jump
@@ -5469,6 +5541,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
 						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING);
 						player->pflags |= PF_THOKKED|PF_BOUNCING;
+						player->powers[pw_strong] = STR_BOUNCE;
 						player->mo->momx >>= 1;
 						player->mo->momy >>= 1;
 						player->mo->momz >>= 1;
@@ -6772,9 +6845,9 @@ static void P_DoNiGHTSCapsule(player_t *player)
 	{
 		if ((player->mo->state == &states[S_PLAY_NIGHTS_PULL])
 		&& (player->mo->sprite2 == SPR2_NPUL))
-			player->mo->rollangle -= ANG30;
+			player->mo->spriteroll -= ANG30;
 		else
-			player->mo->rollangle = 0;
+			player->mo->spriteroll = 0;
 	}
 
 	if (G_IsSpecialStage(gamemap))
@@ -7227,7 +7300,7 @@ static void P_NiGHTSMovement(player_t *player)
 		&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6])
 	{
 		player->mo->momx = player->mo->momy = player->mo->momz = 0;
-		player->mo->rollangle = 0;
+		player->mo->spriteroll = 0;
 		return;
 	}
 
@@ -7245,7 +7318,7 @@ static void P_NiGHTSMovement(player_t *player)
 		{
 			if (player->mo->state != &states[S_PLAY_NIGHTS_DRILL])
 				P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_DRILL);
-			player->mo->rollangle = ANGLE_90;
+			player->mo->spriteroll = ANGLE_90;
 		}
 		else
 #endif
@@ -7578,9 +7651,9 @@ static void P_NiGHTSMovement(player_t *player)
 		P_SetPlayerMobjState(player->mo, flystate);
 
 	if (player->charflags & SF_NONIGHTSROTATION)
-		player->mo->rollangle = 0;
+		player->mo->spriteroll = 0;
 	else
-		player->mo->rollangle = rollangle;
+		player->mo->spriteroll = rollangle;
 
 	P_SetPlayerAngle(player, player->mo->angle);
 
@@ -8499,12 +8572,12 @@ void P_MovePlayer(player_t *player)
 		}
 	}
 
-	// End your chain if you're on the ground or climbing a wall.
+	// End your chain if you're on the ground while not rolling, or climbing a wall.
 	// But not if invincible! Allow for some crazy long chains with it.
 	// Also keep in mind the PF_JUMPED check.
 	// If we lacked this, stepping up while jumping up would reset score.
 	// (for instance, when climbing up off a wall.)
-	if ((onground || player->climbing) && !(player->pflags & PF_JUMPED) && player->powers[pw_invulnerability] <= 1)
+	if ((onground || player->climbing) && ((player->pflags & (PF_STARTDASH|PF_SPINNING)) != PF_SPINNING) && !(player->pflags & PF_JUMPED) && player->powers[pw_invulnerability] <= 1)
 		P_ResetScore(player);
 
 	// Show the "THOK!" graphic when spinning quickly across the ground. (even applies to non-spinners, in the case of zoom tubes)
@@ -8647,7 +8720,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)
@@ -9086,10 +9162,6 @@ mobj_t *P_LookForFocusTarget(player_t *player, mobj_t *exclude, SINT8 direction,
 
 		switch (mo->type)
 		{
-		case MT_TNTBARREL:
-			if (lockonflags & LOCK_INTERESTS)
-				break;
-			/*FALLTHRU*/
 		case MT_PLAYER: // Don't chase other players!
 		case MT_DETON:
 			continue; // Don't be STUPID, Sonic!
@@ -9110,17 +9182,13 @@ mobj_t *P_LookForFocusTarget(player_t *player, mobj_t *exclude, SINT8 direction,
 			/*FALLTHRU*/
 		default:
 
-			if ((lockonflags & LOCK_BOSS) && ((mo->flags & (MF_BOSS|MF_SHOOTABLE)) == (MF_BOSS|MF_SHOOTABLE))) // allows if it has the flags desired XOR it has the invert aimable flag
-			{
-				if (mo->flags2 & MF2_FRET)
-					continue;
+			if ((lockonflags & LOCK_BOSS) && (mo->flags & MF_BOSS)) // always allow targeting bosses
 				break;
-			}
 
 			if ((lockonflags & LOCK_ENEMY) && (!((mo->flags & (MF_ENEMY|MF_SHOOTABLE)) == (MF_ENEMY|MF_SHOOTABLE)) != !(mo->flags2 & MF2_INVERTAIMABLE))) // allows if it has the flags desired XOR it has the invert aimable flag
 				break;
 
-			if ((lockonflags & LOCK_INTERESTS) && (mo->flags & (MF_PUSHABLE|MF_MONITOR))) // allows if it has the flags desired XOR it has the invert aimable flag
+			if ((lockonflags & LOCK_INTERESTS) && (mo->flags & (MF_PUSHABLE|MF_MONITOR))) // allows if it has the flags desired
 				break;
 
 			continue; // not a valid object
@@ -9225,7 +9293,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 
 		{
 			fixed_t zdist = (player->mo->z + player->mo->height/2) - (mo->z + mo->height/2);
-			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
+			dist = R_PointToDist2(0, 0, player->mo->x-mo->x, player->mo->y-mo->y);
 			if (bullet)
 			{
 				if ((R_PointToAngle2(0, 0, dist, zdist) + span) > span*2)
@@ -9242,7 +9310,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 					continue;
 			}
 
-			dist = P_AproxDistance(dist, zdist);
+			dist = R_PointToDist2(0, 0, dist, zdist);
 			if (dist > maxdist)
 				continue; // out of range
 		}
@@ -11068,17 +11136,30 @@ static void P_MinecartThink(player_t *player)
 
 			// Mark interpolation; the old positions need to be relative to the displacement from the minecart _after_ it's moved.
 			// This isn't quite correct (it captures the landing wobble) but it works well enough
+			// Additionally, hide other players' marks
 			if (detleft)
 			{
-				detleft->old_x = detleft->x - (minecart->old_x - minecart->old_x2);
-				detleft->old_y = detleft->y - (minecart->old_y - minecart->old_y2);
-				detleft->old_z = detleft->z - (minecart->old_z - minecart->old_z2);
+				if (P_IsLocalPlayer(player))
+				{
+					detleft->old_x = detleft->x - (minecart->old_x - minecart->old_x2);
+					detleft->old_y = detleft->y - (minecart->old_y - minecart->old_y2);
+					detleft->old_z = detleft->z - (minecart->old_z - minecart->old_z2);
+					detleft->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
+				}
+				else // Don't see others' marks when spectating others
+					P_RemoveMobj(detleft); // Lock-on markers are only spawned client-side, so this SHOULD be safe too...
 			}
 			if (detright)
 			{
-				detright->old_x = detright->x - (minecart->old_x - minecart->old_x2);
-				detright->old_y = detright->y - (minecart->old_y - minecart->old_y2);
-				detright->old_z = detright->z - (minecart->old_z - minecart->old_z2);
+				if (P_IsLocalPlayer(player))
+				{
+					detright->old_x = detright->x - (minecart->old_x - minecart->old_x2);
+					detright->old_y = detright->y - (minecart->old_y - minecart->old_y2);
+					detright->old_z = detright->z - (minecart->old_z - minecart->old_z2);
+					detright->drawonlyforplayer = player;
+				}
+				else
+					P_RemoveMobj(detleft);
 			}
 		}
 		else
@@ -11370,12 +11451,15 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 
 			for (i = -1; i < 2; i += 2)
 			{
+				mobj_t *bubble;
 				offsetH = i*P_ReturnThrustX(fume, fume->movedir, radiusV);
 				offsetV = i*P_ReturnThrustY(fume, fume->movedir, radiusV);
 				x = mo->x + radiusX + FixedMul(offsetH, factorX);
 				y = mo->y + radiusY + FixedMul(offsetH, factorY);
 				z = mo->z + heightoffset + offsetV;
-				P_SpawnMobj(x, y, z, MT_SMALLBUBBLE)->scale = mo->scale >> 1;
+				bubble = P_SpawnMobj(x, y, z, MT_SMALLBUBBLE);
+				bubble->scale = mo->scale >> 1;
+				P_SetTarget(&bubble->dontdrawforviewmobj, mo); // Hide the bubble in first-person
 			}
 
 			fume->movefactor = 0;
@@ -11429,7 +11513,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
@@ -11444,7 +11528,11 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 
 	// If dashmode is high enough, spawn a trail
 	if (player->normalspeed >= skins[player->skin].normalspeed*2)
-		P_SpawnGhostMobj(fume);
+	{
+		mobj_t *ghost = P_SpawnGhostMobj(fume);
+		if (!P_MobjWasRemoved(ghost))
+			P_SetTarget(&ghost->dontdrawforviewmobj, mo); // Hide the trail in first-person
+	}
 }
 
 //
@@ -11515,15 +11603,24 @@ void P_PlayerThink(player_t *player)
 	if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
 	{
 		P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid
-		player->awayviewtics = 0; // reset to zero
+		player->awayviewtics = 1; // reset to one, the below code will immediately set it to zero
+	}
+	
+	if (player->awayviewtics && player->awayviewtics != -1)
+	{
+		player->awayviewtics--;
+		if (!(player->awayviewtics))
+		{
+			if (player == &players[displayplayer])
+				P_ResetCamera(player, &camera); // reset p1 camera on p1 running out of awayviewtics
+			else if (splitscreen && player == &players[secondarydisplayplayer])
+				P_ResetCamera(player, &camera2);  // reset p2 camera on p2 running out of awayviewtics
+		}
 	}
 
 	if (player->flashcount)
 		player->flashcount--;
 
-	if (player->awayviewtics && player->awayviewtics != -1)
-		player->awayviewtics--;
-
 	/// \note do this in the cheat code
 	if (player->pflags & PF_NOCLIP)
 		player->mo->flags |= MF_NOCLIP;
@@ -11532,9 +11629,12 @@ void P_PlayerThink(player_t *player)
 
 	cmd = &player->cmd;
 
-	// Add some extra randomization.
-	if (cmd->forwardmove)
-		P_RandomFixed();
+	if (demoplayback && demo_forwardmove_rng)
+	{
+		// Smelly demo backwards compatibility
+		if (cmd->forwardmove)
+			P_RandomFixed();
+	}
 
 #ifdef PARANOIA
 	if (player->playerstate == PST_REBORN)
@@ -11664,7 +11764,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;
@@ -11672,7 +11772,7 @@ void P_PlayerThink(player_t *player)
 		else
 		{
 			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				D_SendExitLevel(false);
 		}
 	}
 
@@ -11713,7 +11813,7 @@ void P_PlayerThink(player_t *player)
 	if (player->spectator)
 	{
 		if (!(gametyperules & GTR_CAMPAIGN))
-			player->score = 0;
+			player->score = player->recordscore = 0;
 	}
 	else if ((netgame || multiplayer) && player->lives <= 0 && !G_CoopGametype())
 	{
@@ -11770,7 +11870,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;
 
@@ -12079,14 +12179,6 @@ void P_PlayerThink(player_t *player)
 				gmobj->tracer->frame |= tr_trans70<<FF_TRANSSHIFT;
 			}
 		}
-
-		// Hide the mobj from our sights if we're the displayplayer and chasecam is off,
-		// or secondarydisplayplayer and chasecam2 is off.
-		// Why not just not spawn the mobj?  Well, I'd rather only flirt with
-		// consistency so much...
-		if ((player == &players[displayplayer] && !camera.chase)
-		|| (splitscreen && player == &players[secondarydisplayplayer] && !camera2.chase))
-			gmobj->flags2 |= MF2_DONTDRAW;
 	}
 #endif
 
@@ -12180,6 +12272,16 @@ void P_PlayerThink(player_t *player)
 	else
 		player->powers[pw_ignorelatch] = 0;
 
+	if (player->powers[pw_strong] & STR_ANIM)
+	{
+		if (!(player->stronganim))
+			player->stronganim = player->panim;
+		else if (player->panim != player->stronganim)
+			player->powers[pw_strong] = STR_NONE; 
+	}	
+	else if (player->stronganim)
+		player->stronganim = 0;
+			
 	//pw_super acts as a timer now
 	if (player->powers[pw_super]
 	&& (player->mo->state < &states[S_PLAY_SUPER_TRANS1]
@@ -12274,6 +12376,8 @@ void P_PlayerThink(player_t *player)
 			{
 				player->normalspeed = skins[player->skin].normalspeed; // Reset to default if not capable of entering dash mode.
 				player->jumpfactor = skins[player->skin].jumpfactor;
+				if (player->powers[pw_strong] & STR_DASH)
+					player->powers[pw_strong] = STR_NONE;
 			}
 		}
 		else if (P_IsObjectOnGround(player->mo)) // Activate dash mode if we're on the ground.
@@ -12283,6 +12387,9 @@ void P_PlayerThink(player_t *player)
 
 			if (player->jumpfactor < FixedMul(skins[player->skin].jumpfactor, 5*FRACUNIT/4)) // Boost jump height.
 				player->jumpfactor += FRACUNIT/300;
+
+			if ((player->charflags & SF_MACHINE) && (!(player->powers[pw_strong] == STR_METAL))) 
+					player->powers[pw_strong] = STR_METAL;
 		}
 
 		if (player->normalspeed >= skins[player->skin].normalspeed*2)
@@ -12300,6 +12407,8 @@ void P_PlayerThink(player_t *player)
 			player->normalspeed = skins[player->skin].normalspeed;
 			player->jumpfactor = skins[player->skin].jumpfactor;
 			S_StartSound(player->mo, sfx_kc65);
+			if (player->powers[pw_strong] & STR_DASH)
+				player->powers[pw_strong] = STR_NONE;
 		}
 		dashmode = 0;
 	}
diff --git a/src/r_bbox.c b/src/r_bbox.c
new file mode 100644
index 0000000000000000000000000000000000000000..cf417ec37639477b43a5a5e5035b059dc81490b4
--- /dev/null
+++ b/src/r_bbox.c
@@ -0,0 +1,321 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C)      2022 by Kart Krew.
+// 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  r_bbox.c
+/// \brief Boundary box (cube) renderer
+
+#include "doomdef.h"
+#include "command.h"
+#include "r_local.h"
+#include "screen.h" // cv_renderhitbox
+#include "v_video.h" // V_DrawFill
+
+enum {
+	RENDERHITBOX_OFF,
+	RENDERHITBOX_TANGIBLE,
+	RENDERHITBOX_ALL,
+	RENDERHITBOX_INTANGIBLE,
+	RENDERHITBOX_RINGS,
+};
+
+static CV_PossibleValue_t renderhitbox_cons_t[] = {
+	{RENDERHITBOX_OFF, "Off"},
+	{RENDERHITBOX_TANGIBLE, "Tangible"},
+	{RENDERHITBOX_ALL, "All"},
+	{RENDERHITBOX_INTANGIBLE, "Intangible"},
+	{RENDERHITBOX_RINGS, "Rings"},
+	{0}};
+
+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);
+
+struct bbox_col {
+	INT32 x;
+	INT32 y;
+	INT32 h;
+};
+
+struct bbox_config {
+	fixed_t height;
+	fixed_t tz;
+	struct bbox_col col[4];
+	UINT8 color;
+};
+
+static inline void
+raster_bbox_seg
+(		INT32 x,
+		fixed_t y,
+		fixed_t h,
+		UINT8 pixel)
+{
+	y /= FRACUNIT;
+
+	if (y < 0)
+		y = 0;
+
+	h = y + (FixedCeil(abs(h)) / FRACUNIT);
+
+	if (h >= viewheight)
+		h = viewheight;
+
+	while (y < h)
+	{
+		topleft[x + y * vid.width] = pixel;
+		y++;
+	}
+}
+
+static void
+draw_bbox_col
+(		struct bbox_config * bb,
+		size_t p,
+		fixed_t tx,
+		fixed_t ty)
+{
+	struct bbox_col *col = &bb->col[p];
+
+	fixed_t xscale, yscale;
+
+	if (ty < FRACUNIT) // projection breaks down here
+		ty = FRACUNIT;
+
+	xscale = FixedDiv(projection, ty);
+	yscale = FixedDiv(projectiony, ty);
+
+	col->x = (centerxfrac + FixedMul(tx, xscale)) / FRACUNIT;
+	col->y = (centeryfrac - FixedMul(bb->tz, yscale));
+	col->h = FixedMul(bb->height, yscale);
+
+	// Using this function is TOO EASY!
+	V_DrawFill(
+			viewwindowx + col->x,
+			viewwindowy + col->y / FRACUNIT, 1,
+			col->h / FRACUNIT, V_NOSCALESTART | bb->color);
+}
+
+static void
+draw_bbox_row
+(		struct bbox_config * bb,
+		size_t p1,
+		size_t p2)
+{
+	struct bbox_col
+		*a = &bb->col[p1],
+		*b = &bb->col[p2];
+
+	INT32 x1, x2; // left, right
+	INT32 dx; // width
+
+	fixed_t y1, y2; // top, bottom
+	fixed_t s1, s2; // top and bottom increment
+
+	if (a->x > b->x)
+	{
+		struct bbox_col *c = a;
+		a = b;
+		b = c;
+	}
+
+	x1 = a->x;
+	x2 = b->x;
+
+	if (x1 == x2 || x1 >= viewwidth || x2 < 0)
+		return;
+
+	dx = x2 - x1;
+
+	y1 = a->y;
+	y2 = b->y;
+	s1 = (y2 - y1) / dx;
+
+	y2 = y1 + a->h;
+	s2 = ((b->y + b->h) - y2) / dx;
+
+	// FixedCeil needs a minimum!!! :D :D
+
+	if (s1 == 0)
+		s1 = 1;
+
+	if (s2 == 0)
+		s2 = 1;
+
+	if (x1 < 0)
+	{
+		y1 -= x1 * s1;
+		y2 -= x1 * s2;
+		x1 = 0;
+	}
+
+	if (x2 >= viewwidth)
+		x2 = viewwidth - 1;
+
+	while (x1 < x2)
+	{
+		raster_bbox_seg(x1, y1, s1, bb->color);
+		raster_bbox_seg(x1, y2, s2, bb->color);
+
+		y1 += s1;
+		y2 += s2;
+
+		x1++;
+	}
+}
+
+UINT8 R_GetBoundingBoxColor(mobj_t *thing)
+{
+	UINT32 flags = thing->flags;
+
+	if (thing->player)
+		return 255; // 0FF
+
+	if (flags & (MF_NOCLIPTHING))
+		return 7; // BFBFBF
+
+	if (flags & (MF_BOSS|MF_ENEMY))
+		return 35; // F00
+
+	if (flags & (MF_MISSILE|MF_PAIN))
+		return 54; // F70
+
+	if (flags & (MF_SPECIAL|MF_MONITOR))
+		return 73; // FF0
+
+	if (flags & MF_PUSHABLE)
+		return 112; // 0F0
+
+	if (flags & (MF_SPRING))
+		return 181; // F0F
+
+	if (flags & (MF_NOCLIP))
+		return 152; // 00F
+
+	return 0; // FFF
+}
+
+void R_DrawThingBoundingBox(vissprite_t *vis)
+{
+	// radius offsets
+	fixed_t rs = vis->scale;
+	fixed_t rc = vis->xscale;
+
+	// translated coordinates
+	fixed_t tx = vis->gx;
+	fixed_t ty = vis->gy;
+
+	struct bbox_config bb = {
+		.height = vis->thingheight,
+		.tz = vis->texturemid,
+		.color = R_GetBoundingBoxColor(vis->mobj),
+	};
+
+	// 1--3
+	// |  |
+	// 0--2
+
+	// left
+
+	draw_bbox_col(&bb, 0, tx, ty); // bottom
+	draw_bbox_col(&bb, 1, tx - rc, ty + rs); // top
+
+	// right
+
+	tx += rs;
+	ty += rc;
+
+	draw_bbox_col(&bb, 2, tx, ty); // bottom
+	draw_bbox_col(&bb, 3, tx - rc, ty + rs); // top
+
+	// connect all four columns
+
+	draw_bbox_row(&bb, 0, 1);
+	draw_bbox_row(&bb, 1, 3);
+	draw_bbox_row(&bb, 3, 2);
+	draw_bbox_row(&bb, 2, 0);
+}
+
+static boolean is_tangible (mobj_t *thing)
+{
+	// These objects can never touch another
+	if (thing->flags & (MF_NOCLIPTHING))
+	{
+		return false;
+	}
+
+	// These objects probably do nothing! :D
+	if ((thing->flags & (MF_SPECIAL|MF_SOLID|MF_SHOOTABLE
+					|MF_PUSHABLE|MF_BOSS|MF_MISSILE|MF_SPRING
+					|MF_BOUNCE|MF_MONITOR|MF_FIRE|MF_ENEMY
+					|MF_PAIN|MF_STICKY
+					|MF_GRENADEBOUNCE)) == 0U)
+	{
+		return false;
+	}
+
+	return true;
+}
+
+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)
+	{
+		default:
+			// First person / awayviewmobj -- rendering
+			// a bbox too close to the viewpoint causes
+			// anomalies and these are exactly on the
+			// viewpoint!
+			if (thing != r_viewmobj)
+			{
+				break;
+			}
+			// FALLTHRU
+
+		case MT_SKYBOX:
+			// Ditto for skybox viewpoint but because they
+			// are rendered using portals in Software,
+			// r_viewmobj does not point here.
+			return false;
+	}
+
+	switch (cvmode)
+	{
+		case RENDERHITBOX_OFF:
+			return false;
+
+		case RENDERHITBOX_ALL:
+			return true;
+
+		case RENDERHITBOX_INTANGIBLE:
+			return !is_tangible(thing);
+
+		case RENDERHITBOX_TANGIBLE:
+			// Exclude rings from here, lots of them!
+			if (thing->type == MT_RING)
+			{
+				return false;
+			}
+
+			return is_tangible(thing);
+
+		case RENDERHITBOX_RINGS:
+			return (thing->type == MT_RING || thing->type == MT_BLUESPHERE);
+
+		default:
+			return false;
+	}
+}
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 121ddaae5ae1340b5c0b2e33b983b061be060f86..42e050adf831f15a7b3e653c57e265cab781b6cc 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -275,9 +275,9 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->ceilingheight = s->floorheight - 1, !back)) || viewz <= s->floorheight)
 		{ // head-below-floor hack
 			tempsec->floorpic = s->floorpic;
-			tempsec->floor_xoffs = s->floor_xoffs;
-			tempsec->floor_yoffs = s->floor_yoffs;
-			tempsec->floorpic_angle = s->floorpic_angle;
+			tempsec->floorxoffset = s->floorxoffset;
+			tempsec->flooryoffset = s->flooryoffset;
+			tempsec->floorangle = s->floorangle;
 
 			if (underwater)
 			{
@@ -285,16 +285,16 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 				{
 					tempsec->floorheight = tempsec->ceilingheight+1;
 					tempsec->ceilingpic = tempsec->floorpic;
-					tempsec->ceiling_xoffs = tempsec->floor_xoffs;
-					tempsec->ceiling_yoffs = tempsec->floor_yoffs;
-					tempsec->ceilingpic_angle = tempsec->floorpic_angle;
+					tempsec->ceilingxoffset = tempsec->floorxoffset;
+					tempsec->ceilingyoffset = tempsec->flooryoffset;
+					tempsec->ceilingangle = tempsec->floorangle;
 				}
 				else
 				{
 					tempsec->ceilingpic = s->ceilingpic;
-					tempsec->ceiling_xoffs = s->ceiling_xoffs;
-					tempsec->ceiling_yoffs = s->ceiling_yoffs;
-					tempsec->ceilingpic_angle = s->ceilingpic_angle;
+					tempsec->ceilingxoffset = s->ceilingxoffset;
+					tempsec->ceilingyoffset = s->ceilingyoffset;
+					tempsec->ceilingangle = s->ceilingangle;
 				}
 			}
 
@@ -315,25 +315,25 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->floorheight = s->ceilingheight + 1;
 
 			tempsec->floorpic = tempsec->ceilingpic = s->ceilingpic;
-			tempsec->floor_xoffs = tempsec->ceiling_xoffs = s->ceiling_xoffs;
-			tempsec->floor_yoffs = tempsec->ceiling_yoffs = s->ceiling_yoffs;
-			tempsec->floorpic_angle = tempsec->ceilingpic_angle = s->ceilingpic_angle;
+			tempsec->floorxoffset = tempsec->ceilingxoffset = s->ceilingxoffset;
+			tempsec->flooryoffset = tempsec->ceilingyoffset = s->ceilingyoffset;
+			tempsec->floorangle = tempsec->ceilingangle = s->ceilingangle;
 
 			if (s->floorpic == skyflatnum) // SKYFIX?
 			{
 				tempsec->ceilingheight = tempsec->floorheight-1;
 				tempsec->floorpic = tempsec->ceilingpic;
-				tempsec->floor_xoffs = tempsec->ceiling_xoffs;
-				tempsec->floor_yoffs = tempsec->ceiling_yoffs;
-				tempsec->floorpic_angle = tempsec->ceilingpic_angle;
+				tempsec->floorxoffset = tempsec->ceilingxoffset;
+				tempsec->flooryoffset = tempsec->ceilingyoffset;
+				tempsec->floorangle = tempsec->ceilingangle;
 			}
 			else
 			{
 				tempsec->ceilingheight = sec->ceilingheight;
 				tempsec->floorpic = s->floorpic;
-				tempsec->floor_xoffs = s->floor_xoffs;
-				tempsec->floor_yoffs = s->floor_yoffs;
-				tempsec->floorpic_angle = s->floorpic_angle;
+				tempsec->floorxoffset = s->floorxoffset;
+				tempsec->flooryoffset = s->flooryoffset;
+				tempsec->floorangle = s->floorangle;
 			}
 
 			tempsec->lightlevel = s->lightlevel;
@@ -363,12 +363,12 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		&& back->lightlevel == front->lightlevel
 		&& !line->sidedef->midtexture
 		// Check offsets too!
-		&& back->floor_xoffs == front->floor_xoffs
-		&& back->floor_yoffs == front->floor_yoffs
-		&& back->floorpic_angle == front->floorpic_angle
-		&& back->ceiling_xoffs == front->ceiling_xoffs
-		&& back->ceiling_yoffs == front->ceiling_yoffs
-		&& back->ceilingpic_angle == front->ceilingpic_angle
+		&& back->floorxoffset == front->floorxoffset
+		&& back->flooryoffset == front->flooryoffset
+		&& back->floorangle == front->floorangle
+		&& back->ceilingxoffset == front->ceilingxoffset
+		&& back->ceilingyoffset == front->ceilingyoffset
+		&& back->ceilingangle == front->ceilingangle
 		// Consider altered lighting.
 		&& back->floorlightlevel == front->floorlightlevel
 		&& back->floorlightabsolute == front->floorlightabsolute
@@ -909,7 +909,7 @@ static void R_Subsector(size_t num)
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum))
 	{
 		floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel,
-			frontsector->floor_xoffs, frontsector->floor_yoffs, frontsector->floorpic_angle, floorcolormap, NULL, NULL, frontsector->f_slope);
+			frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope);
 	}
 	else
 		floorplane = NULL;
@@ -919,7 +919,7 @@ static void R_Subsector(size_t num)
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum))
 	{
 		ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic,
-			ceilinglightlevel, frontsector->ceiling_xoffs, frontsector->ceiling_yoffs, frontsector->ceilingpic_angle,
+			ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle,
 			ceilingcolormap, NULL, NULL, frontsector->c_slope);
 	}
 	else
@@ -1033,8 +1033,8 @@ static void R_Subsector(size_t num)
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
-					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floor_xoffs, polysec->floor_yoffs,
-					polysec->floorpic_angle-po->angle,
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset,
+					polysec->floorangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
 					NULL); // will ffloors be slopable eventually?
 
@@ -1057,7 +1057,7 @@ static void R_Subsector(size_t num)
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
-					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle-po->angle,
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
 					NULL); // will ffloors be slopable eventually?
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 6d2b7d3d8ab0f1e2db9932e26c6e1ee188f218bb..dfd2d6d708f8a5a9dae0da401719ad1d3eb24235 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -423,12 +423,12 @@ typedef struct sector_s
 	void *fadecolormapdata; // fade colormap thinker
 
 	// floor and ceiling texture offsets
-	fixed_t floor_xoffs, floor_yoffs;
-	fixed_t ceiling_xoffs, ceiling_yoffs;
+	fixed_t floorxoffset, flooryoffset;
+	fixed_t ceilingxoffset, ceilingyoffset;
 
 	// flat angle
-	angle_t floorpic_angle;
-	angle_t ceilingpic_angle;
+	angle_t floorangle;
+	angle_t ceilingangle;
 
 	INT32 heightsec; // other sector, or -1 if no other sector
 	INT32 camsec; // used for camera clipping
@@ -547,7 +547,6 @@ typedef struct line_s
 	size_t validcount; // if == validcount, already checked
 	polyobj_t *polyobj; // Belongs to a polyobject?
 
-	char *text; // a concatenation of all front and back texture names, for linedef specials that require a string.
 	INT16 callcount; // no. of calls left before triggering, for the "X calls" linedef specials, defaults to 0
 } line_t;
 
@@ -559,6 +558,10 @@ typedef struct
 	// add this to the calculated texture top
 	fixed_t rowoffset;
 
+	// per-texture offsets for UDMF
+	fixed_t offsetx_top, offsetx_mid, offsetx_bot;
+	fixed_t offsety_top, offsety_mid, offsety_bot;
+
 	// Texture indices.
 	// We do not maintain names here.
 	INT32 toptexture, bottomtexture, midtexture;
@@ -572,8 +575,6 @@ typedef struct
 	INT16 special; // the special of the linedef this side belongs to
 	INT16 repeatcnt; // # of times to repeat midtexture
 
-	char *text; // a concatenation of all top, bottom, and mid texture names, for linedef specials that require a string.
-
 	extracolormap_t *colormap_data; // storage for colormaps; not applied to sectors.
 } side_t;
 
@@ -756,12 +757,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_draw.c b/src/r_draw.c
index b0467e4f728d4cf757b53484a3d5ca4fda9d91cc..df9e1a4608b568706452df29bbc347adef075b01 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -179,8 +179,6 @@ CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 void R_InitTranslucencyTables(void)
 {
 	// Load here the transparency lookup tables 'TRANSx0'
-	// NOTE: the TRANSx0 resources MUST BE aligned on 64k for the asm
-	// optimised code (in other words, transtables pointer low word is 0)
 	transtables = Z_MallocAlign(NUMTRANSTABLES*0x10000, PU_STATIC,
 		NULL, 16);
 
diff --git a/src/r_draw.h b/src/r_draw.h
index ea03a8e3d53e059570822a0119ee6431f45d105a..0103ed82782b22c7a51beb10c20473a3e8ba3787 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -225,18 +225,6 @@ void R_DrawTiltedTransSolidColorSpan_8(void);
 void R_DrawWaterSolidColorSpan_8(void);
 void R_DrawTiltedWaterSolidColorSpan_8(void);
 
-#ifdef USEASM
-void ASMCALL R_DrawColumn_8_ASM(void);
-void ASMCALL R_DrawShadeColumn_8_ASM(void);
-void ASMCALL R_DrawTranslucentColumn_8_ASM(void);
-void ASMCALL R_Draw2sMultiPatchColumn_8_ASM(void);
-
-void ASMCALL R_DrawColumn_8_MMX(void);
-
-void ASMCALL R_Draw2sMultiPatchColumn_8_MMX(void);
-void ASMCALL R_DrawSpan_8_MMX(void);
-#endif
-
 // ------------------
 // 16bpp DRAWING CODE
 // ------------------
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index faf1cdba80bb1acda4b5c595378f0aa428043431..91f3b06c4270ba28901d35b561f68a002ddbc6f3 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -18,6 +18,11 @@
 #define SPANSIZE 16
 #define INVSPAN 0.0625f
 
+#if defined(__GNUC__) || defined(__clang__) // Suppress intentional libdivide compiler warnings - Also added to libdivide.h
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Waggregate-return"
+#endif
+
 /**	\brief The R_DrawSpan_NPO2_8 function
 	Draws the actual span.
 */
@@ -1572,3 +1577,7 @@ void R_DrawTiltedWaterSpan_NPO2_8(void)
 	}
 #endif
 }
+
+#if defined(__GNUC__) || defined(__clang__) // Stop suppressing intentional libdivide compiler warnings
+    #pragma GCC diagnostic pop
+#endif
diff --git a/src/r_fps.c b/src/r_fps.c
index 2d30c9f01920959c783da056d1db2f0858969209..de450aaa7f465b8d47891baec585c757de361c60 100644
--- a/src/r_fps.c
+++ b/src/r_fps.c
@@ -292,8 +292,13 @@ 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;
+		out->roll = mobj->roll;
+		out->spriteroll = mobj->spriteroll;
 		out->spritexscale = mobj->spritexscale;
 		out->spriteyscale = mobj->spriteyscale;
 		out->spritexoffset = mobj->spritexoffset;
@@ -304,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;
@@ -323,6 +340,10 @@ void R_InterpolateMobjState(mobj_t *mobj, fixed_t frac, interpmobjstate_t *out)
 	{
 		out->angle = mobj->resetinterp ? mobj->angle : R_LerpAngle(mobj->old_angle, mobj->angle, frac);
 	}
+
+	out->pitch = mobj->resetinterp ? mobj->pitch : R_LerpAngle(mobj->old_pitch, mobj->pitch, frac);
+	out->roll = mobj->resetinterp ? mobj->roll : R_LerpAngle(mobj->old_roll, mobj->roll, frac);
+	out->spriteroll = mobj->resetinterp ? mobj->spriteroll : R_LerpAngle(mobj->old_spriteroll, mobj->spriteroll, frac);
 }
 
 void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjstate_t *out)
@@ -333,8 +354,13 @@ 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;
+		out->roll = mobj->roll;
+		out->spriteroll = mobj->spriteroll;
 		out->spritexscale = mobj->spritexscale;
 		out->spriteyscale = mobj->spriteyscale;
 		out->spritexoffset = mobj->spritexoffset;
@@ -346,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);
@@ -354,6 +382,9 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst
 	out->subsector = R_PointInSubsector(out->x, out->y);
 
 	out->angle = R_LerpAngle(mobj->old_angle, mobj->angle, frac);
+	out->pitch = R_LerpAngle(mobj->old_pitch, mobj->pitch, frac);
+	out->roll = R_LerpAngle(mobj->old_roll, mobj->roll, frac);
+	out->spriteroll = R_LerpAngle(mobj->old_spriteroll, mobj->spriteroll, frac);
 }
 
 static void AddInterpolator(levelinterpolator_t* interpolator)
@@ -369,12 +400,11 @@ static void AddInterpolator(levelinterpolator_t* interpolator)
 			levelinterpolators_size *= 2;
 		}
 
-		levelinterpolators = Z_ReallocAlign(
+		levelinterpolators = Z_Realloc(
 			(void*) levelinterpolators,
 			sizeof(levelinterpolator_t*) * levelinterpolators_size,
 			PU_LEVEL,
-			NULL,
-			sizeof(levelinterpolator_t*) * 8
+			NULL
 		);
 	}
 
@@ -384,11 +414,8 @@ static void AddInterpolator(levelinterpolator_t* interpolator)
 
 static levelinterpolator_t *CreateInterpolator(levelinterpolator_type_e type, thinker_t *thinker)
 {
-	levelinterpolator_t *ret = (levelinterpolator_t*) Z_CallocAlign(
-		sizeof(levelinterpolator_t),
-		PU_LEVEL,
-		NULL,
-		sizeof(levelinterpolator_t) * 8
+	levelinterpolator_t *ret = (levelinterpolator_t*) Z_Calloc(
+		sizeof(levelinterpolator_t), PU_LEVEL, NULL
 	);
 
 	ret->type = type;
@@ -421,13 +448,13 @@ void R_CreateInterpolator_SectorScroll(thinker_t *thinker, sector_t *sector, boo
 	interp->sectorscroll.ceiling = ceiling;
 	if (ceiling)
 	{
-		interp->sectorscroll.oldxoffs = interp->sectorscroll.bakxoffs = sector->ceiling_xoffs;
-		interp->sectorscroll.oldyoffs = interp->sectorscroll.bakyoffs = sector->ceiling_yoffs;
+		interp->sectorscroll.oldxoffs = interp->sectorscroll.bakxoffs = sector->ceilingxoffset;
+		interp->sectorscroll.oldyoffs = interp->sectorscroll.bakyoffs = sector->ceilingyoffset;
 	}
 	else
 	{
-		interp->sectorscroll.oldxoffs = interp->sectorscroll.bakxoffs = sector->floor_xoffs;
-		interp->sectorscroll.oldyoffs = interp->sectorscroll.bakyoffs = sector->floor_yoffs;
+		interp->sectorscroll.oldxoffs = interp->sectorscroll.bakxoffs = sector->floorxoffset;
+		interp->sectorscroll.oldyoffs = interp->sectorscroll.bakyoffs = sector->flooryoffset;
 	}
 }
 
@@ -490,9 +517,9 @@ static void UpdateLevelInterpolatorState(levelinterpolator_t *interp)
 		break;
 	case LVLINTERP_SectorScroll:
 		interp->sectorscroll.oldxoffs = interp->sectorscroll.bakxoffs;
-		interp->sectorscroll.bakxoffs = interp->sectorscroll.ceiling ? interp->sectorscroll.sector->ceiling_xoffs : interp->sectorscroll.sector->floor_xoffs;
+		interp->sectorscroll.bakxoffs = interp->sectorscroll.ceiling ? interp->sectorscroll.sector->ceilingxoffset : interp->sectorscroll.sector->floorxoffset;
 		interp->sectorscroll.oldyoffs = interp->sectorscroll.bakyoffs;
-		interp->sectorscroll.bakyoffs = interp->sectorscroll.ceiling ? interp->sectorscroll.sector->ceiling_yoffs : interp->sectorscroll.sector->floor_yoffs;
+		interp->sectorscroll.bakyoffs = interp->sectorscroll.ceiling ? interp->sectorscroll.sector->ceilingyoffset : interp->sectorscroll.sector->flooryoffset;
 		break;
 	case LVLINTERP_SideScroll:
 		interp->sidescroll.oldtextureoffset = interp->sidescroll.baktextureoffset;
@@ -578,13 +605,13 @@ void R_ApplyLevelInterpolators(fixed_t frac)
 		case LVLINTERP_SectorScroll:
 			if (interp->sectorscroll.ceiling)
 			{
-				interp->sectorscroll.sector->ceiling_xoffs = R_LerpFixed(interp->sectorscroll.oldxoffs, interp->sectorscroll.bakxoffs, frac);
-				interp->sectorscroll.sector->ceiling_yoffs = R_LerpFixed(interp->sectorscroll.oldyoffs, interp->sectorscroll.bakyoffs, frac);
+				interp->sectorscroll.sector->ceilingxoffset = R_LerpFixed(interp->sectorscroll.oldxoffs, interp->sectorscroll.bakxoffs, frac);
+				interp->sectorscroll.sector->ceilingyoffset = R_LerpFixed(interp->sectorscroll.oldyoffs, interp->sectorscroll.bakyoffs, frac);
 			}
 			else
 			{
-				interp->sectorscroll.sector->floor_xoffs = R_LerpFixed(interp->sectorscroll.oldxoffs, interp->sectorscroll.bakxoffs, frac);
-				interp->sectorscroll.sector->floor_yoffs = R_LerpFixed(interp->sectorscroll.oldyoffs, interp->sectorscroll.bakyoffs, frac);
+				interp->sectorscroll.sector->floorxoffset = R_LerpFixed(interp->sectorscroll.oldxoffs, interp->sectorscroll.bakxoffs, frac);
+				interp->sectorscroll.sector->flooryoffset = R_LerpFixed(interp->sectorscroll.oldyoffs, interp->sectorscroll.bakyoffs, frac);
 			}
 			break;
 		case LVLINTERP_SideScroll:
@@ -633,13 +660,13 @@ void R_RestoreLevelInterpolators(void)
 		case LVLINTERP_SectorScroll:
 			if (interp->sectorscroll.ceiling)
 			{
-				interp->sectorscroll.sector->ceiling_xoffs = interp->sectorscroll.bakxoffs;
-				interp->sectorscroll.sector->ceiling_yoffs = interp->sectorscroll.bakyoffs;
+				interp->sectorscroll.sector->ceilingxoffset = interp->sectorscroll.bakxoffs;
+				interp->sectorscroll.sector->ceilingyoffset = interp->sectorscroll.bakyoffs;
 			}
 			else
 			{
-				interp->sectorscroll.sector->floor_xoffs = interp->sectorscroll.bakxoffs;
-				interp->sectorscroll.sector->floor_yoffs = interp->sectorscroll.bakyoffs;
+				interp->sectorscroll.sector->floorxoffset = interp->sectorscroll.bakxoffs;
+				interp->sectorscroll.sector->flooryoffset = interp->sectorscroll.bakyoffs;
 			}
 			break;
 		case LVLINTERP_SideScroll:
@@ -703,12 +730,11 @@ void R_AddMobjInterpolator(mobj_t *mobj)
 			interpolated_mobjs_capacity *= 2;
 		}
 
-		interpolated_mobjs = Z_ReallocAlign(
+		interpolated_mobjs = Z_Realloc(
 			interpolated_mobjs,
 			sizeof(mobj_t *) * interpolated_mobjs_capacity,
 			PU_LEVEL,
-			NULL,
-			64
+			NULL
 		);
 	}
 
@@ -771,6 +797,7 @@ void R_ResetMobjInterpolationState(mobj_t *mobj)
 	mobj->old_angle2 = mobj->old_angle;
 	mobj->old_pitch2 = mobj->old_pitch;
 	mobj->old_roll2 = mobj->old_roll;
+	mobj->old_spriteroll2 = mobj->old_spriteroll;
 	mobj->old_scale2 = mobj->old_scale;
 	mobj->old_x = mobj->x;
 	mobj->old_y = mobj->y;
@@ -778,6 +805,7 @@ void R_ResetMobjInterpolationState(mobj_t *mobj)
 	mobj->old_angle = mobj->angle;
 	mobj->old_pitch = mobj->pitch;
 	mobj->old_roll = mobj->roll;
+	mobj->old_spriteroll = mobj->spriteroll;
 	mobj->old_scale = mobj->scale;
 	mobj->old_spritexscale = mobj->spritexscale;
 	mobj->old_spriteyscale = mobj->spriteyscale;
@@ -806,10 +834,14 @@ void R_ResetPrecipitationMobjInterpolationState(precipmobj_t *mobj)
 	mobj->old_angle2 = mobj->old_angle;
 	mobj->old_pitch2 = mobj->old_pitch;
 	mobj->old_roll2 = mobj->old_roll;
+	mobj->old_spriteroll2 = mobj->old_spriteroll;
 	mobj->old_x = mobj->x;
 	mobj->old_y = mobj->y;
 	mobj->old_z = mobj->z;
 	mobj->old_angle = mobj->angle;
+	mobj->old_pitch = mobj->pitch;
+	mobj->old_roll = mobj->roll;
+	mobj->old_spriteroll = mobj->spriteroll;
 	mobj->old_spritexscale = mobj->spritexscale;
 	mobj->old_spriteyscale = mobj->spriteyscale;
 	mobj->old_spritexoffset = mobj->spritexoffset;
diff --git a/src/r_fps.h b/src/r_fps.h
index 85c87a2f49ff1c3177f7fb8b8e9136e88316ebc9..f43d29f300a8a6707a3e4c6f5fa24e1e3f0ea37f 100644
--- a/src/r_fps.h
+++ b/src/r_fps.h
@@ -59,7 +59,12 @@ typedef struct {
 	fixed_t z;
 	subsector_t *subsector;
 	angle_t angle;
+	angle_t pitch;
+	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 ebf7a28bf10b286a2453eb94dbdb52891eef4355..54f7d7639e775f73b1d8de57d9923745eb9ff493 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);
@@ -1092,34 +1083,12 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
 void R_SetupFrame(player_t *player)
 {
 	camera_t *thiscam;
-	boolean chasecam = false;
-
-	if (splitscreen && player == &players[secondarydisplayplayer]
-		&& player != &players[consoleplayer])
-	{
+	boolean chasecam = R_ViewpointHasChasecam(player);
+	
+	if (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer])
 		thiscam = &camera2;
-		chasecam = (cv_chasecam2.value != 0);
-		R_SetViewContext(VIEWCONTEXT_PLAYER2);
-	}
 	else
-	{
 		thiscam = &camera;
-		chasecam = (cv_chasecam.value != 0);
-		R_SetViewContext(VIEWCONTEXT_PLAYER1);
-	}
-
-	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
-		chasecam = true; // force chasecam on
-	else if (player->spectator) // no spectator chasecam
-		chasecam = false; // force chasecam off
-
-	if (chasecam && !thiscam->chase)
-	{
-		P_ResetCamera(player, thiscam);
-		thiscam->chase = true;
-	}
-	else if (!chasecam)
-		thiscam->chase = false;
 
 	newview->sky = false;
 
@@ -1348,11 +1317,37 @@ boolean R_ViewpointHasChasecam(player_t *player)
 {
 	camera_t *thiscam;
 	boolean chasecam = false;
+	boolean isplayer2 = (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer]);
 
-	if (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer])
+	if (isplayer2)
 	{
 		thiscam = &camera2;
 		chasecam = (cv_chasecam2.value != 0);
+	}
+	else
+	{
+		thiscam = &camera;
+		chasecam = (cv_chasecam.value != 0);
+	}
+
+	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
+		chasecam = true; // force chasecam on
+	else if (player->spectator) // no spectator chasecam
+		chasecam = false; // force chasecam off
+		
+	if (chasecam && !thiscam->chase)
+	{
+		P_ResetCamera(player, thiscam);
+		thiscam->chase = true;
+	}
+	else if (!chasecam && thiscam->chase)
+	{
+		P_ResetCamera(player, thiscam);
+		thiscam->chase = false;
+	}
+	
+	if (isplayer2)
+	{
 		R_SetViewContext(VIEWCONTEXT_PLAYER2);
 		if (thiscam->reset)
 		{
@@ -1362,8 +1357,6 @@ boolean R_ViewpointHasChasecam(player_t *player)
 	}
 	else
 	{
-		thiscam = &camera;
-		chasecam = (cv_chasecam.value != 0);
 		R_SetViewContext(VIEWCONTEXT_PLAYER1);
 		if (thiscam->reset)
 		{
@@ -1372,11 +1365,6 @@ boolean R_ViewpointHasChasecam(player_t *player)
 		}
 	}
 
-	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
-		chasecam = true; // force chasecam on
-	else if (player->spectator) // no spectator chasecam
-		chasecam = false; // force chasecam off
-
 	return chasecam;
 }
 
@@ -1482,6 +1470,7 @@ void R_RenderPlayerView(player_t *player)
 		R_ClearClipSegs();
 	}
 	R_ClearDrawSegs();
+	R_ClearSegTables();
 	R_ClearSprites();
 	Portal_InitList();
 
@@ -1492,29 +1481,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])
@@ -1605,6 +1582,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 f08070d0f387b544c9b0b5089e65b6251999db99..a6fb42ba2410ba42c8adc7ba84ef8fde21e0802f 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_patch.h b/src/r_patch.h
index d2106a390019b357702c573c414842a615ba555b..a0ab3e75ac0c81a7fa16b6129cd2deabb8d02d46 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -14,6 +14,7 @@
 
 #include "r_defs.h"
 #include "r_picformats.h"
+#include "r_fps.h"
 #include "doomdef.h"
 
 // Patch functions
@@ -38,6 +39,8 @@ patch_t *Patch_GetRotatedSprite(
 	size_t frame, size_t spriteangle,
 	boolean flip, boolean adjustfeet,
 	void *info, INT32 rotationangle);
+angle_t R_ModelRotationAngle(interpmobjstate_t *interp);
+angle_t R_SpriteRotationAngle(interpmobjstate_t *interp);
 INT32 R_GetRollAngle(angle_t rollangle);
 #endif
 
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
index 3d3c6c512893d1670b4b93b96b32d670b6300e93..b0cbeaa42911d1506d9928b906198afdf50ff0be 100644
--- a/src/r_patchrotation.c
+++ b/src/r_patchrotation.c
@@ -13,11 +13,33 @@
 #include "r_things.h" // FEETADJUST
 #include "z_zone.h"
 #include "w_wad.h"
+#include "r_main.h" // R_PointToAngle
 
 #ifdef ROTSPRITE
 fixed_t rollcosang[ROTANGLES];
 fixed_t rollsinang[ROTANGLES];
 
+angle_t R_ModelRotationAngle(interpmobjstate_t *interp)
+{
+	return interp->spriteroll;
+}
+
+angle_t R_SpriteRotationAngle(interpmobjstate_t *interp)
+{
+#if 0
+	angle_t viewingAngle = R_PointToAngle(interp->x, interp->y);
+
+	fixed_t pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT);
+	fixed_t rollMul = FINECOSINE(viewingAngle >> ANGLETOFINESHIFT);
+
+	angle_t rollOrPitch = FixedMul(interp->pitch, pitchMul) + FixedMul(interp->roll, rollMul);
+
+	return (rollOrPitch + R_ModelRotationAngle(interp));
+#else
+	return R_ModelRotationAngle(interp);
+#endif
+}
+
 INT32 R_GetRollAngle(angle_t rollangle)
 {
 	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 9aea8c6c1496430cac950ad7c7a7de408f6fbf62..3e817f4a06199bb47c38f198c6ceb6cd447a6452 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -1407,7 +1407,6 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 	UINT8 frameFrame = 0xFF;
 	INT16 frameXPivot = 0;
 	INT16 frameYPivot = 0;
-	rotaxis_t frameRotAxis = 0;
 
 	// Sprite identifier
 	sprinfoToken = M_GetToken(NULL);
@@ -1458,12 +1457,6 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 				{
 					Z_Free(sprinfoToken);
 					sprinfoToken = M_GetToken(NULL);
-					if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0))
-						frameRotAxis = ROTAXIS_X;
-					else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0))
-						frameRotAxis = ROTAXIS_Y;
-					else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0))
-						frameRotAxis = ROTAXIS_Z;
 				}
 				Z_Free(sprinfoToken);
 
@@ -1480,7 +1473,6 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 	// set fields
 	info->pivot[frameFrame].x = frameXPivot;
 	info->pivot[frameFrame].y = frameYPivot;
-	info->pivot[frameFrame].rotaxis = frameRotAxis;
 }
 
 //
diff --git a/src/r_picformats.h b/src/r_picformats.h
index 4f9637460085b98ae3e5c629fff57c06678f9b02..4050a1b71dc01e8a60cbb14cbe017716c61038c3 100644
--- a/src/r_picformats.h
+++ b/src/r_picformats.h
@@ -95,7 +95,6 @@ typedef enum
 typedef struct
 {
 	INT32 x, y;
-	rotaxis_t rotaxis;
 } spriteframepivot_t;
 
 typedef struct
diff --git a/src/r_plane.c b/src/r_plane.c
index c568484b6ed7e71335a665a40710753d2608133f..29ce26b292e5ea529d937aded0028e41a8d294ba 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 9870a43e26286e5e888a13f20fa72ecf8b38be08..917e8b041b75775016dbc2a1e10b8cc6ad7a3778 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 71fc9f9b2a9801643e646e603ca2f48721e76c28..9af83f0c7ff8af10057f253a5419dca41c86638c 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -49,6 +49,7 @@ fixed_t rw_distance;
 static INT32 rw_x, rw_stopx;
 static angle_t rw_centerangle;
 static fixed_t rw_offset;
+static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bot;
 static fixed_t rw_offset2; // for splats
 static fixed_t rw_scale, rw_scalestep;
 static fixed_t rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid;
@@ -70,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
 // ==========================================================================
@@ -349,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;
-						}
+						rlight = &dc_lightlist[i];
+						rlight->height += rlight->heightstep;
 					}
-					spryscale += rw_scalestep;
-					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;
+			// calculate lighting
+			if (dc_numlights)
+			{
+				lighttable_t **xwalllights;
 
-					// draw the texture
-					col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);
+				sprbotscreen = INT32_MAX;
+				sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 
-					for (i = 0; i < dc_numlights; i++)
-					{
-						rlight = &dc_lightlist[i];
+				realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
+				dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-						if ((rlight->flags & FOF_NOSHADE))
-							continue;
+				// draw the texture
+				col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
 
-						if (rlight->lightnum < 0)
-							xwalllights = scalelight[0];
-						else if (rlight->lightnum >= LIGHTLEVELS)
-							xwalllights = scalelight[LIGHTLEVELS-1];
-						else
-							xwalllights = scalelight[rlight->lightnum];
+				for (i = 0; i < dc_numlights; i++)
+				{
+					rlight = &dc_lightlist[i];
 
-						pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+					if ((rlight->flags & FOF_NOSHADE))
+						continue;
 
-						if (pindex >= MAXLIGHTSCALE)
-							pindex = MAXLIGHTSCALE - 1;
+					if (rlight->lightnum < 0)
+						xwalllights = scalelight[0];
+					else if (rlight->lightnum >= LIGHTLEVELS)
+						xwalllights = scalelight[LIGHTLEVELS-1];
+					else
+						xwalllights = scalelight[rlight->lightnum];
 
-						if (rlight->extra_colormap)
-							rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
-						else
-							rlight->rcolormap = xwalllights[pindex];
+					pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-						height = rlight->height;
-						rlight->height += rlight->heightstep;
+					if (pindex >= MAXLIGHTSCALE)
+						pindex = MAXLIGHTSCALE - 1;
 
-						if (height <= windowtop)
-						{
-							dc_colormap = rlight->rcolormap;
-							continue;
-						}
+					if (rlight->extra_colormap)
+						rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
+					else
+						rlight->rcolormap = xwalllights[pindex];
 
-						windowbottom = height;
-						if (windowbottom >= realbot)
-						{
-							windowbottom = realbot;
-							colfunc_2s(col);
-							for (i++; i < dc_numlights; i++)
-							{
-								rlight = &dc_lightlist[i];
-								rlight->height += rlight->heightstep;
-							}
+					height = rlight->height;
+					rlight->height += rlight->heightstep;
 
-							continue;
-						}
-						colfunc_2s(col);
-						windowtop = windowbottom + 1;
+					if (height <= windowtop)
+					{
 						dc_colormap = rlight->rcolormap;
+						continue;
 					}
-					windowbottom = realbot;
-					if (windowtop < windowbottom)
+
+					windowbottom = height;
+					if (windowbottom >= realbot)
+					{
+						windowbottom = realbot;
 						colfunc_2s(col);
+						for (i++; i < dc_numlights; i++)
+						{
+							rlight = &dc_lightlist[i];
+							rlight->height += rlight->heightstep;
+						}
 
-					spryscale += rw_scalestep;
-					continue;
+						continue;
+					}
+					colfunc_2s(col);
+					windowtop = windowbottom + 1;
+					dc_colormap = rlight->rcolormap;
 				}
+				windowbottom = realbot;
+				if (windowtop < windowbottom)
+					colfunc_2s(col);
 
-				// 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;
+				spryscale += rw_scalestep;
+				continue;
+			}
 
-					my_topscreen = sprtopscreen + spryscale*col->topdelta;
-					my_bottomscreen = sprbotscreen == INT32_MAX ? my_topscreen + spryscale*col->length
-					                                         : sprbotscreen + spryscale*col->length;
+			// calculate lighting
+			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-					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 (pindex >= MAXLIGHTSCALE)
+				pindex = MAXLIGHTSCALE - 1;
 
-					if (numffloors)
-					{
-						INT32 top = my_yl;
-						INT32 bottom = my_yh;
+			dc_colormap = walllights[pindex];
 
-						for (i = 0; i < numffloors; i++)
-						{
-							if (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg)
-								continue;
+			if (frontsector->extra_colormap)
+				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
 
-							if (ffloor[i].height < viewz)
-							{
-								INT32 top_w = ffloor[i].plane->top[dc_x];
+			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
+			dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-	//							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];
+			// draw the texture
+			col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
+			colfunc_2s(col);
 
-								if (bottom_w > bottom)
-								{
-									ffloor[i].plane->bottom[dc_x] = (INT16)bottom;
-									ffloor[i].plane->picnum = 0;
-								}
-							}
-						}
-					}
-				}
-				else
-#endif
-					colfunc_2s(col);
-			}
 			spryscale += rw_scalestep;
 		}
 	}
@@ -778,7 +737,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 	if (newline)
 	{
-		offsetvalue = sides[newline->sidenum[0]].rowoffset;
+		offsetvalue = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
 		if (newline->flags & ML_DONTPEGBOTTOM)
 		{
 			skewslope = *pfloor->b_slope; // skew using bottom slope
@@ -790,7 +749,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	}
 	else
 	{
-		offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset;
+		offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset + sides[pfloor->master->sidenum[0]].offsety_mid;
 		if (curline->linedef->flags & ML_DONTPEGBOTTOM)
 		{
 			skewslope = *pfloor->b_slope; // skew using bottom slope
@@ -856,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])<<FRACBITS);
-				oldx = dc_x;
-			}
-			// Calculate bounds
-			// clamp the values if necessary to avoid overflows and rendering glitches caused by them
+			if (oldx != -1)
+				dc_texturemid += FixedMul(ffloortextureslide, maskedtexturecol[oldx]-maskedtexturecol[dc_x]);
+			oldx = dc_x;
+		}
 
-			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;
+		// 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;
-					}
+					rlight = &dc_lightlist[i];
+					rlight->height += rlight->heightstep;
+					if (rlight->flags & FOF_CUTLEVEL)
+						rlight->botheight += rlight->botheightstep;
 				}
-				spryscale += rw_scalestep;
-				continue;
 			}
+			spryscale += rw_scalestep;
+			continue;
+		}
 
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
+		dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-			// Get data for the column
-			col = (column_t *)((UINT8 *)R_GetColumn(texnum,maskedtexturecol[dc_x]) - 3);
+		// 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;
+		// 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++)
+			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)
 				{
-					// 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;
+					lightnum = rlight->lightnum;
 
-						if (pindex >= MAXLIGHTSCALE)
-							pindex = MAXLIGHTSCALE-1;
+					if (lightnum < 0)
+						xwalllights = scalelight[0];
+					else if (lightnum >= LIGHTLEVELS)
+						xwalllights = scalelight[LIGHTLEVELS-1];
+					else
+						xwalllights = scalelight[lightnum];
 
-						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];
-						}
-					}
+					pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-					solid = 0; // don't carry over solid-cutting flag from the previous light
+					if (pindex >= MAXLIGHTSCALE)
+						pindex = MAXLIGHTSCALE-1;
 
-					// 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 (pfloor->fofflags & FOF_FOG)
 					{
-						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;
-						}
+						if (pfloor->master->frontsector->extra_colormap)
+							rlight->rcolormap = pfloor->master->frontsector->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
 						else
-							solid = 1;
+							rlight->rcolormap = xwalllights[pindex];
 					}
 					else
-						solid = 0;
-
-					height = rlight->height;
-					rlight->height += rlight->heightstep;
-
-					if (solid)
 					{
-						bheight = rlight->botheight - (FRACUNIT >> 1);
-						rlight->botheight += rlight->botheightstep;
+						if (rlight->extra_colormap)
+							rlight->rcolormap = rlight->extra_colormap->colormap + (xwalllights[pindex] - colormaps);
+						else
+							rlight->rcolormap = xwalllights[pindex];
 					}
+				}
 
-					if (height <= windowtop)
-					{
-						if (lighteffect)
-							dc_colormap = rlight->rcolormap;
-						if (solid && windowtop < bheight)
-							windowtop = bheight;
-						continue;
-					}
+				solid = 0; // don't carry over solid-cutting flag from the previous light
 
-					windowbottom = height;
-					if (windowbottom >= sprbotscreen)
+				// 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)
 					{
-						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;
+						// 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;
 					}
-					// draw the texture
-					colfunc_2s (col);
-					if (solid)
-						windowtop = bheight;
 					else
-						windowtop = windowbottom + 1;
+						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 = sprbotscreen;
-				// draw the texture, if there is any space left
-				if (windowtop < windowbottom)
-					colfunc_2s (col);
 
-				spryscale += rw_scalestep;
-				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);
 
-			// calculate lighting
-			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+			spryscale += rw_scalestep;
+			continue;
+		}
 
-			if (pindex >= MAXLIGHTSCALE)
-				pindex = MAXLIGHTSCALE - 1;
+		// calculate lighting
+		pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
-			dc_colormap = walllights[pindex];
+		if (pindex >= MAXLIGHTSCALE)
+			pindex = MAXLIGHTSCALE - 1;
 
-			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);
+		dc_colormap = walllights[pindex];
 
-			// draw the texture
-			colfunc_2s (col);
-			spryscale += rw_scalestep;
-		}
+		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];
 
@@ -1269,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)
@@ -1335,7 +1293,7 @@ static void R_RenderSegLoop (void)
 				dc_yl = yl;
 				dc_yh = yh;
 				dc_texturemid = rw_midtexturemid;
-				dc_source = R_GetColumn(midtexture,texturecolumn);
+				dc_source = R_GetColumn(midtexture, itexturecolumn + (rw_offset_mid>>FRACBITS));
 				dc_texheight = textureheight[midtexture]>>FRACBITS;
 
 				//profile stuff ---------------------------------------------------------
@@ -1396,7 +1354,7 @@ static void R_RenderSegLoop (void)
 						dc_yl = yl;
 						dc_yh = mid;
 						dc_texturemid = rw_toptexturemid;
-						dc_source = R_GetColumn(toptexture,texturecolumn);
+						dc_source = R_GetColumn(toptexture, itexturecolumn + (rw_offset_top>>FRACBITS));
 						dc_texheight = textureheight[toptexture]>>FRACBITS;
 						colfunc();
 						ceilingclip[rw_x] = (INT16)mid;
@@ -1432,8 +1390,7 @@ static void R_RenderSegLoop (void)
 						dc_yl = mid;
 						dc_yh = yh;
 						dc_texturemid = rw_bottomtexturemid;
-						dc_source = R_GetColumn(bottomtexture,
-							texturecolumn);
+						dc_source = R_GetColumn(bottomtexture, itexturecolumn + (rw_offset_bot>>FRACBITS));
 						dc_texheight = textureheight[bottomtexture]>>FRACBITS;
 						colfunc();
 						floorclip[rw_x] = (INT16)mid;
@@ -1452,7 +1409,7 @@ static void R_RenderSegLoop (void)
 		{
 			// save texturecol
 			//  for backdrawing of masked mid texture
-			maskedtexturecol[rw_x] = (INT16)texturecolumn;
+			maskedtexturecol[rw_x] = texturecolumn + rw_offset_mid;
 
 			if (maskedtextureheight != NULL) {
 				maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ?
@@ -1511,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
@@ -1579,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]);
 
@@ -1783,7 +1776,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			rw_midtexturemid = worldtop;
 			rw_midtextureslide = ceilingfrontslide;
 		}
-		rw_midtexturemid += sidedef->rowoffset;
+		rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
 
 		ds_p->silhouette = SIL_BOTH;
 		ds_p->sprtopclip = screenheightarray;
@@ -1906,9 +1899,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			|| backsector->floorpic != frontsector->floorpic
 			|| backsector->lightlevel != frontsector->lightlevel
 			//SoM: 3/22/2000: Check floor x and y offsets.
-			|| backsector->floor_xoffs != frontsector->floor_xoffs
-			|| backsector->floor_yoffs != frontsector->floor_yoffs
-			|| backsector->floorpic_angle != frontsector->floorpic_angle
+			|| backsector->floorxoffset != frontsector->floorxoffset
+			|| backsector->flooryoffset != frontsector->flooryoffset
+			|| backsector->floorangle != frontsector->floorangle
 			//SoM: 3/22/2000: Prevents bleeding.
 			|| (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
 			|| backsector->floorlightlevel != frontsector->floorlightlevel
@@ -1939,9 +1932,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			|| backsector->ceilingpic != frontsector->ceilingpic
 			|| backsector->lightlevel != frontsector->lightlevel
 			//SoM: 3/22/2000: Check floor x and y offsets.
-			|| backsector->ceiling_xoffs != frontsector->ceiling_xoffs
-			|| backsector->ceiling_yoffs != frontsector->ceiling_yoffs
-			|| backsector->ceilingpic_angle != frontsector->ceilingpic_angle
+			|| backsector->ceilingxoffset != frontsector->ceilingxoffset
+			|| backsector->ceilingyoffset != frontsector->ceilingyoffset
+			|| backsector->ceilingangle != frontsector->ceilingangle
 			//SoM: 3/22/2000: Prevents bleeding.
 			|| (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
 			|| backsector->ceilinglightlevel != frontsector->ceilinglightlevel
@@ -2022,8 +2015,10 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 		}
 
-		rw_toptexturemid += sidedef->rowoffset;
-		rw_bottomtexturemid += sidedef->rowoffset;
+		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))
@@ -2039,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;
@@ -2223,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;
@@ -2266,8 +2261,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					rw_midtexturebackslide = ceilingbackslide;
 				}
 			}
-			rw_midtexturemid += sidedef->rowoffset;
-			rw_midtextureback += sidedef->rowoffset;
+			rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
+			rw_midtextureback += sidedef->rowoffset + sidedef->offsety_mid;
 
 			maskedtexture = true;
 		}
@@ -2305,6 +2300,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		/// don't use texture offset for splats
 		rw_offset2 = rw_offset + curline->offset;
 		rw_offset += sidedef->textureoffset + curline->offset;
+		rw_offset_top = sidedef->offsetx_top;
+		rw_offset_mid = sidedef->offsetx_mid;
+		rw_offset_bot = sidedef->offsetx_bot;
 		rw_centerangle = ANGLE_90 + viewangle - rw_normalangle;
 
 		// calculate light table
@@ -2733,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 09c68b27e95eb34deb1302762d9d8a50e72e44dc..cad0146748bb4ab77325285a6290fc195d4399bc 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 b10a78c182c8c1429e318a1ad2b0ce5e099f5b86..72598f38185acf3d5801365e315e54a920e38511 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;
@@ -190,11 +191,12 @@ UINT32 R_GetSkinAvailabilities(void)
 			// This crash is impossible to trigger as is,
 			// but it could happen if MAXUNLOCKABLES is ever made higher than 32,
 			// and someone makes a mod that has 33+ unlockable characters. :V
-			I_Error("Too many unlockable characters\n");
+			// 2022/03/15: MAXUNLOCKABLES is now higher than 32
+			I_Error("Too many unlockable characters! (maximum is 32)\n");
 			return 0;
 		}
 
-		if (unlockables[i].unlocked)
+		if (clientGamedata->unlocked[i])
 		{
 			response |= (1 << unlockShift);
 		}
@@ -242,11 +244,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 		// Force 3.
 		return true;
 	}
+
 	if (playernum != -1 && players[playernum].bot)
-    {
-        //Force 4.
-        return true;
-    }
+	{
+		// Force 4.
+		return true;
+	}
 
 	// We will now check if this skin is supposed to be locked or not.
 
@@ -284,7 +287,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 	else
 	{
 		// We want to check our global unlockables.
-		return (unlockables[unlockID].unlocked);
+		return (clientGamedata->unlocked[unlockID]);
 	}
 }
 
@@ -499,7 +502,7 @@ static UINT16 W_CheckForPatchSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 	return INT16_MAX; // not found
 }
 
-static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, skin_t *skin)
+static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, skin_t *skin, UINT8 start_spr2)
 {
 	UINT16 newlastlump;
 	UINT8 sprite2;
@@ -521,7 +524,7 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski
 	{
 		newlastlump++;
 		// load all sprite sets we are aware of... for super!
-		for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
+		for (sprite2 = start_spr2; sprite2 < free_spr2; sprite2++)
 			R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, *lastlump);
 
 		newlastlump--;
@@ -529,11 +532,11 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski
 	}
 
 	// load all sprite sets we are aware of... for normal stuff.
-	for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
+	for (sprite2 = start_spr2; sprite2 < free_spr2; sprite2++)
 		R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[sprite2], wadnum, *lump, *lastlump);
 
 	if (skin->sprites[0].numframes == 0)
-		I_Error("R_LoadSkinSprites: no frames found for sprite SPR2_%s\n", spr2names[0]);
+		CONS_Alert(CONS_ERROR, M_GetText("No frames found for sprite SPR2_%s\n"), spr2names[0]);
 }
 
 // returns whether found appropriate property
@@ -680,7 +683,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
@@ -710,7 +713,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)
@@ -753,7 +756,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);
@@ -769,6 +772,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")
@@ -777,6 +793,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")
@@ -795,7 +818,7 @@ next_token:
 		free(buf2);
 
 		// Add sprites
-		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin, 0);
 		//ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere
 
 		R_FlushTranslationColormapCache();
@@ -829,7 +852,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
@@ -853,7 +876,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)
@@ -892,13 +915,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")
@@ -928,7 +964,7 @@ next_token:
 		}
 
 		// Patch sprites
-		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin, 0);
 		//ST_LoadFaceGraphics(skinnum); -- nah let's do this elsewhere
 
 		R_FlushTranslationColormapCache();
@@ -941,3 +977,149 @@ next_token:
 
 #undef HUDNAMEWRITE
 #undef SYMBOLCONVERT
+
+static UINT16 W_CheckForEitherSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
+{
+	UINT16 i;
+	const char *S_SKIN = "S_SKIN";
+	const char *P_SKIN = "P_SKIN";
+	lumpinfo_t *lump_p;
+
+	// scan forward, start at <startlump>
+	if (startlump < wadfiles[wadid]->numlumps)
+	{
+		lump_p = wadfiles[wadid]->lumpinfo + startlump;
+		for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++)
+			if (memcmp(lump_p->name,S_SKIN,6)==0 || memcmp(lump_p->name,P_SKIN,6)==0)
+				return i;
+	}
+	return INT16_MAX; // not found
+}
+
+static void R_RefreshSprite2ForWad(UINT16 wadnum, UINT8 start_spr2)
+{
+	UINT16 lump, lastlump = 0;
+	char *buf;
+	char *buf2;
+	char *stoken;
+	char *value;
+	size_t size;
+	skin_t *skin;
+	boolean noskincomplain;
+
+	//
+	// search for all skin patch markers in pwad
+	//
+
+	while ((lump = W_CheckForEitherSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX)
+	{
+		INT32 skinnum = 0;
+
+		// advance by default
+		lastlump = lump + 1;
+
+		buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
+		size = W_LumpLengthPwad(wadnum, lump);
+
+		// for strtok
+		buf2 = malloc(size+1);
+		if (!buf2)
+			I_Error("R_RefreshSprite2ForWad: No more free memory\n");
+		M_Memcpy(buf2,buf,size);
+		buf2[size] = '\0';
+
+		skin = NULL;
+		noskincomplain = 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)
+		*/
+
+		stoken = strtok(buf2, "\r\n= ");
+		while (stoken)
+		{
+			if ((stoken[0] == '/' && stoken[1] == '/')
+				|| (stoken[0] == '#'))// skip comments
+			{
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto next_token;              // find the real next token
+			}
+
+			value = strtok(NULL, "\r\n= ");
+
+			if (!value)
+				I_Error("R_RefreshSprite2ForWad: syntax error in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
+
+			if (!stricmp(stoken, "name"))
+			{
+				strlwr(value);
+				skinnum = R_SkinAvailable(value);
+				if (skinnum != -1)
+					skin = &skins[skinnum];
+				else
+				{
+					CONS_Debug(DBG_SETUP, "R_RefreshSprite2ForWad: unknown skin name in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
+					noskincomplain = true;
+				}
+			}
+
+			if (!skin)
+				break;
+
+next_token:
+			stoken = strtok(NULL, "\r\n= ");
+		}
+		free(buf2);
+
+		if (!skin) // Didn't include a name parameter? What a waste.
+		{
+			if (!noskincomplain)
+				CONS_Debug(DBG_SETUP, "R_RefreshSprite2ForWad: no skin name given in P_SKIN lump #%d (WAD %s)\n", lump, wadfiles[wadnum]->filename);
+			continue;
+		}
+
+		// Update sprites, in the range of (start_spr2 - free_spr2-1)
+		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin, start_spr2);
+		//R_FlushTranslationColormapCache(); // I don't think this is needed for what we're doing?
+	}
+}
+
+static playersprite_t old_spr2 = SPR2_FIRSTFREESLOT;
+void R_RefreshSprite2(void)
+{
+	// Sprite2s being defined by custom wads can create situations where
+	// a custom character might want to add support, but due to load order,
+	// might not be defined in time.
+
+	// The trick where you load characters then level packs to keep savedata
+	// in particular will practically garantuee a level pack can NEVER add custom animations,
+	// because custom character's Sprite2s will not be added.
+
+	// So, go through every file, and reload the sprite2s that were added.
+
+	INT32 i;
+
+	if (old_spr2 > free_spr2)
+	{
+#ifdef PARANOIA
+		I_Error("R_RefreshSprite2: old_spr2 is too high?! (old_spr2: %d, free_spr2: %d)\n", old_spr2, free_spr2);
+#else
+		// Just silently fix
+		old_spr2 = free_spr2;
+#endif
+	}
+
+	if (old_spr2 == free_spr2)
+	{
+		// No sprite2s were added since the last time we did freeslots.
+		return;
+	}
+
+	for (i = 0; i < numwadfiles; i++)
+	{
+		R_RefreshSprite2ForWad(i, old_spr2);
+	}
+
+	// Update previous value.
+	old_spr2 = free_spr2;
+}
diff --git a/src/r_skins.h b/src/r_skins.h
index 361ebb102950edf99041bd3b7276d87de4972d57..fab6fc12c0f659e8b35f5d111a497850edf127b0 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
@@ -100,4 +101,6 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile);
 
 UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player);
 
+void R_RefreshSprite2(void);
+
 #endif //__R_SKINS__
diff --git a/src/r_splats.c b/src/r_splats.c
index 72bfe74b3a7be6ac50313793f4ea28e7f6bc8c5e..0b482d7988cc0e3a32f719a0f84a272fdf63d2f3 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -31,20 +31,8 @@ static void prepare_rastertab(void);
 
 static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
 
-#ifdef USEASM
-void ASMCALL rasterize_segment_tex_asm(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir);
-#endif
-
 static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir)
 {
-#ifdef USEASM
-	if (R_ASM)
-	{
-		rasterize_segment_tex_asm(x1, y1, x2, y2, tv1, tv2, tc, dir);
-		return;
-	}
-	else
-#endif
 	{
 		fixed_t xs, xe, count;
 		fixed_t dx0, dx1;
@@ -191,7 +179,7 @@ void R_DrawFloorSplat(vissprite_t *spr)
 		splatangle = spr->viewpoint.angle;
 
 	if (!(spr->cut & SC_ISROTATED))
-		splatangle += mobj->rollangle;
+		splatangle += mobj->spriteroll;
 
 	splat.angle = -splatangle;
 	splat.angle += ANGLE_90;
@@ -326,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; \
@@ -379,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;
@@ -404,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);
 
@@ -441,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;
+
+	// Determine which R_DrawWhatever to use
+
+	// Solid color
+	if (ds_solidcolor)
 	{
-		ds_transmap = vis->transmap;
+		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;
 
@@ -512,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;
@@ -561,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_textures.c b/src/r_textures.c
index 69e64074d8acefa5e73e52e6311cc3a776d191da..8b47f455e8bc7e2e3a9cb766fb38b357b96d4091 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -1662,6 +1662,35 @@ INT32 R_CheckTextureNumForName(const char *name)
 	return -1;
 }
 
+//
+// R_CheckTextureNameForNum
+//
+// because sidedefs use numbers and sometimes you want names
+// returns no texture marker if no texture was found
+//
+const char *R_CheckTextureNameForNum(INT32 num)
+{
+	if (num > 0 && num < numtextures)
+		return textures[num]->name;
+	
+	return "-";
+}
+
+//
+// R_TextureNameForNum
+//
+// calls R_CheckTextureNameForNum and returns REDWALL if result is a no texture marker
+//
+const char *R_TextureNameForNum(INT32 num)
+{
+	const char *result = R_CheckTextureNameForNum(num);
+
+	if (strcmp(result, "-") == 0)
+		return "REDWALL";
+
+	return result;
+}
+
 //
 // R_TextureNumForName
 //
diff --git a/src/r_textures.h b/src/r_textures.h
index 4a3c10b9ec999fc133a5f5ffe220f03f86ce2ff6..394b4f8243a7fa7a0de120f2ff893bc1dc9f0702 100644
--- a/src/r_textures.h
+++ b/src/r_textures.h
@@ -104,6 +104,10 @@ INT32 R_TextureNumForName(const char *name);
 INT32 R_CheckTextureNumForName(const char *name);
 lumpnum_t R_GetFlatNumForName(const char *name);
 
+// Returns the texture name for the texture number (in case you ever needed it)
+const char *R_CheckTextureNameForNum(INT32 num);
+const char *R_TextureNameForNum(INT32 num);
+
 extern INT32 numtextures;
 
 #endif
diff --git a/src/r_things.c b/src/r_things.c
index 2916482fb2064a06583d524215c1e5671db5896e..be9c5cdffe26be0a5482489d9540a50a2a599998 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -80,6 +80,33 @@ static spriteframe_t sprtemp[64];
 static size_t maxframe;
 static const char *spritename;
 
+//
+// Clipping against drawsegs optimization, from prboom-plus
+//
+// TODO: This should be done with proper subsector pass through
+// sprites which would ideally remove the need to do it at all.
+// Unfortunately, SRB2's drawing loop has lots of annoying
+// changes from Doom for portals, which make it hard to implement.
+
+typedef struct drawseg_xrange_item_s
+{
+	INT16 x1, x2;
+	drawseg_t *user;
+} drawseg_xrange_item_t;
+
+typedef struct drawsegs_xrange_s
+{
+	drawseg_xrange_item_t *items;
+	INT32 count;
+} drawsegs_xrange_t;
+
+#define DS_RANGES_COUNT 3
+static drawsegs_xrange_t drawsegs_xranges[DS_RANGES_COUNT];
+
+static drawseg_xrange_item_t *drawsegs_xrange;
+static size_t drawsegs_xrange_size = 0;
+static INT32 drawsegs_xrange_count = 0;
+
 // ==========================================================================
 //
 // Sprite loading routines: support sprites in pwad, dehacked sprite renaming,
@@ -497,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};
 
@@ -571,7 +599,7 @@ void R_InitSprites(void)
 //
 void R_ClearSprites(void)
 {
-	visspritecount = clippedvissprites = 0;
+	visspritecount = numvisiblesprites = clippedvissprites = 0;
 }
 
 //
@@ -817,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);
@@ -860,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;
@@ -870,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;
@@ -1157,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);
 
@@ -1221,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;
@@ -1265,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;
@@ -1318,7 +1355,7 @@ 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;
@@ -1328,8 +1365,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t 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;
@@ -1424,6 +1461,105 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	objectsdrawn++;
 }
 
+static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis)
+{
+	fixed_t gx, gy;
+	fixed_t tx, tz;
+
+	vissprite_t *box;
+
+	if (!R_ThingBoundingBoxVisible(thing))
+	{
+		return;
+	}
+
+	// uncapped/interpolation
+	boolean interpolate = cv_renderhitboxinterpolation.value;
+	interpmobjstate_t interp = {0};
+
+	// do interpolation
+	if (R_UsingFrameInterpolation() && !paused && interpolate)
+	{
+		R_InterpolateMobjState(thing, rendertimefrac, &interp);
+	}
+	else
+	{
+		R_InterpolateMobjState(thing, FRACUNIT, &interp);
+	}
+
+	// 1--3
+	// |  |
+	// 0--2
+
+	// start in the (0) corner
+	gx = interp.x - interp.radius - viewx;
+	gy = interp.y - interp.radius - viewy;
+
+	tz = FixedMul(gx, viewcos) + FixedMul(gy, viewsin);
+
+	// thing is behind view plane?
+	// if parent vis is visible, ignore this
+	if (!vis && (tz < FixedMul(MINZ, interp.scale)))
+	{
+		return;
+	}
+
+	tx = FixedMul(gx, viewsin) - FixedMul(gy, viewcos);
+
+	// too far off the side?
+	if (!vis && abs(tx) > FixedMul(tz, fovtan)<<2)
+	{
+		return;
+	}
+
+	box = R_NewVisSprite();
+	box->mobj = thing;
+	box->mobjflags = thing->flags;
+	box->thingheight = interp.height;
+	box->cut = SC_BBOX;
+
+	box->gx = tx;
+	box->gy = tz;
+
+	box->scale  = 2 * FixedMul(interp.radius, viewsin);
+	box->xscale = 2 * FixedMul(interp.radius, viewcos);
+
+	box->pz = interp.z;
+	box->pzt = box->pz + box->thingheight;
+
+	box->gzt = box->pzt;
+	box->gz = box->pz;
+	box->texturemid = box->gzt - viewz;
+
+	if (vis)
+	{
+		box->x1 = vis->x1;
+		box->x2 = vis->x2;
+		box->szt = vis->szt;
+		box->sz = vis->sz;
+
+		box->sortscale = vis->sortscale; // link sorting to sprite
+		box->dispoffset = vis->dispoffset + 5;
+
+		box->cut |= SC_LINKDRAW;
+	}
+	else
+	{
+		fixed_t xscale = FixedDiv(projection, tz);
+		fixed_t yscale = FixedDiv(projectiony, tz);
+		fixed_t top = (centeryfrac - FixedMul(box->texturemid, yscale));
+
+		box->x1 = (centerxfrac + FixedMul(box->gx, xscale)) / FRACUNIT;
+		box->x2 = box->x1;
+
+		box->szt = top / FRACUNIT;
+		box->sz = (top + FixedMul(box->thingheight, yscale)) / FRACUNIT;
+
+		box->sortscale = yscale;
+		box->dispoffset = 0;
+	}
+}
+
 //
 // R_ProjectSprite
 // Generates a vissprite for a thing
@@ -1435,6 +1571,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;
@@ -1477,7 +1614,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	fixed_t paperoffset = 0, paperdistance = 0;
 	angle_t centerangle = 0;
 
-	INT32 dispoffset = thing->info->dispoffset;
+	INT32 dispoffset = thing->dispoffset;
 
 	//SoM: 3/17/2000
 	fixed_t gz = 0, gzt = 0;
@@ -1493,6 +1630,7 @@ static void R_ProjectSprite(mobj_t *thing)
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
 	INT32 rollangle = 0;
+	angle_t spriterotangle = 0;
 #endif
 
 	// uncapped/interpolation
@@ -1509,6 +1647,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;
@@ -1629,9 +1769,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;
@@ -1642,17 +1779,19 @@ static void R_ProjectSprite(mobj_t *thing)
 	patch = W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
 
 #ifdef ROTSPRITE
-	if (thing->rollangle
+	spriterotangle = R_SpriteRotationAngle(&interp);
+
+	if (spriterotangle != 0
 	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		if (papersprite && ang >= ANGLE_180)
 		{
 			// Makes Software act much more sane like OpenGL
-			rollangle = R_GetRollAngle(InvAngle(thing->rollangle));
+			rollangle = R_GetRollAngle(InvAngle(spriterotangle));
 		}
 		else
 		{
-			rollangle = R_GetRollAngle(thing->rollangle);
+			rollangle = R_GetRollAngle(spriterotangle);
 		}
 
 		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
@@ -1679,6 +1818,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;
 
@@ -1846,6 +1993,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;
@@ -1937,7 +2086,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);
 			}
@@ -1952,8 +2101,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;
@@ -1968,7 +2117,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;
@@ -1983,7 +2132,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
@@ -2062,7 +2211,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);
@@ -2185,6 +2334,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (oldthing->shadowscale && cv_shadow.value)
 		R_ProjectDropShadow(oldthing, vis, oldthing->shadowscale, basetx, basetz);
 
+	R_ProjectBoundingBox(oldthing, vis);
+
 	// Debug
 	++objectsdrawn;
 }
@@ -2410,8 +2561,26 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
 	for (thing = sec->thinglist; thing; thing = thing->snext)
 	{
-		if (R_ThingVisibleWithinDist(thing, limit_dist, hoop_limit_dist))
-			R_ProjectSprite(thing);
+		if (R_ThingWithinDist(thing, limit_dist, hoop_limit_dist))
+		{
+			const INT32 oldobjectsdrawn = objectsdrawn;
+
+			if (R_ThingVisible(thing))
+			{
+				R_ProjectSprite(thing);
+			}
+
+			// I'm so smart :^)
+			if (objectsdrawn == oldobjectsdrawn)
+			{
+				/*
+				Object is invisible OR is off screen but
+				render its bbox even if the latter because
+				radius could be bigger than sprite.
+				*/
+				R_ProjectBoundingBox(thing, NULL);
+			}
+		}
 	}
 
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
@@ -2482,6 +2651,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;
 
@@ -2499,26 +2676,36 @@ static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 e
 			if (dsfirst->cut & SC_SHADOW)
 				continue;
 
+			// don't connect to your bounding box!
+			if (dsfirst->cut & SC_BBOX)
+				continue;
+
 			// don't connect if it's not the tracer
 			if (dsfirst->mobj != ds->mobj)
 				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)
@@ -2570,12 +2757,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;
+		}
 	}
 }
 
@@ -2939,7 +3129,9 @@ static void R_DrawSprite(vissprite_t *spr)
 	mfloorclip = spr->clipbot;
 	mceilingclip = spr->cliptop;
 
-	if (spr->cut & SC_SPLAT)
+	if (spr->cut & SC_BBOX)
+		R_DrawThingBoundingBox(spr);
+	else if (spr->cut & SC_SPLAT)
 		R_DrawFloorSplat(spr);
 	else
 		R_DrawVisSprite(spr);
@@ -3004,9 +3196,47 @@ 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
-void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal)
+static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* portal)
 {
 	drawseg_t *ds;
 	INT32		x;
@@ -3026,21 +3256,23 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 	// Pointer check was originally nonportable
 	// and buggy, by going past LEFT end of array:
 
-	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-	for (ds = ds_p; ds-- > dsstart;)
+	// e6y: optimization
+	if (drawsegs_xrange_size)
 	{
-		// determine if the drawseg obscures the sprite
-		if (ds->x1 > x2 ||
-			ds->x2 < x1 ||
-			(!ds->silhouette
-			 && !ds->maskedtexturecol))
-		{
-			// does not cover sprite
-			continue;
-		}
+		const drawseg_xrange_item_t *last = &drawsegs_xrange[drawsegs_xrange_count - 1];
+		drawseg_xrange_item_t *curr = &drawsegs_xrange[-1];
 
-		if (ds->portalpass != 66)
+		while (++curr <= last)
 		{
+			// determine if the drawseg obscures the sprite
+			if (curr->x1 > x2 || curr->x2 < x1)
+			{
+				// does not cover sprite
+				continue;
+			}
+
+			ds = curr->user;
+
 			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
 				continue; // is a portal
 
@@ -3065,43 +3297,43 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 				// seg is behind sprite
 				continue;
 			}
-		}
 
-		r1 = ds->x1 < x1 ? x1 : ds->x1;
-		r2 = ds->x2 > x2 ? x2 : ds->x2;
+			r1 = ds->x1 < x1 ? x1 : ds->x1;
+			r2 = ds->x2 > x2 ? x2 : ds->x2;
 
-		// clip this piece of the sprite
-		silhouette = ds->silhouette;
+			// clip this piece of the sprite
+			silhouette = ds->silhouette;
 
-		if (spr->gz >= ds->bsilheight)
-			silhouette &= ~SIL_BOTTOM;
+			if (spr->gz >= ds->bsilheight)
+				silhouette &= ~SIL_BOTTOM;
 
-		if (spr->gzt <= ds->tsilheight)
-			silhouette &= ~SIL_TOP;
+			if (spr->gzt <= ds->tsilheight)
+				silhouette &= ~SIL_TOP;
 
-		if (silhouette == SIL_BOTTOM)
-		{
-			// bottom sil
-			for (x = r1; x <= r2; x++)
-				if (spr->clipbot[x] == -2)
-					spr->clipbot[x] = ds->sprbottomclip[x];
-		}
-		else if (silhouette == SIL_TOP)
-		{
-			// top sil
-			for (x = r1; x <= r2; x++)
-				if (spr->cliptop[x] == -2)
-					spr->cliptop[x] = ds->sprtopclip[x];
-		}
-		else if (silhouette == (SIL_TOP|SIL_BOTTOM))
-		{
-			// both
-			for (x = r1; x <= r2; x++)
+			if (silhouette == SIL_BOTTOM)
+			{
+				// bottom sil
+				for (x = r1; x <= r2; x++)
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+			}
+			else if (silhouette == SIL_TOP)
 			{
-				if (spr->clipbot[x] == -2)
-					spr->clipbot[x] = ds->sprbottomclip[x];
-				if (spr->cliptop[x] == -2)
-					spr->cliptop[x] = ds->sprtopclip[x];
+				// top sil
+				for (x = r1; x <= r2; x++)
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+			}
+			else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+			{
+				// both
+				for (x = r1; x <= r2; x++)
+				{
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+				}
 			}
 		}
 	}
@@ -3145,8 +3377,7 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 			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)
@@ -3171,16 +3402,119 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 			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)
 {
+	const size_t maxdrawsegs = ds_p - drawsegs;
+	const INT32 cx = viewwidth / 2;
+	drawseg_t* ds;
+	INT32 i;
+
+	// e6y
+	// Reducing of cache misses in the following R_DrawSprite()
+	// Makes sense for scenes with huge amount of drawsegs.
+	// ~12% of speed improvement on epic.wad map05
+	for (i = 0; i < DS_RANGES_COUNT; i++)
+	{
+		drawsegs_xranges[i].count = 0;
+	}
+
+	if (visspritecount - clippedvissprites <= 0)
+	{
+		return;
+	}
+
+	if (drawsegs_xrange_size < maxdrawsegs)
+	{
+		drawsegs_xrange_size = 2 * maxdrawsegs;
+
+		for (i = 0; i < DS_RANGES_COUNT; i++)
+		{
+			drawsegs_xranges[i].items = Z_Realloc(
+				drawsegs_xranges[i].items,
+				drawsegs_xrange_size * sizeof(drawsegs_xranges[i].items[0]),
+				PU_STATIC, NULL
+			);
+		}
+	}
+
+	for (ds = ds_p; ds-- > dsstart;)
+	{
+		if (ds->silhouette || ds->maskedtexturecol)
+		{
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x1 = ds->x1;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x2 = ds->x2;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].user = ds;
+
+			// e6y: ~13% of speed improvement on sunder.wad map10
+			if (ds->x1 < cx)
+			{
+				drawsegs_xranges[1].items[drawsegs_xranges[1].count] =
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[1].count++;
+			}
+
+			if (ds->x2 >= cx)
+			{
+				drawsegs_xranges[2].items[drawsegs_xranges[2].count] =
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[2].count++;
+			}
+
+			drawsegs_xranges[0].count++;
+		}
+	}
+
 	for (; clippedvissprites < visspritecount; clippedvissprites++)
 	{
 		vissprite_t *spr = R_GetVisSprite(clippedvissprites);
+
+		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;
-		R_ClipVisSprite(spr, x1, x2, dsstart, portal);
+
+		if (x2 < cx)
+		{
+			drawsegs_xrange = drawsegs_xranges[1].items;
+			drawsegs_xrange_count = drawsegs_xranges[1].count;
+		}
+		else if (x1 >= cx)
+		{
+			drawsegs_xrange = drawsegs_xranges[2].items;
+			drawsegs_xrange_count = drawsegs_xranges[2].count;
+		}
+		else
+		{
+			drawsegs_xrange = drawsegs_xranges[0].items;
+			drawsegs_xrange_count = drawsegs_xranges[0].count;
+		}
+
+		R_ClipVisSprite(spr, x1, x2, portal);
+
+		if ((spr->cut & SC_NOTVISIBLE) == 0)
+			numvisiblesprites++;
 	}
 }
 
@@ -3188,22 +3522,22 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
 boolean R_ThingVisible (mobj_t *thing)
 {
 	return (!(
-				thing->sprite == SPR_NULL ||
-				( thing->flags2 & (MF2_DONTDRAW) ) ||
-				(r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing)))
+		(thing->sprite == SPR_NULL) || // Don't draw null-sprites
+		(thing->flags2 & MF2_DONTDRAW) || // Don't draw MF2_LINKDRAW objects
+		(thing->drawonlyforplayer && thing->drawonlyforplayer != viewplayer) || // Don't draw other players' personal objects
+		(r_viewmobj && (
+		  (r_viewmobj == thing) || // Don't draw first-person players or awayviewmobj objects
+		  (r_viewmobj->player && r_viewmobj->player->followmobj == thing) || // Don't draw first-person players' followmobj
+		  (r_viewmobj == thing->dontdrawforviewmobj) // Don't draw objects that are hidden for the current view
+		))
 	));
 }
 
-boolean R_ThingVisibleWithinDist (mobj_t *thing,
+boolean R_ThingWithinDist (mobj_t *thing,
 		fixed_t      limit_dist,
 		fixed_t hoop_limit_dist)
 {
-	fixed_t approx_dist;
-
-	if (! R_ThingVisible(thing))
-		return false;
-
-	approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
+	const fixed_t approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
 
 	if (thing->sprite == SPR_HOOP)
 	{
diff --git a/src/r_things.h b/src/r_things.h
index de9ea5dfd7baeb895942c1596c425bef167b5a02..318234886bbbfa2b603f1883d50bbfea4c67d370 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -66,9 +66,12 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
 
+UINT8 R_GetBoundingBoxColor(mobj_t *thing);
+boolean R_ThingBoundingBoxVisible(mobj_t *thing);
+
 boolean R_ThingVisible (mobj_t *thing);
 
-boolean R_ThingVisibleWithinDist (mobj_t *thing,
+boolean R_ThingWithinDist (mobj_t *thing,
 		fixed_t        draw_dist,
 		fixed_t nights_draw_dist);
 
@@ -120,20 +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_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;
 
@@ -212,15 +217,17 @@ typedef struct vissprite_s
 
 	INT16 clipbot[MAXVIDWIDTH], cliptop[MAXVIDWIDTH];
 
-	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
+	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);
-void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal);
 
 boolean R_SpriteIsFlashing(vissprite_t *vis);
+
+void R_DrawThingBoundingBox(vissprite_t *spr);
+
 UINT8 *R_GetSpriteTranslation(vissprite_t *vis);
 
 // ----------
diff --git a/src/s_sound.c b/src/s_sound.c
index 111b6ce256661d9f0adac594dd03f4d63573afd7..ada1a0fd2f399f09849a7330cf14ca283c7b1784 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1692,6 +1692,7 @@ UINT8 soundtestpage = 1;
 //
 boolean S_PrepareSoundTest(void)
 {
+	gamedata_t *data = clientGamedata;
 	musicdef_t *def;
 	INT32 pos = numsoundtestdefs = 0;
 
@@ -1717,9 +1718,9 @@ boolean S_PrepareSoundTest(void)
 		if (!(def->soundtestpage & soundtestpage))
 			continue;
 		soundtestdefs[pos++] = def;
-		if (def->soundtestcond > 0 && !(mapvisited[def->soundtestcond-1] & MV_BEATEN))
+		if (def->soundtestcond > 0 && !(data->mapvisited[def->soundtestcond-1] & MV_BEATEN))
 			continue;
-		if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond))
+		if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond, data))
 			continue;
 		def->allowed = true;
 	}
diff --git a/src/screen.c b/src/screen.c
index f8af4c504a74d2d6b779bd6befdee2ab2277c544..417e793bde540c62a9edf2b6a0b8073250ee7f73 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -44,10 +44,6 @@
 // SRB2Kart
 #include "r_fps.h" // R_GetFramerateCap
 
-#if defined (USEASM) && !defined (NORUSEASM)//&& (!defined (_MSC_VER) || (_MSC_VER <= 1200))
-#define RUSEASM //MSC.NET can't patch itself
-#endif
-
 // --------------------------------------------
 // assembly or c drawer routines for 8bpp/16bpp
 // --------------------------------------------
@@ -70,6 +66,8 @@ static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"},
 //added : 03-02-98: default screen mode, as loaded/saved in config
 consvar_t cv_scr_width = CVAR_INIT ("scr_width", "1280", CV_SAVE, CV_Unsigned, NULL);
 consvar_t cv_scr_height = CVAR_INIT ("scr_height", "800", CV_SAVE, CV_Unsigned, NULL);
+consvar_t cv_scr_width_w = CVAR_INIT ("scr_width_w", "640", CV_SAVE, CV_Unsigned, NULL);
+consvar_t cv_scr_height_w = CVAR_INIT ("scr_height_w", "400", CV_SAVE, CV_Unsigned, NULL);
 consvar_t cv_scr_depth = CVAR_INIT ("scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL);
 
 consvar_t cv_renderview = CVAR_INIT ("renderview", "On", 0, CV_OnOff, NULL);
@@ -100,7 +98,6 @@ UINT8 *scr_borderpatch; // flat used to fill the reduced view borders set at ST_
 //  Short and Tall sky drawer, for the current color mode
 void (*walldrawerfunc)(void);
 
-boolean R_ASM = true;
 boolean R_486 = false;
 boolean R_586 = false;
 boolean R_MMX = false;
@@ -167,26 +164,6 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs_npo2[SPANDRAWFUNC_WATER] = R_DrawWaterSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedWaterSpan_NPO2_8;
 
-#ifdef RUSEASM
-		if (R_ASM)
-		{
-			if (R_MMX)
-			{
-				colfuncs[BASEDRAWFUNC] = R_DrawColumn_8_MMX;
-				//colfuncs[COLDRAWFUNC_SHADE] = R_DrawShadeColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_8_ASM;
-				colfuncs[COLDRAWFUNC_TWOSMULTIPATCH] = R_Draw2sMultiPatchColumn_8_MMX;
-				spanfuncs[BASEDRAWFUNC] = R_DrawSpan_8_MMX;
-			}
-			else
-			{
-				colfuncs[BASEDRAWFUNC] = R_DrawColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_SHADE] = R_DrawShadeColumn_8_ASM;
-				//colfuncs[COLDRAWFUNC_FUZZY] = R_DrawTranslucentColumn_8_ASM;
-				colfuncs[COLDRAWFUNC_TWOSMULTIPATCH] = R_Draw2sMultiPatchColumn_8_ASM;
-			}
-		}
-#endif
 	}
 /*	else if (vid.bpp > 1)
 	{
@@ -269,8 +246,6 @@ void SCR_Startup(void)
 		CONS_Printf("CPU Info: 486: %i, 586: %i, MMX: %i, 3DNow: %i, MMXExt: %i, SSE2: %i\n", R_486, R_586, R_MMX, R_3DNow, R_MMXExt, R_SSE2);
 	}
 
-	if (M_CheckParm("-noASM"))
-		R_ASM = false;
 	if (M_CheckParm("-486"))
 		R_486 = true;
 	if (M_CheckParm("-586"))
@@ -377,10 +352,16 @@ void SCR_CheckDefaultMode(void)
 	}
 	else
 	{
-		CONS_Printf(M_GetText("Default resolution: %d x %d (%d bits)\n"), cv_scr_width.value,
-			cv_scr_height.value, cv_scr_depth.value);
-		// see note above
-		setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1;
+		CONS_Printf(M_GetText("Default resolution: %d x %d\n"), cv_scr_width.value, cv_scr_height.value);
+		CONS_Printf(M_GetText("Windowed resolution: %d x %d\n"), cv_scr_width_w.value, cv_scr_height_w.value);
+		CONS_Printf(M_GetText("Default bit depth: %d bits\n"), cv_scr_depth.value);
+		if (cv_fullscreen.value)
+			setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1; // see note above
+		else
+			setmodeneeded = VID_GetModeForSize(cv_scr_width_w.value, cv_scr_height_w.value) + 1; // see note above
+
+		if (setmodeneeded <= 0)
+			CONS_Alert(CONS_WARNING, "Invalid resolution given, defaulting to base resolution\n");
 	}
 
 	if (cv_renderer.value != (signed)rendermode)
@@ -398,9 +379,8 @@ void SCR_CheckDefaultMode(void)
 // sets the modenum as the new default video mode to be saved in the config file
 void SCR_SetDefaultMode(void)
 {
-	// remember the default screen size
-	CV_SetValue(&cv_scr_width, vid.width);
-	CV_SetValue(&cv_scr_height, vid.height);
+	CV_SetValue(cv_fullscreen.value ? &cv_scr_width : &cv_scr_width_w, vid.width);
+	CV_SetValue(cv_fullscreen.value ? &cv_scr_height : &cv_scr_height_w, vid.height);
 }
 
 // Change fullscreen on/off according to cv_fullscreen
@@ -415,7 +395,16 @@ void SCR_ChangeFullscreen(void)
 	if (graphics_started)
 	{
 		VID_PrepareModeList();
-		setmodeneeded = VID_GetModeForSize(vid.width, vid.height) + 1;
+		if (cv_fullscreen.value)
+			setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1;
+		else
+			setmodeneeded = VID_GetModeForSize(cv_scr_width_w.value, cv_scr_height_w.value) + 1;
+
+		if (setmodeneeded <= 0) // hacky safeguard
+		{
+			CONS_Alert(CONS_WARNING, "Invalid resolution given, defaulting to base resolution.\n");
+			setmodeneeded = VID_GetModeForSize(BASEVIDWIDTH, BASEVIDHEIGHT) + 1;
+		}
 	}
 	return;
 #endif
@@ -462,11 +451,11 @@ double averageFPS = 0.0f;
 #define USE_FPS_SAMPLES
 
 #ifdef USE_FPS_SAMPLES
-#define FPS_SAMPLE_RATE (0.05) // How often to update FPS samples, in seconds
+#define MAX_FRAME_TIME 0.05
 #define NUM_FPS_SAMPLES (16) // Number of samples to store
 
-static double fps_samples[NUM_FPS_SAMPLES];
-static double updateElapsed = 0.0;
+static double total_frame_time = 0.0;
+static int frame_index;
 #endif
 
 static boolean fps_init = false;
@@ -489,34 +478,12 @@ void SCR_CalculateFPS(void)
 	fps_enter = fps_finish;
 
 #ifdef USE_FPS_SAMPLES
-	updateElapsed += frameElapsed;
-
-	if (updateElapsed >= FPS_SAMPLE_RATE)
-	{
-		static int sampleIndex = 0;
-		int i;
-
-		fps_samples[sampleIndex] = frameElapsed;
-
-		sampleIndex++;
-		if (sampleIndex >= NUM_FPS_SAMPLES)
-			sampleIndex = 0;
-
-		averageFPS = 0.0;
-		for (i = 0; i < NUM_FPS_SAMPLES; i++)
-		{
-			averageFPS += fps_samples[i];
-		}
-
-		if (averageFPS > 0.0)
-		{
-			averageFPS = 1.0 / (averageFPS / NUM_FPS_SAMPLES);
-		}
-	}
-
-	while (updateElapsed >= FPS_SAMPLE_RATE)
+	total_frame_time += frameElapsed;
+	if (frame_index++ >= NUM_FPS_SAMPLES || total_frame_time >= MAX_FRAME_TIME)
 	{
-		updateElapsed -= FPS_SAMPLE_RATE;
+		averageFPS = 1.0 / (total_frame_time / frame_index);
+		total_frame_time = 0.0;
+		frame_index = 0;
 	}
 #else
 	// Direct, unsampled counter.
diff --git a/src/screen.h b/src/screen.h
index 19103b0df59036c80ea85112f47a7673596ded67..65e82ff4df2bff3701a0f57d4667ee32a34784f5 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -199,7 +199,9 @@ extern CV_PossibleValue_t cv_renderer_t[];
 extern INT32 scr_bpp;
 extern UINT8 *scr_borderpatch; // patch used to fill the view borders
 
-extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_fullscreen;
+extern consvar_t cv_scr_width, cv_scr_height, cv_scr_width_w, cv_scr_height_w, cv_scr_depth, cv_fullscreen;
+extern consvar_t cv_renderview, cv_renderer;
+extern consvar_t cv_renderhitbox, cv_renderhitboxinterpolation, cv_renderhitboxgldepth;
 // wait for page flipping to end or not
 extern consvar_t cv_vidwait;
 extern consvar_t cv_timescale;
diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt
index be540b778b733976a9ceb08c636bcd30d36d29d2..4c4cdafb640e7806ee1efb79380ee10eaf26e119 100644
--- a/src/sdl/CMakeLists.txt
+++ b/src/sdl/CMakeLists.txt
@@ -1,17 +1,17 @@
 # Declare SDL2 interface sources
 
-target_sources(SRB2SDL2 PRIVATE mixer_sound.c)
-
-target_sourcefile(c)
-
-target_sources(SRB2SDL2 PRIVATE ogl_sdl.c)
-
-target_sources(SRB2SDL2 PRIVATE i_threads.c)
-
-if(${SRB2_USEASM})
-	set_source_files_properties(${SRB2_ASM_SOURCES} PROPERTIES LANGUAGE C)
-	set_source_files_properties(${SRB2_ASM_SOURCES} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp")
-endif()
+target_sources(SRB2SDL2 PRIVATE
+	mixer_sound.c
+	ogl_sdl.c
+	i_threads.c
+	i_net.c
+	i_system.c
+	i_main.c
+	i_video.c
+	dosstr.c
+	endtxt.c
+	hwsym_sdl.c
+)
 
 if("${CMAKE_SYSTEM_NAME}" MATCHES Windows)
 	target_sources(SRB2SDL2 PRIVATE
@@ -68,18 +68,6 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Linux)
 	target_link_libraries(SRB2SDL2 PRIVATE m rt)
 endif()
 
-if(${SRB2_USEASM})
-	if(${SRB2_CONFIG_YASM})
-		set(ASM_ASSEMBLER_TEMP ${CMAKE_ASM_YASM_COMPILER})
-		set(ASM_ASSEMBLER_OBJFORMAT ${CMAKE_ASM_YASM_OBJECT_FORMAT})
-		set_source_files_properties(${SRB2_NASM_SOURCES} LANGUAGE ASM_YASM)
-	else()
-		set(ASM_ASSEMBLER_TEMP ${CMAKE_ASM_NASM_COMPILER})
-		set(ASM_ASSEMBLER_OBJFORMAT ${CMAKE_ASM_NASM_OBJECT_FORMAT})
-		set_source_files_properties(${SRB2_NASM_SOURCES} LANGUAGE ASM_NASM)
-	endif()
-endif()
-
 if("${CMAKE_SYSTEM_NAME}" MATCHES Windows)
 	target_link_libraries(SRB2SDL2 PRIVATE
 		ws2_32
diff --git a/src/sdl/MakeCYG.cfg b/src/sdl/MakeCYG.cfg
index 5907579c1bc9d16abb0e338cb709566fd75b6c61..b78316b00142dfcb96188f6fb45baa047a628ed0 100644
--- a/src/sdl/MakeCYG.cfg
+++ b/src/sdl/MakeCYG.cfg
@@ -7,7 +7,6 @@
 
 	NOHW=1
 	NOHS=1
-	NOASM=1
 
 	OPTS+=-DLINUX
 
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index c20265ed17cde4a7d17844029489a713ba5ad617..0b95cd0b2e0732b3f562d85d5e7454c7ec351b64 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -259,7 +259,7 @@
     <ClInclude Include="..\hardware\hw_md2load.h" />
     <ClInclude Include="..\hardware\hw_md3load.h" />
     <ClInclude Include="..\hardware\hw_model.h" />
-    <ClInclude Include="..\hardware\u_list.h" />
+    <ClInclude Include="..\u_list.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
@@ -424,7 +424,7 @@
     <ClCompile Include="..\hardware\hw_md3load.c" />
     <ClCompile Include="..\hardware\hw_model.c" />
     <ClCompile Include="..\hardware\r_opengl\r_opengl.c" />
-    <ClCompile Include="..\hardware\u_list.c" />
+    <ClCompile Include="..\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
@@ -556,4 +556,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index 1dee379c0d8d95db186f1e9e3bb301554ed0549f..3eeacd83569188bd3bfdb5c8f2b9e720b5dce9f4 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -70,39 +70,6 @@ char logfilename[1024];
 typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID);
 #endif
 
-#if defined (_WIN32)
-static inline VOID MakeCodeWritable(VOID)
-{
-#ifdef USEASM // Disable write-protection of code segment
-	DWORD OldRights;
-	const DWORD NewRights = PAGE_EXECUTE_READWRITE;
-	PBYTE pBaseOfImage = (PBYTE)GetModuleHandle(NULL);
-	PIMAGE_DOS_HEADER dosH =(PIMAGE_DOS_HEADER)pBaseOfImage;
-	PIMAGE_NT_HEADERS ntH = (PIMAGE_NT_HEADERS)(pBaseOfImage + dosH->e_lfanew);
-	PIMAGE_OPTIONAL_HEADER oH = (PIMAGE_OPTIONAL_HEADER)
-		((PBYTE)ntH + sizeof (IMAGE_NT_SIGNATURE) + sizeof (IMAGE_FILE_HEADER));
-	LPVOID pA = pBaseOfImage+oH->BaseOfCode;
-	SIZE_T pS = oH->SizeOfCode;
-#if 1 // try to find the text section
-	PIMAGE_SECTION_HEADER ntS = IMAGE_FIRST_SECTION (ntH);
-	WORD s;
-	for (s = 0; s < ntH->FileHeader.NumberOfSections; s++)
-	{
-		if (memcmp (ntS[s].Name, ".text\0\0", 8) == 0)
-		{
-			pA = pBaseOfImage+ntS[s].VirtualAddress;
-			pS = ntS[s].Misc.VirtualSize;
-			break;
-		}
-	}
-#endif
-
-	if (!VirtualProtect(pA,pS,NewRights,&OldRights))
-		I_Error("Could not make code writable\n");
-#endif
-}
-#endif
-
 #ifdef LOGMESSAGES
 static void InitLogging(void)
 {
@@ -243,7 +210,6 @@ int main(int argc, char **argv)
 #ifndef __MINGW32__
 	prevExceptionFilter = SetUnhandledExceptionFilter(RecordExceptionInfo);
 #endif
-	MakeCodeWritable();
 #endif
 
 	// startup SRB2
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index d4055ba2675cd172b90c82851cd2149c9629e0be..962c46b43aceab00fe21c1598622dc54b439f7df 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -23,12 +23,6 @@
 /// \file
 /// \brief SRB2 system stuff for SDL
 
-#ifdef CMAKECONFIG
-#include "config.h"
-#else
-#include "../config.h.in"
-#endif
-
 #include <signal.h>
 
 #ifdef _WIN32
@@ -41,6 +35,12 @@ typedef DWORD (WINAPI *p_timeGetTime) (void);
 typedef UINT (WINAPI *p_timeEndPeriod) (UINT);
 typedef HANDLE (WINAPI *p_OpenFileMappingA) (DWORD, BOOL, LPCSTR);
 typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
+
+// This is for RtlGenRandom.
+#define SystemFunction036 NTAPI SystemFunction036
+#include <ntsecapi.h>
+#undef SystemFunction036
+
 #endif
 #include <stdio.h>
 #include <stdlib.h>
@@ -90,7 +90,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include <kvm.h>
 #endif
 #include <nlist.h>
-#include <sys/vmmeter.h>
+#include <sys/sysctl.h>
 #endif
 #endif
 
@@ -326,7 +326,6 @@ static void write_backtrace(INT32 signal)
 	backtrace_symbols_fd(funcptrs, bt_size, STDERR_FILENO);
 
 	bt_write_file(fd, "\n"); // Write another newline to the log so it looks nice :)
-
 	if (fd != -1) {
 		fsync(fd); // reaaaaally make sure we got that data written.
 		close(fd);
@@ -338,56 +337,90 @@ 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;
-	char msg[128];
+	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)
 	{
 //	case SIGINT:
-//		sigmsg = "SIGINT - interrupted";
+//		sigttl = "SIGINT"
+//		sigmsg = "SRB2 was interrupted prematurely by the user.";
 //		break;
 	case SIGILL:
-		sigmsg = "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 = "SIGFPE - mathematical exception";
+		sigmsg = "SRB2 has encountered a mathematical exception and needs to close.";
+		signame = "SIGFPE"; // mathematical exception
 		break;
 	case SIGSEGV:
-		sigmsg = "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 = "SIGTERM - Software termination signal from kill";
+//		sigmsg = "SRB2 was terminated by a kill signal.";
+//		sigttl = "SIGTERM"; // Software termination signal from kill
 //		break;
 //	case SIGBREAK:
-//		sigmsg = "SIGBREAK - Ctrl-Break sequence";
+//		sigmsg = "SRB2 was terminated by a Ctrl-Break sequence.";
+//		sigttl = "SIGBREAK" // Ctrl-Break sequence
 //		break;
 	case SIGABRT:
-		sigmsg = "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:
-		sprintf(msg,"signal number %d", num);
+		sigmsg = "SRB2 was terminated by an unknown signal.";
+
+		sprintf(ttl, "number %d", num);
 		if (coredumped)
-			sigmsg = 0;
+			signame = 0;
 		else
-			sigmsg = msg;
+			signame = ttl;
 	}
 
 	if (coredumped)
 	{
-		if (sigmsg)
-			sprintf(msg, "%s (core dumped)", sigmsg);
+		if (signame)
+			sprintf(ttl, "%s (core dumped)", signame);
 		else
-			strcat(msg, " (core dumped)");
+			strcat(ttl, " (core dumped)");
 
-		sigmsg = msg;
+		signame = ttl;
 	}
 
-	I_OutputMsg("\nProcess killed by signal: %s\n\n", sigmsg);
+	strcat(sigttl, signame);
+	I_OutputMsg("%s\n", sigttl);
 
-	if (!M_CheckParm("-dedicated"))
-		SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-			"Process killed by signal",
-			sigmsg, NULL);
+	if (M_CheckParm("-dedicated"))
+		return;
+
+	const SDL_MessageBoxButtonData buttons[] = {
+		{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0,		"OK" },
+		{ 										0, 1,  "Discord" },
+	};
+
+	const SDL_MessageBoxData messageboxdata = {
+		SDL_MESSAGEBOX_ERROR, /* .flags */
+		NULL, /* .window */
+		sigttl, /* .title */
+		va("%s %s", sigmsg, reportmsg), /* .message */
+		SDL_arraysize(buttons), /* .numbuttons */
+		buttons, /* .buttons */
+		NULL /* .colorScheme */
+	};
+
+	int buttonid;
+
+	SDL_ShowMessageBox(&messageboxdata, &buttonid);
+
+#if SDL_VERSION_ATLEAST(2,0,14)
+	if (buttonid == 1)
+		SDL_OpenURL("https://www.srb2.org/discord");
+#endif
 }
 
 #ifndef NEWSIGNALHANDLER
@@ -2243,7 +2276,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];
 
@@ -2335,7 +2368,10 @@ INT32 I_StartupSystem(void)
 #endif
 	I_StartupConsole();
 #ifdef NEWSIGNALHANDLER
-	I_Fork();
+	// This is useful when debugging. It lets GDB attach to
+	// the correct process easily.
+	if (!M_CheckParm("-nofork"))
+		I_Fork();
 #endif
 	I_RegisterSignals();
 	I_OutputMsg("Compiled for SDL version: %d.%d.%d\n",
@@ -2365,7 +2401,7 @@ void I_Quit(void)
 #ifndef NONET
 	D_SaveBan(); // save the ban list
 #endif
-	G_SaveGameData(); // Tails 12-08-2002
+	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.
 
@@ -2449,7 +2485,7 @@ void I_Error(const char *error, ...)
 		if (errorcount == 8)
 		{
 			M_SaveConfig(NULL);
-			G_SaveGameData();
+			G_SaveGameData(clientGamedata);
 		}
 		if (errorcount > 20)
 		{
@@ -2482,7 +2518,7 @@ void I_Error(const char *error, ...)
 #ifndef NONET
 	D_SaveBan(); // save the ban list
 #endif
-	G_SaveGameData(); // Tails 12-08-2002
+	G_SaveGameData(clientGamedata); // Tails 12-08-2002
 
 	// Shutdown. Here might be other errors.
 	if (demorecording)
@@ -2621,9 +2657,10 @@ void I_ShutdownSystem(void)
 {
 	INT32 c;
 
-#ifndef NEWSIGNALHANDLER
-	I_ShutdownConsole();
+#ifdef NEWSIGNALHANDLER
+	if (M_CheckParm("-nofork"))
 #endif
+		I_ShutdownConsole();
 
 	for (c = MAX_QUIT_FUNCS-1; c >= 0; c--)
 		if (quit_funcs[c])
@@ -2755,6 +2792,38 @@ INT32 I_PutEnv(char *variable)
 #endif
 }
 
+size_t I_GetRandomBytes(char *destination, size_t count)
+{
+#if defined (__unix__) || defined (UNIXCOMMON) || defined(__APPLE__)
+	FILE *rndsource;
+	size_t actual_bytes;
+
+	if (!(rndsource = fopen("/dev/urandom", "r")))
+		if (!(rndsource = fopen("/dev/random", "r")))
+			actual_bytes = 0;
+
+	if (rndsource)
+	{
+		actual_bytes = fread(destination, 1, count, rndsource);
+		fclose(rndsource);
+	}
+
+	if (actual_bytes == 0)
+		I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+
+	return actual_bytes;
+#elif defined (_WIN32)
+	if (RtlGenRandom(destination, count))
+		return count;
+
+	I_OutputMsg("I_GetRandomBytes(): couldn't get any random bytes");
+	return 0;
+#else
+	#warning SDL I_GetRandomBytes is not implemented on this platform.
+	return 0;
+#endif
+}
+
 INT32 I_ClipboardCopy(const char *data, size_t size)
 {
 	char storage[256];
@@ -2975,44 +3044,20 @@ static long get_entry(const char* name, const char* buf)
 }
 #endif
 
-// quick fix for compil
-UINT32 I_GetFreeMem(UINT32 *total)
+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)
@@ -3024,14 +3069,14 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	info.dwLength = sizeof (MEMORYSTATUS);
 	GlobalMemoryStatus( &info );
 	if (total)
-		*total = (UINT32)info.dwTotalPhys;
-	return (UINT32)info.dwAvailPhys;
+		*total = (size_t)info.dwTotalPhys;
+	return (size_t)info.dwAvailPhys;
 #elif defined (__linux__)
 	/* Linux */
 	char buf[1024];
 	char *memTag;
-	UINT32 freeKBytes;
-	UINT32 totalKBytes;
+	size_t freeKBytes;
+	size_t totalKBytes;
 	INT32 n;
 	INT32 meminfo_fd = -1;
 	long Cached;
@@ -3062,7 +3107,7 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	}
 
 	memTag += sizeof (MEMTOTAL);
-	totalKBytes = atoi(memTag);
+	totalKBytes = (size_t)atoi(memTag);
 
 	if ((memTag = strstr(buf, MEMAVAILABLE)) == NULL)
 	{
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index d2fbb90063f13f5ed663ee7a2665b95f8aac296e..590d7d142a7a536678bfb96d3891c78264a19fba 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)
@@ -1859,7 +1858,7 @@ void I_StartupGraphics(void)
 	borderlesswindow = M_CheckParm("-borderless");
 
 	//SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY>>1,SDL_DEFAULT_REPEAT_INTERVAL<<2);
-	VID_Command_ModeList_f();
+	//VID_Command_ModeList_f();
 
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
@@ -1907,7 +1906,7 @@ void I_StartupGraphics(void)
 	realwidth = (Uint16)vid.width;
 	realheight = (Uint16)vid.height;
 
-	VID_Command_Info_f();
+	//VID_Command_Info_f();
 	SDLdoUngrabMouse();
 
 	SDL_RaiseWindow(window);
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index f13aaef5d660f7e626f3e9c4c522b007fa6abd2d..0a39c7f286f8dc3e4b563709de240b0370e1b2b9 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -108,6 +108,7 @@ static UINT32 fading_timer;
 static UINT32 fading_duration;
 static INT32 fading_id;
 static void (*fading_callback)(void);
+static boolean fading_do_callback;
 static boolean fading_nocleanup;
 
 #ifdef HAVE_GME
@@ -213,7 +214,10 @@ static void var_cleanup(void)
 	// HACK: See music_loop, where we want the fade timing to proceed after a non-looping
 	// song has stopped playing
 	if (!fading_nocleanup)
+	{
 		fading_callback = NULL;
+		fading_do_callback = false;
+	}
 	else
 		fading_nocleanup = false; // use it once, set it back immediately
 
@@ -330,6 +334,13 @@ void I_ShutdownSound(void)
 
 void I_UpdateSound(void)
 {
+	if (fading_do_callback)
+	{
+		if (fading_callback)
+			(*fading_callback)();
+		fading_callback = NULL;
+		fading_do_callback = false;
+	}
 }
 
 /// ------------------------
@@ -654,9 +665,8 @@ static UINT32 get_adjusted_position(UINT32 position)
 
 static void do_fading_callback(void)
 {
-	if (fading_callback)
-		(*fading_callback)();
-	fading_callback = NULL;
+	// TODO: Should I use a mutex here or something?
+	fading_do_callback = true;
 }
 
 /// ------------------------
diff --git a/src/st_stuff.c b/src/st_stuff.c
index cce8ea9f264106e812449ea898e2d4f6271de700..b9f0c6bb93e1ab4d1b9e35a958670571883086e4 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -108,6 +108,9 @@ static patch_t *sneakers;
 static patch_t *gravboots;
 static patch_t *nonicon;
 static patch_t *nonicon2;
+static patch_t *nightopianhelper;
+static patch_t *linkfreeze;
+static patch_t *superparaloop;
 static patch_t *bluestat;
 static patch_t *byelstat;
 static patch_t *orngstat;
@@ -167,6 +170,15 @@ hudinfo_t hudinfo[NUMHUDITEMS] =
 static huddrawlist_h luahuddrawlist_game[2];
 static huddrawlist_h luahuddrawlist_titlecard;
 
+// NiGHTS link colors; 3 sets with increasingly fancy colors (1 to 299, 300 to 599, 600 and above)
+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_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}};
+
 //
 // STATUS BAR CODE
 //
@@ -313,6 +325,10 @@ void ST_LoadGraphics(void)
 	nonicon2 = W_CachePatchName("NONICON2", PU_HUDGFX);
 
 	// NiGHTS HUD things
+	nightopianhelper = W_CachePatchName("NHLPICON", PU_HUDGFX);
+	linkfreeze = W_CachePatchName("NLFZICON", PU_HUDGFX);
+	superparaloop = W_CachePatchName("NSPRICON", PU_HUDGFX);
+
 	bluestat = W_CachePatchName("BLUESTAT", PU_HUDGFX);
 	byelstat = W_CachePatchName("BYELSTAT", PU_HUDGFX);
 	orngstat = W_CachePatchName("ORNGSTAT", PU_HUDGFX);
@@ -799,7 +815,7 @@ static inline void ST_drawRings(void)
 static void ST_drawLivesArea(void)
 {
 	INT32 v_colmap = V_YELLOWMAP, livescount;
-	boolean notgreyedout;
+	boolean notgreyedout = false;
 
 	if (!stplyr->skincolor)
 		return; // Just joined a server, skin isn't loaded yet!
@@ -852,39 +868,36 @@ static void ST_drawLivesArea(void)
 	if (metalrecording)
 	{
 		if (((2*leveltime)/TICRATE) & 1)
+		{
 			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
 				hudinfo[HUD_LIVES].f|V_PERPLAYER|V_REDMAP|V_HUDTRANS, "REC");
+		}
 	}
 	// Spectator
 	else if (stplyr->spectator)
-		v_colmap = V_GRAYMAP;
-	// Tag
-	else if (gametyperules & GTR_TAG)
 	{
-		if (stplyr->pflags & PF_TAGIT)
-		{
-			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "IT!");
-			v_colmap = V_ORANGEMAP;
-		}
+		v_colmap = V_GRAYMAP;
 	}
-	// Team name
-	else if (G_GametypeHasTeams())
+	else
 	{
-		if (stplyr->ctfteam == 1)
+		boolean candrawlives = false;
+
+		// Set the player's name color.
+		if (G_TagGametype() && (stplyr->pflags & PF_TAGIT))
 		{
-			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "RED");
-			v_colmap = V_REDMAP;
+			v_colmap = V_ORANGEMAP;
 		}
-		else if (stplyr->ctfteam == 2)
+		else if (G_GametypeHasTeams())
 		{
-			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "BLUE");
-			v_colmap = V_BLUEMAP;
+			if (stplyr->ctfteam == 1)
+			{
+				v_colmap = V_REDMAP;
+			}
+			else if (stplyr->ctfteam == 2)
+			{
+				v_colmap = V_BLUEMAP;
+			}
 		}
-	}
-	// Lives number
-	else
-	{
-		boolean candrawlives = true;
 
 		// Co-op and Competition, normal life counter
 		if (G_GametypeUsesLives())
@@ -920,12 +933,15 @@ static void ST_drawLivesArea(void)
 				livescount = (((netgame || multiplayer) && G_GametypeUsesCoopLives() && cv_cooplives.value == 0) ? INFLIVES : stplyr->lives);
 				notgreyedout = true;
 			}
+
+			candrawlives = true;
 		}
 		// Infinity symbol (Race)
 		else if (G_PlatformGametype() && !(gametyperules & GTR_LIVES))
 		{
 			livescount = INFLIVES;
 			notgreyedout = true;
+			candrawlives = true;
 		}
 		// Otherwise nothing, sorry.
 		// Special Stages keep not showing lives,
@@ -934,8 +950,6 @@ static void ST_drawLivesArea(void)
 		// cannot show up because Special Stages
 		// still have the GTR_LIVES gametype rule
 		// by default.
-		else
-			candrawlives = false;
 
 		// Draw the lives counter here.
 		if (candrawlives)
@@ -943,8 +957,10 @@ static void ST_drawLivesArea(void)
 			// x
 			V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10, hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, stlivex);
 			if (livescount == INFLIVES)
+			{
 				V_DrawCharacter(hudinfo[HUD_LIVES].x+50, hudinfo[HUD_LIVES].y+8,
 					'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
+			}
 			else
 			{
 				if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)) && !(stplyr->pflags & PF_FINISHED))
@@ -955,6 +971,25 @@ static void ST_drawLivesArea(void)
 					hudinfo[HUD_LIVES].f|V_PERPLAYER|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF), va("%d",livescount));
 			}
 		}
+		else
+		{
+			// Draw team name instead of lives, if possible.
+			if (G_TagGametype() && (stplyr->pflags & PF_TAGIT))
+			{
+				V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "IT!");
+			}
+			else if (G_GametypeHasTeams())
+			{
+				if (stplyr->ctfteam == 1)
+				{
+					V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "RED");
+				}
+				else if (stplyr->ctfteam == 2)
+				{
+					V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "BLUE");
+				}
+			}
+		}
 #undef ST_drawLivesX
 	}
 
@@ -1412,7 +1447,7 @@ void ST_drawTitleCard(void)
 	lt_lasttic = lt_ticker;
 
 luahook:
-	if (renderisnewtic)
+	//if (renderisnewtic)
 	{
 		LUA_HUD_ClearDrawList(luahuddrawlist_titlecard);
 		LUA_HUDHOOK(titlecard, luahuddrawlist_titlecard);
@@ -1448,6 +1483,21 @@ void ST_drawWipeTitleCard(void)
 	}
 }
 
+#define ICONSEP (16+4) // matches weapon rings HUD
+
+static INT32 ST_powerupHUDoffset(UINT16 timer)
+{
+	if (timer > 7)
+		return ICONSEP;
+	else
+	{
+		UINT8 a = ICONSEP, b = 7-timer;
+		while (b--)
+			a = 2*a/3;
+		return a;
+	}
+}
+
 static void ST_drawPowerupHUD(void)
 {
 	patch_t *p = NULL;
@@ -1455,7 +1505,6 @@ static void ST_drawPowerupHUD(void)
 	INT32 offs = hudinfo[HUD_POWERUPS].x;
 	const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
 	static INT32 flagoffs[2] = {0, 0}, shieldoffs[2] = {0, 0}, finishoffs[2] = {0, 0};
-#define ICONSEP (16+4) // matches weapon rings HUD
 
 	if (F_GetPromptHideHud(hudinfo[HUD_POWERUPS].y))
 		return;
@@ -1567,15 +1616,7 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(invincibility, invulntime)
 	}
 
-	if (invulntime > 7)
-		offs -= ICONSEP;
-	else
-	{
-		UINT8 a = ICONSEP, b = 7-invulntime;
-		while (b--)
-			a = 2*a/3;
-		offs -= a;
-	}
+	offs -= ST_powerupHUDoffset(invulntime);
 
 	// Super Sneakers
 	if (stplyr->powers[pw_sneakers] > 3*TICRATE || (stplyr->powers[pw_sneakers] && leveltime & 1))
@@ -1583,15 +1624,7 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(sneakers, stplyr->powers[pw_sneakers])
 	}
 
-	if (stplyr->powers[pw_sneakers] > 7)
-		offs -= ICONSEP;
-	else
-	{
-		UINT8 a = ICONSEP, b = 7-stplyr->powers[pw_sneakers];
-		while (b--)
-			a = 2*a/3;
-		offs -= a;
-	}
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_sneakers]);
 
 	// Gravity Boots
 	if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
@@ -1599,6 +1632,36 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(gravboots, stplyr->powers[pw_gravityboots])
 	}
 
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_gravityboots]);
+
+// --------------------
+// NiGHTS timer-based powerups
+// --------------------
+
+	// Nightopian Helper
+	if (stplyr->powers[pw_nights_helper] > 3*TICRATE || (stplyr->powers[pw_nights_helper] && leveltime & 1))
+	{
+		DRAWTIMERICON(nightopianhelper, stplyr->powers[pw_nights_helper])
+	}
+
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_helper]);
+
+	// Link Freeze
+	if (stplyr->powers[pw_nights_linkfreeze] > 3*TICRATE || (stplyr->powers[pw_nights_linkfreeze] && leveltime & 1))
+	{
+		DRAWTIMERICON(linkfreeze, stplyr->powers[pw_nights_linkfreeze])
+	}
+
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_linkfreeze]);
+
+	// Super Paraloop
+	if (stplyr->powers[pw_nights_superloop] > 3*TICRATE || (stplyr->powers[pw_nights_superloop] && leveltime & 1))
+	{
+		DRAWTIMERICON(superparaloop, stplyr->powers[pw_nights_superloop])
+	}
+
+	//offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_superloop]);
+
 #undef DRAWTIMERICON
 #undef ICONSEP
 }
@@ -1697,7 +1760,7 @@ static void ST_drawNightsRecords(void)
 			ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 56)<<FRACBITS, 160<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
 
 			// If new record, say so!
-			if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
+			if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1, clientGamedata) <= stplyr->lastmarescore)
 			{
 				if (stplyr->texttimer & 16)
 					V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_YELLOWMAP|aflag, "* NEW RECORD *");
@@ -1716,43 +1779,11 @@ static void ST_drawNightsRecords(void)
 	}
 }
 
-// 2.0-1: [21:42] <+Rob> Beige - Lavender - Steel Blue - Peach - Orange - Purple - Silver - Yellow - Pink - Red - Blue - Green - Cyan - Gold
-/*#define NUMLINKCOLORS 14
-static skincolornum_t linkColor[NUMLINKCOLORS] =
-{SKINCOLOR_BEIGE,  SKINCOLOR_LAVENDER, SKINCOLOR_AZURE, SKINCOLOR_PEACH, SKINCOLOR_ORANGE,
- SKINCOLOR_MAGENTA, SKINCOLOR_SILVER, SKINCOLOR_SUPERGOLD4, SKINCOLOR_PINK,  SKINCOLOR_RED,
- SKINCOLOR_BLUE, SKINCOLOR_GREEN, SKINCOLOR_CYAN, SKINCOLOR_GOLD};*/
-
-// 2.2 indev list: (unix time 1470866042) <Rob> Emerald, Aqua, Cyan, Blue, Pastel, Purple, Magenta, Rosy, Red, Orange, Gold, Yellow, Peridot
-/*#define NUMLINKCOLORS 13
-static skincolornum_t linkColor[NUMLINKCOLORS] =
-{SKINCOLOR_EMERALD, SKINCOLOR_AQUA, SKINCOLOR_CYAN, SKINCOLOR_BLUE, SKINCOLOR_PASTEL,
- SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA, SKINCOLOR_ROSY, SKINCOLOR_RED,  SKINCOLOR_ORANGE,
- SKINCOLOR_GOLD, SKINCOLOR_YELLOW, SKINCOLOR_PERIDOT};*/
-
-// 2.2 indev list again: [19:59:52] <baldobo> Ruby > Red > Flame > Sunset > Orange > Gold > Yellow > Lime > Green > Aqua  > cyan > Sky > Blue > Pastel > Purple > Bubblegum > Magenta > Rosy > repeat
-// [20:00:25] <baldobo> Also Icy for the link freeze text color
-// [20:04:03] <baldobo> I would start it on lime
-/*#define NUMLINKCOLORS 18
-static skincolornum_t linkColor[NUMLINKCOLORS] =
-{SKINCOLOR_LIME, SKINCOLOR_EMERALD, SKINCOLOR_AQUA, SKINCOLOR_CYAN, SKINCOLOR_SKY,
- SKINCOLOR_SAPPHIRE, SKINCOLOR_PASTEL, SKINCOLOR_PURPLE, SKINCOLOR_BUBBLEGUM, SKINCOLOR_MAGENTA,
- SKINCOLOR_ROSY, SKINCOLOR_RUBY, SKINCOLOR_RED, SKINCOLOR_FLAME, SKINCOLOR_SUNSET,
- SKINCOLOR_ORANGE, SKINCOLOR_GOLD, SKINCOLOR_YELLOW};*/
-
-// 2.2+ list for real this time: https://wiki.srb2.org/wiki/User:Rob/Sandbox (check history around 31/10/17, spoopy)
-#define NUMLINKCOLORS 12
-static skincolornum_t linkColor[2][NUMLINKCOLORS] = {
-{SKINCOLOR_EMERALD, SKINCOLOR_AQUA, SKINCOLOR_SKY, SKINCOLOR_BLUE, SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA,
- SKINCOLOR_ROSY, SKINCOLOR_RED, SKINCOLOR_ORANGE, SKINCOLOR_GOLD, SKINCOLOR_YELLOW, SKINCOLOR_PERIDOT},
-{SKINCOLOR_SEAFOAM, SKINCOLOR_CYAN, SKINCOLOR_WAVE, SKINCOLOR_SAPPHIRE, SKINCOLOR_VAPOR, SKINCOLOR_BUBBLEGUM,
- SKINCOLOR_VIOLET, SKINCOLOR_RUBY, SKINCOLOR_FLAME, SKINCOLOR_SUNSET, SKINCOLOR_SANDY, SKINCOLOR_LIME}};
-
 static void ST_drawNiGHTSLink(void)
 {
 	static INT32 prevsel[2] = {0, 0}, prevtime[2] = {0, 0};
 	const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
-	INT32 sel = ((stplyr->linkcount-1) / 5) % NUMLINKCOLORS, aflag = V_PERPLAYER, mag = ((stplyr->linkcount-1 >= 300) ? 1 : 0);
+	INT32 sel = ((stplyr->linkcount-1) / 5) % NUMLINKCOLORS, aflag = V_PERPLAYER, mag = ((stplyr->linkcount-1 >= 300) ? (stplyr->linkcount-1 >= 600) ? 2 : 1 : 0);
 	skincolornum_t colornum;
 	fixed_t x, y, scale;
 
@@ -2481,6 +2512,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)
@@ -2545,7 +2578,7 @@ static void ST_doHuntIconsAndSound(void)
 		S_StartSound(NULL, sfx_emfind);
 }
 
-static void ST_doItemFinderIconsAndSound(void)
+static boolean ST_doItemFinderIconsAndSound(void)
 {
 	INT32 emblems[16];
 	thinker_t *th;
@@ -2556,6 +2589,12 @@ static void ST_doItemFinderIconsAndSound(void)
 	INT32 interval = 0, newinterval = 0;
 	INT32 soffset;
 
+	if (!(cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)))
+	{
+		// Not unlocked, or not enabled. Use emerald hunt radar.
+		return false;
+	}
+
 	for (i = 0; i < numemblems; ++i)
 	{
 		if (emblemlocations[i].type > ET_SKIN || emblemlocations[i].level != gamemap)
@@ -2563,15 +2602,21 @@ static void ST_doItemFinderIconsAndSound(void)
 
 		emblems[stemblems++] = i;
 
-		if (!emblemlocations[i].collected)
+		if (!P_EmblemWasCollected(i) && P_CanPickupEmblem(stplyr, i))
+		{
 			++stunfound;
+		}
 
 		if (stemblems >= 16)
 			break;
 	}
+
 	// Found all/none exist? Don't waste our time
 	if (!stunfound)
-		return;
+	{
+		// Allow emerald hunt radar to function after they're all collected.
+		return false;
+	}
 
 	// Scan thinkers to find emblem mobj with these ids
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -2591,6 +2636,9 @@ static void ST_doItemFinderIconsAndSound(void)
 		{
 			if (mo2->health == emblems[i] + 1)
 			{
+				if (P_EmblemWasCollected(emblems[i]) || !P_CanPickupEmblem(stplyr, emblems[i]))
+					break;
+
 				soffset = (i * 20) - ((stemblems - 1) * 10);
 
 				newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
@@ -2605,6 +2653,8 @@ static void ST_doItemFinderIconsAndSound(void)
 
 	if (!(P_AutoPause() || paused) && interval > 0 && leveltime && leveltime % interval == 0 && renderisnewtic)
 		S_StartSound(NULL, sfx_emfind);
+
+	return true;
 }
 
 //
@@ -2723,9 +2773,7 @@ static void ST_overlayDrawer(void)
 			ST_drawRaceHUD();
 
 		// Emerald Hunt Indicators
-		if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER))
-			ST_doItemFinderIconsAndSound();
-		else
+		if (!ST_doItemFinderIconsAndSound())
 			ST_doHuntIconsAndSound();
 
 		if(!P_IsLocalPlayer(stplyr))
@@ -2740,18 +2788,16 @@ static void ST_overlayDrawer(void)
 		}
 
 		// This is where we draw all the fun cheese if you have the chasecam off!
-		if (!(maptol & TOL_NIGHTS))
+		if ((stplyr == &players[displayplayer] && !camera.chase)
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
 		{
-			if ((stplyr == &players[displayplayer] && !camera.chase)
-			|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
-			{
-				ST_drawFirstPersonHUD();
-				if (cv_powerupdisplay.value)
-					ST_drawPowerupHUD();  // same as it ever was...
-			}
-			else if (cv_powerupdisplay.value == 2)
+			ST_drawFirstPersonHUD();
+			if (cv_powerupdisplay.value)
 				ST_drawPowerupHUD();  // same as it ever was...
 		}
+		else if (cv_powerupdisplay.value == 2)
+			ST_drawPowerupHUD();  // same as it ever was...
+		
 	}
 	else if (!(netgame || multiplayer) && cv_powerupdisplay.value == 2)
 		ST_drawPowerupHUD(); // same as it ever was...
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 68ac900f7724f4a64f27a0dc2a0a499707387d58..603be3c309c761b31fa269f2b0fbad795a489676 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -124,4 +124,7 @@ extern hudinfo_t hudinfo[NUMHUDITEMS];
 
 extern UINT16 objectsdrawn;
 
+#define NUMLINKCOLORS 12
+extern skincolornum_t linkColor[3][NUMLINKCOLORS];
+
 #endif
diff --git a/src/strcasestr.c b/src/strcasestr.c
index 2796f11d52658f2376b547a3d41d6b2d2974f15e..37899a8425b3d2082957c41cbdae07056798fe74 100644
--- a/src/strcasestr.c
+++ b/src/strcasestr.c
@@ -61,7 +61,7 @@ swapp (char ***ppap, char ***ppbp, char **cpap, char **cpbp)
 }
 
 char *
-strcasestr (const char *s, const char *q)
+nongnu_strcasestr (const char *s, const char *q)
 {
 	size_t  qn;
 
diff --git a/src/string.c b/src/string.c
index 28bb7d2d1ee481afdb9f6a52d2245e064da0ca1e..2f16fa4c68a35fa287156f546fc2973e9d4ad426 100644
--- a/src/string.c
+++ b/src/string.c
@@ -15,7 +15,7 @@
 #include <string.h>
 #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.
@@ -52,3 +52,19 @@ size_t strlcpy(char *dst, const char *src, size_t siz)
 #endif
 
 #include "strcasestr.c"
+
+int startswith(const char *path, const char *tag)
+{
+	return !strncmp(path, tag, strlen(tag));
+}
+
+int endswith(const char *base, const char *tag)
+{
+	const size_t base_length = strlen(base);
+	const size_t tag_length = strlen(tag);
+
+	if (tag_length > base_length)
+		return false;
+
+	return !memcmp(&base[base_length - tag_length], tag, tag_length);
+}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..28c4ce492f2d8b4e7590e3ac10f9ce6a95d98a0f
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,3 @@
+target_sources(srb2tests PRIVATE
+	boolcompat.cpp
+)
diff --git a/src/tests/boolcompat.cpp b/src/tests/boolcompat.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fee40cd36f2bce34217a875024d6b41fe71adbd1
--- /dev/null
+++ b/src/tests/boolcompat.cpp
@@ -0,0 +1,8 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include "../doomtype.h"
+
+TEST_CASE("C++ bool is convertible to doomtype.h boolean") {
+	REQUIRE(static_cast<boolean>(true) == 1);
+	REQUIRE(static_cast<boolean>(false) == 0);
+}
diff --git a/src/tmap.nas b/src/tmap.nas
deleted file mode 100644
index 85091cbd5d8dd9b1ab33cdd4938325eefeb1922b..0000000000000000000000000000000000000000
--- a/src/tmap.nas
+++ /dev/null
@@ -1,957 +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:
-;;      tmap.nas
-;; DESCRIPTION:
-;;      Assembler optimised rendering code for software mode.
-;;      Draw wall columns.
-
-
-[BITS 32]
-
-%define FRACBITS 16
-%define TRANSPARENTPIXEL 255
-
-%ifdef LINUX
-%macro cextern 1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-[global %1]
-%endmacro
-
-%else
-%macro cextern 1
-%define %1 _%1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-%endif
-
-
-; The viddef_s structure. We only need the width field.
-struc viddef_s
-        resb 12
-.width: resb 4
-        resb 44
-endstruc
-
-;; externs
-;; columns
-cextern dc_x
-cextern dc_yl
-cextern dc_yh
-cextern ylookup
-cextern columnofs
-cextern dc_source
-cextern dc_texturemid
-cextern dc_texheight
-cextern dc_iscale
-cextern dc_hires
-cextern centery
-cextern centeryfrac
-cextern dc_colormap
-cextern dc_transmap
-cextern colormaps
-cextern vid
-cextern topleft
-
-; DELME
-cextern R_DrawColumn_8
-
-; polygon edge rasterizer
-cextern prastertab
-
-[SECTION .data]
-
-;;.align        4
-loopcount       dd      0
-pixelcount      dd      0
-tystep          dd      0
-
-[SECTION .text]
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawColumn : 8bpp column drawer
-;;
-;; New  optimised version 10-01-1998 by D.Fabrice and P.Boris
-;; Revised by G. Dick July 2010 to support the intervening twelve years'
-;; worth of changes to the renderer. Since I only vaguely know what I'm
-;; doing, this is probably rather suboptimal. Help appreciated!
-;;
-;;----------------------------------------------------------------------
-;; fracstep, vid.width in memory
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = heightmask
-;; esi = source
-;; edi = dest
-;; ebp = frac
-;;----------------------------------------------------------------------
-
-cglobal R_DrawColumn_8_ASM
-;       align   16
-R_DrawColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     edi,[ylookup+ebp*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     ecx,[dc_yh]
-        add     ecx,1
-        sub     ecx,ebp                 ;; pixel count
-        jle     near .done              ;; nothing to scale
-;;
-;; fracstep = dc_iscale;	// But we just use [dc_iscale]
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-        mov     eax,ebp                 ;; dc_yl
-        shl     eax,FRACBITS
-        sub     eax,[centeryfrac]
-        imul    dword [dc_iscale]
-        shrd    eax,edx,FRACBITS
-        add     eax,[dc_texturemid]
-        mov     ebp,eax                 ;; ebp = frac
-
-        mov     ebx,[dc_colormap]
-
-        mov     esi,[dc_source]
-;;
-;; if (dc_hires) frac = 0;
-;;
-        test    byte [dc_hires],0x01
-        jz      .texheightcheck
-        xor     ebp,ebp
-
-;;
-;; Check for power of two
-;;
-.texheightcheck:
-        mov     edx,[dc_texheight]
-        sub     edx,1                   ;; edx = heightmask
-        test    edx,[dc_texheight]
-        jnz     .notpowertwo
-
-        test    ecx,0x01                ;; Test for odd no. pixels
-        jnz     .odd
-
-;;
-;; Texture height is a power of two, so we get modular arithmetic by
-;; masking
-;;
-.powertwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-.odd:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-
-        sub     ecx,2                   ;; count -= 2
-        jg      .powertwo
-
-        jmp     .done
-
-.notpowertwo:
-        add     edx,1
-        shl     edx,FRACBITS
-        test    ebp,ebp
-        jns     .notpowtwoloop
-
-.makefracpos:
-        add     ebp,edx                 ;; frac is negative; make it positive
-        js      .makefracpos
-
-.notpowtwoloop:
-        cmp     ebp,edx                 ;; Reduce mod height
-        jl      .writenonpowtwo
-        sub     ebp,edx
-        jmp     .notpowtwoloop
-
-.writenonpowtwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part.
-        mov     bl,[esi + eax]          ;; ebx = colormap + texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        movzx   eax,byte [ebx]          ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-        sub     ecx,1
-        jnz     .notpowtwoloop
-
-;;
-
-.done:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_Draw2sMultiPatchColumn : Like R_DrawColumn, but omits transparent
-;;                            pixels.
-;;
-;; New  optimised version 10-01-1998 by D.Fabrice and P.Boris
-;; Revised by G. Dick July 2010 to support the intervening twelve years'
-;; worth of changes to the renderer. Since I only vaguely know what I'm
-;; doing, this is probably rather suboptimal. Help appreciated!
-;;
-;;----------------------------------------------------------------------
-;; fracstep, vid.width in memory
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = heightmask
-;; esi = source
-;; edi = dest
-;; ebp = frac
-;;----------------------------------------------------------------------
-
-cglobal R_Draw2sMultiPatchColumn_8_ASM
-;       align   16
-R_Draw2sMultiPatchColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     edi,[ylookup+ebp*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     ecx,[dc_yh]
-        add     ecx,1
-        sub     ecx,ebp                 ;; pixel count
-        jle     near .done              ;; nothing to scale
-;;
-;; fracstep = dc_iscale;	// But we just use [dc_iscale]
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-        mov     eax,ebp                 ;; dc_yl
-        shl     eax,FRACBITS
-        sub     eax,[centeryfrac]
-        imul    dword [dc_iscale]
-        shrd    eax,edx,FRACBITS
-        add     eax,[dc_texturemid]
-        mov     ebp,eax                 ;; ebp = frac
-
-        mov     ebx,[dc_colormap]
-
-        mov     esi,[dc_source]
-;;
-;; if (dc_hires) frac = 0;
-;;
-        test    byte [dc_hires],0x01
-        jz      .texheightcheck
-        xor     ebp,ebp
-
-;;
-;; Check for power of two
-;;
-.texheightcheck:
-        mov     edx,[dc_texheight]
-        sub     edx,1                   ;; edx = heightmask
-        test    edx,[dc_texheight]
-        jnz     .notpowertwo
-
-        test    ecx,0x01                ;; Test for odd no. pixels
-        jnz     .odd
-
-;;
-;; Texture height is a power of two, so we get modular arithmetic by
-;; masking
-;;
-.powertwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     al,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextpowtwoeven         ;; If so, advance.
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov	    [edi],al                ;; Write pixel
-.nextpowtwoeven:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-.odd:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part
-        and     eax,edx                 ;; eax &= heightmask
-        movzx   eax,byte [esi + eax]    ;; eax = texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     al,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextpowtwoodd          ;; If so, advance.
-        movzx   eax,byte [ebx+eax]      ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-.nextpowtwoodd:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-
-        sub     ecx,2                   ;; count -= 2
-        jg      .powertwo
-
-        jmp     .done
-
-.notpowertwo:
-        add     edx,1
-        shl     edx,FRACBITS
-        test    ebp,ebp
-        jns     .notpowtwoloop
-
-.makefracpos:
-        add     ebp,edx                 ;; frac is negative; make it positive
-        js      .makefracpos
-
-.notpowtwoloop:
-        cmp     ebp,edx                 ;; Reduce mod height
-        jl      .writenonpowtwo
-        sub     ebp,edx
-        jmp     .notpowtwoloop
-
-.writenonpowtwo:
-        mov     eax,ebp                 ;; eax = frac
-        sar     eax,FRACBITS            ;; Integer part.
-        mov     bl,[esi + eax]          ;; ebx = colormap + texel
-        add     ebp,[dc_iscale]         ;; frac += fracstep
-        cmp     bl,TRANSPARENTPIXEL     ;; Is pixel transparent?
-        je      .nextnonpowtwo          ;; If so, advance.
-        movzx   eax,byte [ebx]          ;; Map through colormap
-        mov     [edi],al                ;; Write pixel
-.nextnonpowtwo:
-                                        ;; dest += vid.width
-        add     edi,[vid + viddef_s.width]
-
-        sub     ecx,1
-        jnz     .notpowtwoloop
-
-;;
-
-.done:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-;;----------------------------------------------------------------------
-;; R_DrawTranslucentColumnA_8
-;;
-;; Vertical column texture drawer, with transparency. Replaces Doom2's
-;; 'fuzz' effect, which was not so beautiful.
-;; Transparency is always impressive in some way, don't know why...
-;;----------------------------------------------------------------------
-
-cglobal R_DrawTranslucentColumn_8_ASM
-R_DrawTranslucentColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     ebx,ebp
-        mov     edi,[ylookup+ebx*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]   ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     eax,[dc_yh]
-        inc     eax
-        sub     eax,ebp                 ;; pixel count
-        mov     [pixelcount],eax        ;; save for final pixel
-        jle     near    vtdone         ;; nothing to scale
-;;
-;; frac = dc_texturemid - (centery-dc_yl)*fracstep;
-;;
-        mov     ecx,[dc_iscale]        ;; fracstep
-        mov     eax,[centery]
-        sub     eax,ebp
-        imul    eax,ecx
-        mov     edx,[dc_texturemid]
-        sub     edx,eax
-        mov     ebx,edx
-
-        shr     ebx,16                  ;; frac int.
-        and     ebx,0x7f
-        shl     edx,16                  ;; y frac up
-
-        mov     ebp,ecx
-        shl     ebp,16                  ;; fracstep f. up
-        shr     ecx,16                  ;; fracstep i. ->cl
-        and     cl,0x7f
-        push    cx
-        mov     ecx,edx
-        pop     cx
-        mov     edx,[dc_colormap]
-        mov     esi,[dc_source]
-;;
-;; lets rock :) !
-;;
-        mov     eax,[pixelcount]
-        shr     eax,0x2
-        test    byte [pixelcount],0x3
-        mov     ch,al                   ;; quad count
-        mov     eax,[dc_transmap]
-        je      vt4quadloop
-;;
-;;  do un-even pixel
-;;
-        test    byte [pixelcount],0x1
-        je      trf2
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-pf:     add     edi,0x12345678
-;;
-;;  do two non-quad-aligned pixels
-;;
-trf2:    test    byte [pixelcount],0x2
-        je      trf3
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-pg:     add     edi,0x12345678
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     ecx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     dl,[edx]
-        mov     [edi],dl
-ph:     add     edi,0x12345678
-;;
-;;  test if there was at least 4 pixels
-;;
-trf3:   test    ch,0xff                 ;; test quad count
-        je near vtdone
-
-;;
-;; ebp : ystep frac. upper 24 bits
-;; edx : y     frac. upper 24 bits
-;; ebx : y     i.    lower 7 bits,  masked for index
-;; ecx : ch = counter, cl = y step i.
-;; eax : colormap aligned 256
-;; esi : source texture column
-;; edi : dest screen
-;;
-vt4quadloop:
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [tystep],ebp
-pi:     add     edi,0x12345678
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-pj:     sub     edi,0x12345678
-        mov     ebp,edi
-pk:     sub     edi,0x12345678
-        jmp short inloop
-align 4
-vtquadloop:
-        add     ecx,[tystep]
-        adc     bl,cl
-q1:     add     ebp,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest   : index into colormap
-inloop:
-        add     ecx,[tystep]
-        adc     bl,cl
-q2:     add     edi,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [ebp+0x0],dl
-        mov     al,[edi]                ;; fetch dest   : index into colormap
-
-        add     ecx,[tystep]
-        adc     bl,cl
-q3:     add     ebp,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest   : index into colormap
-
-        add     ecx,[tystep]
-        adc     bl,cl
-q4:     add     edi,0x23456789
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     dl,[edx]
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest   : index into colormap
-
-        dec     ch
-        jne     vtquadloop
-vtdone:
-        pop     ebx
-        pop     edi
-        pop     esi
-        pop     ebp
-        ret
-
-;;----------------------------------------------------------------------
-;; R_DrawShadeColumn
-;;
-;;   for smoke..etc.. test.
-;;----------------------------------------------------------------------
-cglobal R_DrawShadeColumn_8_ASM
-R_DrawShadeColumn_8_ASM:
-        push    ebp                     ;; preserve caller's stack frame pointer
-        push    esi                     ;; preserve register variables
-        push    edi
-        push    ebx
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-        mov     ebp,[dc_yl]
-        mov     ebx,ebp
-        mov     edi,[ylookup+ebx*4]
-        mov     ebx,[dc_x]
-        add     edi,[columnofs+ebx*4]  ;; edi = dest
-;;
-;; pixelcount = yh - yl + 1
-;;
-        mov     eax,[dc_yh]
-        inc     eax
-        sub     eax,ebp                 ;; pixel count
-        mov     [pixelcount],eax       ;; save for final pixel
-        jle near shdone                ;; nothing to scale
-;;
-;; frac = dc_texturemid - (centery-dc_yl)*fracstep;
-;;
-        mov     ecx,[dc_iscale]        ;; fracstep
-        mov     eax,[centery]
-        sub     eax,ebp
-        imul    eax,ecx
-        mov     edx,[dc_texturemid]
-        sub     edx,eax
-        mov     ebx,edx
-        shr     ebx,16                  ;; frac int.
-        and     ebx,byte +0x7f
-        shl     edx,16                  ;; y frac up
-
-        mov     ebp,ecx
-        shl     ebp,16                  ;; fracstep f. up
-        shr     ecx,16                  ;; fracstep i. ->cl
-        and     cl,0x7f
-
-        mov     esi,[dc_source]
-;;
-;; lets rock :) !
-;;
-        mov     eax,[pixelcount]
-        mov     dh,al
-        shr     eax,2
-        mov     ch,al                   ;; quad count
-        mov     eax,[colormaps]
-        test    dh,3
-        je      sh4quadloop
-;;
-;;  do un-even pixel
-;;
-        test    dh,0x1
-        je      shf2
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pl:     add     edi,0x12345678
-;;
-;;  do two non-quad-aligned pixels
-;;
-shf2:
-        test    dh,0x2
-        je      shf3
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pm:     add     edi,0x12345678
-
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        add     edx,ebp
-        adc     bl,cl
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-        and     bl,0x7f
-        mov     dl,[eax]
-        mov     [edi],dl
-pn:     add     edi,0x12345678
-;;
-;;  test if there was at least 4 pixels
-;;
-shf3:
-        test    ch,0xff                 ;; test quad count
-        je near shdone
-
-;;
-;; ebp : ystep frac. upper 24 bits
-;; edx : y     frac. upper 24 bits
-;; ebx : y     i.    lower 7 bits,  masked for index
-;; ecx : ch = counter, cl = y step i.
-;; eax : colormap aligned 256
-;; esi : source texture column
-;; edi : dest screen
-;;
-sh4quadloop:
-        mov     dh,0x7f                 ;; prep mask
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [tystep],ebp
-po:     add     edi,0x12345678
-        mov     al,[edi]                ;; fetch dest  : index into colormap
-pp:     sub     edi,0x12345678
-        mov     ebp,edi
-pq:     sub     edi,0x12345678
-        jmp short shinloop
-
-align  4
-shquadloop:
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q5:     add     ebp,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest : index into colormap
-shinloop:
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q6:     add     edi,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest : index into colormap
-
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q7:     add     ebp,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [edi],dl
-        mov     al,[ebp]                ;; fetch dest : index into colormap
-
-        add     edx,[tystep]
-        adc     bl,cl
-        and     bl,dh
-q8:     add     edi,0x12345678
-        mov     dl,[eax]
-        mov     ah,[esi+ebx]            ;; fetch texel : colormap number
-        mov     [ebp],dl
-        mov     al,[edi]                ;; fetch dest : index into colormap
-
-        dec     ch
-        jne     shquadloop
-
-shdone:
-        pop     ebx                     ;; restore register variables
-        pop     edi
-        pop     esi
-        pop     ebp                     ;; restore caller's stack frame pointer
-        ret
-
-
-;; ========================================================================
-;;  Rasterization of the segments of a LINEAR polygne textur of manire.
-;;  It is thus a question of interpolating coordinate them at the edges of texture in
-;;  the time that the X-coordinates minx/maxx for each line.
-;;  the argument ' dir' indicates which edges of texture are Interpol?:
-;;    0:  segments associs at edge TOP? and BOTTOM? (constant TY)
-;;    1:  segments associs at the LEFT and RIGHT edge (constant TX)
-;; ========================================================================
-;;
-;;  void   rasterize_segment_tex( LONG x1, LONG y1, LONG x2, LONG y2, LONG tv1, LONG tv2, LONG tc, LONG dir );
-;;                                   ARG1     ARG2     ARG3     ARG4      ARG5      ARG6     ARG7       ARG8
-;;
-;;  Pour dir = 0, (tv1,tv2) = (tX1,tX2), tc = tY, en effet TY est constant.
-;;
-;;  Pour dir = 1, (tv1,tv2) = (tY1,tY2), tc = tX, en effet TX est constant.
-;;
-;;
-;;  Uses:  extern struct rastery *_rastertab;
-;;
-
-MINX            EQU    0
-MAXX            EQU    4
-TX1             EQU    8
-TY1             EQU    12
-TX2             EQU    16
-TY2             EQU    20
-RASTERY_SIZEOF  EQU    24
-
-cglobal rasterize_segment_tex_asm
-rasterize_segment_tex_asm:
-        push    ebp
-        mov     ebp,esp
-
-        sub     esp,byte +0x8           ;; allocate the local variables
-
-        push    ebx
-        push    esi
-        push    edi
-        o16 mov ax,es
-        push    eax
-
-;;        #define DX       [ebp-4]
-;;        #define TD       [ebp-8]
-
-        mov     eax,[ebp+0xc]           ;; y1
-        mov     ebx,[ebp+0x14]          ;; y2
-        cmp     ebx,eax
-        je near .L_finished             ;; special (y1==y2) segment horizontal, exit!
-
-        jg near .L_rasterize_right
-
-;;rasterize_left:       ;; one rasterize a segment LEFT of the polygne
-
-        mov     ecx,eax
-        sub     ecx,ebx
-        inc     ecx                     ;; y1-y2+1
-
-        mov     eax,RASTERY_SIZEOF
-        mul     ebx                     ;; * y2
-        mov     esi,[prastertab]
-        add     esi,eax                 ;; point into rastertab[y2]
-
-        mov     eax,[ebp+0x8]           ;; ARG1
-        sub     eax,[ebp+0x10]          ;; ARG3
-        shl     eax,0x10                ;;     ((x1-x2)<<PRE) ...
-        cdq
-        idiv    ecx                     ;; dx =     ...        / (y1-y2+1)
-        mov     [ebp-0x4],eax           ;; DX
-
-        mov     eax,[ebp+0x18]          ;; ARG5
-        sub     eax,[ebp+0x1c]          ;; ARG6
-        shl     eax,0x10
-        cdq
-        idiv    ecx                     ;;      tdx =((tx1-tx2)<<PRE) / (y1-y2+1)
-        mov     [ebp-0x8],eax           ;; idem tdy =((ty1-ty2)<<PRE) / (y1-y2+1)
-
-        mov     eax,[ebp+0x10]          ;; ARG3
-        shl     eax,0x10                ;; x = x2<<PRE
-
-        mov     ebx,[ebp+0x1c]          ;; ARG6
-        shl     ebx,0x10                ;; tx = tx2<<PRE    d0
-                                        ;; ty = ty2<<PRE    d1
-        mov     edx,[ebp+0x20]          ;; ARG7
-        shl     edx,0x10                ;; ty = ty<<PRE     d0
-                                        ;; tx = tx<<PRE     d1
-        push    ebp
-        mov     edi,[ebp-0x4]           ;; DX
-        cmp     dword [ebp+0x24],byte +0x0      ;; ARG8   direction ?
-
-        mov     ebp,[ebp-0x8]           ;; TD
-        je      .L_rleft_h_loop
-;;
-;; TY varies, TX is constant
-;;
-.L_rleft_v_loop:
-        mov     [esi+MINX],eax           ;; rastertab[y].minx = x
-          add     ebx,ebp
-        mov     [esi+TX1],edx           ;;             .tx1  = tx
-          add     eax,edi
-        mov     [esi+TY1],ebx           ;;             .ty1  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // ty    += tdy
-
-        add     esi,RASTERY_SIZEOF      ;; next raster line into rastertab[]
-        dec     ecx
-        jne     .L_rleft_v_loop
-        pop     ebp
-        jmp     .L_finished
-;;
-;; TX varies, TY is constant
-;;
-.L_rleft_h_loop:
-        mov     [esi+MINX],eax           ;; rastertab[y].minx = x
-          add     eax,edi
-        mov     [esi+TX1],ebx           ;;             .tx1  = tx
-          add     ebx,ebp
-        mov     [esi+TY1],edx           ;;             .ty1  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // tx    += tdx
-
-        add     esi,RASTERY_SIZEOF      ;; next raster line into rastertab[]
-        dec     ecx
-        jne     .L_rleft_h_loop
-        pop     ebp
-        jmp     .L_finished
-;;
-;; one rasterize a segment LINE of the polygne
-;;
-.L_rasterize_right:
-        mov     ecx,ebx
-        sub     ecx,eax
-        inc     ecx                     ;; y2-y1+1
-
-        mov     ebx,RASTERY_SIZEOF
-        mul     ebx                     ;;   * y1
-        mov     esi,[prastertab]
-        add     esi,eax                 ;;  point into rastertab[y1]
-
-        mov     eax,[ebp+0x10]          ;; ARG3
-        sub     eax,[ebp+0x8]           ;; ARG1
-        shl     eax,0x10                ;; ((x2-x1)<<PRE) ...
-        cdq
-        idiv    ecx                     ;;  dx =     ...        / (y2-y1+1)
-        mov     [ebp-0x4],eax           ;; DX
-
-        mov     eax,[ebp+0x1c]          ;; ARG6
-        sub     eax,[ebp+0x18]          ;; ARG5
-        shl     eax,0x10
-        cdq
-        idiv    ecx                     ;;       tdx =((tx2-tx1)<<PRE) / (y2-y1+1)
-        mov     [ebp-0x8],eax           ;;  idem tdy =((ty2-ty1)<<PRE) / (y2-y1+1)
-
-        mov     eax,[ebp+0x8]           ;; ARG1
-        shl     eax,0x10                ;; x  = x1<<PRE
-
-        mov     ebx,[ebp+0x18]          ;; ARG5
-        shl     ebx,0x10                ;; tx = tx1<<PRE    d0
-                                        ;; ty = ty1<<PRE    d1
-        mov     edx,[ebp+0x20]          ;; ARG7
-        shl     edx,0x10                ;; ty = ty<<PRE     d0
-                                        ;; tx = tx<<PRE     d1
-        push    ebp
-        mov     edi,[ebp-0x4]           ;; DX
-
-        cmp     dword [ebp+0x24], 0     ;; direction ?
-
-         mov     ebp,[ebp-0x8]          ;; TD
-        je      .L_rright_h_loop
-;;
-;; TY varies, TX is constant
-;;
-.L_rright_v_loop:
-
-        mov     [esi+MAXX],eax           ;; rastertab[y].maxx = x
-          add     ebx,ebp
-        mov     [esi+TX2],edx          ;;             .tx2  = tx
-          add     eax,edi
-        mov     [esi+TY2],ebx          ;;             .ty2  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // ty    += tdy
-
-        add     esi,RASTERY_SIZEOF
-        dec     ecx
-        jne     .L_rright_v_loop
-
-        pop     ebp
-
-        jmp     short .L_finished
-;;
-;; TX varies, TY is constant
-;;
-.L_rright_h_loop:
-        mov     [esi+MAXX],eax           ;; rastertab[y].maxx = x
-          add     eax,edi
-        mov     [esi+TX2],ebx          ;;             .tx2  = tx
-          add     ebx,ebp
-        mov     [esi+TY2],edx          ;;             .ty2  = ty
-
-        ;;addl    DX, %eax        // x     += dx
-        ;;addl    TD, %ebx        // tx    += tdx
-
-        add     esi,RASTERY_SIZEOF
-        dec     ecx
-        jne     .L_rright_h_loop
-
-        pop     ebp
-
-.L_finished:
-        pop     eax
-        o16 mov es,ax
-        pop     edi
-        pop     esi
-        pop     ebx
-
-        mov     esp,ebp
-        pop     ebp
-        ret
diff --git a/src/tmap.s b/src/tmap.s
deleted file mode 100644
index d98d82e25cedbea383b71beb122e7f250e12d765..0000000000000000000000000000000000000000
--- a/src/tmap.s
+++ /dev/null
@@ -1,1587 +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  tmap.s
-/// \brief optimised drawing routines for span/column rendering
-
-// structures, must match the C structures!
-#include "asm_defs.inc"
-
-// Rappel: seuls EAX, ECX, EDX peuvent �tre �cras�s librement.
-//         il faut sauver esi,edi, cd...gs
-
-/* Attention aux comparaisons!                                              */
-/*                                                                          */
-/*      Intel_compare:                                                      */
-/*                                                                          */
-/*              cmp     A,B                     // A-B , set flags          */
-/*              jg      A_greater_than_B                                    */
-/*                                                                          */
-/*      AT&T_compare:                                                       */
-/*                                                                          */
-/*              cmp     A,B                     // B-A , set flags          */
-/*              jg      B_greater_than_A                                    */
-/*                                                                          */
-/*        (soustrait l'op�rande source DE l'op�rande destination,           */
-/*         comme sur Motorola! )                                            */
-
-// RAPPEL: Intel
-//         SECTION:[BASE+INDEX*SCALE+DISP]
-// devient SECTION:DISP(BASE,INDEX,SCALE)
-
-//----------------------------------------------------------------------
-//
-// R_DrawColumn
-//
-//   New optimised version 10-01-1998 by D.Fabrice and P.Boris
-//   TO DO: optimise it much farther... should take at most 3 cycles/pix
-//          once it's fixed, add code to patch the offsets so that it
-//          works in every screen width.
-//
-//----------------------------------------------------------------------
-
-    .data
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-C(loopcount):   .long   0
-C(pixelcount):  .long   0
-C(tystep):      .long   0
-
-C(vidwidth):    .long   0       //use this one out of the inner loops
-                                //so you don't need to patch everywhere...
-
-#ifdef USEASM
-#if !defined( LINUX)
-    .text
-#endif
-.globl C(ASM_PatchRowBytes)
-C(ASM_PatchRowBytes):
-    pushl   %ebp
-    movl    %esp, %ebp      // assure l'"adressabilit� du stack"
-
-    movl    ARG1, %edx         // read first arg
-    movl    %edx, C(vidwidth)
-
-    // 1 * vidwidth
-    movl    %edx,p1+2
-    movl    %edx,w1+2   //water
-    movl    %edx,p1b+2  //sky
-
-    movl    %edx,p5+2
-      movl    %edx,sh5+2        //smokie test
-
-    // 2 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p2+2
-    movl    %edx,w2+2   //water
-    movl    %edx,p2b+2  //sky
-
-    movl    %edx,p6+2
-    movl    %edx,p7+2
-    movl    %edx,p8+2
-    movl    %edx,p9+2
-      movl    %edx,sh6+2         //smokie test
-      movl    %edx,sh7+2
-      movl    %edx,sh8+2
-      movl    %edx,sh9+2
-
-    // 3 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p3+2
-    movl    %edx,w3+2   //water
-    movl    %edx,p3b+2  //sky
-
-    // 4 * vidwidth
-    addl    ARG1,%edx
-
-    movl    %edx,p4+2
-    movl    %edx,w4+2   //water
-    movl    %edx,p4b+2  //sky
-
-    popl    %ebp
-    ret
-
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawColumn_8)
-C(R_DrawColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_colormap),%eax
-    testb   $3,%dh
-    jz      v4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-v4quadloop:
-    movb    $0x7f,%dh           // prep mask
-//    .align  4
-vquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-     andb    $0x7f,%bl            // mask 0-127 texture index
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p1:    movb    %dl,0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p2:    movb    %dl,2*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p3:    movb    %dl,3*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-p4:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    vquadloop
-
-vdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-#ifdef HORIZONTALDRAW
-// --------------------------------------------------------------------------
-// Horizontal Column Drawer Optimisation
-// --------------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawHColumn_8)
-C(R_DrawHColumn_8):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = yhlookup[dc_x] + hcolumnofs[dc_yl];
-//
-    movl    C(dc_x),%ebx
-    movl    C(yhlookup)(,%ebx,4),%edi
-    movl    C(dc_yl),%ebp
-    movl    %ebp,%ebx
-    addl    C(hcolumnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vhdone                      // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch     // quad count
-
-    testb   %ch, %ch
-    jz      vhnearlydone
-
-    movl    C(dc_colormap),%eax
-    decl    %edi                  //-----
-
-vhloop:
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     andb    $0x7f,%bl
-    incl    %edi                 //-----
-     movb    (%eax),%dh
-    movb    %dh,(%edi)           //-----
-
-     movb    (%esi,%ebx),%al      // fetch source texel
-    addl    %ebp,%edx
-     incl    %edi                //-----
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-    andb    $0x7f,%bl
-     movb    %dl,(%edi)          //-----
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-//    shll    $16,%edx
-     andb    $0x7f,%bl
-    incl    %edi                //-----
-     movb    (%eax),%dh
-    movb    %dh,(%edi)          //-----
-
-     movb    (%esi,%ebx),%al      // fetch source texel
-    addl    %ebp,%edx
-     incl    %edi               //-----
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-    andb    $0x7f,%bl
-     movb    %dl,(%edi)
-//     movl    %edx,(%edi)
-//    addl    $4,%edi
-
-    decb   %ch
-     jnz    vhloop
-
-vhnearlydone:
-//    movl    C(pixelcount)
-
-vhdone:
-    popl    %ebx
-    popl    %edi
-    popl    %esi
-    popl    %ebp
-    ret
-
-
-// --------------------------------------------------------------------------
-// Rotate a buffer 90 degree in clockwise order after horiz.col. draws
-// --------------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_RotateBuffer)
-C(R_RotateBuffer):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-
-    movl    C(dc_source),%esi
-    movl    C(dc_colormap),%edi
-
-
-    movb    (%esi),%ah
-     addl    $200,%esi
-    movb    (%ebx),%al
-     addl    $200,%ebx
-    bswap    %eax
-    movb    (%esi),%ah
-     addl    $200,%esi
-    movb    (%ebx),%al
-     addl    $200,%ebx
-    movl    %eax,(%edi)
-     addl    $4,%edi
-
-
-    popl    %ebx
-    popl    %edi
-    popl    %esi
-    popl    %ebp
-    ret
-#endif
-
-//----------------------------------------------------------------------
-//13-02-98:
-//   R_DrawSkyColumn : same as R_DrawColumn but:
-//
-//            - wrap around 256 instead of 127.
-//   this is needed because we have a higher texture for mouselook,
-//   we need at least 200 lines for the sky.
-//
-//   NOTE: the sky should never wrap, so it could use a faster method.
-//         for the moment, we'll still use a wrapping method...
-//
-//  IT S JUST A QUICK CUT N PASTE, WAS NOT OPTIMISED AS IT SHOULD BE !!!
-//
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawSkyColumn_8)
-C(R_DrawSkyColumn_8):
-    pushl   %ebp
-    pushl   %esi
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vskydone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x000000ff,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_colormap),%eax
-    testb   $3,%dh
-    jz      v4skyquadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vskydone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-v4skyquadloop:
-//    .align  4
-vskyquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p1b:    movb    %dl,0x12345678(%edi)
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p2b:    movb    %dl,2*0x12345678(%edi)
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-p3b:    movb    %dl,3*0x12345678(%edi)
-
-p4b:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    vskyquadloop
-
-vskydone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-//----------------------------------------------------------------------
-//
-// R_DrawSpan
-//
-// Horizontal texture mapping
-//
-//----------------------------------------------------------------------
-
-    .data
-
-ystep:          .long   0
-xstep:          .long   0
-C(texwidth):    .long   64      // texture width
-#if !defined( LINUX)
-    .text
-#endif
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_DrawSpan_8)
-C(R_DrawSpan_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-
-//
-// find loop count
-//
-    movl    C(ds_x2),%eax
-    incl    %eax
-    subl    C(ds_x1),%eax               // pixel count
-    movl    %eax,C(pixelcount)          // save for final pixel
-    js      hdone                       // nothing to scale
-    shrl    $1,%eax                     // double pixel count
-    movl    %eax,C(loopcount)
-
-//
-// build composite position
-//
-    movl    C(ds_xfrac),%ebp
-    shll    $10,%ebp
-    andl    $0x0ffff0000,%ebp
-    movl    C(ds_yfrac),%eax
-    shrl    $6,%eax
-    andl    $0x0ffff,%eax
-    movl    C(ds_y),%edi
-    orl     %eax,%ebp
-
-    movl    C(ds_source),%esi
-
-//
-// calculate screen dest
-//
-
-    movl    C(ylookup)(,%edi,4),%edi
-    movl    C(ds_x1),%eax
-    addl    C(columnofs)(,%eax,4),%edi
-
-//
-// build composite step
-//
-    movl    C(ds_xstep),%ebx
-    shll    $10,%ebx
-    andl    $0x0ffff0000,%ebx
-    movl    C(ds_ystep),%eax
-    shrl    $6,%eax
-    andl    $0x0ffff,%eax
-    orl     %eax,%ebx
-
-    //movl        %eax,OFFSET hpatch1+2        // convice tasm to modify code...
-    movl    %ebx,hpatch1+2
-    //movl        %eax,OFFSET hpatch2+2        // convice tasm to modify code...
-    movl    %ebx,hpatch2+2
-    movl    %esi,hpatch3+2
-    movl    %esi,hpatch4+2
-// %eax      aligned colormap
-// %ebx      aligned colormap
-// %ecx,%edx  scratch
-// %esi      virtual source
-// %edi      moving destination pointer
-// %ebp      frac
-    movl    C(ds_colormap),%eax
-//    shld    $22,%ebp,%ecx           // begin calculating third pixel (y units)
-//    shld    $6,%ebp,%ecx            // begin calculating third pixel (x units)
-     movl    %ebp,%ecx
-    addl    %ebx,%ebp               // advance frac pointer
-     shrw    $10,%cx
-     roll    $6,%ecx
-    andl    $4095,%ecx              // finish calculation for third pixel
-//    shld    $22,%ebp,%edx           // begin calculating fourth pixel (y units)
-//    shld    $6,%ebp,%edx            // begin calculating fourth pixel (x units)
-     movl    %ebp,%edx
-     shrw    $10,%dx
-     roll    $6,%edx
-    addl    %ebx,%ebp               // advance frac pointer
-    andl    $4095,%edx              // finish calculation for fourth pixel
-    movl    %eax,%ebx
-    movb    (%esi,%ecx),%al         // get first pixel
-    movb    (%esi,%edx),%bl         // get second pixel
-    testl   $0x0fffffffe,C(pixelcount)
-    movb    (%eax),%dl             // color translate first pixel
-
-//    jnz hdoubleloop             // at least two pixels to map
-//    jmp hchecklast
-
-//    movw $0xf0f0,%dx //see visplanes start
-
-    jz      hchecklast
-    movb    (%ebx),%dh              // color translate second pixel
-    movl    C(loopcount),%esi
-//    .align  4
-hdoubleloop:
-//    shld    $22,%ebp,%ecx        // begin calculating third pixel (y units)
-//    shld    $6,%ebp,%ecx         // begin calculating third pixel (x units)
-    movl    %ebp,%ecx
-    shrw    $10,%cx
-    roll    $6,%ecx
-hpatch1:
-    addl    $0x012345678,%ebp    // advance frac pointer
-    movw    %dx,(%edi)           // write first pixel
-    andl    $4095,%ecx           // finish calculation for third pixel
-//    shld    $22,%ebp,%edx        // begin calculating fourth pixel (y units)
-//    shld    $6,%ebp,%edx         // begin calculating fourth pixel (x units)
-    movl    %ebp,%edx
-    shrw    $10,%dx
-    roll    $6,%edx
-hpatch3:
-    movb    0x012345678(%ecx),%al      // get third pixel
-//    movb    %bl,1(%edi)          // write second pixel
-    andl    $4095,%edx           // finish calculation for fourth pixel
-hpatch2:
-    addl    $0x012345678,%ebp    // advance frac pointer
-hpatch4:
-    movb    0x012345678(%edx),%bl      // get fourth pixel
-    movb    (%eax),%dl           // color translate third pixel
-    addl    $2,%edi              // advance to third pixel destination
-    decl    %esi                 // done with loop?
-    movb    (%ebx),%dh           // color translate fourth pixel
-    jnz hdoubleloop
-
-// check for final pixel
-hchecklast:
-    testl   $1,C(pixelcount)
-    jz      hdone
-    movb    %dl,(%edi)           // write final pixel
-
-hdone:
-    popl    %ebx                 // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                 // restore caller's stack frame pointer
-    ret
-
-
-//.endif
-
-
-//----------------------------------------------------------------------
-// R_DrawTransColumn
-//
-// Vertical column texture drawer, with transparency. Replaces Doom2's
-// 'fuzz' effect, which was not so beautiful.
-// Transparency is always impressive in some way, don't know why...
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-
-.globl C(R_DrawTranslucentColumn_8)
-C(R_DrawTranslucentColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      vtdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-    movl     %edx,%ebx
-
-    shrl     $16,%ebx          // frac int.
-    andl     $0x0000007f,%ebx
-    shll     $16,%edx          // y frac up
-
-    movl     %ecx,%ebp
-    shll     $16,%ebp          // fracstep f. up
-    shrl     $16,%ecx          // fracstep i. ->cl
-    andb     $0x7f,%cl
-    pushw    %cx
-    movl     %edx,%ecx
-    popw     %cx
-    movl     C(dc_colormap),%edx
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    shrl    $2,%eax
-    testb   $0x03,C(pixelcount)
-    movb    %al,%ch             // quad count
-    movl    C(dc_transmap),%eax
-    jz      vt4quadloop
-//
-//  do un-even pixel
-//
-    testb   $1,C(pixelcount)
-    jz      2f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,C(pixelcount)
-    jz      3f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%ecx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      vtdone
-
-//
-// tystep : ystep frac. upper 24 bits
-// edx : upper 24 bit : colomap
-//  dl : tmp pixel to write
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : y     frac. upper 16 bits
-// ecx : ch = counter, cl = y step i.
-// eax : transmap aligned 65535 (upper 16 bit)
-//  ah : background pixel (from the screen buffer)
-//  al : foreground pixel (from the texture)
-// esi : source texture column
-// ebp,edi : dest screen
-//
-vt4quadloop:
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-p5: movb    0x12345678(%edi),%al           // fetch dest  : index into colormap
-
-    movl    %ebp,C(tystep)
-    movl    %edi,%ebp
-    subl    C(vidwidth),%edi
-    jmp inloop
-//    .align  4
-vtquadloop:
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p6: addl    $2*0x12345678,%ebp
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-inloop:
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p7: addl    $2*0x12345678,%edi
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p8: addl    $2*0x12345678,%ebp
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%ecx
-    adcb    %cl,%bl
-p9: addl    $2*0x12345678,%edi
-    andb    $0x7f,%bl
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    (%edx), %dl          // use colormap now !
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    decb   %ch
-     jnz    vtquadloop
-
-vtdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-#endif // ifdef USEASM
-
-
-
-//----------------------------------------------------------------------
-// R_DrawShadeColumn
-//
-//   for smoke..etc.. test.
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawShadeColumn_8)
-C(R_DrawShadeColumn_8):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      shdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(colormaps),%eax
-    testb   $0x03,%dh
-    jz      sh4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%edi),%al           // fetch dest  : index into colormap
-    andb    $0x7f,%bl
-     movb    (%eax),%dl
-    movb    %dl,(%edi)
-     addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      shdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-sh4quadloop:
-    movb    $0x7f,%dh           // prep mask
-
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-sh5:    movb    0x12345678(%edi),%al           // fetch dest  : index into colormap
-
-    movl    %ebp,C(tystep)
-    movl    %edi,%ebp
-    subl    C(vidwidth),%edi
-    jmp shinloop
-//    .align  4
-shquadloop:
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh6:    addl    $2*0x12345678,%ebp
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-shinloop:
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh7:    addl    $2*0x12345678,%edi
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh8:    addl    $2*0x12345678,%ebp
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%edi)
-    movb    (%ebp),%al           // fetch dest  : index into colormap
-
-    addl    C(tystep),%edx
-    adcb    %cl,%bl
-    andb    %dh,%bl
-sh9:    addl    $2*0x12345678,%edi
-    movb    (%eax),%dl
-    movb    (%esi,%ebx),%ah      // fetch texel : colormap number
-    movb    %dl,(%ebp)
-    movb    (%edi),%al           // fetch dest  : index into colormap
-
-    decb   %ch
-     jnz    shquadloop
-
-shdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-//----------------------------------------------------------------------
-//
-//  R_DrawWaterColumn : basically it's just a copy of R_DrawColumn,
-//                      but it uses dc_colormap from dc_yl to dc_yw-1
-//                      then it uses dc_wcolormap from dc_yw to dc_yh
-//
-//  Thus, the 'underwater' part of the walls is remapped to 'water-like'
-//  colors.
-//
-//----------------------------------------------------------------------
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 5
-#endif
-.globl C(R_DrawWaterColumn)
-C(R_DrawWaterColumn):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// dest = ylookup[dc_yl] + columnofs[dc_x];
-//
-    movl     C(dc_yl),%ebp
-    movl     %ebp,%ebx
-    movl     C(ylookup)(,%ebx,4),%edi
-    movl     C(dc_x),%ebx
-    addl     C(columnofs)(,%ebx,4),%edi  // edi = dest
-
-//
-// pixelcount = yh - yl + 1
-//
-    movl     C(dc_yh),%eax
-    incl     %eax
-    subl     %ebp,%eax                   // pixel count
-    movl     %eax,C(pixelcount)          // save for final pixel
-    jle      wdone                       // nothing to scale
-
-//
-// frac = dc_texturemid - (centery-dc_yl)*fracstep;
-//
-    movl     C(dc_iscale),%ecx           // fracstep
-    movl     C(centery),%eax
-    subl     %ebp,%eax
-    imul     %ecx,%eax
-    movl     C(dc_texturemid),%edx
-    subl     %eax,%edx
-     movl     %edx,%ebx
-     shrl     $16,%ebx          // frac int.
-     andl     $0x0000007f,%ebx
-     shll     $16,%edx          // y frac up
-
-     movl     %ecx,%ebp
-     shll     $16,%ebp          // fracstep f. up
-     shrl     $16,%ecx          // fracstep i. ->cl
-     andb     $0x7f,%cl
-
-    movl     C(dc_source),%esi
-
-//
-// lets rock :) !
-//
-    movl    C(pixelcount),%eax
-    movb    %al,%dh
-    shrl    $2,%eax
-    movb    %al,%ch             // quad count
-    movl    C(dc_wcolormap),%eax
-    testb   $3,%dh
-    jz      w4quadloop
-
-//
-//  do un-even pixel
-//
-    testb   $1,%dh
-    jz      2f
-
-    movb    (%esi,%ebx),%al     // prep un-even loops
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-    addl    C(vidwidth),%edi
-
-//
-//  do two non-quad-aligned pixels
-//
-2:
-    testb   $2,%dh
-    jz      3f
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-     movb    %dl,(%edi)           // output pixel
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    andb    $0x7f,%bl            // mask 0-127 texture index
-    addl    C(vidwidth),%edi
-     movb    %dl,(%edi)           // output pixel
-
-    addl    C(vidwidth),%edi
-
-//
-//  test if there was at least 4 pixels
-//
-3:
-    testb   $0xFF,%ch           // test quad count
-    jz      wdone
-
-//
-// ebp : ystep frac. upper 24 bits
-// edx : y     frac. upper 24 bits
-// ebx : y     i.    lower 7 bits,  masked for index
-// ecx : ch = counter, cl = y step i.
-// eax : colormap aligned 256
-// esi : source texture column
-// edi : dest screen
-//
-w4quadloop:
-    movb    $0x7f,%dh           // prep mask
-//    .align  4
-wquadloop:
-    movb    (%esi,%ebx),%al     // prep loop
-     addl    %ebp,%edx            // ypos f += ystep f
-    adcb    %cl,%bl              // ypos i += ystep i
-     movb    (%eax),%dl           // colormap texel
-    movb    %dl,(%edi)           // output pixel
-     andb    $0x7f,%bl            // mask 0-127 texture index
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w1:    movb    %dl,0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w2:    movb    %dl,2*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-    movb    (%esi,%ebx),%al      // fetch source texel
-     addl    %ebp,%edx
-    adcb    %cl,%bl
-     movb    (%eax),%dl
-w3:    movb    %dl,3*0x12345678(%edi)
-     andb    $0x7f,%bl
-
-w4:    addl    $4*0x12345678,%edi
-
-    decb   %ch
-     jnz    wquadloop
-
-wdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-
-
-
-
-
-//----------------------------------------------------------------------
-//
-//  R_DrawSpanNoWrap
-//
-//      Horizontal texture mapping, does not remap colors,
-//      neither needs to wrap around the source texture.
-//
-//      Thus, a special optimisation can be used...
-//
-//----------------------------------------------------------------------
-
-    .data
-
-advancetable:   .long   0, 0
-#if !defined( LINUX)
-    .text
-#endif
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_DrawSpanNoWrap)
-C(R_DrawSpanNoWrap):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-//
-// find loop count
-//
-
-    movl    C(ds_x2),%eax
-    incl    %eax
-    subl    C(ds_x1),%eax               // pixel count
-    movl    %eax,C(pixelcount)          // save for final pixel
-    jle     htvdone                       // nothing to scale
-//    shrl    $1,%eax                     // double pixel count
-//    movl    %eax,C(loopcount)
-
-//
-// calculate screen dest
-//
-
-    movl    C(ds_y),%edi        //full destination start address
-
-//
-// set up advancetable
-//
-
-    movl    C(ds_xstep),%ebp
-    movl    C(ds_ystep),%ecx
-    movl    %ecx,%eax
-    movl    %ebp,%edx
-    sarl    $16,%edx            // xstep >>= 16;
-    movl    C(vidwidth),%ebx
-    sarl    $16,%eax            // ystep >>= 16;
-    jz      0f
-    imull   %ebx,%eax           // (ystep >> 16) * texwidth;
-0:
-    addl    %edx,%eax           // add in xstep
-                                // (ystep >> 16) * texwidth + (xstep >> 16);
-
-    movl    %eax,advancetable+4 // advance base in y
-    addl    %ebx,%eax           // ((ystep >> 16) + 1) * texwidth +
-                                //  (xstep >> 16);
-    movl    %eax,advancetable   // advance extra in y
-
-    shll    $16,%ebp            // left-justify xstep fractional part
-    movl    %ebp,xstep
-    shll    $16,%ecx            // left-justify ystep fractional part
-    movl    %ecx,ystep
-
-//
-// calculate the texture starting address
-//
-    movl    C(ds_source),%esi       // texture source
-
-     movl    C(ds_yfrac),%eax
-     movl    %eax,%edx
-     sarl    $16,%eax
-    movl    C(ds_xfrac),%ecx
-     imull   %ebx,%eax               // (yfrac >> 16) * texwidth
-    movl    %ecx,%ebx
-    sarl    $16,%ecx
-    movl    %ecx,%ebp
-     addl    %eax,%ebp               // source = (xfrac >> 16) +
-                                    //           ((yfrac >> 16) * texwidth);
-
-//
-//  esi : texture source
-//  edi : screen dest
-//  eax : colormap aligned on 256 boundary, hehehe...
-//  ebx : xfrac << 16
-//  ecx : used in loop, contains either 0 or -1, *4, offset into advancetable
-//  edx : yfrac << 16
-//  ebp : offset into texture
-//
-
-    shll    $16,%edx             // yfrac upper word, lower byte will be used
-    movl    C(ds_colormap),%eax
-    shll    $16,%ebx             // xfrac upper word, lower unused
-
-    movl    C(pixelcount),%ecx
-    shrl    $2,%ecx
-    movb    %cl,%dh             // quad pixels count
-
-    movl    C(pixelcount),%ecx
-    andl    $3,%ecx
-    jz      htvquadloop         // pixelcount is multiple of 4
-    decl    %ecx
-    jz      1f
-    decl    %ecx
-    jz      2f
-
-//
-//  do one to three pixels first
-//
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-2:
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-1:
-    addl    ystep,%edx          // yfrac += ystep
-   sbbl    %ecx,%ecx           // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al          // get texture pixel
-   addl    xstep,%ebx           // xfrac += xstep
-//    movb    (%eax),%dl           // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)           // write pixel dest
-
-   incl    %edi
-
-//
-//  test if there was at least 4 pixels
-//
-    testb   $0xFF,%dh
-    jz      htvdone
-
-//
-//  two pixels per loop
-// U
-//  V
-htvquadloop:
-    addl    ystep,%edx             // yfrac += ystep
-   sbbl    %ecx,%ecx               // turn carry into 0 or -1 if set
-    movb    (%esi,%ebp),%al        // get texture pixel
-   addl    xstep,%ebx              // xfrac += xstep
-//    movb    (%eax),%dl             // pixel goes through colormap
-   adcl    advancetable+4(,%ecx,4),%ebp       // advance source
-    movb    %al,(%edi)             // write pixel dest
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,1(%edi)
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,2(%edi)
-
-    addl    ystep,%edx
-   sbbl    %ecx,%ecx
-    movb    (%esi,%ebp),%al
-   addl    xstep,%ebx
-//    movb    (%eax),%dl
-   adcl    advancetable+4(,%ecx,4),%ebp
-    movb    %al,3(%edi)
-
-   addl    $4, %edi
-    incl    %ecx    //dummy
-
-   decb   %dh
-    jnz    htvquadloop          // paire dans V-pipe
-
-htvdone:
-    popl    %ebx                // restore register variables
-    popl    %edi
-    popl    %esi
-    popl    %ebp                // restore caller's stack frame pointer
-    ret
-
-
-//.endif
-
-#ifdef HORIZONTALDRAW
-// void R_RotateBuffere (void)
-
-#ifdef LINUX
-    .align 2
-#else
-    .align 4
-#endif
-.globl C(R_RotateBufferasm)
-C(R_RotateBufferasm):
-    pushl   %ebp                // preserve caller's stack frame pointer
-    pushl   %esi                // preserve register variables
-    pushl   %edi
-    pushl   %ebx
-
-    movl    C(dc_source),%esi
-    movl    C(dc_colormap),%edi
-
-    movl    $200,%edx
-ra2:
-    movl    $40,%ecx
-ra:
-    movb    -2*200(%esi),%al
-    movb    -6*200(%esi),%bl
-    movb    -3*200(%esi),%ah
-    movb    -7*200(%esi),%bh
-    shll    $16,%eax
-    shll    $16,%ebx
-    movb    (%esi),%al
-    movb    -4*200(%esi),%bl
-    movb    -1*200(%esi),%ah
-    movb    -5*200(%esi),%bh
-    movl    %eax,(%edi)
-    subl    $8*200,%esi
-    movl    %ebx,4(%edi)
-    addl    $8,%edi
-    decl    %ecx
-    jnz     ra
-
-    addl    $320*200+1,%esi      //32*480 passe a la ligne suivante
-//    addl    320-32,%edi
-
-    decl    %edx
-    jnz     ra2
-
-    pop   %ebp                // preserve caller's stack frame pointer
-    pop   %esi                // preserve register variables
-    pop   %edi
-    pop   %ebx
-    ret
-#endif
diff --git a/src/tmap_asm.s b/src/tmap_asm.s
deleted file mode 100644
index d8967178cdf28e3b9bedbda863232ef0bf0978d4..0000000000000000000000000000000000000000
--- a/src/tmap_asm.s
+++ /dev/null
@@ -1,322 +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  tmap_asm.s
-/// \brief ???
-
-//.comm _dc_colormap,4
-//.comm _dc_x,4
-//.comm _dc_yl,4
-//.comm _dc_yh,4
-//.comm _dc_iscale,4
-//.comm _dc_texturemid,4
-//.comm _dc_source,4
-//.comm _ylookup,4
-//.comm _columnofs,4
-//.comm _loopcount,4
-//.comm _pixelcount,4
-.data
-_pixelcount:
-.long 0x00000000
-_loopcount:
-.long 0x00000000
-.align 8
-_mmxcomm:
-.long 0x00000000
-.text
-
-        .align 4
-.globl _R_DrawColumn8_NOMMX
-_R_DrawColumn8_NOMMX:
-   pushl %ebp
-   pushl %esi
-   pushl %edi
-   pushl %ebx
-	movl _dc_yl,%edx
-	movl _dc_yh,%eax
-	subl %edx,%eax
-	leal 1(%eax),%ebx
-	testl %ebx,%ebx
-	jle rdc8ndone
-	movl _dc_x,%eax
-        movl _ylookup, %edi
-	movl (%edi,%edx,4),%esi
-	movl _columnofs, %edi
-	addl (%edi,%eax,4),%esi
-	movl _dc_iscale,%edi
-	movl %edx,%eax
-	imull %edi,%eax
-	movl _dc_texturemid,%ecx
-	addl %eax,%ecx
-
-	movl _dc_source,%ebp
-   xorl %edx, %edx
-   subl $0x12345678, %esi
-.globl rdc8nwidth1
-rdc8nwidth1:
-	.align 4,0x90
-rdc8nloop:
-	movl %ecx,%eax
-	shrl $16,%eax
-	addl %edi,%ecx
-	andl $127,%eax
-	addl $0x12345678,%esi
-.globl rdc8nwidth2
-rdc8nwidth2:
-	movb (%eax,%ebp),%dl
-	movl _dc_colormap,%eax
-	movb (%eax,%edx),%al
-	movb %al,(%esi)
-	decl %ebx
-	jne rdc8nloop
-rdc8ndone:
-   popl %ebx
-   popl %edi
-   popl %esi
-   popl %ebp
-   ret
-
-//
-// Optimised specifically for P54C/P55C (aka Pentium with/without MMX)
-// By ES 1998/08/01
-//
-
-.globl _R_DrawColumn_8_Pentium
-_R_DrawColumn_8_Pentium:
-	pushl %ebp
-        pushl %ebx
-	pushl %esi
-        pushl %edi
-	movl _dc_yl,%eax        // Top pixel
-	movl _dc_yh,%ebx        // Bottom pixel
-        movl _ylookup, %edi
-	movl (%edi,%ebx,4),%ecx
-	subl %eax,%ebx          // ebx=number of pixels-1
-	jl rdc8pdone            // no pixel to draw, done
-	jnz rdc8pmany
-	movl _dc_x,%edx         // Special case: only one pixel
-        movl _columnofs, %edi
-	addl (%edi,%edx,4),%ecx // dest pixel at (%ecx)
-	movl _dc_iscale,%esi
-	imull %esi,%eax
-	movl _dc_texturemid,%edi
-	addl %eax,%edi          // texture index in edi
-	movl _dc_colormap,%edx
-   	shrl $16, %edi
-   	movl _dc_source,%ebp
-	andl $127,%edi
-	movb (%edi,%ebp),%dl    // read texture pixel
-	movb (%edx),%al	        // lookup for light
-	movb %al,0(%ecx) 	// write it
-	jmp rdc8pdone		// done!
-.align 4, 0x90
-rdc8pmany:			// draw >1 pixel
-	movl _dc_x,%edx
-        movl _columnofs, %edi
-	movl (%edi,%edx,4),%edx
-	leal 0x12345678(%edx, %ecx), %edi  // edi = two pixels above bottom
-.globl rdc8pwidth5
-rdc8pwidth5:  // DeadBeef = -2*SCREENWIDTH
-        movl _dc_iscale,%edx	// edx = fracstep
-	imull %edx,%eax
-   	shll $9, %edx           // fixme: Should get 7.25 fix as input
-	movl _dc_texturemid,%ecx
-	addl %eax,%ecx          // ecx = frac
-	movl _dc_colormap,%eax  // eax = lighting/special effects LUT
-   	shll $9, %ecx
-   	movl _dc_source,%esi    // esi = source ptr
-
-	imull $0x12345678, %ebx // ebx = negative offset to pixel
-.globl rdc8pwidth6
-rdc8pwidth6:  // DeadBeef = -SCREENWIDTH
-
-// Begin the calculation of the two first pixels
-        leal (%ecx, %edx), %ebp
-	shrl $25, %ecx
-	movb (%esi, %ecx), %al
-	leal (%edx, %ebp), %ecx
-	shrl $25, %ebp
-        movb (%eax), %dl
-
-// The main loop
-rdc8ploop:
-	movb (%esi,%ebp), %al		// load 1
-        leal (%ecx, %edx), %ebp         // calc frac 3
-
-	shrl $25, %ecx                  // shift frac 2
-        movb %dl, 0x12345678(%edi, %ebx)// store 0
-.globl rdc8pwidth1
-rdc8pwidth1:  // DeadBeef = 2*SCREENWIDTH
-
-        movb (%eax), %al                // lookup 1
-
-        movb %al, 0x12345678(%edi, %ebx)// store 1
-.globl rdc8pwidth2
-rdc8pwidth2:  // DeadBeef = 3*SCREENWIDTH
-        movb (%esi, %ecx), %al          // load 2
-
-        leal (%ebp, %edx), %ecx         // calc frac 4
-
-        shrl $25, %ebp                  // shift frac 3
-        movb (%eax), %dl                // lookup 2
-
-        addl $0x12345678, %ebx          // counter
-.globl rdc8pwidth3
-rdc8pwidth3:  // DeadBeef = 2*SCREENWIDTH
-        jl rdc8ploop                    // loop
-
-// End of loop. Write extra pixel or just exit.
-        jnz rdc8pdone
-        movb %dl, 0x12345678(%edi, %ebx)// Write odd pixel
-.globl rdc8pwidth4
-rdc8pwidth4:  // DeadBeef = 2*SCREENWIDTH
-
-rdc8pdone:
-
-        popl %edi
-	popl %esi
-        popl %ebx
-	popl %ebp
-        ret
-
-//
-// MMX asm version, optimised for K6
-// By ES 1998/07/05
-//
-
-.globl _R_DrawColumn_8_K6_MMX
-_R_DrawColumn_8_K6_MMX:
-	pushl %ebp
-        pushl %ebx
-	pushl %esi
-        pushl %edi
-
-        movl %esp, %eax // Push 8 or 12, so that (%esp) gets aligned by 8
-        andl $7,%eax
-        addl $8,%eax
-        movl %eax, _mmxcomm // Temp storage in mmxcomm: (%esp) is used instead
-        subl %eax,%esp
-
-	movl _dc_yl,%edx        // Top pixel
-	movl _dc_yh,%ebx        // Bottom pixel
-        movl _ylookup, %edi
-	movl (%edi,%ebx,4),%ecx
-	subl %edx,%ebx         // ebx=number of pixels-1
-	jl 0x12345678            // no pixel to draw, done
-.globl rdc8moffs1
-rdc8moffs1:
-	jnz rdc8mmany
-	movl _dc_x,%eax         // Special case: only one pixel
-        movl _columnofs, %edi
-	addl (%edi,%eax,4),%ecx  // dest pixel at (%ecx)
-	movl _dc_iscale,%esi
-	imull %esi,%edx
-	movl _dc_texturemid,%edi
-	addl %edx,%edi         // texture index in edi
-	movl _dc_colormap,%edx
-   	shrl $16, %edi
-   	movl _dc_source,%ebp
-	andl $127,%edi
-	movb (%edi,%ebp),%dl  // read texture pixel
-	movb (%edx),%al	 // lookup for light
-	movb %al,0(%ecx) 	 // write it
-	jmp rdc8mdone		 // done!
-.globl rdc8moffs2
-rdc8moffs2:
-.align 4, 0x90
-rdc8mmany:			 // draw >1 pixel
-	movl _dc_x,%eax
-        movl _columnofs, %edi
-	movl (%edi,%eax,4),%eax
-	leal 0x12345678(%eax, %ecx), %esi  // esi = two pixels above bottom
-.globl rdc8mwidth3
-rdc8mwidth3:  // DeadBeef = -2*SCREENWIDTH
-        movl _dc_iscale,%ecx	 // ecx = fracstep
-	imull %ecx,%edx
-   	shll $9, %ecx           // fixme: Should get 7.25 fix as input
-	movl _dc_texturemid,%eax
-	addl %edx,%eax         // eax = frac
-	movl _dc_colormap,%edx  // edx = lighting/special effects LUT
-   	shll $9, %eax
-	leal (%ecx, %ecx), %edi
-   	movl _dc_source,%ebp    // ebp = source ptr
-	movl %edi, 0(%esp)     // Start moving frac and fracstep to MMX regs
-
-	imull $0x12345678, %ebx  // ebx = negative offset to pixel
-.globl rdc8mwidth5
-rdc8mwidth5:  // DeadBeef = -SCREENWIDTH
-
-	movl %edi, 4(%esp)
-	leal (%eax, %ecx), %edi
-	movq 0(%esp), %mm1     // fracstep:fracstep in mm1
-	movl %eax, 0(%esp)
-	shrl $25, %eax
-	movl %edi, 4(%esp)
-	movzbl (%ebp, %eax), %eax
-	movq 0(%esp), %mm0     // frac:frac in mm0
-
-	paddd %mm1, %mm0
-	shrl $25, %edi
-	movq %mm0, %mm2
-	psrld $25, %mm2         // texture index in mm2
-	paddd %mm1, %mm0
-	movq %mm2, 0(%esp)
-
-.globl rdc8mloop
-rdc8mloop:                      		// The main loop
-	movq %mm0, %mm2                    // move 4-5 to temp reg
-	movzbl (%ebp, %edi), %edi 		// read 1
-
-	psrld $25, %mm2 			// shift 4-5
-	movb (%edx,%eax), %cl 		// lookup 0
-
-	movl 0(%esp), %eax 			// load 2
-	addl $0x12345678, %ebx 		// counter
-.globl rdc8mwidth2
-rdc8mwidth2:  // DeadBeef = 2*SCREENWIDTH
-
-	movb %cl, (%esi, %ebx)		// write 0
-	movb (%edx,%edi), %ch 		// lookup 1
-
-	movb %ch, 0x12345678(%esi, %ebx) 	// write 1
-.globl rdc8mwidth1
-rdc8mwidth1:  // DeadBeef = SCREENWIDTH
-	movl 4(%esp), %edi			// load 3
-
-	paddd %mm1, %mm0 			// frac 6-7
-	movzbl (%ebp, %eax), %eax 		// lookup 2
-
-	movq %mm2, 0(%esp) 		     // store texture index 4-5
-	jl rdc8mloop
-
-	jnz rdc8mno_odd
-	movb (%edx,%eax), %cl  // write the last odd pixel
-	movb %cl, 0x12345678(%esi)
-.globl rdc8mwidth4
-rdc8mwidth4:  // DeadBeef = 2*SCREENWIDTH
-rdc8mno_odd:
-
-.globl rdc8mdone
-rdc8mdone:
-        emms
-
-        addl _mmxcomm, %esp
-        popl %edi
-	popl %esi
-        popl %ebx
-	popl %ebp
-        ret
-
-// Need some extra space to align run-time
-.globl R_DrawColumn_8_K6_MMX_end
-R_DrawColumn_8_K6_MMX_end:
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;nop;
-nop;nop;nop;nop;nop;nop;nop;
diff --git a/src/tmap_mmx.nas b/src/tmap_mmx.nas
deleted file mode 100644
index a45667e23d539997193e0df23862dba71458c6f6..0000000000000000000000000000000000000000
--- a/src/tmap_mmx.nas
+++ /dev/null
@@ -1,674 +0,0 @@
-;; SONIC ROBO BLAST 2
-;;-----------------------------------------------------------------------------
-;; Copyright (C) 1998-2000 by DOSDOOM.
-;; Copyright (C) 2010-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:
-;;      tmap_mmx.nas
-;; DESCRIPTION:
-;;      Assembler optimised rendering code for software mode, using SIMD
-;;      instructions.
-;;      Draw wall columns.
-
-
-[BITS 32]
-
-%define FRACBITS 16
-%define TRANSPARENTPIXEL 255
-
-%ifdef LINUX
-%macro cextern 1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-[global %1]
-%endmacro
-
-%else
-%macro cextern 1
-%define %1 _%1
-[extern %1]
-%endmacro
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-%endif
-
-
-; The viddef_s structure. We only need the width field.
-struc viddef_s
-		resb 12
-.width: resb 4
-		resb 44
-endstruc
-
-
-;; externs
-;; columns
-cextern dc_colormap
-cextern dc_x
-cextern dc_yl
-cextern dc_yh
-cextern dc_iscale
-cextern dc_texturemid
-cextern dc_texheight
-cextern dc_source
-cextern dc_hires
-cextern centery
-cextern centeryfrac
-cextern dc_transmap
-
-cextern R_DrawColumn_8_ASM
-cextern R_Draw2sMultiPatchColumn_8_ASM
-
-;; spans
-cextern nflatshiftup
-cextern nflatxshift
-cextern nflatyshift
-cextern nflatmask
-cextern ds_xfrac
-cextern ds_yfrac
-cextern ds_xstep
-cextern ds_ystep
-cextern ds_x1
-cextern ds_x2
-cextern ds_y
-cextern ds_source
-cextern ds_colormap
-
-cextern ylookup
-cextern columnofs
-cextern vid
-
-[SECTION .data]
-
-nflatmask64		dq		0
-
-
-[SECTION .text]
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawColumn : 8bpp column drawer
-;;
-;; MMX column drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = vid.width
-;; mm0 = accumulator
-;; mm1 = heightmask, twice
-;; mm2 = 2 * fracstep, twice
-;; mm3 = pair of consecutive fracs
-;;----------------------------------------------------------------------
-
-
-cglobal R_DrawColumn_8_MMX
-R_DrawColumn_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; Our algorithm requires that the texture height be a power of two.
-;; If not, fall back to the non-MMX drawer.
-;;
-.texheightcheck:
-		mov			edx, [dc_texheight]
-		sub			edx, 1					;; edx = heightmask
-		test		edx, [dc_texheight]
-		jnz			near .usenonMMX
-
-		mov			ebp, edx				;; Keep a copy of heightmask in a
-											;; GPR for the time being.
-
-;;
-;; Fill mm1 with heightmask
-;;
-		movd		mm1, edx				;; low dword = heightmask
-		punpckldq	mm1, mm1				;; copy low dword to high dword
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-		mov			eax, [dc_yl]
-		mov			edi, [ylookup+eax*4]
-		mov			ebx, [dc_x]
-		add			edi, [columnofs+ebx*4]	;; edi = dest
-
-
-;;
-;; pixelcount = yh - yl + 1
-;;
-		mov			ecx, [dc_yh]
-		add			ecx, 1
-		sub			ecx, eax				;; pixel count
-		jle			near .done				;; nothing to scale
-
-;;
-;; fracstep = dc_iscale;
-;;
-		movd		mm2, [dc_iscale]		;; fracstep in low dword
-		punpckldq	mm2, mm2				;; copy to high dword
-
-		mov			ebx, [dc_colormap]
-		mov			esi, [dc_source]
-
-;;
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-											;; eax == dc_yl already
-		shl			eax, FRACBITS
-		sub			eax, [centeryfrac]
-		imul		dword [dc_iscale]
-		shrd		eax, edx, FRACBITS
-		add			eax, [dc_texturemid]
-
-;;
-;; if (dc_hires) frac = 0;
-;;
-		test		byte [dc_hires], 0x01
-		jz			.mod2
-		xor			eax, eax
-
-
-;;
-;; Do mod-2 pixel.
-;;
-.mod2:
-		test		ecx, 1
-		jz			.pairprepare
-		mov			edx, eax				;; edx = frac
-		add			eax, [dc_iscale]		;; eax += fracstep
-		sar			edx, FRACBITS
-		and			edx, ebp				;; edx &= heightmask
-		movzx		edx, byte [esi + edx]
-		movzx		edx, byte [ebx + edx]
-		mov			[edi], dl
-
-		add			edi, [vid + viddef_s.width]
-		sub			ecx, 1
-		jz			.done
-
-.pairprepare:
-;;
-;; Prepare for the main loop.
-;;
-		movd		mm3, eax				;; Low dword = frac
-		movq		mm4, mm3				;; Copy to intermediate register
-		paddd		mm4, mm2				;; dwords of mm4 += fracstep
-		punpckldq	mm3, mm4				;; Low dword = first frac, high = second
-		pslld		mm2, 1					;; fracstep *= 2
-
-;;
-;; ebp = vid.width
-;;
-		mov			ebp, [vid + viddef_s.width]
-
-		align		16
-.pairloop:
-		movq		mm0, mm3				;; 3B 1u.
-		psrad		mm0, FRACBITS			;; 4B 1u.
-		pand		mm0, mm1				;; 3B 1u. frac &= heightmask
-		paddd		mm3, mm2				;; 3B 1u. frac += fracstep
-
-		movd		eax, mm0				;; 3B 1u. Get first frac
-;; IFETCH boundary
-		movzx		eax, byte [esi + eax]	;; 4B 1u. Texture map
-		movzx		eax, byte [ebx + eax]	;; 4B 1u. Colormap
-
-		punpckhdq	mm0, mm0				;; 3B 1(2)u. low dword = high dword
-		movd		edx, mm0				;; 3B 1u. Get second frac
-		mov			[edi], al				;; 2B 1(2)u. First pixel
-;; IFETCH boundary
-
-		movzx		edx, byte [esi + edx]	;; 4B 1u. Texture map
-		movzx		edx, byte [ebx + edx]	;; 4B 1u. Colormap
-		mov			[edi + 1*ebp], dl		;; 3B 1(2)u. Second pixel
-
-		lea			edi, [edi + 2*ebp]		;; 3B 1u. edi += 2 * vid.width
-;; IFETCH boundary
-		sub			ecx, 2					;; 3B 1u. count -= 2
-		jnz			.pairloop				;; 2B 1u. if(count != 0) goto .pairloop
-
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
-
-.usenonMMX:
-		call		R_DrawColumn_8_ASM
-		jmp			.done
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_Draw2sMultiPatchColumn : Like R_DrawColumn, but omits transparent
-;;                            pixels.
-;;
-;; MMX column drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = vid.width
-;; mm0 = accumulator
-;; mm1 = heightmask, twice
-;; mm2 = 2 * fracstep, twice
-;; mm3 = pair of consecutive fracs
-;;----------------------------------------------------------------------
-
-
-cglobal R_Draw2sMultiPatchColumn_8_MMX
-R_Draw2sMultiPatchColumn_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; Our algorithm requires that the texture height be a power of two.
-;; If not, fall back to the non-MMX drawer.
-;;
-.texheightcheck:
-		mov			edx, [dc_texheight]
-		sub			edx, 1					;; edx = heightmask
-		test		edx, [dc_texheight]
-		jnz			near .usenonMMX
-
-		mov			ebp, edx				;; Keep a copy of heightmask in a
-											;; GPR for the time being.
-
-;;
-;; Fill mm1 with heightmask
-;;
-		movd		mm1, edx				;; low dword = heightmask
-		punpckldq	mm1, mm1				;; copy low dword to high dword
-
-;;
-;; dest = ylookup[dc_yl] + columnofs[dc_x];
-;;
-		mov			eax, [dc_yl]
-		mov			edi, [ylookup+eax*4]
-		mov			ebx, [dc_x]
-		add			edi, [columnofs+ebx*4]	;; edi = dest
-
-
-;;
-;; pixelcount = yh - yl + 1
-;;
-		mov			ecx, [dc_yh]
-		add			ecx, 1
-		sub			ecx, eax				;; pixel count
-		jle			near .done				;; nothing to scale
-;;
-;; fracstep = dc_iscale;
-;;
-		movd		mm2, [dc_iscale]		;; fracstep in low dword
-		punpckldq	mm2, mm2				;; copy to high dword
-
-		mov			ebx, [dc_colormap]
-		mov			esi, [dc_source]
-
-;;
-;; frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep));
-;;
-											;; eax == dc_yl already
-		shl			eax, FRACBITS
-		sub			eax, [centeryfrac]
-		imul		dword [dc_iscale]
-		shrd		eax, edx, FRACBITS
-		add			eax, [dc_texturemid]
-
-;;
-;; if (dc_hires) frac = 0;
-;;
-		test		byte [dc_hires], 0x01
-		jz			.mod2
-		xor			eax, eax
-
-
-;;
-;; Do mod-2 pixel.
-;;
-.mod2:
-		test		ecx, 1
-		jz			.pairprepare
-		mov			edx, eax				;; edx = frac
-		add			eax, [dc_iscale]		;; eax += fracstep
-		sar			edx, FRACBITS
-		and			edx, ebp				;; edx &= heightmask
-		movzx		edx, byte [esi + edx]
-		cmp			dl, TRANSPARENTPIXEL
-		je			.nextmod2
-		movzx		edx, byte [ebx + edx]
-		mov			[edi], dl
-
-.nextmod2:
-		add			edi, [vid + viddef_s.width]
-		sub			ecx, 1
-		jz			.done
-
-.pairprepare:
-;;
-;; Prepare for the main loop.
-;;
-		movd		mm3, eax				;; Low dword = frac
-		movq		mm4, mm3				;; Copy to intermediate register
-		paddd		mm4, mm2				;; dwords of mm4 += fracstep
-		punpckldq	mm3, mm4				;; Low dword = first frac, high = second
-		pslld		mm2, 1					;; fracstep *= 2
-
-;;
-;; ebp = vid.width
-;;
-		mov			ebp, [vid + viddef_s.width]
-
-		align		16
-.pairloop:
-		movq		mm0, mm3				;; 3B 1u.
-		psrad		mm0, FRACBITS			;; 4B 1u.
-		pand		mm0, mm1				;; 3B 1u. frac &= heightmask
-		paddd		mm3, mm2				;; 3B 1u. frac += fracstep
-
-		movd		eax, mm0				;; 3B 1u. Get first frac
-;; IFETCH boundary
-		movzx		eax, byte [esi + eax]	;; 4B 1u. Texture map
-		punpckhdq	mm0, mm0				;; 3B 1(2)u. low dword = high dword
-		movd		edx, mm0				;; 3B 1u. Get second frac
-		cmp			al, TRANSPARENTPIXEL	;; 2B 1u.
-		je			.secondinpair			;; 2B 1u.
-;; IFETCH boundary
-		movzx		eax, byte [ebx + eax]	;; 4B 1u. Colormap
-		mov			[edi], al				;; 2B 1(2)u. First pixel
-
-.secondinpair:
-		movzx		edx, byte [esi + edx]	;; 4B 1u. Texture map
-		cmp			dl, TRANSPARENTPIXEL	;; 2B 1u.
-		je			.nextpair				;; 2B 1u.
-;; IFETCH boundary
-		movzx		edx, byte [ebx + edx]	;; 4B 1u. Colormap
-		mov			[edi + 1*ebp], dl		;; 3B 1(2)u. Second pixel
-
-.nextpair:
-		lea			edi, [edi + 2*ebp]		;; 3B 1u. edi += 2 * vid.width
-		sub			ecx, 2					;; 3B 1u. count -= 2
-		jnz			.pairloop				;; 2B 1u. if(count != 0) goto .pairloop
-
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
-
-.usenonMMX:
-		call		R_Draw2sMultiPatchColumn_8_ASM
-		jmp			.done
-
-
-;;----------------------------------------------------------------------
-;;
-;; R_DrawSpan : 8bpp span drawer
-;;
-;; MMX span drawer.
-;;
-;;----------------------------------------------------------------------
-;; eax = accumulator
-;; ebx = colormap
-;; ecx = count
-;; edx = accumulator
-;; esi = source
-;; edi = dest
-;; ebp = two pixels
-;; mm0 = accumulator
-;; mm1 = xposition
-;; mm2 = yposition
-;; mm3 = 2 * xstep
-;; mm4 = 2 * ystep
-;; mm5 = nflatxshift
-;; mm6 = nflatyshift
-;; mm7 = accumulator
-;;----------------------------------------------------------------------
-
-cglobal R_DrawSpan_8_MMX
-R_DrawSpan_8_MMX:
-		push		ebp						;; preserve caller's stack frame pointer
-		push		esi						;; preserve register variables
-		push		edi
-		push		ebx
-
-;;
-;; esi = ds_source
-;; ebx = ds_colormap
-;;
-		mov			esi, [ds_source]
-		mov			ebx, [ds_colormap]
-
-;;
-;; edi = ylookup[ds_y] + columnofs[ds_x1]
-;;
-		mov			eax, [ds_y]
-		mov			edi, [ylookup + eax*4]
-		mov			edx, [ds_x1]
-		add			edi, [columnofs + edx*4]
-
-;;
-;; ecx = ds_x2 - ds_x1 + 1
-;;
-		mov			ecx, [ds_x2]
-		sub			ecx, edx
-		add			ecx, 1
-
-;;
-;; Needed for fracs and steps
-;;
-		movd		mm7, [nflatshiftup]
-
-;;
-;; mm3 = xstep
-;;
-		movd		mm3, [ds_xstep]
-		pslld		mm3, mm7
-		punpckldq	mm3, mm3
-
-;;
-;; mm4 = ystep
-;;
-		movd		mm4, [ds_ystep]
-		pslld		mm4, mm7
-		punpckldq	mm4, mm4
-
-;;
-;; mm1 = pair of consecutive xpositions
-;;
-		movd		mm1, [ds_xfrac]
-		pslld		mm1, mm7
-		movq		mm6, mm1
-		paddd		mm6, mm3
-		punpckldq	mm1, mm6
-
-;;
-;; mm2 = pair of consecutive ypositions
-;;
-		movd		mm2, [ds_yfrac]
-		pslld		mm2, mm7
-		movq		mm6, mm2
-		paddd		mm6, mm4
-		punpckldq	mm2, mm6
-
-;;
-;; mm5 = nflatxshift
-;; mm6 = nflatyshift
-;;
-		movd		mm5, [nflatxshift]
-		movd		mm6, [nflatyshift]
-
-;;
-;; Mask is in memory due to lack of registers.
-;;
-		mov			eax, [nflatmask]
-		mov			[nflatmask64], eax
-		mov			[nflatmask64 + 4], eax
-
-
-;;
-;; Go until we reach a dword boundary.
-;;
-.unaligned:
-		test		edi, 3
-		jz			.alignedprep
-.stragglers:
-		cmp			ecx, 0
-		je			.done					;; If ecx == 0, we're finished.
-
-;;
-;; eax = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep (once!)
-		paddd		mm2, mm4				;; yposition += ystep (once!)
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of first pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-
-		mov			[edi], al
-		add			edi, 1
-
-		sub			ecx, 1
-		jmp			.unaligned
-
-
-.alignedprep:
-;;
-;; We can double the steps now.
-;;
-		pslld		mm3, 1
-		pslld		mm4, 1
-
-
-;;
-;; Generate chunks of four pixels.
-;;
-.alignedloop:
-
-;;
-;; Make sure we have at least four pixels.
-;;
-		cmp			ecx, 4
-		jl			.prestragglers
-
-;;
-;; First two pixels.
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep
-		paddd		mm2, mm4				;; yposition += ystep
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of first pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		ebp, byte [ebx + eax]	;; ebp = colormap[al]
-
-		punpckhdq	mm0, mm0				;; both dwords = high dword
-		movd		eax, mm0				;; eax = index of second pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 8					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-;;
-;; Next two pixels.
-;;
-		movq		mm0, mm1				;; mm0 = xposition
-		movq		mm7, mm2				;; mm7 = yposition
-		paddd		mm1, mm3				;; xposition += xstep
-		paddd		mm2, mm4				;; yposition += ystep
-		psrld		mm0, mm5				;; shift
-		psrld		mm7, mm6				;; shift
-		pand		mm7, [nflatmask64]		;; mask
-		por			mm0, mm7				;; or x and y together
-
-		movd		eax, mm0				;; eax = index of third pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 16					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-		punpckhdq	mm0, mm0				;; both dwords = high dword
-		movd		eax, mm0				;; eax = index of second pixel
-		movzx		eax, byte [esi + eax]	;; al = source[eax]
-		movzx		eax, byte [ebx + eax]	;; al = colormap[al]
-		shl			eax, 24					;; get pixel in right byte
-		or			ebp, eax				;; put pixel in ebp
-
-;;
-;; Write pixels.
-;;
-		mov			[edi], ebp
-		add			edi, 4
-
-		sub			ecx, 4
-		jmp			.alignedloop
-
-.prestragglers:
-;;
-;; Back to one step at a time.
-;;
-		psrad		mm3, 1
-		psrad		mm4, 1
-		jmp			.stragglers
-
-.done:
-;;
-;; Clear MMX state, or else FPU operations will go badly awry.
-;;
-		emms
-
-		pop			ebx
-		pop			edi
-		pop			esi
-		pop			ebp
-		ret
diff --git a/src/tmap_vc.nas b/src/tmap_vc.nas
deleted file mode 100644
index c85cf70035f8588387420d479725242bb708cc42..0000000000000000000000000000000000000000
--- a/src/tmap_vc.nas
+++ /dev/null
@@ -1,48 +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:
-;;      tmap_vc.nas
-;; DESCRIPTION:
-;;      Assembler optimised math code for Visual C++.
-
-
-[BITS 32]
-
-%macro cglobal 1
-%define %1 _%1
-[global %1]
-%endmacro
-
-[SECTION .text write]
-
-;----------------------------------------------------------------------------
-;fixed_t FixedMul (fixed_t a, fixed_t b)
-;----------------------------------------------------------------------------
-cglobal FixedMul
-;       align   16
-FixedMul:
-        mov     eax,[esp+4]
-        imul    dword [esp+8]
-        shrd    eax,edx,16
-        ret
-
-;----------------------------------------------------------------------------
-;fixed_t FixedDiv2 (fixed_t a, fixed_t b);
-;----------------------------------------------------------------------------
-cglobal FixedDiv2
-;       align   16
-FixedDiv2:
-        mov     eax,[esp+4]
-        mov     edx,eax                 ;; these two instructions allow the next
-        sar     edx,31                  ;; two to pair, on the Pentium processor.
-        shld    edx,eax,16
-        sal     eax,16
-        idiv    dword [esp+8]
-        ret
diff --git a/src/hardware/u_list.c b/src/u_list.c
similarity index 99%
rename from src/hardware/u_list.c
rename to src/u_list.c
index dc49a74e7f7ab1a0502fbf5fb5f13cbbad888275..d5cfd5717ac2786e846abaf61c1dd91e2de184fe 100644
--- a/src/hardware/u_list.c
+++ b/src/u_list.c
@@ -8,7 +8,7 @@
 */
 
 #include "u_list.h"
-#include "../z_zone.h"
+#include "z_zone.h"
 
 // Utility for managing
 // structures in a linked
diff --git a/src/hardware/u_list.h b/src/u_list.h
similarity index 100%
rename from src/hardware/u_list.h
rename to src/u_list.h
diff --git a/src/v_video.c b/src/v_video.c
index 461a5e3bc7671684f5fdcc62a8c1a728ea913a55..3f958b286cdfdcc275a23c5822605812d218c242 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -447,12 +447,6 @@ static void CV_palette_OnChange(void)
 	V_SetPalette(0);
 }
 
-#if defined (__GNUC__) && defined (__i386__) && !defined (NOASM) && !defined (__APPLE__) && !defined (NORUSEASM)
-void VID_BlitLinearScreen_ASM(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes,
-	size_t destrowbytes);
-#define HAVE_VIDCOPY
-#endif
-
 static void CV_constextsize_OnChange(void)
 {
 	if (!con_refresh)
@@ -466,9 +460,6 @@ static void CV_constextsize_OnChange(void)
 void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes,
 	size_t destrowbytes)
 {
-#ifdef HAVE_VIDCOPY
-    VID_BlitLinearScreen_ASM(srcptr,destptr,width,height,srcrowbytes,destrowbytes);
-#else
 	if (srcrowbytes == destrowbytes)
 		M_Memcpy(destptr, srcptr, srcrowbytes * height);
 	else
@@ -481,7 +472,6 @@ void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT3
 			srcptr += srcrowbytes;
 		}
 	}
-#endif
 }
 
 static UINT8 hudplusalpha[11]  = { 10,  8,  6,  4,  2,  0,  0,  0,  0,  0,  0};
diff --git a/src/version.h b/src/version.h
index 7a12fbbbe4638b5552510e23c09b6ba00c706b36..3242cad672df6757e74741ca482a403f7544e31b 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,4 +1,4 @@
-#define SRB2VERSION "2.2.10"/* 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 51
+#define MODVERSION 54
 
 // Define this as a prerelease version suffix (pre#, RC#)
-// #define BETAVERSION "pre1"
+//#define BETAVERSION "pre1"
diff --git a/src/vid_copy.s b/src/vid_copy.s
deleted file mode 100644
index 1473a3856f192145e3739738de85bd4f6cb96222..0000000000000000000000000000000000000000
--- a/src/vid_copy.s
+++ /dev/null
@@ -1,61 +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  vid_copy.s
-/// \brief code for updating the linear frame buffer screen.
-
-#include "asm_defs.inc"           // structures, must match the C structures!
-
-// DJGPPv2 is as fast as this one, but then someone may compile with a less
-// good version of DJGPP than mine, so this little asm will do the trick!
-
-#define srcptr          4+16
-#define destptr         8+16
-#define width           12+16
-#define height          16+16
-#define srcrowbytes     20+16
-#define destrowbytes    24+16
-
-// VID_BlitLinearScreen( src, dest, width, height, srcwidth, destwidth );
-//         width is given as BYTES
-
-#ifdef __i386__
-
-.globl C(VID_BlitLinearScreen_ASM)
-C(VID_BlitLinearScreen_ASM):
-    pushl   %ebp                // preserve caller's stack frame
-    pushl   %edi
-    pushl   %esi                // preserve register variables
-    pushl   %ebx
-
-    cld
-    movl    srcptr(%esp),%esi
-    movl    destptr(%esp),%edi
-    movl    width(%esp),%ebx
-    movl    srcrowbytes(%esp),%eax
-    subl    %ebx,%eax
-    movl    destrowbytes(%esp),%edx
-    subl    %ebx,%edx
-    shrl    $2,%ebx
-    movl    height(%esp),%ebp
-LLRowLoop:
-    movl    %ebx,%ecx
-    rep/movsl   (%esi),(%edi)
-    addl    %eax,%esi
-    addl    %edx,%edi
-    decl    %ebp
-    jnz     LLRowLoop
-
-    popl    %ebx                // restore register variables
-    popl    %esi
-    popl    %edi
-    popl    %ebp                // restore the caller's stack frame
-
-    ret
-#endif
diff --git a/src/w_wad.c b/src/w_wad.c
index 40073fc5566aa3ff218fd3f2ebddee67b255d74f..c8880f6934f0f04bbe892da599e90f487339b242 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -201,7 +201,7 @@ FILE *W_OpenWadFile(const char **filename, boolean useerrors)
 }
 
 // Look for all DEHACKED and Lua scripts inside a PK3 archive.
-static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
+static void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 {
 	UINT16 posStart, posEnd;
 
@@ -241,7 +241,7 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 }
 
 // search for all DEHACKED lump in all wads and load it
-static inline void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
+static void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
 {
 	UINT16 lump;
 
@@ -305,7 +305,7 @@ static inline void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
   * \param resblock resulting MD5 checksum
   * \return 0 if MD5 checksum was made, and is at resblock, 1 if error was found
   */
-static inline INT32 W_MakeFileMD5(const char *filename, void *resblock)
+static INT32 W_MakeFileMD5(const char *filename, void *resblock)
 {
 #ifdef NOMD5
 	(void)filename;
@@ -972,7 +972,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	// add the wadfile
 	//
 	CONS_Printf(M_GetText("Added file %s (%u lumps)\n"), filename, numlumps);
-	wadfiles = Z_Realloc(wadfiles, sizeof(wadfile_t) * (numwadfiles + 1), PU_STATIC, NULL);
+	wadfiles = Z_Realloc(wadfiles, sizeof(wadfile_t *) * (numwadfiles + 1), PU_STATIC, NULL);
 	wadfiles[numwadfiles] = wadfile;
 	numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded
 
@@ -1035,7 +1035,7 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
 		return W_InitFileError(path, startup);
 	}
 
-	important = 0; /// \todo Implement a W_VerifyFolder.
+	important = 1; /// \todo Implement a W_VerifyFolder.
 
 	// Remove path delimiters.
 	p = path + (strlen(path) - 1);
@@ -1150,6 +1150,7 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
 	Z_Calloc(numlumps * sizeof (*wadfile->patchcache), PU_STATIC, &wadfile->patchcache);
 
 	CONS_Printf(M_GetText("Added folder %s (%u files, %u folders)\n"), fn, numlumps, foldercount);
+	wadfiles = Z_Realloc(wadfiles, sizeof(wadfile_t *) * (numwadfiles + 1), PU_STATIC, NULL);
 	wadfiles[numwadfiles] = wadfile;
 	numwadfiles++;
 
@@ -1956,7 +1957,7 @@ void *W_CacheLumpNumForce(lumpnum_t lumpnum, INT32 tag)
 // return false.
 //
 // no outside code uses the PWAD form, for now
-static inline boolean W_IsLumpCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
+static boolean W_IsLumpCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
 {
 	void *lcache;
 
@@ -1988,7 +1989,7 @@ boolean W_IsLumpCached(lumpnum_t lumpnum, void *ptr)
 // return false.
 //
 // no outside code uses the PWAD form, for now
-static inline boolean W_IsPatchCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
+static boolean W_IsPatchCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
 {
 	void *lcache;
 
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index 07898dbc139d69535ec4e489122a4c3efec78f9d..b699007463ad3f37527367b40e4a1c29411012ab 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,10,0
- PRODUCTVERSION 2,2,10,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 7b4b6f5cfdfef471888840e78420b3f5ba99935a..8bec2b30f4e9314eb529defe4578daac5d0b697b 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -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++;
 
@@ -1092,12 +1091,14 @@ void Y_Ticker(void)
 			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
-			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
+			if (!demoplayback)
 			{
-				if (M_UpdateUnlockablesAndExtraEmblems())
+				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					S_StartSound(NULL, sfx_s3k68);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 			}
 		}
 		else if (!(intertic & 1))
@@ -1133,7 +1134,12 @@ void Y_Ticker(void)
 		}
 
 		// emerald bounce
-		if (intertic <= 1)
+		if (dedicated || !LUA_HudEnabled(hud_intermissionemeralds))
+		{
+			// dedicated servers don't need this, especially since it crashes when stagefailed
+			// also skip this if Lua disabled intermission emeralds, so it doesn't play sounds
+		}
+		else if (intertic <= 1)
 		{
 			data.spec.emeraldbounces = 0;
 			data.spec.emeraldmomy = 20;
@@ -1223,12 +1229,14 @@ void Y_Ticker(void)
 			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
-			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
+			if (!demoplayback)
 			{
-				if (M_UpdateUnlockablesAndExtraEmblems())
+				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					S_StartSound(NULL, sfx_s3k68);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 			}
 		}
 		else if (!(intertic & 1))
@@ -1468,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
@@ -2035,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));
@@ -2056,6 +2064,9 @@ static void Y_AwardCoopBonuses(void)
 			players[i].score += localbonuses[j].points;
 			if (players[i].score > MAXSCORE)
 				players[i].score = MAXSCORE;
+			players[i].recordscore += localbonuses[j].points;
+			if (players[i].recordscore > MAXSCORE)
+				players[i].recordscore = MAXSCORE;
 		}
 
 		ptlives = min(
@@ -2112,6 +2123,10 @@ static void Y_AwardSpecialStageBonus(void)
 		players[i].score += localbonuses[1].points;
 		if (players[i].score > MAXSCORE)
 			players[i].score = MAXSCORE;
+		players[i].recordscore += localbonuses[0].points;
+		players[i].recordscore += localbonuses[1].points;
+		if (players[i].recordscore > MAXSCORE)
+			players[i].recordscore = MAXSCORE;
 
 		// grant extra lives right away since tally is faked
 		ptlives = min(
diff --git a/src/z_zone.c b/src/z_zone.c
index c012816ffd38df0b37a8dacecaca76f9216add52..5750f8ae016becd9c9548b690b0e7fd5b32ec7cb 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -51,27 +51,11 @@ static boolean Z_calloc = false;
 //#define ZDEBUG2
 #endif
 
-struct memblock_s;
-
-typedef struct
-{
-	struct memblock_s *block; // Describing this memory
-	UINT32 id; // Should be ZONEID
-} ATTRPACK memhdr_t;
-
-// Some code might want aligned memory. Assume it wants memory n bytes
-// aligned -- then we allocate n-1 extra bytes and return a pointer to
-// the first byte aligned as requested.
-// Thus, "real" is the pointer we get from malloc() and will free()
-// later, but "hdr" is where the memhdr_t starts.
-// For non-aligned allocations they will be the same.
 typedef struct memblock_s
 {
-	void *real;
-	memhdr_t *hdr;
-
 	void **user;
 	INT32 tag; // purgelevel
+	UINT32 id; // Should be ZONEID
 
 	size_t size; // including the header and blocks
 	size_t realsize; // size of real data only
@@ -82,7 +66,10 @@ typedef struct memblock_s
 #endif
 
 	struct memblock_s *next, *prev;
-} ATTRPACK memblock_t;
+} memblock_t;
+
+#define MEMORY(x) (void *)((uintptr_t)(x) + sizeof(memblock_t))
+#define MEMBLOCK(x) (memblock_t *)((uintptr_t)(x) - sizeof(memblock_t))
 
 // both the head and tail of the zone memory block list
 static memblock_t head;
@@ -106,14 +93,14 @@ static void Command_Memdump_f(void);
   */
 void Z_Init(void)
 {
-	UINT32 total, memfree;
+	size_t total, memfree;
 
 	memset(&head, 0x00, sizeof(head));
 
 	head.next = head.prev = &head;
 
 	memfree = I_GetFreeMem(&total)>>20;
-	CONS_Printf("System memory: %uMB - Free: %uMB\n", total>>20, memfree);
+	CONS_Printf("System memory: %sMB - Free: %sMB\n", sizeu1(total>>20), sizeu2(memfree));
 
 	// Note: This allocates memory. Watch out.
 	COM_AddCommand("memfree", Command_Memfree_f, COM_LUA);
@@ -128,64 +115,6 @@ void Z_Init(void)
 // Zone memory allocation
 // ----------------------
 
-/** Returns the corresponding memblock_t for a given memory block.
-  *
-  * \param ptr A pointer to allocated memory,
-  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
-  * \param func A string containing the name of the function that called this,
-  *              to be printed if the function I_Errors
-  * \return A pointer to the memblock_t for the given memory.
-  * \sa Z_Free, Z_ReallocAlign
-  */
-#ifdef ZDEBUG
-#define Ptr2Memblock(s, f) Ptr2Memblock2(s, f, __FILE__, __LINE__)
-static memblock_t *Ptr2Memblock2(void *ptr, const char* func, const char *file, INT32 line)
-#else
-static memblock_t *Ptr2Memblock(void *ptr, const char* func)
-#endif
-{
-	memhdr_t *hdr;
-	memblock_t *block;
-
-	if (ptr == NULL)
-		return NULL;
-
-#ifdef ZDEBUG2
-	CONS_Printf("%s %s:%d\n", func, file, line);
-#endif
-
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
-
-#ifdef VALGRIND_MEMPOOL_EXISTS
-	if (!VALGRIND_MEMPOOL_EXISTS(hdr->block))
-	{
-#ifdef ZDEBUG
-		I_Error("%s: bad memblock from %s:%d", func, file, line);
-#else
-		I_Error("%s: bad memblock", func);
-#endif
-	}
-#endif
-	if (hdr->id != ZONEID)
-	{
-#ifdef ZDEBUG
-		I_Error("%s: wrong id from %s:%d", func, file, line);
-#else
-		I_Error("%s: wrong id", func);
-#endif
-	}
-	block = hdr->block;
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
-	return block;
-
-}
-
 /** Frees allocated memory.
   *
   * \param ptr A pointer to allocated memory,
@@ -207,10 +136,14 @@ void Z_Free(void *ptr)
 	CONS_Debug(DBG_MEMORY, "Z_Free %s:%d\n", file, line);
 #endif
 
+	block = MEMBLOCK(ptr);
+#ifdef PARANOIA
+	if (block->id != ZONEID)
 #ifdef ZDEBUG
-	block = Ptr2Memblock2(ptr, "Z_Free", file, line);
+		I_Error("Z_Free at %s:%d: wrong id", file, line);
 #else
-	block = Ptr2Memblock(ptr, "Z_Free");
+		I_Error("Z_Free: wrong id");
+#endif
 #endif
 
 #ifdef ZDEBUG
@@ -229,8 +162,6 @@ void Z_Free(void *ptr)
 	if (block->user != NULL)
 		*block->user = NULL;
 
-	// Free the memory and get rid of the block.
-	free(block->real);
 #ifdef VALGRIND_DESTROY_MEMPOOL
 	VALGRIND_DESTROY_MEMPOOL(block);
 #endif
@@ -287,34 +218,17 @@ void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits,
 void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 {
-	size_t extrabytes = (1<<alignbits) - 1;
-	size_t padsize = 0;
 	memblock_t *block;
 	void *ptr;
-	memhdr_t *hdr;
-	void *given;
-	size_t blocksize = extrabytes + sizeof *hdr + size;
+	(void)(alignbits); // no longer used, so silence warnings.
 
 #ifdef ZDEBUG2
 	CONS_Debug(DBG_MEMORY, "Z_Malloc %s:%d\n", file, line);
 #endif
 
-	if (blocksize < size)/* overflow check */
-		I_Error("You are allocating memory too large!");
-
-	block = xm(sizeof *block);
-#ifdef HAVE_VALGRIND
-	padsize += (1<<sizeof(size_t))*2;
-#endif
-	ptr = xm(blocksize + padsize*2);
-
-	// This horrible calculation makes sure that "given" is aligned
-	// properly.
-	given = (void *)((size_t)((UINT8 *)ptr + extrabytes + sizeof *hdr + padsize/2)
-		& ~extrabytes);
-
-	// The mem header lives 'sizeof (memhdr_t)' bytes before given.
-	hdr = (memhdr_t *)((UINT8 *)given - sizeof *hdr);
+	block = xm(sizeof (memblock_t) + size);
+	ptr = MEMORY(block);
+	I_Assert((intptr_t)ptr % sizeof (void *) == 0);
 
 #ifdef HAVE_VALGRIND
 	Z_calloc = false;
@@ -325,41 +239,31 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	head.next = block;
 	block->next->prev = block;
 
-	block->real = ptr;
-	block->hdr = hdr;
 	block->tag = tag;
 	block->user = NULL;
 #ifdef ZDEBUG
 	block->ownerline = line;
 	block->ownerfile = file;
 #endif
-	block->size = blocksize;
+	block->size = sizeof (memblock_t) + size;
 	block->realsize = size;
 
 #ifdef VALGRIND_CREATE_MEMPOOL
-	VALGRIND_CREATE_MEMPOOL(block, padsize, Z_calloc);
+	VALGRIND_CREATE_MEMPOOL(block, size, Z_calloc);
 #endif
-//#ifdef VALGRIND_MEMPOOL_ALLOC
-//	VALGRIND_MEMPOOL_ALLOC(block, hdr, size + sizeof *hdr);
-//#endif
 
-	hdr->id = ZONEID;
-	hdr->block = block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
+	block->id = ZONEID;
 
 	if (user != NULL)
 	{
 		block->user = user;
-		*(void **)user = given;
+		*(void **)user = ptr;
 	}
 	else if (tag >= PU_PURGELEVEL)
 		I_Error("Z_Malloc: attempted to allocate purgable block "
 			"(size %s) with no user", sizeu1(size));
 
-	return given;
+	return ptr;
 }
 
 /** The Z_CallocAlign function.
@@ -436,10 +340,14 @@ void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignb
 #endif
 	}
 
+	block = MEMBLOCK(ptr);
+#ifdef PARANOIA
+	if (block->id != ZONEID)
 #ifdef ZDEBUG
-	block = Ptr2Memblock2(ptr, "Z_Realloc", file, line);
+		I_Error("Z_ReallocAlign at %s:%d: wrong id", file, line);
 #else
-	block = Ptr2Memblock(ptr, "Z_Realloc");
+		I_Error("Z_ReallocAlign: wrong id");
+#endif
 #endif
 
 	if (block == NULL)
@@ -490,9 +398,8 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	for (block = head.next; block != &head; block = next)
 	{
 		next = block->next; // get link before freeing
-
 		if (block->tag >= lowtag && block->tag <= hightag)
-			Z_Free((UINT8 *)block->hdr + sizeof *block->hdr);
+			Z_Free(MEMORY(block));
 	}
 }
 
@@ -515,7 +422,7 @@ void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *))
 
 		if (block->tag >= lowtag && block->tag <= hightag)
 		{
-			void *mem = (UINT8 *)block->hdr + sizeof *block->hdr;
+			void *mem = MEMORY(block);
 			boolean free = iterfunc(mem);
 			if (free)
 				Z_Free(mem);
@@ -560,15 +467,13 @@ void Z_CheckMemCleanup(void)
 void Z_CheckHeap(INT32 i)
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 	UINT32 blocknumon = 0;
 	void *given;
 
 	for (block = head.next; block != &head; block = block->next)
 	{
 		blocknumon++;
-		hdr = block->hdr;
-		given = (UINT8 *)hdr + sizeof *hdr;
+		given = MEMORY(block);
 #ifdef ZDEBUG2
 		CONS_Debug(DBG_MEMORY, "block %u owned by %s:%d\n",
 			blocknumon, block->ownerfile, block->ownerline);
@@ -584,7 +489,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			        );
+				);
 		}
 #endif
 		if (block->user != NULL && *(block->user) != given)
@@ -597,7 +502,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
 		if (block->next->prev != block)
 		{
@@ -609,7 +514,7 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
 		if (block->prev->next != block)
 		{
@@ -621,25 +526,9 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-			       );
+				);
 		}
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-		VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
-		if (hdr->block != block)
-		{
-			I_Error("Z_CheckHeap %d: block %u"
-#ifdef ZDEBUG
-				"(owned by %s:%d)"
-#endif
-				" doesn't have linkback from allocated memory",
-				i, blocknumon
-#ifdef ZDEBUG
-				, block->ownerfile, block->ownerline
-#endif
-					);
-		}
-		if (hdr->id != ZONEID)
+		if (block->id != ZONEID)
 		{
 			I_Error("Z_CheckHeap %d: block %u"
 #ifdef ZDEBUG
@@ -649,11 +538,8 @@ void Z_CheckHeap(INT32 i)
 #ifdef ZDEBUG
 				, block->ownerfile, block->ownerline
 #endif
-					);
+				);
 		}
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
 	}
 }
 
@@ -675,35 +561,14 @@ void Z_ChangeTag(void *ptr, INT32 tag)
 #endif
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 
 	if (ptr == NULL)
 		return;
 
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
+	block = MEMBLOCK(ptr);
 
-#ifdef VALGRIND_MEMPOOL_EXISTS
-	if (!VALGRIND_MEMPOOL_EXISTS(hdr->block))
-	{
-#ifdef PARANOIA
-		I_Error("Z_CT at %s:%d: bad memblock", file, line);
-#else
-		I_Error("Z_CT: bad memblock");
-#endif
-	}
-#endif
 #ifdef PARANOIA
-	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
-#endif
-
-	block = hdr->block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
+	if (block->id != ZONEID) I_Error("Z_ChangeTag at %s:%d: wrong id", file, line);
 #endif
 
 	if (tag >= PU_PURGELEVEL && block->user == NULL)
@@ -727,25 +592,14 @@ void Z_SetUser(void *ptr, void **newuser)
 #endif
 {
 	memblock_t *block;
-	memhdr_t *hdr;
 
 	if (ptr == NULL)
 		return;
 
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
+	block = MEMBLOCK(ptr);
 
 #ifdef PARANOIA
-	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
-#endif
-
-	block = hdr->block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
+	if (block->id != ZONEID) I_Error("Z_SetUser at %s:%d: wrong id", file, line);
 #endif
 
 	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
@@ -791,7 +645,7 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag)
   */
 static void Command_Memfree_f(void)
 {
-	UINT32 freebytes, totalbytes;
+	size_t freebytes, totalbytes;
 
 	Z_CheckHeap(-1);
 	CONS_Printf("\x82%s", M_GetText("Memory Info\n"));
@@ -824,8 +678,8 @@ static void Command_Memfree_f(void)
 
 	CONS_Printf("\x82%s", M_GetText("System Memory Info\n"));
 	freebytes = I_GetFreeMem(&totalbytes);
-	CONS_Printf(M_GetText("    Total physical memory: %7u KB\n"), totalbytes>>10);
-	CONS_Printf(M_GetText("Available physical memory: %7u KB\n"), freebytes>>10);
+	CONS_Printf(M_GetText("    Total physical memory: %s KB\n"), sizeu1(totalbytes>>10));
+	CONS_Printf(M_GetText("Available physical memory: %s KB\n"), sizeu1(freebytes>>10));
 }
 
 #ifdef ZDEBUG
diff --git a/src/z_zone.h b/src/z_zone.h
index c3cd4f01157b3871c6727c2a223a97d49a34bfdd..ce7af4a159555e3a6c2be83e8d0eadcf8a7a69eb 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -15,6 +15,7 @@
 #define __Z_ZONE__
 
 #include <stdio.h>
+#include "doomdef.h"
 #include "doomtype.h"
 
 #ifdef __GNUC__ // __attribute__ ((X))
@@ -101,10 +102,10 @@ void *Z_CallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALL
 void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(2);
 #endif
 
-// Alloc with no alignment
-#define Z_Malloc(s,t,u)    Z_MallocAlign(s, t, u, 0)
-#define Z_Calloc(s,t,u)    Z_CallocAlign(s, t, u, 0)
-#define Z_Realloc(p,s,t,u) Z_ReallocAlign(p, s, t, u, 0)
+// Alloc with standard alignment
+#define Z_Malloc(s,t,u)    Z_MallocAlign(s, t, u, sizeof(void *))
+#define Z_Calloc(s,t,u)    Z_CallocAlign(s, t, u, sizeof(void *))
+#define Z_Realloc(p,s,t,u) Z_ReallocAlign(p, s, t, u, sizeof(void *))
 
 // Free all memory by tag
 // these don't give line numbers for ZDEBUG currently though
diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt
index 7aff16601efd49d71693a137a4aab7b98721e7fa..f33b3bf3f836b86b98fda8e78f73ec5be19758d8 100644
--- a/thirdparty/CMakeLists.txt
+++ b/thirdparty/CMakeLists.txt
@@ -9,521 +9,13 @@ else()
 	set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON)
 endif()
 
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME SDL2
-		VERSION 2.24.2
-		URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL_TEST OFF"
-			"SDL2_DISABLE_SDL2MAIN ON"
-			"SDL2_DISABLE_INSTALL ON"
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME SDL2_mixer
-		VERSION 2.6.2
-		URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"SDL2MIXER_INSTALL OFF"
-			"SDL2MIXER_DEPS_SHARED OFF"
-			"SDL2MIXER_SAMPLES OFF"
-			"SDL2MIXER_VENDORED ON"
-			"SDL2MIXER_FLAC ON"
-			"SDL2MIXER_FLAC_LIBFLAC OFF"
-			"SDL2MIXER_FLAC_DRFLAC ON"
-			"SDL2MIXER_MOD OFF"
-			"SDL2MIXER_MP3 ON"
-			"SDL2MIXER_MP3_DRMP3 ON"
-			"SDL2MIXER_MIDI ON"
-			"SDL2MIXER_OPUS OFF"
-			"SDL2MIXER_VORBIS STB"
-			"SDL2MIXER_WAVE ON"
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME ZLIB
-		VERSION 1.2.13
-		URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
-		EXCLUDE_FROM_ALL
-		DOWNLOAD_ONLY YES
-	)
-	if(ZLIB_ADDED)
-		set(ZLIB_SRCS
-			crc32.h
-			deflate.h
-			gzguts.h
-			inffast.h
-			inffixed.h
-			inflate.h
-			inftrees.h
-			trees.h
-			zutil.h
-
-			adler32.c
-			compress.c
-			crc32.c
-			deflate.c
-			gzclose.c
-			gzlib.c
-			gzread.c
-			gzwrite.c
-			inflate.c
-			infback.c
-			inftrees.c
-			inffast.c
-			trees.c
-			uncompr.c
-			zutil.c
-		)
-		list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
-
-		configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
-		configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
-		configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
-
-		add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
-		set_target_properties(ZLIB PROPERTIES
-			VERSION 1.2.13
-			OUTPUT_NAME "z"
-		)
-		target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
-		target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
-		if(MSVC)
-			target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
-		endif()
-		add_library(ZLIB::ZLIB ALIAS ZLIB)
-	endif()
-endif()
-
 if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME png
-		VERSION 1.6.38
-		URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
-		# png cmake build is broken on msys/mingw32
-		DOWNLOAD_ONLY YES
-	)
-
-	if(png_ADDED)
-		# Since png's cmake build is broken, we're going to create a target manually
-		set(
-			PNG_SOURCES
-
-			png.h
-			pngconf.h
-
-			pngpriv.h
-			pngdebug.h
-			pnginfo.h
-			pngstruct.h
-
-			png.c
-			pngerror.c
-			pngget.c
-			pngmem.c
-			pngpread.c
-			pngread.c
-			pngrio.c
-			pngrtran.c
-			pngrutil.c
-			pngset.c
-			pngtrans.c
-			pngwio.c
-			pngwrite.c
-			pngwtran.c
-			pngwutil.c
-		)
-		list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
-
-		add_custom_command(
-			OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
-			COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
-			DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
-			VERBATIM
-		)
-		add_custom_command(
-			OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
-			COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
-			DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
-			VERBATIM
-		)
-		list(
-			APPEND PNG_SOURCES
-			"${png_BINARY_DIR}/include/png.h"
-			"${png_BINARY_DIR}/include/pngconf.h"
-			"${png_BINARY_DIR}/include/pnglibconf.h"
-		)
-		add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
-
-		# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
-		target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
-
-		# The png includes need to be available to consumers
-		target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
-
-		# ... and these also need to be present only for png build
-		target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
-		target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
-		target_include_directories(png PRIVATE "${png_BINARY_DIR}")
-
-		target_link_libraries(png PRIVATE ZLIB::ZLIB)
-		add_library(PNG::PNG ALIAS png)
-	endif()
+	include("cpm-sdl2.cmake")
+	include("cpm-sdl2-mixer.cmake")
+	include("cpm-zlib.cmake")
+	include("cpm-png.cmake")
+	include("cpm-curl.cmake")
+	include("cpm-openmpt.cmake")
 endif()
 
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	set(
-		internal_curl_options
-
-		"BUILD_CURL_EXE OFF"
-		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-		"CURL_DISABLE_TESTS ON"
-		"HTTP_ONLY ON"
-		"CURL_DISABLE_CRYPTO_AUTH ON"
-		"CURL_DISABLE_NTLM ON"
-		"ENABLE_MANUAL OFF"
-		"ENABLE_THREADED_RESOLVER OFF"
-		"CURL_USE_LIBPSL OFF"
-		"CURL_USE_LIBSSH2 OFF"
-		"USE_LIBIDN2 OFF"
-		"CURL_ENABLE_EXPORT_TARGET OFF"
-	)
-	if(${CMAKE_SYSTEM} MATCHES Windows)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
-		list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
-	endif()
-	if(${CMAKE_SYSTEM} MATCHES Darwin)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
-		list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
-	endif()
-	if(${CMAKE_SYSTEM} MATCHES Linux)
-		list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
-	endif()
-
-	CPMAddPackage(
-		NAME curl
-		VERSION 7.86.0
-		URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS ${internal_curl_options}
-	)
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME openmpt
-		VERSION 0.4.30
-		URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip"
-		DOWNLOAD_ONLY ON
-	)
-
-	if(openmpt_ADDED)
-		set(
-			openmpt_SOURCES
-
-			# minimp3
-			# -DMPT_WITH_MINIMP3
-			include/minimp3/minimp3.c
-
-			common/mptStringParse.cpp
-			common/mptLibrary.cpp
-			common/Logging.cpp
-			common/Profiler.cpp
-			common/version.cpp
-			common/mptCPU.cpp
-			common/ComponentManager.cpp
-			common/mptOS.cpp
-			common/serialization_utils.cpp
-			common/mptStringFormat.cpp
-			common/FileReader.cpp
-			common/mptWine.cpp
-			common/mptPathString.cpp
-			common/mptAlloc.cpp
-			common/mptUUID.cpp
-			common/mptTime.cpp
-			common/mptString.cpp
-			common/mptFileIO.cpp
-			common/mptStringBuffer.cpp
-			common/mptRandom.cpp
-			common/mptIO.cpp
-			common/misc_util.cpp
-
-			common/mptCRC.h
-			common/mptLibrary.h
-			common/mptIO.h
-			common/version.h
-			common/stdafx.h
-			common/ComponentManager.h
-			common/Endianness.h
-			common/mptStringFormat.h
-			common/mptMutex.h
-			common/mptUUID.h
-			common/mptExceptionText.h
-			common/BuildSettings.h
-			common/mptAlloc.h
-			common/mptTime.h
-			common/FileReaderFwd.h
-			common/Logging.h
-			common/mptException.h
-			common/mptWine.h
-			common/mptStringBuffer.h
-			common/misc_util.h
-			common/mptBaseMacros.h
-			common/mptMemory.h
-			common/mptFileIO.h
-			common/serialization_utils.h
-			common/mptSpan.h
-			common/mptThread.h
-			common/FlagSet.h
-			common/mptString.h
-			common/mptStringParse.h
-			common/mptBaseUtils.h
-			common/mptRandom.h
-			common/CompilerDetect.h
-			common/FileReader.h
-			common/mptAssert.h
-			common/mptPathString.h
-			common/Profiler.h
-			common/mptOS.h
-			common/mptBaseTypes.h
-			common/mptCPU.h
-			common/mptBufferIO.h
-			common/versionNumber.h
-
-			soundlib/WAVTools.cpp
-			soundlib/ITTools.cpp
-			soundlib/AudioCriticalSection.cpp
-			soundlib/Load_stm.cpp
-			soundlib/MixerLoops.cpp
-			soundlib/Load_dbm.cpp
-			soundlib/ModChannel.cpp
-			soundlib/Load_gdm.cpp
-			soundlib/Snd_fx.cpp
-			soundlib/Load_mid.cpp
-			soundlib/mod_specifications.cpp
-			soundlib/Snd_flt.cpp
-			soundlib/Load_psm.cpp
-			soundlib/Load_far.cpp
-			soundlib/patternContainer.cpp
-			soundlib/Load_med.cpp
-			soundlib/Load_dmf.cpp
-			soundlib/Paula.cpp
-			soundlib/modcommand.cpp
-			soundlib/Message.cpp
-			soundlib/SoundFilePlayConfig.cpp
-			soundlib/Load_uax.cpp
-			soundlib/plugins/PlugInterface.cpp
-			soundlib/plugins/LFOPlugin.cpp
-			soundlib/plugins/PluginManager.cpp
-			soundlib/plugins/DigiBoosterEcho.cpp
-			soundlib/plugins/dmo/DMOPlugin.cpp
-			soundlib/plugins/dmo/Flanger.cpp
-			soundlib/plugins/dmo/Distortion.cpp
-			soundlib/plugins/dmo/ParamEq.cpp
-			soundlib/plugins/dmo/Gargle.cpp
-			soundlib/plugins/dmo/I3DL2Reverb.cpp
-			soundlib/plugins/dmo/Compressor.cpp
-			soundlib/plugins/dmo/WavesReverb.cpp
-			soundlib/plugins/dmo/Echo.cpp
-			soundlib/plugins/dmo/Chorus.cpp
-			soundlib/Load_ams.cpp
-			soundlib/tuningbase.cpp
-			soundlib/ContainerUMX.cpp
-			soundlib/Load_ptm.cpp
-			soundlib/ContainerXPK.cpp
-			soundlib/SampleFormatMP3.cpp
-			soundlib/tuning.cpp
-			soundlib/Sndfile.cpp
-			soundlib/ContainerMMCMP.cpp
-			soundlib/Load_amf.cpp
-			soundlib/Load_669.cpp
-			soundlib/modsmp_ctrl.cpp
-			soundlib/Load_mtm.cpp
-			soundlib/OggStream.cpp
-			soundlib/Load_plm.cpp
-			soundlib/Tables.cpp
-			soundlib/Load_c67.cpp
-			soundlib/Load_mod.cpp
-			soundlib/Load_sfx.cpp
-			soundlib/Sndmix.cpp
-			soundlib/load_j2b.cpp
-			soundlib/ModSequence.cpp
-			soundlib/SampleFormatFLAC.cpp
-			soundlib/ModInstrument.cpp
-			soundlib/Load_mo3.cpp
-			soundlib/ModSample.cpp
-			soundlib/Dlsbank.cpp
-			soundlib/Load_itp.cpp
-			soundlib/UpgradeModule.cpp
-			soundlib/MIDIMacros.cpp
-			soundlib/ContainerPP20.cpp
-			soundlib/RowVisitor.cpp
-			soundlib/Load_imf.cpp
-			soundlib/SampleFormatVorbis.cpp
-			soundlib/Load_dsm.cpp
-			soundlib/Load_mt2.cpp
-			soundlib/MixerSettings.cpp
-			soundlib/S3MTools.cpp
-			soundlib/Load_xm.cpp
-			soundlib/MIDIEvents.cpp
-			soundlib/pattern.cpp
-			soundlib/Load_digi.cpp
-			soundlib/Load_s3m.cpp
-			soundlib/tuningCollection.cpp
-			soundlib/SampleIO.cpp
-			soundlib/Dither.cpp
-			soundlib/Load_mdl.cpp
-			soundlib/OPL.cpp
-			soundlib/WindowedFIR.cpp
-			soundlib/SampleFormats.cpp
-			soundlib/Load_wav.cpp
-			soundlib/Load_it.cpp
-			soundlib/UMXTools.cpp
-			soundlib/Load_stp.cpp
-			soundlib/Load_okt.cpp
-			soundlib/Load_ult.cpp
-			soundlib/MixFuncTable.cpp
-			soundlib/SampleFormatOpus.cpp
-			soundlib/Fastmix.cpp
-			soundlib/Tagging.cpp
-			soundlib/ITCompression.cpp
-			soundlib/Load_dtm.cpp
-			soundlib/MPEGFrame.cpp
-			soundlib/XMTools.cpp
-			soundlib/SampleFormatMediaFoundation.cpp
-			soundlib/InstrumentExtensions.cpp
-
-			soundlib/MixerInterface.h
-			soundlib/SoundFilePlayConfig.h
-			soundlib/ModSample.h
-			soundlib/MIDIEvents.h
-			soundlib/ModSampleCopy.h
-			soundlib/patternContainer.h
-			soundlib/ChunkReader.h
-			soundlib/ITCompression.h
-			soundlib/Dither.h
-			soundlib/S3MTools.h
-			soundlib/MPEGFrame.h
-			soundlib/WAVTools.h
-			soundlib/mod_specifications.h
-			soundlib/ITTools.h
-			soundlib/RowVisitor.h
-			soundlib/plugins/PluginMixBuffer.h
-			soundlib/plugins/PluginStructs.h
-			soundlib/plugins/LFOPlugin.h
-			soundlib/plugins/PlugInterface.h
-			soundlib/plugins/DigiBoosterEcho.h
-			soundlib/plugins/OpCodes.h
-			soundlib/plugins/dmo/Echo.h
-			soundlib/plugins/dmo/I3DL2Reverb.h
-			soundlib/plugins/dmo/WavesReverb.h
-			soundlib/plugins/dmo/ParamEq.h
-			soundlib/plugins/dmo/Gargle.h
-			soundlib/plugins/dmo/DMOPlugin.h
-			soundlib/plugins/dmo/Chorus.h
-			soundlib/plugins/dmo/Compressor.h
-			soundlib/plugins/dmo/Distortion.h
-			soundlib/plugins/dmo/Flanger.h
-			soundlib/plugins/PluginManager.h
-			soundlib/SampleIO.h
-			soundlib/Container.h
-			soundlib/ModSequence.h
-			soundlib/UMXTools.h
-			soundlib/Message.h
-			soundlib/modcommand.h
-			soundlib/XMTools.h
-			soundlib/Snd_defs.h
-			soundlib/MixFuncTable.h
-			soundlib/pattern.h
-			soundlib/modsmp_ctrl.h
-			soundlib/Tagging.h
-			soundlib/tuningcollection.h
-			soundlib/Mixer.h
-			soundlib/FloatMixer.h
-			soundlib/AudioCriticalSection.h
-			soundlib/Tables.h
-			soundlib/tuningbase.h
-			soundlib/WindowedFIR.h
-			soundlib/Sndfile.h
-			soundlib/Paula.h
-			soundlib/ModInstrument.h
-			soundlib/Dlsbank.h
-			soundlib/IntMixer.h
-			soundlib/OPL.h
-			soundlib/Resampler.h
-			soundlib/ModChannel.h
-			soundlib/MixerSettings.h
-			soundlib/AudioReadTarget.h
-			soundlib/MixerLoops.h
-			soundlib/tuning.h
-			soundlib/MIDIMacros.h
-			soundlib/OggStream.h
-			soundlib/Loaders.h
-			soundlib/BitReader.h
-			soundlib/opal.h
-
-			sounddsp/AGC.cpp
-			sounddsp/EQ.cpp
-			sounddsp/DSP.cpp
-			sounddsp/Reverb.cpp
-			sounddsp/Reverb.h
-			sounddsp/EQ.h
-			sounddsp/DSP.h
-			sounddsp/AGC.h
-
-			libopenmpt/libopenmpt_c.cpp
-			libopenmpt/libopenmpt_cxx.cpp
-			libopenmpt/libopenmpt_impl.cpp
-			libopenmpt/libopenmpt_ext_impl.cpp
-		)
-		list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/")
-
-		# -DLIBOPENMPT_BUILD
-		configure_file("openmpt_svn_version.h" "svn_version.h")
-		add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h)
-		if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang)
-			target_compile_options(openmpt PRIVATE "-g0")
-		endif()
-		if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC)
-			target_link_libraries(openmpt PRIVATE Rpcrt4)
-		endif()
-		target_compile_features(openmpt PRIVATE cxx_std_11)
-		target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD)
-
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include")
-		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}")
-		target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
-
-		# I wish this wasn't necessary, but it is
-		target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}")
-	endif()
-endif()
-
-if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}")
-	CPMAddPackage(
-		NAME libgme
-		VERSION 0.6.3
-		URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
-		EXCLUDE_FROM_ALL ON
-		OPTIONS
-			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
-			"ENABLE_UBSAN OFF"
-			"GME_YM2612_EMU MAME"
-	)
-	target_compile_features(gme PRIVATE cxx_std_11)
-	target_link_libraries(gme PRIVATE ZLIB::ZLIB)
-endif()
+include("cpm-libgme.cmake")
diff --git a/thirdparty/cpm-curl.cmake b/thirdparty/cpm-curl.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..3d8c6e61d46dee6f06f4e6d69beb09d1605f6584
--- /dev/null
+++ b/thirdparty/cpm-curl.cmake
@@ -0,0 +1,35 @@
+set(
+	internal_curl_options
+
+	"BUILD_CURL_EXE OFF"
+	"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+	"CURL_DISABLE_TESTS ON"
+	"HTTP_ONLY ON"
+	"CURL_DISABLE_CRYPTO_AUTH ON"
+	"CURL_DISABLE_NTLM ON"
+	"ENABLE_MANUAL OFF"
+	"ENABLE_THREADED_RESOLVER OFF"
+	"CURL_USE_LIBPSL OFF"
+	"CURL_USE_LIBSSH2 OFF"
+	"USE_LIBIDN2 OFF"
+	"CURL_ENABLE_EXPORT_TARGET OFF"
+)
+if(${CMAKE_SYSTEM} MATCHES Windows)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
+	list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON")
+endif()
+if(${CMAKE_SYSTEM} MATCHES Darwin)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF")
+	list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON")
+endif()
+if(${CMAKE_SYSTEM} MATCHES Linux)
+	list(APPEND internal_curl_options "CURL_USE_OPENSSL ON")
+endif()
+
+CPMAddPackage(
+	NAME curl
+	VERSION 7.86.0
+	URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS ${internal_curl_options}
+)
diff --git a/thirdparty/cpm-libgme.cmake b/thirdparty/cpm-libgme.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f15bc3b31cb23ed7663ed950c14c1d6a0dc36567
--- /dev/null
+++ b/thirdparty/cpm-libgme.cmake
@@ -0,0 +1,16 @@
+CPMAddPackage(
+	NAME libgme
+	VERSION 0.6.3
+	URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"ENABLE_UBSAN OFF"
+		"GME_YM2612_EMU MAME"
+)
+
+if(libgme_ADDED)
+	target_compile_features(gme PRIVATE cxx_std_11)
+	# libgme's CMakeLists.txt already links this
+	#target_link_libraries(gme PRIVATE ZLIB::ZLIB)
+endif()
diff --git a/thirdparty/cpm-openmpt.cmake b/thirdparty/cpm-openmpt.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..01f7ff75f64d915799e432f2923a2c7e8c344466
--- /dev/null
+++ b/thirdparty/cpm-openmpt.cmake
@@ -0,0 +1,289 @@
+CPMAddPackage(
+	NAME openmpt
+	VERSION 0.4.30
+	URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip"
+	DOWNLOAD_ONLY ON
+)
+
+if(openmpt_ADDED)
+	set(
+		openmpt_SOURCES
+
+		# minimp3
+		# -DMPT_WITH_MINIMP3
+		include/minimp3/minimp3.c
+
+		common/mptStringParse.cpp
+		common/mptLibrary.cpp
+		common/Logging.cpp
+		common/Profiler.cpp
+		common/version.cpp
+		common/mptCPU.cpp
+		common/ComponentManager.cpp
+		common/mptOS.cpp
+		common/serialization_utils.cpp
+		common/mptStringFormat.cpp
+		common/FileReader.cpp
+		common/mptWine.cpp
+		common/mptPathString.cpp
+		common/mptAlloc.cpp
+		common/mptUUID.cpp
+		common/mptTime.cpp
+		common/mptString.cpp
+		common/mptFileIO.cpp
+		common/mptStringBuffer.cpp
+		common/mptRandom.cpp
+		common/mptIO.cpp
+		common/misc_util.cpp
+
+		common/mptCRC.h
+		common/mptLibrary.h
+		common/mptIO.h
+		common/version.h
+		common/stdafx.h
+		common/ComponentManager.h
+		common/Endianness.h
+		common/mptStringFormat.h
+		common/mptMutex.h
+		common/mptUUID.h
+		common/mptExceptionText.h
+		common/BuildSettings.h
+		common/mptAlloc.h
+		common/mptTime.h
+		common/FileReaderFwd.h
+		common/Logging.h
+		common/mptException.h
+		common/mptWine.h
+		common/mptStringBuffer.h
+		common/misc_util.h
+		common/mptBaseMacros.h
+		common/mptMemory.h
+		common/mptFileIO.h
+		common/serialization_utils.h
+		common/mptSpan.h
+		common/mptThread.h
+		common/FlagSet.h
+		common/mptString.h
+		common/mptStringParse.h
+		common/mptBaseUtils.h
+		common/mptRandom.h
+		common/CompilerDetect.h
+		common/FileReader.h
+		common/mptAssert.h
+		common/mptPathString.h
+		common/Profiler.h
+		common/mptOS.h
+		common/mptBaseTypes.h
+		common/mptCPU.h
+		common/mptBufferIO.h
+		common/versionNumber.h
+
+		soundlib/WAVTools.cpp
+		soundlib/ITTools.cpp
+		soundlib/AudioCriticalSection.cpp
+		soundlib/Load_stm.cpp
+		soundlib/MixerLoops.cpp
+		soundlib/Load_dbm.cpp
+		soundlib/ModChannel.cpp
+		soundlib/Load_gdm.cpp
+		soundlib/Snd_fx.cpp
+		soundlib/Load_mid.cpp
+		soundlib/mod_specifications.cpp
+		soundlib/Snd_flt.cpp
+		soundlib/Load_psm.cpp
+		soundlib/Load_far.cpp
+		soundlib/patternContainer.cpp
+		soundlib/Load_med.cpp
+		soundlib/Load_dmf.cpp
+		soundlib/Paula.cpp
+		soundlib/modcommand.cpp
+		soundlib/Message.cpp
+		soundlib/SoundFilePlayConfig.cpp
+		soundlib/Load_uax.cpp
+		soundlib/plugins/PlugInterface.cpp
+		soundlib/plugins/LFOPlugin.cpp
+		soundlib/plugins/PluginManager.cpp
+		soundlib/plugins/DigiBoosterEcho.cpp
+		soundlib/plugins/dmo/DMOPlugin.cpp
+		soundlib/plugins/dmo/Flanger.cpp
+		soundlib/plugins/dmo/Distortion.cpp
+		soundlib/plugins/dmo/ParamEq.cpp
+		soundlib/plugins/dmo/Gargle.cpp
+		soundlib/plugins/dmo/I3DL2Reverb.cpp
+		soundlib/plugins/dmo/Compressor.cpp
+		soundlib/plugins/dmo/WavesReverb.cpp
+		soundlib/plugins/dmo/Echo.cpp
+		soundlib/plugins/dmo/Chorus.cpp
+		soundlib/Load_ams.cpp
+		soundlib/tuningbase.cpp
+		soundlib/ContainerUMX.cpp
+		soundlib/Load_ptm.cpp
+		soundlib/ContainerXPK.cpp
+		soundlib/SampleFormatMP3.cpp
+		soundlib/tuning.cpp
+		soundlib/Sndfile.cpp
+		soundlib/ContainerMMCMP.cpp
+		soundlib/Load_amf.cpp
+		soundlib/Load_669.cpp
+		soundlib/modsmp_ctrl.cpp
+		soundlib/Load_mtm.cpp
+		soundlib/OggStream.cpp
+		soundlib/Load_plm.cpp
+		soundlib/Tables.cpp
+		soundlib/Load_c67.cpp
+		soundlib/Load_mod.cpp
+		soundlib/Load_sfx.cpp
+		soundlib/Sndmix.cpp
+		soundlib/load_j2b.cpp
+		soundlib/ModSequence.cpp
+		soundlib/SampleFormatFLAC.cpp
+		soundlib/ModInstrument.cpp
+		soundlib/Load_mo3.cpp
+		soundlib/ModSample.cpp
+		soundlib/Dlsbank.cpp
+		soundlib/Load_itp.cpp
+		soundlib/UpgradeModule.cpp
+		soundlib/MIDIMacros.cpp
+		soundlib/ContainerPP20.cpp
+		soundlib/RowVisitor.cpp
+		soundlib/Load_imf.cpp
+		soundlib/SampleFormatVorbis.cpp
+		soundlib/Load_dsm.cpp
+		soundlib/Load_mt2.cpp
+		soundlib/MixerSettings.cpp
+		soundlib/S3MTools.cpp
+		soundlib/Load_xm.cpp
+		soundlib/MIDIEvents.cpp
+		soundlib/pattern.cpp
+		soundlib/Load_digi.cpp
+		soundlib/Load_s3m.cpp
+		soundlib/tuningCollection.cpp
+		soundlib/SampleIO.cpp
+		soundlib/Dither.cpp
+		soundlib/Load_mdl.cpp
+		soundlib/OPL.cpp
+		soundlib/WindowedFIR.cpp
+		soundlib/SampleFormats.cpp
+		soundlib/Load_wav.cpp
+		soundlib/Load_it.cpp
+		soundlib/UMXTools.cpp
+		soundlib/Load_stp.cpp
+		soundlib/Load_okt.cpp
+		soundlib/Load_ult.cpp
+		soundlib/MixFuncTable.cpp
+		soundlib/SampleFormatOpus.cpp
+		soundlib/Fastmix.cpp
+		soundlib/Tagging.cpp
+		soundlib/ITCompression.cpp
+		soundlib/Load_dtm.cpp
+		soundlib/MPEGFrame.cpp
+		soundlib/XMTools.cpp
+		soundlib/SampleFormatMediaFoundation.cpp
+		soundlib/InstrumentExtensions.cpp
+
+		soundlib/MixerInterface.h
+		soundlib/SoundFilePlayConfig.h
+		soundlib/ModSample.h
+		soundlib/MIDIEvents.h
+		soundlib/ModSampleCopy.h
+		soundlib/patternContainer.h
+		soundlib/ChunkReader.h
+		soundlib/ITCompression.h
+		soundlib/Dither.h
+		soundlib/S3MTools.h
+		soundlib/MPEGFrame.h
+		soundlib/WAVTools.h
+		soundlib/mod_specifications.h
+		soundlib/ITTools.h
+		soundlib/RowVisitor.h
+		soundlib/plugins/PluginMixBuffer.h
+		soundlib/plugins/PluginStructs.h
+		soundlib/plugins/LFOPlugin.h
+		soundlib/plugins/PlugInterface.h
+		soundlib/plugins/DigiBoosterEcho.h
+		soundlib/plugins/OpCodes.h
+		soundlib/plugins/dmo/Echo.h
+		soundlib/plugins/dmo/I3DL2Reverb.h
+		soundlib/plugins/dmo/WavesReverb.h
+		soundlib/plugins/dmo/ParamEq.h
+		soundlib/plugins/dmo/Gargle.h
+		soundlib/plugins/dmo/DMOPlugin.h
+		soundlib/plugins/dmo/Chorus.h
+		soundlib/plugins/dmo/Compressor.h
+		soundlib/plugins/dmo/Distortion.h
+		soundlib/plugins/dmo/Flanger.h
+		soundlib/plugins/PluginManager.h
+		soundlib/SampleIO.h
+		soundlib/Container.h
+		soundlib/ModSequence.h
+		soundlib/UMXTools.h
+		soundlib/Message.h
+		soundlib/modcommand.h
+		soundlib/XMTools.h
+		soundlib/Snd_defs.h
+		soundlib/MixFuncTable.h
+		soundlib/pattern.h
+		soundlib/modsmp_ctrl.h
+		soundlib/Tagging.h
+		soundlib/tuningcollection.h
+		soundlib/Mixer.h
+		soundlib/FloatMixer.h
+		soundlib/AudioCriticalSection.h
+		soundlib/Tables.h
+		soundlib/tuningbase.h
+		soundlib/WindowedFIR.h
+		soundlib/Sndfile.h
+		soundlib/Paula.h
+		soundlib/ModInstrument.h
+		soundlib/Dlsbank.h
+		soundlib/IntMixer.h
+		soundlib/OPL.h
+		soundlib/Resampler.h
+		soundlib/ModChannel.h
+		soundlib/MixerSettings.h
+		soundlib/AudioReadTarget.h
+		soundlib/MixerLoops.h
+		soundlib/tuning.h
+		soundlib/MIDIMacros.h
+		soundlib/OggStream.h
+		soundlib/Loaders.h
+		soundlib/BitReader.h
+		soundlib/opal.h
+
+		sounddsp/AGC.cpp
+		sounddsp/EQ.cpp
+		sounddsp/DSP.cpp
+		sounddsp/Reverb.cpp
+		sounddsp/Reverb.h
+		sounddsp/EQ.h
+		sounddsp/DSP.h
+		sounddsp/AGC.h
+
+		libopenmpt/libopenmpt_c.cpp
+		libopenmpt/libopenmpt_cxx.cpp
+		libopenmpt/libopenmpt_impl.cpp
+		libopenmpt/libopenmpt_ext_impl.cpp
+	)
+	list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/")
+
+	# -DLIBOPENMPT_BUILD
+	configure_file("openmpt_svn_version.h" "svn_version.h")
+	add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h)
+	if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang)
+		target_compile_options(openmpt PRIVATE "-g0")
+	endif()
+	if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC)
+		target_link_libraries(openmpt PRIVATE Rpcrt4)
+	endif()
+	target_compile_features(openmpt PRIVATE cxx_std_11)
+	target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD)
+
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include")
+	target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}")
+	target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
+
+	# I wish this wasn't necessary, but it is
+	target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}")
+endif()
diff --git a/thirdparty/cpm-png.cmake b/thirdparty/cpm-png.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f16ac037b0cc9c077f3ef315b67b430e68cf9b40
--- /dev/null
+++ b/thirdparty/cpm-png.cmake
@@ -0,0 +1,69 @@
+CPMAddPackage(
+	NAME png
+	VERSION 1.6.38
+	URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip"
+	# png cmake build is broken on msys/mingw32
+	DOWNLOAD_ONLY YES
+)
+
+if(png_ADDED)
+	# Since png's cmake build is broken, we're going to create a target manually
+	set(
+		PNG_SOURCES
+		png.h
+		pngconf.h
+		pngpriv.h
+		pngdebug.h
+		pnginfo.h
+		pngstruct.h
+		png.c
+		pngerror.c
+		pngget.c
+		pngmem.c
+		pngpread.c
+		pngread.c
+		pngrio.c
+		pngrtran.c
+		pngrutil.c
+		pngset.c
+		pngtrans.c
+		pngwio.c
+		pngwrite.c
+		pngwtran.c
+		pngwutil.c
+	)
+	list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/")
+
+	add_custom_command(
+		OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h"
+		COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include"
+		DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h"
+		VERBATIM
+	)
+	add_custom_command(
+		OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h"
+		COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h"
+		DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt"
+		VERBATIM
+	)
+	list(
+		APPEND PNG_SOURCES
+		"${png_BINARY_DIR}/include/png.h"
+		"${png_BINARY_DIR}/include/pngconf.h"
+		"${png_BINARY_DIR}/include/pnglibconf.h"
+	)
+	add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES})
+
+	# Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason
+	target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0)
+
+	# The png includes need to be available to consumers
+	target_include_directories(png PUBLIC "${png_BINARY_DIR}/include")
+
+	# ... and these also need to be present only for png build
+	target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}")
+	target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}")
+	target_include_directories(png PRIVATE "${png_BINARY_DIR}")
+	target_link_libraries(png PRIVATE ZLIB::ZLIB)
+	add_library(PNG::PNG ALIAS png)
+endif()
diff --git a/thirdparty/cpm-sdl2-mixer.cmake b/thirdparty/cpm-sdl2-mixer.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..b7dfeae0d3eab254651f0c5e6e69e7af0626012e
--- /dev/null
+++ b/thirdparty/cpm-sdl2-mixer.cmake
@@ -0,0 +1,22 @@
+CPMAddPackage(
+	NAME SDL2_mixer
+	VERSION 2.6.2
+	URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL2MIXER_INSTALL OFF"
+		"SDL2MIXER_DEPS_SHARED OFF"
+		"SDL2MIXER_SAMPLES OFF"
+		"SDL2MIXER_VENDORED ON"
+		"SDL2MIXER_FLAC ON"
+		"SDL2MIXER_FLAC_LIBFLAC OFF"
+		"SDL2MIXER_FLAC_DRFLAC ON"
+		"SDL2MIXER_MOD OFF"
+		"SDL2MIXER_MP3 ON"
+		"SDL2MIXER_MP3_DRMP3 ON"
+		"SDL2MIXER_MIDI ON"
+		"SDL2MIXER_OPUS OFF"
+		"SDL2MIXER_VORBIS STB"
+		"SDL2MIXER_WAVE ON"
+)
diff --git a/thirdparty/cpm-sdl2.cmake b/thirdparty/cpm-sdl2.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..58cf9afc2a8c9f443379625049ebd28446382b84
--- /dev/null
+++ b/thirdparty/cpm-sdl2.cmake
@@ -0,0 +1,13 @@
+CPMAddPackage(
+	NAME SDL2
+	VERSION 2.24.2
+	URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip"
+	EXCLUDE_FROM_ALL ON
+	OPTIONS
+		"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}"
+		"SDL_TEST OFF"
+		"SDL2_DISABLE_SDL2MAIN ON"
+		"SDL2_DISABLE_INSTALL ON"
+)
diff --git a/thirdparty/cpm-zlib.cmake b/thirdparty/cpm-zlib.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..5368366fd14a4246da476e48bb98ce708812646e
--- /dev/null
+++ b/thirdparty/cpm-zlib.cmake
@@ -0,0 +1,53 @@
+CPMAddPackage(
+	NAME ZLIB
+	VERSION 1.2.13
+	URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip"
+	EXCLUDE_FROM_ALL
+	DOWNLOAD_ONLY YES
+)
+
+if(ZLIB_ADDED)
+	set(ZLIB_SRCS
+		crc32.h
+		deflate.h
+		gzguts.h
+		inffast.h
+		inffixed.h
+		inflate.h
+		inftrees.h
+		trees.h
+		zutil.h
+		adler32.c
+		compress.c
+		crc32.c
+		deflate.c
+		gzclose.c
+		gzlib.c
+		gzread.c
+		gzwrite.c
+		inflate.c
+		infback.c
+		inftrees.c
+		inffast.c
+		trees.c
+		uncompr.c
+		zutil.c
+	)
+	list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/")
+
+	configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY)
+	configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY)
+	configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY)
+
+	add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS})
+	set_target_properties(ZLIB PROPERTIES
+		VERSION 1.2.13
+		OUTPUT_NAME "z"
+	)
+	target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}")
+	target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include")
+	if(MSVC)
+		target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE)
+	endif()
+	add_library(ZLIB::ZLIB ALIAS ZLIB)
+endif()
diff --git a/tools/anglechk.c b/tools/anglechk.c
index 4a67069bf744772082afeac5d8875991f2075903..7f56abff7e56336090af76e81335d76951dcec39 100644
--- a/tools/anglechk.c
+++ b/tools/anglechk.c
@@ -22,7 +22,6 @@
 #ifdef _MSC_VER
 #include <assert.h>
 #endif
-#define NOASM
 #include "../src/tables.h"
 #define NO_M
 #include "../src/m_fixed.c"