diff --git a/.circleci/config.yml b/.circleci/config.yml
index ca9105685d1d3696fbd350954b703db7d61b5568..61e508e4d713f193edc10668849548a039c39b58 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@ jobs:
   build:
     working_directory: /root/SRB2
     docker:
-      - image: debian:jessie
+      - image: debian:stretch
         environment:
           CC: ccache gcc -m32
           PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
@@ -25,6 +25,13 @@ jobs:
       - run:
           name: Add i386 arch
           command: 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
       - run:
           name: Update APT listing
           command: apt-get -qq update
@@ -36,14 +43,21 @@ jobs:
             - v1-SRB2-APT
       - run:
           name: Install SDK
-          command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib upx
+          command: apt-get -qq -y --no-install-recommends install git build-essential nasm libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client
+
       - save_cache:
           key: v1-SRB2-APT
           paths:
             - /var/cache/apt/archives
       - checkout
       - run:
-          name: Clean build
+          name: Compile without network support and BLUA
+          command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NO_LUA=1
+      - run:
+          name: wipe build
+          command: make -C src LINUX=1 cleandep
+      - run:
+          name: rebuild depend
           command: make -C src LINUX=1 clean
       - restore_cache:
           keys:
@@ -57,4 +71,4 @@ jobs:
       - save_cache:
           key: v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
           paths:
-            - /root/.ccache
+            - /root/.ccache
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 922fac4aaf187f58ab2145c69641397c4ff81dc5..3090417dd6b00b8796d2743675301615e488707d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@ Win32_LIB_ASM_Release
 *.db
 *.opendb
 /.vs
+/debian
+/assets/debian
diff --git a/.travis.yml b/.travis.yml
index 4648ae567fe77f0522e7d52c2c7d924b92ea61e4..f2ed43000addf70ea07416fad3d5281472eec927 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,136 +1,150 @@
+# Travis-CI Config
+#
+# You may use the Deployer to upload packages and builds to external servers.
+# See deployer/travis/deployer_defaults.sh for environment variables to configure.
+
 language: c
 sudo: required
 dist: trusty
 
 matrix:
     include:
+################################
+# Test Buildbots
+# Deployer does not operate on these. See Deployer Buildbots, below.
+# These bots are disabled when a deployment is triggered by 'deployer' branch name AND DPL_TERMINATE_TESTS=1.
+# These bots remain enabled when a deployment is triggered by release tag.
+################################
         - os: linux
           addons:
             apt:
+              sources:
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.4
           compiler: gcc-4.4
+          env: GCC44=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #gcc-4.4 (Ubuntu/Linaro 4.4.7-8ubuntu1) 4.4.7
         - os: linux
           addons:
             apt:
+              sources:
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.6
           compiler: gcc-4.6
+          env: GCC46=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #gcc-4.6 (Ubuntu/Linaro 4.6.4-6ubuntu2) 4.6.4
         - os: linux
           addons:
             apt:
+              sources:
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.7
           compiler: gcc-4.7
+          env: GCC47=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #gcc-4.7
         - os: linux
           compiler: gcc
+          env: GCC48=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
         - os: linux
           addons:
             apt:
               sources:
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
+          env: GCC48=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
         - os: linux
           addons:
             apt:
               sources:
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
-              - gcc-4.9
-          compiler: gcc-4.9
-          #gcc-4.9 (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3
-        - os: linux
-          addons:
-            apt:
-              sources:
-              - ubuntu-toolchain-r-test
-              packages:
-              - libsdl2-mixer-dev
-              - libpng-dev
-              - libgl1-mesa-dev
-              - libgme-dev
-              - p7zip-full
-              - gcc-5
-          compiler: gcc-5
-          #gcc-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204
-        - os: linux
-          addons:
-            apt:
-              sources:
-              - ubuntu-toolchain-r-test
-              packages:
-              - libsdl2-mixer-dev
-              - libpng-dev
-              - libgl1-mesa-dev
-              - libgme-dev
-              - p7zip-full
-              - gcc-6
-          compiler: gcc-6
-          env: WFLAGS="-Wno-tautological-compare"
-          #gcc-6 (Ubuntu 6.1.1-3ubuntu11~14.04.1) 6.1.1 20160511
+              - gcc-7
+          compiler: gcc-7
+          env: WFLAGS="-Wno-tautological-compare -Wno-error=implicit-fallthrough -Wno-implicit-fallthrough" GCC72=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
+          #gcc-7 (Ubuntu 7.2.0-1ubuntu1~14.04) 7.2.0 20170802
         - os: linux
           addons:
             apt:
               sources:
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
-              - gcc-7
-          compiler: gcc-7
-          env: WFLAGS="-Wno-tautological-compare -Wno-error=implicit-fallthrough -Wimplicit-fallthrough=3"
-          #gcc-7 (Ubuntu 7.2.0-1ubuntu1~14.04) 7.2.0 20170802
+              - gcc-8
+          compiler: gcc-8
+          env: WFLAGS="-Wno-tautological-compare -Wno-error=implicit-fallthrough -Wno-implicit-fallthrough -Wno-error=format-overflow -Wno-error=format-truncation" GCC81=1
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
+          #gcc-8 (Ubuntu 7.2.0-1ubuntu1~14.04) 8.1.0
         - os: linux
           compiler: clang
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #clang version 3.5.0 (tags/RELEASE_350/final)
         - os: linux
           addons:
             apt:
               sources:
               - llvm-toolchain-precise-3.5
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - clang-3.5
           compiler: clang-3.5
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #Ubuntu clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
         - os: linux
           addons:
@@ -138,14 +152,17 @@ matrix:
               sources:
               - llvm-toolchain-precise-3.6
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - clang-3.6
           compiler: clang-3.6
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #Ubuntu clang version 3.6.2-svn240577-1~exp1 (branches/release_36) (based on LLVM 3.6.2)
         - os: linux
           addons:
@@ -153,14 +170,17 @@ matrix:
               sources:
               - llvm-toolchain-precise-3.7
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - clang-3.7
           compiler: clang-3.7
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #Ubuntu clang version 3.7.1-svn253571-1~exp1 (branches/release_37) (based on LLVM 3.7.1)
         - os: linux
           addons:
@@ -168,14 +188,17 @@ matrix:
               sources:
               - llvm-toolchain-precise-3.8
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - clang-3.8
           compiler: clang-3.8
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #clang version 3.8.1-svn271127-1~exp1 (branches/release_38)
         - os: linux
           addons:
@@ -183,14 +206,17 @@ matrix:
               sources:
               - llvm-toolchain-precise-3.9
               - ubuntu-toolchain-r-test
+              - sourceline: 'ppa:stjr/srb2'
               packages:
               - libsdl2-mixer-dev
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libopenmpt-dev
               - p7zip-full
               - clang-3.9
           compiler: clang-3.9
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
           #clang version 3.9.X
 #        - os: linux
 #          addons:
@@ -198,14 +224,17 @@ matrix:
 #              sources:
 #              - llvm-toolchain-precise-4.0
 #              - ubuntu-toolchain-r-test
+#              - sourceline: 'ppa:stjr/srb2'
 #              packages:
 #              - libsdl2-mixer-dev
 #              - libpng-dev
 #              - libgl1-mesa-dev
 #              - libgme-dev
+#              - libopenmpt-dev
 #              - p7zip-full
 #              - clang-4.0
 #          compiler: clang-4.0
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #clang version 4.0.X
 #        - os: linux
 #          addons:
@@ -213,40 +242,335 @@ matrix:
 #              sources:
 #              - llvm-toolchain-precise-5.0
 #              - ubuntu-toolchain-r-test
+#              - sourceline: 'ppa:stjr/srb2'
 #              packages:
 #              - libsdl2-mixer-dev
 #              - libpng-dev
 #              - libgl1-mesa-dev
 #              - libgme-dev
+#              - libopenmpt-dev
 #              - p7zip-full
 #              - clang-5.0
 #          compiler: clang-5.0
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #clang version 5.0.X
 #        - os: osx
 #          osx_image: beta-xcode6.1
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn)
 #        - os: osx
 #          osx_image: beta-xcode6.2
 #          compiler: gcc
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
 ##        - os: osx
 ##          osx_image: beta-xcode6.3
+##          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 ##          #I think xcode.6.3 VM is broken, it does not boot
 #        - os: osx
 #          osx_image: xcode6.4
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
 #        - os: osx
 #          osx_image: xcode7
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 7.0.0 (clang-700.0.72)
 #        - os: osx
 #          osx_image: xcode7.1
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 7.0.0 (clang-700.1.76)
 #        - os: osx
 #          osx_image: xcode7.2
+#          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
 #          #Apple LLVM version 7.0.2 (clang-700.1.81)
+#        - os: osx
+#          osx_image: xcode7.3
+#          #Apple LLVM version 7.3.0 (clang-703.0.31)
+#        - os: osx
+#          osx_image: xcode7.3
+#          #Apple LLVM version 7.3.0 (clang-703.0.31)
         - os: osx
-          osx_image: xcode7.3
+          if: env(DPL_ENABLED) != "1" OR env(DPL_TERMINATE_TESTS) != "1" OR NOT branch =~ /^.*deployer.*$/
+          #Default: macOS 10.13 and Xcode 9.4.1
+
+
+################################
+# Deployer Buildbots - OSX
+################################
+        - os: osx
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=osx
+          - _DPL_FTP_TARGET=1
+          - _DPL_PACKAGE_BINARY=1
           #Apple LLVM version 7.3.0 (clang-703.0.31)
+
+
+################################
+# Deployer Buildbots - Linux assets
+# Set DPL_TERMINATE_ASSETS to disable all of these
+# List Ubuntu LTS next, newest to oldest
+# Then list non-LTS, newest to oldest
+################################
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: xenial
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_ASSETS) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=bionic-asset
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - _DPL_PACKAGE_MAIN=0
+          - _DPL_PACKAGE_ASSET=1
+          - PACKAGE_DISTRO=bionic
+          #- PACKAGE_SUBVERSION=~18.04bionic
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+
+        ################################
+        # The below asset bots produce packages that occupy too much space.
+        # It would be nice if the asset files were not included in the source package itself,
+        # so these can deploy to each Ubuntu target without manual intervention.
+        #
+        # Currently, to get around Launchpad's space limitation,
+        # copy the packages from *one* bot and the space usage is not increased.
+        ################################
+        # - os: linux
+        #   addons:
+        #     apt:
+        #       packages:
+        #       - libsdl2-mixer-dev
+        #       - libpng-dev
+        #       - libgl1-mesa-dev
+        #       - libgme-dev
+        #       - p7zip-full
+        #       - gcc-4.8
+        #   compiler: gcc-4.8
+        #   dist: trusty
+        #   if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+        #       AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+        #       AND env(DPL_TERMINATE_ASSETS) != "1"
+        #   env:
+        #   - _DPL_JOB_ENABLED=1
+        #   - _DPL_JOB_NAME=trusty-asset
+        #   - _DPL_DPUT_TARGET=1
+        #   - _DPL_PACKAGE_SOURCE=1
+        #   - _DPL_PACKAGE_MAIN=0
+        #   - _DPL_PACKAGE_ASSET=1
+        #   - PACKAGE_DISTRO=trusty
+        #   #- PACKAGE_SUBVERSION=~14.04trusty
+        #   #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        # - os: linux
+        #   addons:
+        #     apt:
+        #       packages:
+        #       - libsdl2-mixer-dev
+        #       - libpng-dev
+        #       - libgl1-mesa-dev
+        #       - libgme-dev
+        #       - p7zip-full
+        #       - gcc-4.8
+        #   compiler: gcc-4.8
+        #   dist: xenial
+        #   if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+        #       AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+        #       AND env(DPL_TERMINATE_ASSETS) != "1"
+        #   env:
+        #   - _DPL_JOB_ENABLED=1
+        #   - _DPL_JOB_NAME=disco-asset
+        #   - _DPL_DPUT_TARGET=1
+        #   - _DPL_PACKAGE_SOURCE=1
+        #   - _DPL_PACKAGE_MAIN=0
+        #   - _DPL_PACKAGE_ASSET=1
+        #   - PACKAGE_DISTRO=disco
+        #   #- PACKAGE_SUBVERSION=~19.04disco
+        #   #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        # - os: linux
+        #   addons:
+        #     apt:
+        #       packages:
+        #       - libsdl2-mixer-dev
+        #       - libpng-dev
+        #       - libgl1-mesa-dev
+        #       - libgme-dev
+        #       - p7zip-full
+        #       - gcc-4.8
+        #   compiler: gcc-4.8
+        #   dist: xenial
+        #   if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+        #       AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+        #       AND env(DPL_TERMINATE_ASSETS) != "1"
+        #   env:
+        #   - _DPL_JOB_ENABLED=1
+        #   - _DPL_JOB_NAME=cosmic-asset
+        #   - _DPL_DPUT_TARGET=1
+        #   - _DPL_PACKAGE_SOURCE=1
+        #   - _DPL_PACKAGE_MAIN=0
+        #   - _DPL_PACKAGE_ASSET=1
+        #   - PACKAGE_DISTRO=cosmic
+        #   #- PACKAGE_SUBVERSION=~18.10cosmic
+        #   #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        # - os: linux
+        #   addons:
+        #     apt:
+        #       packages:
+        #       - libsdl2-mixer-dev
+        #       - libpng-dev
+        #       - libgl1-mesa-dev
+        #       - libgme-dev
+        #       - p7zip-full
+        #       - gcc-4.8
+        #   compiler: gcc-4.8
+        #   dist: xenial
+        #   if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+        #       AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+        #       AND env(DPL_TERMINATE_ASSETS) != "1"
+        #   env:
+        #   - _DPL_JOB_ENABLED=1
+        #   - _DPL_JOB_NAME=xenial-asset
+        #   - _DPL_DPUT_TARGET=1
+        #   - _DPL_PACKAGE_SOURCE=1
+        #   - _DPL_PACKAGE_MAIN=0
+        #   - _DPL_PACKAGE_ASSET=1
+        #   - PACKAGE_DISTRO=xenial
+        #   #- PACKAGE_SUBVERSION=~16.04xenial
+        #   #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+
+
+################################
+# Deployer Buildbots - Linux binaries
+# List Ubuntu LTS, newest to oldest
+# Then list non-LTS, newest to oldest
+################################
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: xenial
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=bionic
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - PACKAGE_DISTRO=bionic
+          - PACKAGE_SUBVERSION=~18.04bionic
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: trusty
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=trusty
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - PACKAGE_DISTRO=trusty
+          - PACKAGE_SUBVERSION=~14.04trusty
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: xenial
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=disco
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - PACKAGE_DISTRO=disco
+          - PACKAGE_SUBVERSION=~19.04disco
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: xenial
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=cosmic
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - PACKAGE_DISTRO=cosmic
+          - PACKAGE_SUBVERSION=~18.10cosmic
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
+        - os: linux
+          addons:
+            apt:
+              packages:
+              - libsdl2-mixer-dev
+              - libpng-dev
+              - libgl1-mesa-dev
+              - libgme-dev
+              - p7zip-full
+              - gcc-4.8
+          compiler: gcc-4.8
+          dist: xenial
+          if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1")
+              AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1"))
+              AND env(DPL_TERMINATE_MAIN) != "1"
+          env:
+          - _DPL_JOB_ENABLED=1
+          - _DPL_JOB_NAME=xenial
+          - _DPL_DPUT_TARGET=1
+          - _DPL_PACKAGE_SOURCE=1
+          - PACKAGE_DISTRO=xenial
+          - PACKAGE_SUBVERSION=~16.04xenial
+          #gcc-4.8 (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
     allow_failures:
       - compiler: clang-3.5
       - compiler: clang-3.6
@@ -256,36 +580,147 @@ matrix:
       - compiler: clang-4.0
       - compiler: clang-5.0
 
+
 cache:
   apt: true
   ccache: true
   directories:
   - $HOME/srb2_cache
 
+
 addons:
   apt:
+    sources:
+    - sourceline: 'ppa:stjr/srb2'
     packages:
     - libsdl2-mixer-dev
     - libpng-dev
     - libgl1-mesa-dev
     - libgme-dev
+    - zlib1g-dev
+    - libopenmpt-dev
     - p7zip-full
+  homebrew:
+    taps:
+    - mazmazz/srb2
+    packages:
+    - sdl2_mixer
+    - game-music-emu
+    - p7zip
+    - libopenmpt
+    - cmake
+    update: true
+
+
+
+before_install:
+  # Initialize Deployer defaults
+  - . ./deployer/travis/deployer_defaults.sh
+
+  # Initialize Deployer; check if Deployer is enabled
+  # This needs to be run in the current shell so that $__DPL_ACTIVE is set for this session
+  - . ./deployer/travis/deployer.sh
+
+  # Also check if we should now terminate -- see `deployer.sh` for conditions.
+  # This should never happen on non-release buildbots when Deployer is not triggered.
+  - if [[ "$__DPL_TRY_TERMINATE_EARLY" == "1" ]]; then
+      if [[ "$__DPL_ACTIVE" != "1" ]]; then
+        echo "Exiting early because this job is not deploying.";
+        exit;
+      fi;
+    fi
+
+  # If we're triggered by release tag, force ASSET_FILES_OPTIONAL_GET=1
+  - if [[ "$__DPL_TAG_ELIGIBLE" = "1" ]]; then
+      ASSET_FILES_OPTIONAL_GET=1;
+    fi;
+
+
+install:
+  # Install OS X library dependencies via Homebrew
+  # Do this differently for release buildbots:
+  #     * `brew install --build-bottle` builds libraries for x86_64's lowest common denominator CPU, core2
+  #     * `sdl2_mixer` requires options from the formula tap https://github.com/mazmazz/homebrew-srb2
+  #     * `brew postinstall` runs post-install scripts after building a bottle
+  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+      if [[ "$__DPL_ACTIVE" == "1" ]]; then
+        brew install --build-bottle sdl2 game-music-emu;
+        brew install --build-bottle mazmazz/srb2/sdl2_mixer --with-flac --with-mpg123;
+        brew postinstall sdl2 game-music-emu mazmazz/srb2/sdl2_mixer;
+      fi;
+    fi
+  - mkdir -p $HOME/srb2_cache
+
 
 before_script:
-  - wget --verbose --server-response -c http://rosenthalcastle.org/srb2/SRB2-v2115-assets-2.7z -O $HOME/srb2_cache/SRB2-v2115-assets-2.7z
-  - 7z x $HOME/srb2_cache/SRB2-v2115-assets-2.7z -oassets
+  # OLDPWD is root repo folder
+  - OLDPWD=$PWD
+  - __ASSET_DIRECTORY="$OLDPWD/assets/installer"
+  - mkdir -p "$__ASSET_DIRECTORY"
+  - cd "$HOME/srb2_cache"
+
+  # Get stat command so we know what the cached archive date is.
+  # stat is different for OSX
+  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+      STATCMD="stat -f %m";
+    else
+      STATCMD="stat -c %y";
+    fi
+
+  # Get asset files (required for MD5)
+  # See `deployer_defaults.sh` for asset download path
+  - if [[ "$ASSET_ARCHIVE_PATH" != "" ]]; then
+      if [ -f "$(basename $ASSET_ARCHIVE_PATH)" ]; then
+        echo "$(basename $ASSET_ARCHIVE_PATH) cache date -- $($STATCMD $(basename $ASSET_ARCHIVE_PATH))";
+      fi;
+      wget --verbose --server-response -N "$ASSET_ARCHIVE_PATH";
+      7z x "$(basename $ASSET_ARCHIVE_PATH)" -o"$__ASSET_DIRECTORY" -aos;
+    fi;
+
+  # Get optional files too
+  - if [[ "$__DPL_ACTIVE" == "1" ]] && [[ "$ASSET_FILES_OPTIONAL_GET" == "1" ]] && [[ "$ASSET_ARCHIVE_OPTIONAL_PATH" != "" ]]; then
+      if [ -f "$(basename $ASSET_ARCHIVE_OPTIONAL_PATH)" ]; then
+        echo "$(basename $ASSET_ARCHIVE_OPTIONAL_PATH) cache date -- $($STATCMD $(basename $ASSET_ARCHIVE_OPTIONAL_PATH))";
+      fi;
+      wget --verbose --server-response -N "$ASSET_ARCHIVE_OPTIONAL_PATH";
+      7z x "$(basename $ASSET_ARCHIVE_OPTIONAL_PATH)" -o"$__ASSET_DIRECTORY" -aos;
+    fi;
+
+  # Go back to root repo folder
+  - cd "$OLDPWD"
+
+  # Prepare CMake asset lists
+  - SRB2_ASSET_HASHED=$(echo ${ASSET_FILES_HASHED// /\;})
+  - SRB2_ASSET_DOCS=$(echo ${ASSET_FILES_DOCS// /\;})
+  - SRB2_ASSET_DIRECTORY="$__ASSET_DIRECTORY"
+
+  # Prepare CMake
   - mkdir build
   - cd build
+  - mkdir package
   - export CFLAGS="-Wall -W -Werror $WFLAGS"
   - export CCACHE_COMPRESS=true
-  - cmake .. -DCMAKE_BUILD_TYPE=Release
+  # If OS X, set -march=core2 to build compatible binaries with old Macs
+  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+      export CFLAGS="${CFLAGS} -march=core2";
+    fi;
+  - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$PWD/bin -DCPACK_PACKAGE_DIRECTORY=$PWD/package
+      -DSRB2_ASSET_HASHED="${SRB2_ASSET_HASHED}" -DSRB2_ASSET_DOCS="${SRB2_ASSET_DOCS}"
+      -DSRB2_ASSET_DIRECTORY="${SRB2_ASSET_DIRECTORY}"
+      -DCPACK_PACKAGE_DESCRIPTION_SUMMARY="${PROGRAM_NAME}"
+      -DCPACK_PACKAGE_VENDOR="${PROGRAM_VENDOR}"
+      -DSRB2_SDL2_EXE_NAME="${PROGRAM_FILENAME}"
 
-before_install:
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sdl2 sdl2_mixer game-music-emu p7zip; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install cmake||true; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then curl -O -L https://www.libsdl.org/release/SDL2-2.0.6.dmg; hdiutil attach SDL2-2.0.6.dmg; sudo cp -a /Volumes/SDL2/SDL2.framework /Library/Frameworks/; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then curl -O -L https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.1.dmg; hdiutil attach SDL2_mixer-2.0.1.dmg; sudo cp -a /Volumes/SDL2_mixer/SDL2_mixer.framework /Library/Frameworks/; fi
-  - mkdir -p $HOME/srb2_cache
+script:
+  # Build our Makefile from Cmake!
+  - if [[ "$__DPL_ACTIVE" == "1" ]]; then
+      . ../deployer/travis/deployer_build.sh;
+    else
+      make -k;
+    fi;
 
-script: make -k
+after_success:
+  # Run the upload scripts
+  # These do nothing if Deployer is not triggered
+  - . ../deployer/travis/deployer_ftp.sh
+  - . ../deployer/travis/deployer_dput.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb91866f003bd53629bdfbfcfcda362873d54ea2..b702218592579c74176c96e7cf8ea761c9bc911f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,8 @@
 cmake_minimum_required(VERSION 3.0)
+# DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
+# Version change is fine.
 project(SRB2
-	VERSION 2.1.20
+	VERSION 2.1.24
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
@@ -54,13 +56,19 @@ macro(copy_files_to_build_dir target dlllist_var)
 	endif()
 endmacro()
 
-# 64-bit check
-if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
+# bitness check
+set(SRB2_SYSTEM_BITS 0)
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
 	message(STATUS "Target is 64-bit")
 	set(SRB2_SYSTEM_BITS 64)
-else()
+endif()
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+	message(STATUS "Target is 32-bit")
 	set(SRB2_SYSTEM_BITS 32)
 endif()
+if(${SRB2_SYSTEM_BITS} EQUAL 0)
+	message(STATUS "Target bitness is unknown")
+endif()
 
 # OS macros
 if (UNIX)
@@ -86,8 +94,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)
-set(SRB2_WIN_EXE_NAME srb2dd)
+set(SRB2_SDL2_EXE_NAME srb2 CACHE STRING "Executable binary output name")
+set(SRB2_WIN_EXE_NAME srb2dd CACHE STRING "Executable binary output name for DirectDraw build")
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR}/src)
 
@@ -98,10 +106,10 @@ add_subdirectory(assets)
 ## config.h generation
 set(GIT_EXECUTABLE "git" CACHE FILEPATH "Path to git binary")
 include(GitUtilities)
-git_describe(SRB2_GIT_DESCRIBE "${CMAKE_SOURCE_DIR}")
+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_GIT_DESCRIBE}")
+set(SRB2_COMP_REVISION "${SRB2_COMP_COMMIT}")
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/config.h)
 
 ##### PACKAGE CONFIGURATION #####
@@ -116,8 +124,8 @@ if(${CMAKE_SYSTEM} MATCHES "Darwin")
 	set(CPACK_GENERATOR "DragNDrop")
 endif()
 
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Sonic Robo Blast 2")
-set(CPACK_PACKAGE_VENDOR "Sonic Team Jr.")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Sonic Robo Blast 2" CACHE STRING "Program name for display purposes")
+set(CPACK_PACKAGE_VENDOR "Sonic Team Jr." CACHE STRING "Vendor name for display purposes")
 #set(CPACK_PACKAGE_DESCRIPTION_FILE )
 set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
 set(CPACK_PACKAGE_VERSION_MAJOR ${SRB2_VERSION_MAJOR})
diff --git a/README.md b/README.md
index d16071454876bc178222e778ca7c20f63821bb97..8a5ca1a1ff0193df4aeeb9d59f5b38ae65869458 100644
--- a/README.md
+++ b/README.md
@@ -12,8 +12,7 @@
 - SDL2-Mixer (Linux/OS X only)
 - libupnp (Linux/OS X only)
 - libgme (Linux/OS X only)
-
-Warning: 64-bit builds are not netgame compatible with 32-bit builds. Use at your own risk.
+- libopenmpt (Linux/OS X only)
 
 ## Compiling
 
diff --git a/SRB2_Debug.props b/SRB2_Debug.props
index 8be11c58a9376343e7d679ad454806dd0ff5b0b9..74177c6ef04ffa7c78e9386de3ad2d754e7476ab 100644
--- a/SRB2_Debug.props
+++ b/SRB2_Debug.props
@@ -22,6 +22,7 @@
     </ClCompile>
     <Link>
       <GenerateDebugInformation>Debug</GenerateDebugInformation>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup />
diff --git a/SRB2_Release.props b/SRB2_Release.props
index a216ea45a9835d20421748384755c99658178fbd..905dfdcf941876e8f7fdb83eec93c34244569242 100644
--- a/SRB2_Release.props
+++ b/SRB2_Release.props
@@ -24,6 +24,7 @@
       <GenerateDebugInformation>DebugFastLink</GenerateDebugInformation>
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup />
diff --git a/SRB2_common.props b/SRB2_common.props
index 2fb2eb8c6c3bb6187e769c67d0224e8462d16ef9..0f80ceb174874e682f0205de06733cb25e2b247a 100644
--- a/SRB2_common.props
+++ b/SRB2_common.props
@@ -18,7 +18,7 @@
       <DisableSpecificWarnings>4244;4267</DisableSpecificWarnings>
     </ClCompile>
     <Link>
-      <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>advapi32.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <SubSystem>Windows</SubSystem>
       <RandomizedBaseAddress>false</RandomizedBaseAddress>
       <GenerateDebugInformation>true</GenerateDebugInformation>
diff --git a/appveyor.yml b/appveyor.yml
index 69913cfc87f8e4097fa397e1f2a8a1f87146454c..b37bd1a650daf6704c40e0b9527be64e04e4f29d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,11 +1,15 @@
-version: 2.1.20.{branch}-{build}
+version: 2.1.24.{branch}-{build}
 os: MinGW
 
 environment:
  CC: ccache
  CCACHE_CC: i686-w64-mingw32-gcc
+ CCACHE_CC_64: x86_64-w64-mingw32-gcc
  WINDRES: windres
+ # c:\mingw-w64 i686 has gcc 6.3.0, so use c:\msys64 7.3.0 instead
  MINGW_SDK: c:\msys64\mingw32
+ # c:\msys64 x86_64 has gcc 8.2.0, so use c:\mingw-w64 7.3.0 instead
+ MINGW_SDK_64: C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64
  CFLAGS: -Wall -W -Werror -Wno-error=implicit-fallthrough -Wimplicit-fallthrough=3 -Wno-tautological-compare -Wno-error=suggest-attribute=noreturn
  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
@@ -15,65 +19,114 @@ environment:
  CCACHE_URL: http://alam.srb2.org/ccache.exe
  CCACHE_COMPRESS: true
  CCACHE_DIR: C:\Users\appveyor\.ccache
+ # Disable UPX by default. The user can override this in their Appveyor project settings
+ NOUPX: 1
+ ##############################
+ # DEPLOYER VARIABLES
+ # DPL_ENABLED=1 builds installers for branch names starting with `deployer`.
+ # DPL_TAG_ENABLED=1 will also build installers for release tags. DPL_ENABLED=1 must also be set.
+ # Set these in the Appveyor project settings
+ ##############################
+ DPL_ENABLED: 0
+ DPL_TAG_ENABLED: 0
+ DPL_INSTALLER_NAME: SRB2-v2123
+ # Asset handling is barebones vs. Travis Deployer. We operate on 7z only.
+ # Include the README files and the OpenGL batch in the main and patch archives.
+ # The x86/x64 archives contain the DLL binaries.
+ ASSET_ARCHIVE_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-assets.7z
+ ASSET_ARCHIVE_PATCH_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-patch-assets.7z
+ ASSET_ARCHIVE_X86_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x86-assets.7z
+ ASSET_ARCHIVE_X64_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-x64-assets.7z
+ ASSET_ARCHIVE_OPTIONAL_PATH: https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-optional-assets.7z
+ # This is overridden to 1 for release tag builds
+ ASSET_FILES_OPTIONAL_GET: 0
+ # For patches, also include the X86/X64 DLLs.
+ PACKAGE_PATCH_DLL_GET: 0
+ # Delete all asset downloads so they can be redownloaded
+ ASSET_CLEAN: 0
 
 cache:
 - nasm-2.12.01.zip
 - upx391w.zip
 - ccache.exe
 - C:\Users\appveyor\.ccache
+- C:\Users\appveyor\srb2_cache
 
 install:
+- if [%CONFIGURATION%] == [SDL64] ( set "X86_64=1" )
+- if [%CONFIGURATION%] == [SDL64] ( set "CONFIGURATION=SDL" )
+- if [%CONFIGURATION%] == [DD64] ( set "X86_64=1" )
+- if [%CONFIGURATION%] == [DD64] ( set "CONFIGURATION=DD" )
+- if [%X86_64%] == [1] ( set "MINGW_SDK=%MINGW_SDK_64%" )
+- if [%X86_64%] == [1] ( set "CCACHE_CC=%CCACHE_CC_64%" )
+
 - 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
+- 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
+- robocopy /S /xx /ns /nc /nfl /ndl /np /njh /njs "%TMP%\%UPX_ZIP%" "%MINGW_SDK%\bin" upx.exe || exit 0
 
 - if not exist "%CCACHE_EXE%" appveyor DownloadFile "%CCACHE_URL%" -FileName "%CCACHE_EXE%"
 - ccache -M 99M
-- xcopy /Y /V /I ccache.exe %MINGW_SDK%\bin
+- xcopy /Y /V /I ccache.exe "%MINGW_SDK%\bin"
 
 configuration:
 - SDL
+- SDL64
 - DD
+- DD64
 
 matrix:
   allow_failures:
     - configuration: DD
+    - configuration: DD64
 
 before_build:
-- set Path=%MINGW_SDK%\bin;%Path%
-- i686-w64-mingw32-gcc --version
+- set "Path=%MINGW_SDK%\bin;%Path%"
+- if [%X86_64%] == [1] ( x86_64-w64-mingw32-gcc --version ) else ( i686-w64-mingw32-gcc --version )
 - mingw32-make --version
-- nasm -v
-- upx -V
+- if not [%X86_64%] == [1] ( nasm -v )
+- if not [%NOUPX%] == [1] ( upx -V )
 - ccache -V
 - ccache -s
-- set SRB2_MFLAGS=-C src MINGW=1 WARNINGMODE=1 GCC72=1 CCACHE=1 NOOBJDUMP=1
+- if [%NOUPX%] == [1] ( set "NOUPX=NOUPX=1" ) else ( set "NOUPX=" )
+- set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 GCC73=1 NOOBJDUMP=1 %NOUPX%"
+- if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )
+- set "SRB2_MFLAGS=%SRB2_MFLAGS% %MINGW_FLAGS% %CONFIGURATION%=1"
 
 build_script:
-- cmd: mingw32-make.exe %SRB2_MFLAGS% %CONFIGURATION%=1 clean
-- cmd: mingw32-make.exe %SRB2_MFLAGS% %CONFIGURATION%=1 ERRORMODE=1 -k
+- cmd: mingw32-make.exe %SRB2_MFLAGS% clean
+- cmd: mingw32-make.exe %SRB2_MFLAGS% ERRORMODE=1 -k
 
 after_build:
+- if [%X86_64%] == [1] (
+    set "BUILD_PATH=bin\Mingw64\Release"
+  ) else (
+    set "BUILD_PATH=bin\Mingw\Release"
+  )
+- if [%X86_64%] == [1] ( set "CONFIGURATION=%CONFIGURATION%64" )
 - ccache -s
 - cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 - set BUILD_ARCHIVE=%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%.7z
 - set BUILDSARCHIVE=%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%.7z
-- cmd: 7z a %BUILD_ARCHIVE% bin\Mingw\Release -xr!.gitignore
+- cmd: 7z a %BUILD_ARCHIVE% %BUILD_PATH% -xr!.gitignore
 - appveyor PushArtifact %BUILD_ARCHIVE%
 - cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
 - appveyor PushArtifact %BUILDSARCHIVE%
+##############################
+# DEPLOYER SCRIPT
+##############################
+- if [%DPL_ENABLED%] == [1] ( call "deployer\appveyor\deployer.bat" )
 
 test: off
 
 #deploy:
 #  - provider: FTP
 #    protocol: ftps
-#    host: 
+#    host:
 #      secure: NsLJEPIBvmwCOj8Tg8RoRQ==
 #    username:
 #      secure: ejxi5mvk7oLYu7QtbYojajEPigMy0mokaKhuEVuDZcA=
diff --git a/assets/.gitignore b/assets/.gitignore
index 37bb465dc2db82f22a8bfeacbcde73069b7eb711..d6e46a75b0f0d3aa04d5c319d41c456ef202bc54 100644
--- a/assets/.gitignore
+++ b/assets/.gitignore
@@ -1,2 +1,10 @@
-*
-*.*
+*.srb
+*.pk3
+*.dta
+*.wad
+*.txt
+!README.txt
+!LICENSE.txt
+!LICENSE-3RD-PARTY.txt
+!CMakeLists.txt
+!debian-template/*
diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 292e184c72d20a3d2e2b5295de8dc818d6826a58..68ff0fdf9fdd64134989b8a2a16fd7c74ff589a5 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -1,37 +1,58 @@
 ## Assets Target Configuration ##
 
-# MD5 generation
-set(SRB2_ASSET_ALL
-	${CMAKE_CURRENT_SOURCE_DIR}/srb2.srb
-	${CMAKE_CURRENT_SOURCE_DIR}/player.dta
-	${CMAKE_CURRENT_SOURCE_DIR}/rings.dta
-	${CMAKE_CURRENT_SOURCE_DIR}/zones.dta
-	${CMAKE_CURRENT_SOURCE_DIR}/patch.dta
-	${CMAKE_CURRENT_SOURCE_DIR}/music.dta
-)
+# For prepending the current source path, later
+FUNCTION(PREPEND var prefix)
+   SET(listVar "")
+   FOREACH(f ${ARGN})
+      LIST(APPEND listVar "${prefix}/${f}")
+   ENDFOREACH(f)
+   SET(${var} "${listVar}" PARENT_SCOPE)
+ENDFUNCTION(PREPEND)
+
+set(SRB2_ASSET_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/installer"
+	CACHE STRING "Path to directory that contains all asset files for the installer.")
 
 set(SRB2_ASSET_HASHED
-	srb2.srb
-	player.dta
-	rings.dta
-	zones.dta
-	patch.dta
+"srb2.srb;\
+player.dta;\
+rings.dta;\
+zones.dta;\
+patch.dta"
+	CACHE STRING "Asset filenames to apply MD5 checks. No spaces between entries!"
+)
+
+set(SRB2_ASSET_DOCS
+"README.txt;\
+LICENSE.txt;\
+LICENSE-3RD-PARTY.txt"
+	CACHE STRING "Documentation filenames. In OS X, these are packaged separately from other assets. No spaces between entries!"
 )
 
+PREPEND(SRB2_ASSET_DOCS ${SRB2_ASSET_DIRECTORY} ${SRB2_ASSET_DOCS})
+
 foreach(SRB2_ASSET ${SRB2_ASSET_HASHED})
-	file(MD5 ${CMAKE_CURRENT_SOURCE_DIR}/${SRB2_ASSET} "SRB2_ASSET_${SRB2_ASSET}_HASH")
+	file(MD5 ${SRB2_ASSET_DIRECTORY}/${SRB2_ASSET} "SRB2_ASSET_${SRB2_ASSET}_HASH")
 	set(SRB2_ASSET_${SRB2_ASSET}_HASH ${SRB2_ASSET_${SRB2_ASSET}_HASH} PARENT_SCOPE)
 endforeach()
 
 # Installation
 
-if(CLANG)
+if(${CMAKE_SYSTEM} MATCHES Darwin)
 	get_target_property(outname SRB2SDL2 OUTPUT_NAME)
-	install(FILES ${SRB2_ASSET_ALL}
+	install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
 		DESTINATION "${outname}.app/Contents/Resources"
 	)
+	install(FILES ${SRB2_ASSET_DOCS}
+		DESTINATION .
+		OPTIONAL
+	)
 else()
-	install(FILES ${SRB2_ASSET_ALL}
+	install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
 		DESTINATION .
 	)
+	# Docs are assumed to be located in SRB2_ASSET_DIRECTORY, so don't install again
+	#install(FILES ${SRB2_ASSET_DOCS}
+	#	DESTINATION .
+	#	OPTIONAL
+	#)
 endif()
diff --git a/assets/LICENSE-3RD-PARTY.txt b/assets/LICENSE-3RD-PARTY.txt
new file mode 100644
index 0000000000000000000000000000000000000000..42ea20e96773028ca44b24c9c3d6abac33ce8163
--- /dev/null
+++ b/assets/LICENSE-3RD-PARTY.txt
@@ -0,0 +1,1710 @@
+--------------------------------------------------------------------------------
+                        3-Clause BSD License
+        applies to:
+        - MiniUPnPc
+          Copyright (c) 2005-2011, Thomas BERNARD
+          All rights reserved.
+          http://miniupnp.free.fr
+--------------------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * The name of the author may not be used to endorse or promote products
+	  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+                        curl License
+        applies to:
+        - curl
+          Copyright (c) 1996 - 2018, Daniel Stenberg, daniel@haxx.se,
+          and many contributors, see the THANKS file.
+          https://curl.haxx.se
+--------------------------------------------------------------------------------
+
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall not be
+used in advertising or otherwise to promote the sale, use or other dealings in
+this Software without prior written authorization of the copyright holder.
+
+--------------------------------------------------------------------------------
+                        FMOD End User License Agreement
+        applies to:
+        - FMOD Ex
+          Copyright (c), Firelight Technologies Pty, Ltd. 2004-2018.
+          https://www.fmod.com
+--------------------------------------------------------------------------------
+
+This FMOD End User Licence Agreement (EULA) is a legal agreement between you and Firelight
+Technologies Pty Ltd (ACN 099 182 448) (us or we) and governs your use of FMOD Studio and FMOD
+Engine software (FMOD).
+
+1. GRANT OF LICENCE
+This EULA grants you the right to use FMOD, in a software application (Product), for
+personal (hobbyist), educational (students and teachers) or Non-Commercial use only,
+subject to the following:
+i)	Non-Commercial use does not involve any form of monetisation, sponsorship
+or promotion.
+ii)	FMOD is distributed as integrated into a Product only;
+iii)	FMOD is not distributed as part of any Commercial Product or service;
+iv)	FMOD is not distributed as part of a game engine or tool set;
+v)	FMOD is not used in any Commercial enterprise or for any Commercial
+production or subcontracting, except for the purposes of Evaluation or
+Development of a Commercial Product;
+vi)	Product includes attribution in accordance with Clause 3;
+
+2.	OTHER USE
+For all Commercial use, and any Non Commercial use not permitted by this license, a
+separate license is required. Refer to www.fmod.com/licensing for information.
+
+3.	CREDITS AND LOGO
+All Products require an in game credit line which must include the words "FMOD" or
+"FMOD Studio" (if applicable) and "Firelight Technologies Pty Ltd". This is non
+negotiable. Refer to www.fmod.com/licensing for examples. All products require a
+logo to be presented during start up of the application, before encountering any
+menus or interactivity. See www.fmod.com/licensing for logo information. The user
+has the option to 'buy out'	the requirement to have a logo at the start of the
+product.
+
+4.	INTELLECTUAL PROPERTY RIGHTS
+a)	We are and remain at all times the owner of FMOD (including all intellectual
+property rights in or to the Software). For the avoidance of doubt, nothing in
+this EULA may be deemed to grant or assign to you any proprietary or ownership
+interest or intellectual property rights in or to FMOD other than the rights
+licensed pursuant to clause 1.
+b)	You acknowledge and agree that you have no right, title or interest in and to the
+intellectual property rights in FMOD.
+
+5.	SECURITY AND RISK
+You are responsible for protecting FMOD and any related materials at all times from
+unauthorised access, use or damage.
+
+6.	WARRANTY AND LIMITATION OF LIABILITY
+a)	FMOD is provided by us "as is" and, to the maximum extent permitted by law,
+any express or implied warranties of any kind, including (but not limited to) all
+implied warranties of merchantability and fitness for a particular purpose are
+disclaimed.
+b)	In no event shall we (and our employees, contractors and subcontractors),
+developers and contributors be liable for any direct, special, indirect or
+consequential damages whatsoever resulting from loss of use of data or profits,
+whether in an action of contract, negligence or other tortious conduct, arising
+out of or in connection with the use or performance FMOD.
+
+7.	OGG VORBIS CODEC
+a)	FMOD uses the Ogg Vorbis codec.
+b)	(c) 2002, Xiph.Org Foundation
+c)	Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+i)	Redistributions of source code must retain the above copyright notice, the
+list of conditions and the following disclaimer.
+ii)	Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other material provided with the distribution.
+iii)	Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
+d)	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTIAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+8.	GOOGLE VR (GVR)
+FMOD includes Google VR, licensed under the Apache Licence, Version 2.0 (the Licence);
+you may not use this file except in compliance with the License. You may obtain a copy of
+the License at:
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under the
+License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the License for the specific language governing permissions
+and limitations under the License.
+
+9. ANDROID PLATFORM CODE
+Copyright (C) 2010 The Android Open Source Project All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in
+the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+10.	AUDIOGAMING AUDIOMOTORS DEMO CONTENT
+AudioGaming AudioMotors Demo Engine.agp is provided for evaluation purposes
+only and is not to be redistributed. To create your own engine content, you
+will need AudioMotors V2 Pro. A trial version be found at
+http://store.audiogaming.net/content/audiomotors-v2-pro-trial. For access to
+the full version, contact sales@fmod.com.
+
+© 2018 Firelight Technologies Pty Ltd.
+
+--------------------------------------------------------------------------------
+                        GCC Runtime Library Exception, Version 3.1
+        applies to:
+        - GCC Runtime Library
+          Copyright (C) Free Software Foundation, Inc.
+          https://www.gnu.org/software/gcc/
+--------------------------------------------------------------------------------
+
+GCC RUNTIME LIBRARY EXCEPTION Version 3.1, 31 March 2009
+
+Copyright © 2009 Free Software Foundation, Inc. <https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+This GCC Runtime Library Exception ("Exception") is an additional permission
+under section 7 of the GNU General Public License, version 3 ("GPLv3"). It
+applies to a given file (the "Runtime Library") that bears a notice placed by
+the copyright holder of the file stating that the file is governed by GPLv3
+along with this Exception.
+
+When you use GCC to compile a program, GCC may combine portions of certain GCC
+header files and runtime libraries with the compiled program. The purpose of
+this Exception is to allow compilation of non-GPL (including proprietary)
+programs to use, in this way, the header files and runtime libraries covered by
+this Exception.
+
+0. Definitions. A file is an "Independent Module" if it either requires the
+Runtime Library for execution after a Compilation Process, or makes use of an
+interface provided by the Runtime Library, but is not otherwise based on the
+Runtime Library.
+
+"GCC" means a version of the GNU Compiler Collection, with or without
+modifications, governed by version 3 (or a specified later version) of the GNU
+General Public License (GPL) with the option of using any subsequent versions
+published by the FSF.
+
+"GPL-compatible Software" is software whose conditions of propagation,
+modification and use would permit combination with GCC in accord with the
+license of GCC.
+
+"Target Code" refers to output from any compiler for a real or virtual target
+processor architecture, in executable form or suitable for input to an
+assembler, loader, linker and/or execution phase. Notwithstanding that, Target
+Code does not include data in any format that is used as a compiler
+intermediate representation, or used for producing a compiler intermediate
+representation.
+
+The "Compilation Process" transforms code entirely represented in
+non-intermediate languages designed for human-written code, and/or in Java
+Virtual Machine byte code, into Target Code. Thus, for example, use of source
+code generators and preprocessors need not be considered part of the
+Compilation Process, since the Compilation Process can be understood as
+starting with the output of the generators or preprocessors.
+
+A Compilation Process is "Eligible" if it is done using GCC, alone or with
+other GPL-compatible software, or if it is done without using any work based on
+GCC. For example, using non-GPL-compatible Software to optimize any GCC
+intermediate representations would not qualify as an Eligible Compilation
+Process.
+
+1. Grant of Additional Permission. You have permission to propagate a work of
+Target Code formed by combining the Runtime Library with Independent Modules,
+even if such propagation would otherwise violate the terms of GPLv3, provided
+that all Target Code was generated by Eligible Compilation Processes. You may
+then convey such a combination under terms of your choice, consistent with the
+licensing of the Independent Modules.
+
+2. No Weakening of GCC Copyleft. The availability of this Exception does not
+imply any general presumption that third-party software is unaffected by the
+copyleft requirements of the license of GCC.
+
+--------------------------------------------------------------------------------
+                        GNU General Public License, Version 3
+        applies to:
+        - GCC Runtime Library
+          Copyright (C) Free Software Foundation, Inc.
+          https://www.gnu.org/software/gcc/
+--------------------------------------------------------------------------------
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
+
+--------------------------------------------------------------------------------
+                        GNU Lesser General Public License, Version 2.1
+        applies to:
+        - Game_Music_Emu
+          Shay Green <gblargg@gmail.com>
+          http://www.slack.net/~ant/
+
+        - libintl
+          Copyright (C) 1995-2018 Free Software Foundation, Inc.
+          https://www.gnu.org/software/gettext/
+
+        - mpg123
+          Copyright (c) 1995-2013 by Michael Hipp and others,
+          free software under the terms of the LGPL v2.1
+          https://www.mpg123.de
+--------------------------------------------------------------------------------
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+--------------------------------------------------------------------------------
+                  libpng License
+        applies to:
+        - libpng
+          Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson
+          http://www.libpng.org/pub/png/libpng.html
+--------------------------------------------------------------------------------
+
+This copy of the libpng notices is provided for your convenience.  In case of
+any discrepancy between this copy and the notices in the file png.h that is
+included in the libpng distribution, the latter shall prevail.
+
+COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
+
+If you modify libpng you may insert additional notices immediately following
+this sentence.
+
+This code is released under the libpng license.
+
+libpng versions 1.0.7, July 1, 2000 through 1.6.35, July 15, 2018 are
+Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are
+derived from libpng-1.0.6, and are distributed according to the same
+disclaimer and license as libpng-1.0.6 with the following individuals
+added to the list of Contributing Authors:
+
+   Simon-Pierre Cadieux
+   Eric S. Raymond
+   Mans Rullgard
+   Cosmin Truta
+   Gilles Vollant
+   James Yu
+   Mandar Sahastrabuddhe
+   Google Inc.
+   Vadim Barkov
+
+and with the following additions to the disclaimer:
+
+   There is no warranty against interference with your enjoyment of the
+   library or against infringement.  There is no warranty that our
+   efforts or the library will fulfill any of your particular purposes
+   or needs.  This library is provided with all faults, and the entire
+   risk of satisfactory quality, performance, accuracy, and effort is with
+   the user.
+
+Some files in the "contrib" directory and some configure-generated
+files that are distributed with libpng have other copyright owners and
+are released under other open source licenses.
+
+libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
+Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from
+libpng-0.96, and are distributed according to the same disclaimer and
+license as libpng-0.96, with the following individuals added to the list
+of Contributing Authors:
+
+   Tom Lane
+   Glenn Randers-Pehrson
+   Willem van Schaik
+
+libpng versions 0.89, June 1996, through 0.96, May 1997, are
+Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88,
+and are distributed according to the same disclaimer and license as
+libpng-0.88, with the following individuals added to the list of
+Contributing Authors:
+
+   John Bowler
+   Kevin Bracey
+   Sam Bushell
+   Magnus Holmgren
+   Greg Roelofs
+   Tom Tanner
+
+Some files in the "scripts" directory have other copyright owners
+but are released under this license.
+
+libpng versions 0.5, May 1995, through 0.88, January 1996, are
+Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
+
+For the purposes of this copyright and license, "Contributing Authors"
+is defined as the following set of individuals:
+
+   Andreas Dilger
+   Dave Martindale
+   Guy Eric Schalnat
+   Paul Schmidt
+   Tim Wegner
+
+The PNG Reference Library is supplied "AS IS".  The Contributing Authors
+and Group 42, Inc. disclaim all warranties, expressed or implied,
+including, without limitation, the warranties of merchantability and of
+fitness for any purpose.  The Contributing Authors and Group 42, Inc.
+assume no liability for direct, indirect, incidental, special, exemplary,
+or consequential damages, which may result from the use of the PNG
+Reference Library, even if advised of the possibility of such damage.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+source code, or portions hereof, for any purpose, without fee, subject
+to the following restrictions:
+
+  1. The origin of this source code must not be misrepresented.
+
+  2. Altered versions must be plainly marked as such and must not
+     be misrepresented as being the original source.
+
+  3. This Copyright notice may not be removed or altered from any
+     source or altered source distribution.
+
+The Contributing Authors and Group 42, Inc. specifically permit, without
+fee, and encourage the use of this source code as a component to
+supporting the PNG file format in commercial products.  If you use this
+source code in a product, acknowledgment is not required but would be
+appreciated.
+
+END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE.
+
+TRADEMARK:
+
+The name "libpng" has not been registered by the Copyright owner
+as a trademark in any jurisdiction.  However, because libpng has
+been distributed and maintained world-wide, continually since 1995,
+the Copyright owner claims "common-law trademark protection" in any
+jurisdiction where common-law trademark is recognized.
+
+OSI CERTIFICATION:
+
+Libpng is OSI Certified Open Source Software.  OSI Certified Open Source is
+a certification mark of the Open Source Initiative. OSI has not addressed
+the additional disclaimers inserted at version 1.0.7.
+
+EXPORT CONTROL:
+
+The Copyright owner believes that the Export Control Classification
+Number (ECCN) for libpng is EAR99, which means not subject to export
+controls or International Traffic in Arms Regulations (ITAR) because
+it is open source, publicly available software, that does not contain
+any encryption software.  See the EAR, paragraphs 734.3(b)(3) and
+734.7(b).
+
+Glenn Randers-Pehrson
+glennrp at users.sourceforge.net
+July 15, 2018
+
+--------------------------------------------------------------------------------
+                  New BSD License
+        applies to:
+        - FLAC
+          Copyright (C) 2000-2009  Josh Coalson
+          Copyright (C) 2011-2016  Xiph.Org Foundation
+          https://xiph.org/flac/api/
+
+        - Vorbis
+          Copyright (c) 2002-2008 Xiph.org Foundation
+          https://xiph.org/vorbis/
+
+        - Opus
+          Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+                    Jean-Marc Valin, Timothy B. Terriberry,
+                    CSIRO, Gregory Maxwell, Mark Borgerding,
+                    Erik de Castro Lopo
+          https://opus-codec.org
+
+        - Opus File
+          Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
+          https://opus-codec.org
+--------------------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+[ For FLAC, Vorbis, and Opus File
+- Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+]
+
+[ For Opus
+- Neither the name of Internet Society, IETF or IETF Trust, nor the
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+]
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+                  Public Domain
+        applies to:
+        - win_iconv
+          Yukihiro Nakadaira <yukihiro.nakadaira@gmail.com>
+          win_iconv is placed in the public domain.
+          https://github.com/win-iconv/win-iconv
+
+        - libmodplug
+          ModPlug-XMMS and libmodplug are now in the public domain.
+          http://modplug-xmms.sourceforge.net
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+                        zlib License
+        applies to:
+        - Simple DirectMedia Layer
+          Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+          https://www.libsdl.org/hg.php
+
+        - SDL_mixer:  An audio mixer library based on the SDL library
+          Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+          https://www.libsdl.org/projects/SDL_mixer/
+
+        - zlib
+          Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
+          https://zlib.net
+--------------------------------------------------------------------------------
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
diff --git a/assets/LICENSE.txt b/assets/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1
--- /dev/null
+++ b/assets/LICENSE.txt
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/assets/README.txt b/assets/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..37f7d8f29e3cf0e1744ce3f34c032f3ff0dbd414
--- /dev/null
+++ b/assets/README.txt
@@ -0,0 +1,51 @@
+SONIC ROBO BLAST 2
+
+Sonic Robo Blast 2 (SRB2) is a 3D Sonic the Hedgehog fangame based on a
+modified version of Doom Legacy.
+
+LICENSE
+
+The source code for SRB2 is licensed under the GNU General Public
+License, Version 2. See LICENSE.txt for the full text of this license.
+
+SRB2 uses various third-party libraries, including SDL, SDL Mixer, and
+their dependencies. See LICENSE-3RD-PARTY.txt for the licenses of these
+libraries.
+
+SOURCE CODE
+
+You may obtain the source code for SRB2, including the source code for
+specific version releases, at the following web sites:
+
+STJr GitLab:
+https://git.magicalgirl.moe/STJr/SRB2
+
+GitHub:
+https://github.com/STJr/SRB2
+
+CONTACT
+
+You may contact Sonic Team Junior via the following web sites:
+
+SRB2.ORG:
+https://www.srb2.org
+
+SRB2 Message Board:
+https://mb.srb2.org
+
+SRB2 Official Discord:
+https://discord.gg/pYDXzpX
+
+COPYRIGHT AND DISCLAIMER
+
+Design and content on SRB2 is copyright 1998-2018 by Sonic Team Junior.
+All non-original material on SRB2.ORG is copyrighted by their
+respective owners, and no copyright infringement is intended. The owner
+of the SRB2.ORG domain is only acting as an ISP, and is therefore not
+responsible for any content on SRB2.ORG under the 1998 DMCA. This
+site, its webmaster, and its staff make no profit whatsoever (in fact,
+we lose money). Sonic Team Junior assumes no responsibility for the
+content on any Sonic Team Junior fan sites.
+
+Sonic Team Junior is in no way affiliated with SEGA or Sonic Team. We do
+not claim ownership of any of SEGA's intellectual property used in SRB2.
diff --git a/assets/debian-template/README.Debian b/assets/debian-template/README.Debian
new file mode 100644
index 0000000000000000000000000000000000000000..f3fe90030427b38888e2edb73312702580b52fef
--- /dev/null
+++ b/assets/debian-template/README.Debian
@@ -0,0 +1,73 @@
+srb2 for Debian
+---------------
+
+SRB2 Debian package!
+Hi there, to rebuild this package just use the SRB2 Makefile system, or, optionally, run
+dpkg-buildpackage in the in /assets directory. You can build these with or without a key
+if you want, but if you want to put these on a repo, generate your own GnuPG key as per the
+https://help.ubuntu.com/community/GnuPrivacyGuardHowto instructions and pass the -k<keyid>
+command to debuild. Make sure you export the key footprint and give them to your users to install
+with apt-key add. Thanks!
+
+ -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Fri, 26 Nov 2010 18:25:31 +1300
+
+
+---------------
+
+
+Templating
+
+Note that you MUST run [repo-root]/debian_template.sh before running debuild
+on these scripts! debian_template.sh fills these template files with working values.
+
+You should also set PACKAGE_NAME_EMAIL="John Doe <jdoe@example.com>" to match
+the identity of the key you will use to sign the package.
+
+
+Building for Launchpad PPA
+
+Run this step first:
+
+    1. source [repo-root]/debian_template.sh
+       * Initializes defaults for the package variables and fills in templates.
+
+Use these steps to prepare building a source package for Launchpad:
+
+    1. cd [repo-root]/assets/
+    2. debuild -T clean-all (optional; if you already have asset files, this clears them)
+
+Build the source package:
+
+    1. debuild -T build (this downloads the asset files from srb2.org if necessary)
+    2. debuild -S (builds the source package for Launchpad, including the asset files)
+
+
+Signing for Launchpad PPA
+
+First, follow Callum's instructions to generate a GnuPG key with your identity. You will need
+to publish the fingerprint of that key to Ubuntu's key server.
+
+    https://help.ubuntu.com/community/GnuPrivacyGuardHowto#Uploading_the_key_to_Ubuntu_keyserver
+
+Next, you will have to add that key fingerprint to your Launchpad account. Go to your Launchpad
+profile and click the yellow Edit button next to "OpenPGP keys". Once you add the key, you can
+upload signed source packages and publish them onto your PPA.
+
+IF YOU UPLOAD A PACKAGE and Launchpad does NOT send you a confirmation or rejection email, that
+means your key is not set up correctly with your Launchpad account.
+
+Finally, if your packages have not already been signed, follow these steps:
+
+    1. cd ..
+       * Packages are located in the parent folder of where debuild was called
+    2. debsign "srb2-data_[version]_source.changes"
+       * You may need to specify -k [key-fingerprint]
+
+
+Uploading for Launchpad PPA
+
+Follow the instructions at <https://help.launchpad.net/Packaging/PPA/Uploading> to upload
+to your PPA and have Launchpad build your binary deb packages.
+
+
+ -- Marco Zafra <marco.a.zafra@gmail.com>  Mon, 26 Nov 2018 21:13:00 -0500
diff --git a/assets/debian/README.source b/assets/debian-template/README.source
similarity index 100%
rename from assets/debian/README.source
rename to assets/debian-template/README.source
diff --git a/assets/debian-template/changelog b/assets/debian-template/changelog
new file mode 100644
index 0000000000000000000000000000000000000000..64562e2a3140dbe67052291a8a7c2fab0a85f25e
--- /dev/null
+++ b/assets/debian-template/changelog
@@ -0,0 +1,5 @@
+${PACKAGE_NAME}-data (${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION}) ${PACKAGE_DISTRO}; urgency=${PACKAGE_URGENCY}
+
+  * ${PROGRAM_NAME} v${PROGRAM_VERSION} asset data
+
+ -- ${PACKAGE_NAME_EMAIL}  ${__PACKAGE_DATETIME}
diff --git a/assets/debian/compat b/assets/debian-template/compat
similarity index 100%
rename from assets/debian/compat
rename to assets/debian-template/compat
diff --git a/assets/debian/control b/assets/debian-template/control
similarity index 54%
rename from assets/debian/control
rename to assets/debian-template/control
index 123b58429cd5306897688b5809cfc3bd851237e8..ae5c0ce67f7286bcc0b8f058f584747e6b1a6380 100644
--- a/assets/debian/control
+++ b/assets/debian-template/control
@@ -1,22 +1,22 @@
 # SRB2-data Debian package control file.
 
-Source: srb2-data
+Source: ${PACKAGE_NAME}-data
 Section: games
 Priority: extra
-Maintainer: Callum Dickinson <gcfreak_ag20@hotmail.com>
-Build-Depends: debhelper (>= 7.0.50~)
+Maintainer: ${PACKAGE_GROUP_NAME_EMAIL}
+Build-Depends: debhelper (>= 7.0.50~),
+ wget
 Standards-Version: 3.8.4
-Homepage: http://www.srb2.org
+Homepage: ${PACKAGE_WEBSITE}
 
-Package: srb2-data
+Package: ${PACKAGE_NAME}-data
 Architecture: all
 Description: A cross-platform 3D Sonic fangame
  Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
  fangame built using a modified version of the Doom Legacy
  port of Doom. SRB2 is closely inspired by the original
  Sonic games from the Sega Genesis, and attempts to recreate
- the design in 3D. While SRB2 isn't fully completed, it already
- features tons of levels, enemies, speed, and quite a lot
- of the fun that the original Sonic games provided.
+ the design in 3D. It features tons of levels, enemies, speed,
+ and quite a lot of the fun that the original Sonic games provided.
  This is the data package that provides the data files that
- SRB2 requires to run, it will not work without it.
+ SRB2 requires to run; it will not work without it.
diff --git a/debian/copyright b/assets/debian-template/copyright
similarity index 60%
rename from debian/copyright
rename to assets/debian-template/copyright
index 8a87051901174db490161b4b7588b2e9aae1bfbd..cc47c453bf2b0ccc6db34c57a03dfe1ce2fc75a7 100644
--- a/debian/copyright
+++ b/assets/debian-template/copyright
@@ -1,18 +1,18 @@
 This work was packaged for Debian by:
 
-    Callum Dickinson <gcfreak_ag20@hotmail.com> on Fri, 26 Nov 2010 15:19:16 +1300
+    ${PACKAGE_NAME_EMAIL}  ${__PACKAGE_DATETIME}
 
 It was downloaded from:
 
-    <http://srb2.org>
+    ${PACKAGE_WEBSITE}
 
 Upstream Author(s):
 
-    Sonic Team Junior <stjr@srb2.org>
+    ${PACKAGE_GROUP_NAME_EMAIL}
 
 Copyright:
 
-    Copyright (C) 1998-2010 Sonic Team Junior
+    Copyright (C) 1998-2018 by Sonic Team Junior
 
 License:
 
@@ -21,6 +21,7 @@ License:
 The Debian packaging is:
 
     Copyright (C) 2010 Callum Dickinson <gcfreak_ag20@hotmail.com>
+    Copyright (C) 2010-2018 by Sonic Team Junior <stjr@srb2.org>
 
 and is licensed under the GPL version 2,
 see "/usr/share/common-licenses/GPL-2".
diff --git a/assets/debian/rules b/assets/debian-template/rules
old mode 100755
new mode 100644
similarity index 59%
rename from assets/debian/rules
rename to assets/debian-template/rules
index d86f92af2f81a8f63755431b95911ee2c2dfd204..c2d19922d406ea891324d8f066d40b4fee1c193a
--- a/assets/debian/rules
+++ b/assets/debian-template/rules
@@ -23,6 +23,16 @@
 #
 #############################################################################
 
+#############################################################################
+#
+# !!!!!!!!!! DEPLOYER NOTE !!!!!!!!!!
+#
+# Variables to be templated are curly-braced ${PACKAGE_INSTALL_PATH}
+# Variables used by the rules script are parenthese'd $(DATADIR)
+# See [repo-root]/debian_template.sh
+#
+#############################################################################
+
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
@@ -37,25 +47,32 @@ RM	:= rm -rf
 DIR	:= $(shell pwd)
 
 PACKAGE := $(shell cat $(DIR)/debian/control | grep 'Package:' | sed -e 's/Package: //g')
-DATAFILES := srb2.srb zones.dta player.dta rings.dta music.dta
+ARCHIVEPATH := ${ASSET_ARCHIVE_PATH}
+ARCHIVEOPTIONALPATH := ${ASSET_ARCHIVE_OPTIONAL_PATH}
+GETOPTIONALFILES := ${ASSET_FILES_OPTIONAL_GET}
 
-DATADIR	:= usr/games/SRB2
+DATADIR	:= $(shell echo "${PACKAGE_INSTALL_PATH}" | sed -e 's/^\///')
 RESOURCEDIR := .
+STAGINGDIR := $(RESOURCEDIR)/installer
 WGET	:= wget -P $(RESOURCEDIR) -c -nc
 
 build:
 	$(MKDIR) $(DIR)/debian/tmp/$(DATADIR)
-	# This will need to be updated every time SRB2 official version is
+	> $(DIR)/debian/source/include-binaries
 	# Copy data files to their install locations, and add data files to include-binaries
-	for file in $(DATAFILES); do \
-		$(WGET) http://alam.srb2.org/SRB2/2.1.14-Final/Resources/$$file; \
-		if test "$$file" = "srb2.wad"; then \
-			$(INSTALL) $(RESOURCEDIR)/$$file $(DIR)/debian/tmp/$(DATADIR)/srb2.srb; \
-		else \
-			$(INSTALL) $(RESOURCEDIR)/$$file $(DIR)/debian/tmp/$(DATADIR)/$$file; \
+	if [ ! -d $(STAGINGDIR) ]; then \
+		mkdir -p "$(STAGINGDIR)"; \
+		$(WGET) $(ARCHIVEPATH); \
+		7z x "$(RESOURCEDIR)/$(shell basename $(ARCHIVEPATH))" -aos; \
+		if [ "$(GETOPTIONALFILES)" = "1" ]; then \
+			$(WGET) $(ARCHIVEOPTIONALPATH); \
+			7z x "$(RESOURCEDIR)/$(shell basename $(ARCHIVEOPTIONALPATH))" -aos; \
 		fi; \
-		echo $(RESOURCEDIR)/$$file >> $(DIR)/debian/source/include-binaries; \
-	done
+	fi
+	# Install asset directory and add asset file to include-binaries
+	cp -vr "$(STAGINGDIR)/." "$(DIR)/debian/tmp/$(DATADIR)"
+	find "$(STAGINGDIR)" >> $(DIR)/debian/source/include-binaries
+
 
 binary-indep:
 	# Generate install folder file
@@ -95,15 +112,18 @@ binary: binary-indep
 	dh_builddeb
 
 clean:
-	$(RM) $(RESOURCEDIR)/*.wad
-	$(RM) $(RESOURCEDIR)/*.dta
-	$(RM) $(RESOURCEDIR)/*.plr
-	$(RM) $(RESOURCEDIR)/*.wpn
-	$(RM) $(RESOURCEDIR)/*.srb
-	$(RM) $(RESOURCEDIR)/*.dll
-	$(RM) $(DIR)/debian/tmp/*
-	$(RM) $(DIR)/debian/$(PACKAGE).install
-	$(RM) $(DIR)/debian/files
-	$(RM) $(DIR)/debian/source/include-binaries
+	$(RM) $(DIR)/debian/tmp/*; \
+	$(RM) $(DIR)/debian/$(PACKAGE).install; \
+	$(RM) $(DIR)/debian/files; \
+
+clean-all: clean
+	$(RM) $(RESOURCEDIR)/*.wad; \
+	$(RM) $(RESOURCEDIR)/*.dta; \
+	$(RM) $(RESOURCEDIR)/*.plr; \
+	$(RM) $(RESOURCEDIR)/*.wpn; \
+	$(RM) $(RESOURCEDIR)/*.srb; \
+	$(RM) $(RESOURCEDIR)/*.dll; \
+	$(RM) $(RESOURCEDIR)/*.txt; \
+	$(RM) $(DIR)/debian/source/include-binaries; \
 
 .PHONY: all clean binary binary-arch binary-indep build
diff --git a/assets/debian/source/format b/assets/debian-template/source/format
similarity index 100%
rename from assets/debian/source/format
rename to assets/debian-template/source/format
diff --git a/assets/debian-template/source/options b/assets/debian-template/source/options
new file mode 100644
index 0000000000000000000000000000000000000000..8b331485a6486e38af36b64be37954c2aa2579c4
--- /dev/null
+++ b/assets/debian-template/source/options
@@ -0,0 +1 @@
+tar-ignore = "tmp/*"
diff --git a/assets/debian/README.Debian b/assets/debian/README.Debian
deleted file mode 100644
index 4d9f067ac008e3b9ecba2ee390eaea563174b1cc..0000000000000000000000000000000000000000
--- a/assets/debian/README.Debian
+++ /dev/null
@@ -1,12 +0,0 @@
-srb2 for Debian
----------------
-
-SRB2 Debian package!
-Hi there, to rebuild this package just use the SRB2 Makefile system, or, optionally, run
-dpkg-buildpackage in the in /bin/Resources directory. You can build these with or without a key
-if you want, but if you want to put these on a repo, generate your own GnuPG key as per the
-https://help.ubuntu.com/community/GnuPrivacyGuardHowto instructions and pass the -k<keyid>
-command to debuild. Make sure you export the key footprint and give them to your users to install
-with apt-key add. Thanks!
-
- -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Fri, 26 Nov 2010 18:25:31 +1300
diff --git a/assets/debian/changelog b/assets/debian/changelog
deleted file mode 100644
index a316b7df73f0a0f4e865d71416464f95358d6e3f..0000000000000000000000000000000000000000
--- a/assets/debian/changelog
+++ /dev/null
@@ -1,12 +0,0 @@
-srb2-data (2.1.14~1) unstable; urgency=low
-
-  * Updated for SRB2 v2.1.14
-
- -- Alam Arias <alam+debian@srb2.org>  Sat, 6 Jan 2016 11:00:00 -0500
-
-
-srb2-data (2.0.6-2) maverick; urgency=high
-
-  * Initial proper release..
-
- -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Sat,  29 Jan 2011 01:18:42 +1300
diff --git a/bin/Mingw/Release/.gitignore b/bin/Mingw/Release/.gitignore
index 834f313e3eae612617885430c8071e6e41483d88..3458ff7648f27c14076ff2aee101446a323f5a04 100644
--- a/bin/Mingw/Release/.gitignore
+++ b/bin/Mingw/Release/.gitignore
@@ -1,3 +1,4 @@
 *.exe
 *.mo
 r_opengl.dll
+*.bat
diff --git a/bin/Resources/exchndl.dll b/bin/Resources/exchndl.dll
deleted file mode 100644
index d836a676225f6d81dbfab9f23a4c55cd57c2c063..0000000000000000000000000000000000000000
Binary files a/bin/Resources/exchndl.dll and /dev/null differ
diff --git a/bin/Resources/libgme.dll b/bin/Resources/libgme.dll
deleted file mode 100644
index ddf8b0d82e703c2ecb847ed9453f1d58bc6f7cad..0000000000000000000000000000000000000000
Binary files a/bin/Resources/libgme.dll and /dev/null differ
diff --git a/cmake/Modules/FindOPENMPT.cmake b/cmake/Modules/FindOPENMPT.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..2d334b6f04e3b262c200a0ea0f4bc0b895940879
--- /dev/null
+++ b/cmake/Modules/FindOPENMPT.cmake
@@ -0,0 +1,23 @@
+include(LibFindMacros)
+
+libfind_pkg_check_modules(OPENMPT_PKGCONF OPENMPT)
+
+find_path(OPENMPT_INCLUDE_DIR
+	NAMES libopenmpt.h
+	PATHS
+		${OPENMPT_PKGCONF_INCLUDE_DIRS}
+		"/usr/include/libopenmpt"
+		"/usr/local/include/libopenmpt"
+)
+
+find_library(OPENMPT_LIBRARY
+	NAMES openmpt
+	PATHS
+		${OPENMPT_PKGCONF_LIBRARY_DIRS}
+		"/usr/lib"
+		"/usr/local/lib"
+)
+
+set(OPENMPT_PROCESS_INCLUDES OPENMPT_INCLUDE_DIR)
+set(OPENMPT_PROCESS_LIBS OPENMPT_LIBRARY)
+libfind_process(OPENMPT)
\ No newline at end of file
diff --git a/cmake/Modules/GitUtilities.cmake b/cmake/Modules/GitUtilities.cmake
index 683cf9b6b505e788bd3139bf0513b433a3190a2e..d29e6b509dd27015f429c9e8f4de12e20fcc5b12 100644
--- a/cmake/Modules/GitUtilities.cmake
+++ b/cmake/Modules/GitUtilities.cmake
@@ -27,5 +27,17 @@ function(git_current_branch variable path)
 		OUTPUT_STRIP_TRAILING_WHITESPACE
 	)
 
+	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
+	)
+
 	set(${variable} "${output}" PARENT_SCOPE)
 endfunction()
\ No newline at end of file
diff --git a/debian-template/README.Debian b/debian-template/README.Debian
new file mode 100644
index 0000000000000000000000000000000000000000..3aa52787ea82d786d8e3fcfbc4166fbf62ce982f
--- /dev/null
+++ b/debian-template/README.Debian
@@ -0,0 +1,70 @@
+srb2 for Debian
+---------------
+
+SRB2 Debian package!
+Hi there, to rebuild these packages just use debuild in the root source directory (not /src!).
+You can build these with or without a key if you want, but if you want to put these on a repo,
+generate your own GnuPG key as per the https://help.ubuntu.com/community/GnuPrivacyGuardHowto
+instructions and pass the -k<keyid> command to debuild. Make sure you export the key footprint
+and give them to your users to install with apt-key add. Thanks!
+
+ -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Fri, 26 Nov 2010 18:25:31 +1300
+
+---------------
+
+
+Templating
+
+Note that you MUST run [repo-root]/debian_template.sh before running debuild
+on these scripts! debian_template.sh fills these template files with working values.
+
+You should also set PACKAGE_NAME_EMAIL="John Doe <jdoe@example.com>" to match
+the identity of the key you will use to sign the package.
+
+
+Building for Launchpad PPA
+
+Use these steps to prepare building a source package for Launchpad:
+
+    1. cd [repo-root]
+    2. git reset --hard; git clean -fd; git clean -fx;
+       * Resets your repo folder to a committed state and removes untracked files
+       * If you built srb2-data in the assets/ folder, MAKE SURE THAT FOLDER DOES NOT HAVE ASSETS,
+         OR THEY MAY BE INCLUDED IN THE MAIN SOURCE PACKAGE!
+
+Build the source package:
+
+    1. source [repo-root]/debian_template.sh
+       * Initializes defaults for the package variables and fills in templates.
+    2. debuild -S (builds the source package for Launchpad)
+
+
+Signing for Launchpad PPA
+
+First, follow Callum's instructions to generate a GnuPG key with your identity. You will need
+to publish the fingerprint of that key to Ubuntu's key server.
+
+    https://help.ubuntu.com/community/GnuPrivacyGuardHowto#Uploading_the_key_to_Ubuntu_keyserver
+
+Next, you will have to add that key fingerprint to your Launchpad account. Go to your Launchpad
+profile and click the yellow Edit button next to "OpenPGP keys". Once you add the key, you can
+upload signed source packages and publish them onto your PPA.
+
+IF YOU UPLOAD A PACKAGE and Launchpad does NOT send you a confirmation or rejection email, that
+means your key is not set up correctly with your Launchpad account.
+
+Finally, if your packages have not already been signed, follow these steps:
+
+    1. cd ..
+       * Packages are located in the parent folder of where debuild was called
+    2. debsign "srb2_[version]_source.changes"
+       * You may need to specify -k [key-fingerprint]
+
+
+Uploading for Launchpad PPA
+
+Follow the instructions at <https://help.launchpad.net/Packaging/PPA/Uploading> to upload
+to your PPA and have Launchpad build your binary deb packages.
+
+
+ -- Marco Zafra <marco.a.zafra@gmail.com>  Mon, 26 Nov 2018 21:13:00 -0500
diff --git a/debian/README.source b/debian-template/README.source
similarity index 92%
rename from debian/README.source
rename to debian-template/README.source
index ff2dddd41ca2c88c56206f5b26d0da2c195bf7ba..f63a42cac60d685306c740792907b39dae522b0d 100644
--- a/debian/README.source
+++ b/debian-template/README.source
@@ -22,6 +22,10 @@ Build instructions:
 
 make -C src LINUX=1
 
+Build instructions for non-X86 devices (such as X64):
+
+make -C src LINUX=1 NONX86=1
+
 Build instructions to build for Wii Linux/SRB2Wii on a PowerPC system,
 follow cross-compiling instructions for cross-compiling on a x86 system:
 
diff --git a/debian-template/changelog b/debian-template/changelog
new file mode 100644
index 0000000000000000000000000000000000000000..fb08908cdfa720a114f2d8d01cfe6ca2993bdf87
--- /dev/null
+++ b/debian-template/changelog
@@ -0,0 +1,5 @@
+${PACKAGE_NAME} (${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION}) ${PACKAGE_DISTRO}; urgency=${PACKAGE_URGENCY}
+
+  * ${PROGRAM_NAME} v${PROGRAM_VERSION} program build
+
+ -- ${PACKAGE_NAME_EMAIL}  ${__PACKAGE_DATETIME}
diff --git a/debian/compat b/debian-template/compat
similarity index 100%
rename from debian/compat
rename to debian-template/compat
diff --git a/debian-template/control b/debian-template/control
new file mode 100644
index 0000000000000000000000000000000000000000..e1348d704e3187dfc0e70a7165d467a2c6354492
--- /dev/null
+++ b/debian-template/control
@@ -0,0 +1,49 @@
+# SRB2 Debian package control file.
+
+Source: ${PACKAGE_NAME}
+Section: games
+Priority: extra
+Maintainer: ${PACKAGE_GROUP_NAME_EMAIL}
+Build-Depends: debhelper (>= 7.0.50~),
+ libsdl2-dev,
+ libsdl2-mixer-dev,
+ libpng-dev | libpng16-dev | libpng12-dev (>= 1.2.7),
+ zlib1g-dev,
+ libgme-dev,
+ libglu1-dev | libglu-dev,
+ libosmesa6-dev | libgl-dev,
+ nasm [i386]
+Standards-Version: 3.8.4
+Homepage: ${PACKAGE_WEBSITE}
+
+Package: ${PACKAGE_NAME}
+Architecture: any
+Depends: ${SHLIBS_DEPENDS}, ${MISC_DEPENDS},
+ ${PACKAGE_NAME}-data (>> ${PACKAGE_ASSET_MINVERSION}), ${PACKAGE_NAME}-data (<< ${PACKAGE_ASSET_MAXVERSION}),
+ libsdl2-2.0-0,
+ libsdl2-mixer-2.0-0,
+ zlib1g,
+ libgme0,
+ libpng | libpng16-16 | libpng12-0
+Description: A cross-platform 3D Sonic fangame
+ Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
+ fangame built using a modified version of the Doom Legacy
+ port of Doom. SRB2 is closely inspired by the original
+ Sonic games from the Sega Genesis, and attempts to recreate
+ the design in 3D. It features tons of levels, enemies, speed,
+ and quite a lot of the fun that the original Sonic games provided.
+
+
+Package: ${PACKAGE_NAME}-dbg
+Architecture: any
+# FIXME: should be Depends: ${SHLIBS_DEPENDS}, ${MISC_DEPENDS}, srb2-data (= 2.1.14), srb2 but dh_shlibdeps is being an asshat
+Depends: libc6, ${MISC_DEPENDS}, ${PACKAGE_NAME}-data (>> ${PACKAGE_ASSET_MINVERSION}), ${PACKAGE_NAME}-data (<< ${PACKAGE_ASSET_MAXVERSION}), ${PACKAGE_NAME}
+Description: A cross-platform 3D Sonic fangame
+ Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
+ fangame built using a modified version of the Doom Legacy
+ port of Doom. SRB2 is closely inspired by the original
+ Sonic games from the Sega Genesis, and attempts to recreate
+ the design in 3D. It features tons of levels, enemies, speed,
+ and quite a lot of the fun that the original Sonic games provided.
+ This is a debug binary; its symbols will be loaded by gdb
+ when the user starts the game with gdb for debugging.
diff --git a/assets/debian/copyright b/debian-template/copyright
similarity index 60%
rename from assets/debian/copyright
rename to debian-template/copyright
index 8a87051901174db490161b4b7588b2e9aae1bfbd..cc47c453bf2b0ccc6db34c57a03dfe1ce2fc75a7 100644
--- a/assets/debian/copyright
+++ b/debian-template/copyright
@@ -1,18 +1,18 @@
 This work was packaged for Debian by:
 
-    Callum Dickinson <gcfreak_ag20@hotmail.com> on Fri, 26 Nov 2010 15:19:16 +1300
+    ${PACKAGE_NAME_EMAIL}  ${__PACKAGE_DATETIME}
 
 It was downloaded from:
 
-    <http://srb2.org>
+    ${PACKAGE_WEBSITE}
 
 Upstream Author(s):
 
-    Sonic Team Junior <stjr@srb2.org>
+    ${PACKAGE_GROUP_NAME_EMAIL}
 
 Copyright:
 
-    Copyright (C) 1998-2010 Sonic Team Junior
+    Copyright (C) 1998-2018 by Sonic Team Junior
 
 License:
 
@@ -21,6 +21,7 @@ License:
 The Debian packaging is:
 
     Copyright (C) 2010 Callum Dickinson <gcfreak_ag20@hotmail.com>
+    Copyright (C) 2010-2018 by Sonic Team Junior <stjr@srb2.org>
 
 and is licensed under the GPL version 2,
 see "/usr/share/common-licenses/GPL-2".
diff --git a/debian-template/docs b/debian-template/docs
new file mode 100644
index 0000000000000000000000000000000000000000..dba2cd4c89e5439e9c8f0fddeae28dfaaf62dbf3
--- /dev/null
+++ b/debian-template/docs
@@ -0,0 +1,4 @@
+README.md
+assets/README.txt
+assets/LICENSE.txt
+assets/LICENSE-3RD-PARTY.txt
diff --git a/debian/rules b/debian-template/rules
old mode 100755
new mode 100644
similarity index 76%
rename from debian/rules
rename to debian-template/rules
index e49784a0f21925400271c273bd547a1ffac93faf..0a77624cb490639564b0212abc902dbfdda88be5
--- a/debian/rules
+++ b/debian-template/rules
@@ -23,6 +23,16 @@
 #
 #############################################################################
 
+#############################################################################
+#
+# !!!!!!!!!! DEPLOYER NOTE !!!!!!!!!!
+#
+# Variables to be templated are curly-braced ${PACKAGE_INSTALL_PATH}
+# Variables used by the rules script are parenthese'd $(PKGDIR)
+# See [repo-root]/debian_template.sh
+#
+#############################################################################
+
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
@@ -50,28 +60,40 @@ DIR	:= $(shell pwd)
 
 # FIXME: hate hate hate head/tail hack :(
 CONTROLF = $(DIR)/debian/control
-PACKAGE  = srb2
-DBGPKG   = $(PACKAGE)-dbg
-TITLE	= Sonic Robo Blast 2
+PACKAGE  = ${PACKAGE_NAME}
+DBGPKG   = ${PACKAGE}-dbg
+TITLE	= ${PROGRAM_NAME}
 SECTION = Games/Action
-EXENAME = srb2
+EXENAME = ${PROGRAM_FILENAME}
 DBGNAME	= debug/$(EXENAME)
 
-PKGDIR	= usr/games
+PKGDIR	= $(shell echo "${PACKAGE_INSTALL_PATH}" | sed -e 's/^\///')
 DBGDIR	= usr/lib/debug/$(PKGDIR)
+LINKDIR = $(shell echo "${PACKAGE_LINK_PATH}" | sed -e 's/^\///')
 PIXMAPS_DIR = usr/share/pixmaps
 DESKTOP_DIR = usr/share/applications
 PREFIX	= $(shell test "$(CROSS_COMPILE_BUILD)" != "$(CROSS_COMPILE_HOST)" && echo "PREFIX=$(CROSS_COMPILE_HOST)")
 OS	= LINUX=1
 NONX86	= $(shell test "`echo $(CROSS_COMPILE_HOST) | grep 'i[3-6]86'`" || echo "NONX86=1")
-MAKEARGS = $(OS) $(NONX86) $(PREFIX) EXENAME=$(EXENAME) DBGNAME=$(DBGNAME) SDL_PKGCONFIG=sdl2 PNG_PKGCONFIG=libpng NOOBJDUMP=1
+MAKEARGS = $(OS) $(NONX86) $(PREFIX) EXENAME=$(EXENAME) DBGNAME=$(DBGNAME) NOOBJDUMP=1 # SDL_PKGCONFIG=sdl2 PNG_PKGCONFIG=libpng
 MENUFILE1 = ?package($(PACKAGE)):needs="X11" section="$(SECTION)"
 MENUFILE2 = title="$(TITLE)" command="/$(PKGDIR)/$(PACKAGE)"
-# FIXME pkg-config dir hacks
-export PKG_CONFIG_LIBDIR = /usr/lib/$(CROSS_COMPILE_HOST)/pkgconfig
 BINDIR :=  $(DIR)/bin/Linux/Release
+
+# FIXME pkg-config dir hacks
+# Launchpad doesn't need this; it actually makes i386 builds fail due to cross-compile
+# export PKG_CONFIG_LIBDIR = /usr/lib/$(CROSS_COMPILE_HOST)/pkgconfig
 LDFLAGS += "-Wl,-rpath=/usr/lib/$(CROSS_COMPILE_HOST)"
 
+# Some libgme-dev packages don't use pkg-config yet, so include the linker flag ourselves
+PKG_CONFIG?=pkg-config
+LIBGME_PKGCONFIG?=libgme
+LIBGME_LDFLAGS?=$(shell $(PKG_CONFIG) $(LIBGME_PKGCONFIG) --libs)
+
+ifeq ($(LIBGME_LDFLAGS),)
+MAKEARGS += LIBGME_LDFLAGS=-lgme
+endif
+
 build:
 	$(MKDIR) $(BINDIR)/debug
 	$(MAKE) -C $(DIR)/src $(MAKEARGS)
@@ -90,8 +112,8 @@ binary-arch:
 	$(INSTALL) $(BINDIR)/$(EXENAME) $(DIR)/debian/tmp/$(PKGDIR)/$(PACKAGE)
 	$(INSTALL) $(BINDIR)/$(DBGNAME) $(DIR)/debian/tmp/$(DBGDIR)/$(PACKAGE)
 	# Install desktop file and banner image
-	$(INSTALL) $(DIR)/srb2.png $(DIR)/debian/tmp/usr/share/pixmaps
-	$(INSTALL) $(DIR)/debian/srb2.desktop $(DIR)/debian/tmp/usr/share/applications
+	$(INSTALL) $(DIR)/srb2.png $(DIR)/debian/tmp/usr/share/pixmaps/${PROGRAM_FILENAME}.png
+	$(INSTALL) $(DIR)/debian/srb2.desktop $(DIR)/debian/tmp/usr/share/applications/${PROGRAM_FILENAME}.desktop
 	# add compiled binaries to include-binaries
 	echo $(BINDIR)/$(EXENAME) >> $(DIR)/debian/source/include-binaries
 	echo $(BINDIR)/$(EXENAME) >> $(DIR)/debian/source/include-binaries
@@ -100,8 +122,8 @@ binary-arch:
 	echo $(DESKTOP_DIR) >> $(DIR)/debian/$(PACKAGE).install
 	echo $(PIXMAPS_DIR) >> $(DIR)/debian/$(PACKAGE).install
 	echo $(DBGDIR) > $(DIR)/debian/$(DBGPKG).install
-
-binary: binary-arch
+# Launchpad only calls binary-arch, so just move everything up
+#binary: binary-arch
 	# Generate .desktop specifications
 	echo "`echo '$(MENUFILE1)\\'`" > $(DIR)/debian/menu
 	echo " `echo '$(MENUFILE2)'`" >> $(DIR)/debian/menu
@@ -122,7 +144,7 @@ binary: binary-arch
 	# dh_installcron
 	# dh_installinfo
 	# dh_installman
-	# dh_link
+	dh_link $(PKGDIR)/$(EXENAME) $(LINKDIR)/$(EXENAME)
 	dh_compress
 	dh_fixperms
 	# dh_perl
@@ -133,6 +155,8 @@ binary: binary-arch
 	dh_md5sums
 	dh_builddeb
 
+binary: binary-arch
+
 clean:
 	$(MAKE) -C $(DIR)/src $(MAKEARGS) clean cleandep
 	$(RM) $(BINDIR)/*
@@ -145,4 +169,4 @@ clean:
 	$(RM) $(DIR)/debian/files
 	$(RM) $(DIR)/debian/source/include-binaries
 
-.PHONY: all clean binary binary-arch binary-indep build
+.PHONY: all clean binary binary-indep build
diff --git a/debian/source/format b/debian-template/source/format
similarity index 100%
rename from debian/source/format
rename to debian-template/source/format
diff --git a/debian-template/source/options b/debian-template/source/options
new file mode 100644
index 0000000000000000000000000000000000000000..1ef771ddf4a618e4594d324addc38a0f3e401412
--- /dev/null
+++ b/debian-template/source/options
@@ -0,0 +1,10 @@
+tar-ignore = "assets/*.srb"
+tar-ignore = "assets/*.pk3"
+tar-ignore = "assets/*.dta"
+tar-ignore = "assets/*.wad"
+tar-ignore = "assets/debian/${PACKAGE_NAME}-data/*"
+tar-ignore = "assets/debian/tmp/*"
+tar-ignore = "*.obj"
+tar-ignore = "*.dep"
+tar-ignore = ".git/*"
+tar-ignore = ".git*"
diff --git a/debian-template/srb2.desktop b/debian-template/srb2.desktop
new file mode 100644
index 0000000000000000000000000000000000000000..07c7906e05c5b0b1690c5be817140ad5b628e341
--- /dev/null
+++ b/debian-template/srb2.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=${PROGRAM_NAME}
+Comment=${PROGRAM_DESCRIPTION}
+Encoding=UTF-8
+Exec=${PACKAGE_INSTALL_PATH}/${PROGRAM_FILENAME}
+Icon=/usr/share/pixmaps/${PROGRAM_FILENAME}.png
+Terminal=false
+Type=Application
+StartupNotify=false
+Categories=Application;Game;
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index bbc306b163b7bd8b9b5864f1542b54fccacc47cd..0000000000000000000000000000000000000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,11 +0,0 @@
-srb2 for Debian
----------------
-
-SRB2 Debian package!
-Hi there, to rebuild these packages just use debuild in the root source directory (not /src!).
-You can build these with or without a key if you want, but if you want to put these on a repo,
-generate your own GnuPG key as per the https://help.ubuntu.com/community/GnuPrivacyGuardHowto
-instructions and pass the -k<keyid> command to debuild. Make sure you export the key footprint
-and give them to your users to install with apt-key add. Thanks!
-
- -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Fri, 26 Nov 2010 18:25:31 +1300
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index b454b1abdd5405d131b5daeb091c73526b16af28..0000000000000000000000000000000000000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-srb2 (2.0.6-5) maverick; urgency=high
-
-  * Initial proper release..
-
- -- Callum Dickinson <gcfreak_ag20@hotmail.com>  Sat, 29 Jan 2011 01:18:42 +1300
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 63b075f17d9eaa8c18680629cc9fab1cf0652ce3..0000000000000000000000000000000000000000
--- a/debian/control
+++ /dev/null
@@ -1,42 +0,0 @@
-# SRB2 Debian package control file.
-
-Source: srb2
-Section: games
-Priority: extra
-Maintainer: Callum Dickinson <gcfreak_ag20@hotmail.com>
-Build-Depends: debhelper (>= 7.0.50~),
- libsdl2-dev,
- libsdl2-mixer-dev,
- libpng12-dev (>= 1.2.7),
- libglu1-dev | libglu-dev,
- libosmesa6-dev | libgl-dev,
- nasm [i386]
-Standards-Version: 3.8.4
-Homepage: http://www.srb2.org
-
-Package: srb2
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, srb2-data (= 2.1.14)
-Description: A cross-platform 3D Sonic fangame
- Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
- fangame built using a modified version of the Doom Legacy
- port of Doom. SRB2 is closely inspired by the original
- Sonic games from the Sega Genesis, and attempts to recreate
- the design in 3D. While SRB2 isn't fully completed, it already
- features tons of levels, enemies, speed, and quite a lot
- of the fun that the original Sonic games provided.
-
-Package: srb2-dbg
-Architecture: any
-# FIXME: should be Depends: ${shlibs:Depends}, ${misc:Depends}, srb2-data (= 2.1.14), srb2 but dh_shlibdeps is being an asshat
-Depends: libc6, ${misc:Depends}, srb2-data (= 2.1.14), srb2
-Description: A cross-platform 3D Sonic fangame
- Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog
- fangame built using a modified version of the Doom Legacy
- port of Doom. SRB2 is closely inspired by the original
- Sonic games from the Sega Genesis, and attempts to recreate
- the design in 3D. While SRB2 isn't fully completed, it already
- features tons of levels, enemies, speed, and quite a lot
- of the fun that the original Sonic games provided.
- This is a debug binary, its symbols will be loaded by gdb
- when the user starts the game with gdb for debugging.
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index b43bf86b50fd8d3529a0dc062c30006ed38f309e..0000000000000000000000000000000000000000
--- a/debian/docs
+++ /dev/null
@@ -1 +0,0 @@
-README.md
diff --git a/debian/srb2.desktop b/debian/srb2.desktop
deleted file mode 100644
index 661832b93d4364c8011e08c841df415b6131553e..0000000000000000000000000000000000000000
--- a/debian/srb2.desktop
+++ /dev/null
@@ -1,10 +0,0 @@
-[Desktop Entry]
-Name=Sonic Robo Blast 2
-Comment=A free 3D Sonic the Hedgehog fan-game built using a modified ver. of the Doom Legacy source port
-Encoding=UTF-8
-Exec=srb2
-Icon=/usr/share/pixmaps/srb2.png
-Terminal=false
-Type=Application
-StartupNotify=false
-Categories=Application;Game;
diff --git a/debian_template.sh b/debian_template.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c1af3c19f0a3a66e46389b6a0ec67d9b43c11cfb
--- /dev/null
+++ b/debian_template.sh
@@ -0,0 +1,166 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# Debian package templating
+#
+# Call this script BEFORE running debuild!
+# source ./debian_template.sh [clean] [main/asset]
+#
+# Before running this script,
+# you should also set PACKAGE_NAME_EMAIL="John Doe <jdoe@example.com>" to match
+# the identity of the key you will use to sign the package.
+#
+
+# Get script's actual path
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
+
+# Recursive function for directory crawling
+# $1 = Directory root to crawl
+# $2 = Code to eval on file
+# $3 = Code to eval on directory
+# Exposes $dirtails, $dirlevel, and $dirtailname
+dirlevel=0 # initialize
+dirtails=()
+
+# Utility function to make dira/dirb/dirc string
+makedirtailname () {
+	dirtailname=""
+	for tail in $dirtails; do
+		if [[ "$dirtailname" == "" ]]; then
+			dirtailname="/$tail";
+		else
+			dirtailname="$dirtailname/$tail";
+		fi;
+	done;
+}
+
+evaldirectory () {
+	if [ -d "$1" ]; then
+		# Set contextual variables
+		# dirtails is an array of directory basenames after the crawl root
+		if (( $dirlevel > 0 )); then
+			dirtails+=( "$(basename $1)" );
+		else
+			dirtails=();
+		fi;
+		dirlevel=$((dirlevel+1));
+
+		# Generate directory path after the crawl root
+		makedirtailname;
+
+		# Eval our directory with the latest contextual info
+		# Don't eval on root
+		if (( $dirlevel > 1 )) && [[ "$3" != "" ]]; then
+			eval "$3";
+		fi;
+
+		# Iterate entries
+		for name in $1/*; do
+			if [ -d "$name" ]; then
+				# Name is a directory, but don't eval yet
+				# Recurse so our vars are updated
+				evaldirectory "$name" "$2" "$3";
+
+				# Decrement our directory level and remove a dirtail
+				unset 'dirtails[ ${#dirtails[@]}-1 ]';
+				dirlevel=$((dirlevel-1));
+				makedirtailname;
+			else
+				# Name is a file
+				if [ -f "$name" ] && [[ "$2" != "" ]]; then
+					eval "$2";
+				fi;
+			fi;
+		done;
+
+		# Reset our variables; we're done iterating
+		if (( $dirlevel == 1 )); then
+			dirlevel=0;
+		fi;
+	fi;
+}
+
+#
+# Initialize package parameter defaults
+#
+if [[ "$__DEBIAN_PARAMETERS_INITIALIZED" != "1" ]]; then
+	. ${DIR}/deployer/travis/deployer_defaults.sh;
+fi;
+
+# Clean up after ourselves; we only expect to run this script once
+# during buildboting
+__DEBIAN_PARAMETERS_INITIALIZED=0
+
+# for envsubst
+export __PACKAGE_DATETIME="$(date '+%a, %d %b %Y %H:%M:%S %z')"
+export __PACKAGE_DATETIME_DIGIT="$(date -u '+%Y%m%d%H%M%S')"
+
+if [[ "$PACKAGE_REVISION" == "" ]]; then
+	PACKAGE_REVISION="-$__PACKAGE_DATETIME_DIGIT";
+	__PACKAGE_REVISION_BY_DATE=1;
+	export PACKAGE_REVISION=${PACKAGE_REVISION}; # for envsubst
+fi;
+
+#
+# Clean the old debian/ directories
+#
+if [[ "$1" == "clean" ]]; then
+	toclean=$2;
+else
+	toclean=$1;
+fi;
+
+if [[ "$toclean" == "" ]] || [[ "$toclean" == "main" ]]; then
+	echo "Cleaning main package scripts";
+	if [[ ! -f ${DIR}/debian ]]; then
+		rm -rf ${DIR}/debian;
+	fi;
+fi;
+if [[ "$toclean" == "" ]] || [[ "$toclean" == "asset" ]]; then
+	echo "Cleaning asset package scripts";
+	if [[ ! -f ${DIR}/assets/debian ]]; then
+		rm -rf ${DIR}/assets/debian;
+	fi;
+fi;
+
+#
+# Make new templates
+#
+if [[ "$1" != "clean" ]]; then
+	totemplate=$1;
+
+	# HACK: ${shlibs:Depends} in the templates make the templating fail
+	# So just define replacemment variables
+	export SHLIBS_DEPENDS=${SHLIBS_DEPENDS};
+	export MISC_DEPENDS=${MISC_DEPENDS};
+	export DEBFILEVAR='$$file'; # used in assets/debian/rules
+
+	# Package parameters are exported for envsubst in deployer_defaults.sh
+
+	if [[ "$totemplate" == "" ]] || [[ "$totemplate" == "main" ]]; then
+		echo "Generating main package scripts";
+		fromroot=${DIR}/debian-template;
+		toroot=${DIR}/debian;
+		mkdir ${toroot};
+
+		evaldirectory ${fromroot} \
+			"cat \$name | envsubst > ${toroot}\${dirtailname}/\$( basename \$name )" \
+			"mkdir \"${toroot}\${dirtailname}\"";
+	fi;
+
+	if [[ "$totemplate" == "" ]] || [[ "$totemplate" == "asset" ]]; then
+		echo "Generating asset package scripts";
+		fromroot=${DIR}/assets/debian-template;
+		toroot=${DIR}/assets/debian;
+		mkdir ${toroot};
+
+		# Root dir to crawl; file eval; directory eval
+		evaldirectory ${fromroot} \
+			"cat \$name | envsubst > ${toroot}\${dirtailname}/\$( basename \$name )" \
+			"mkdir \"${toroot}\${dirtailname}\"";
+	fi;
+fi;
+
+if [[ "$__DPL_ACTIVE" != "1" ]] && [[ "$__PACKAGE_REVISION_BY_DATE" == "1" ]]; then
+	unset PACKAGE_REVISION; # so we can reset the date on subsequent runs
+fi;
diff --git a/deployer/appveyor/deployer.bat b/deployer/appveyor/deployer.bat
new file mode 100644
index 0000000000000000000000000000000000000000..fae388590f1603ea87586192a392fa1658e668bf
--- /dev/null
+++ b/deployer/appveyor/deployer.bat
@@ -0,0 +1,195 @@
+@setlocal enableextensions enabledelayedexpansion
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Appveyor Deployer
+: See appveyor.yml for default variables
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Evaluate whether we should be deploying
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+if not [%DPL_ENABLED%] == [1] (
+    echo Deployer is not enabled...
+    exit /b
+)
+
+: Don't do DD installs because fmodex DLL handling is not implemented.
+if [%CONFIGURATION%] == [DD] (
+    echo Deployer does not support DD builds...
+    exit /b
+)
+
+if [%CONFIGURATION%] == [DD64] (
+    echo Deployer does not support DD builds...
+    exit /b
+)
+
+: Substring match from https://stackoverflow.com/questions/7005951/batch-file-find-if-substring-is-in-string-not-in-a-file
+: The below line says "if deployer is NOT in string"
+: Note that APPVEYOR_REPO_BRANCH for pull request builds is the BASE branch that PR is merging INTO
+if x%APPVEYOR_REPO_BRANCH:deployer=%==x%APPVEYOR_REPO_BRANCH% (
+    if not [%APPVEYOR_REPO_TAG%] == [true] (
+        echo Deployer is enabled but we are not in a release tag or a 'deployer' branch...
+        exit /b
+    ) else (
+        if not [%DPL_TAG_ENABLED%] == [1] (
+            echo Deployer is not enabled for release tags...
+            exit /b
+        )
+    )
+)
+
+: Release tags always get optional assets (music.dta)
+if [%APPVEYOR_REPO_TAG%] == [true] (
+    set "ASSET_FILES_OPTIONAL_GET=1"
+)
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Get asset archives
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+if exist "C:\Users\appveyor\srb2_cache\archives\" (
+    if [%ASSET_CLEAN%] == [1] (
+        echo Cleaning asset archives...
+        rmdir /s /q "C:\Users\appveyor\srb2_cache\archives"
+    )
+)
+
+if not exist "C:\Users\appveyor\srb2_cache\archives\" mkdir "C:\Users\appveyor\srb2_cache\archives"
+
+goto EXTRACT_ARCHIVES
+
+::::::::::::::::::::::::::::::::
+: ARCHIVE_NAME_PARTS
+: Call this like a function. %archivepath% is the path to extract parts from.
+::::::::::::::::::::::::::::::::
+
+for %%a in (%archivepath%) do (
+    set "file=%%~fa"
+    set "filepath=%%~dpa"
+    set "filename=%%~nxa"
+)
+
+set "localarchivepath=C:\Users\appveyor\srb2_cache\archives\%filename%"
+
+goto EOF
+
+::::::::::::::::::::::::::::::::
+: EXTRACT_ARCHIVES
+::::::::::::::::::::::::::::::::
+
+set "archivepath=%ASSET_ARCHIVE_PATH%"
+call :ARCHIVE_NAME_PARTS
+set "ASSET_ARCHIVE_PATH_LOCAL=%localarchivepath%"
+if not exist "%localarchivepath%" appveyor DownloadFile "%ASSET_ARCHIVE_PATH%" -FileName "%localarchivepath%"
+
+set "archivepath=%ASSET_ARCHIVE_PATCH_PATH%"
+call :ARCHIVE_NAME_PARTS
+set "ASSET_ARCHIVE_PATCH_PATH_LOCAL=%localarchivepath%"
+if not exist "%localarchivepath%" appveyor DownloadFile "%ASSET_ARCHIVE_PATCH_PATH%" -FileName "%localarchivepath%"
+
+if not [%X86_64%] == [1] (
+    set "archivepath=%ASSET_ARCHIVE_X86_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_ARCHIVE_X86_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_ARCHIVE_X86_PATH%" -FileName "!localarchivepath!"
+)
+
+if [%X86_64%] == [1] (
+    set "archivepath=%ASSET_ARCHIVE_X64_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_ARCHIVE_X64_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_ARCHIVE_X64_PATH%" -FileName "!localarchivepath!"
+)
+
+if [%ASSET_FILES_OPTIONAL_GET%] == [1] (
+    set "archivepath=%ASSET_ARCHIVE_OPTIONAL_PATH%"
+    call :ARCHIVE_NAME_PARTS
+    set "ASSET_ARCHIVE_OPTIONAL_PATH_LOCAL=!localarchivepath!"
+    if not exist "!localarchivepath!" appveyor DownloadFile "%ASSET_ARCHIVE_OPTIONAL_PATH%" -FileName "!localarchivepath!"
+)
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: Build the installers
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+mkdir "assets\installer"
+mkdir "assets\patch"
+
+7z x -y "%ASSET_ARCHIVE_PATH_LOCAL%" -o"assets\installer" >null
+7z x -y "%ASSET_ARCHIVE_PATCH_PATH_LOCAL%" -o"assets\patch" >null
+
+: Copy optional files to full installer (music.dta)
+if [%ASSET_FILES_OPTIONAL_GET%] == [1] (
+    7z x -y "%ASSET_ARCHIVE_OPTIONAL_PATH_LOCAL%" -o"assets\installer" >null
+)
+
+: Copy EXE -- BUILD_PATH is from appveyor.yml
+robocopy /S /ns /nc /nfl /ndl /np /njh /njs "%BUILD_PATH%" "assets\installer" /XF "*.debug" ".gitignore"
+robocopy /S /ns /nc /nfl /ndl /np /njh /njs "%BUILD_PATH%" "assets\patch" /XF "*.debug" ".gitignore"
+
+: Are we building DD? (we were supposed to exit earlier!)
+if [%CONFIGURATION%] == [DD] ( set "DPL_INSTALLER_NAME=%DPL_INSTALLER_NAME%-DD" )
+if [%CONFIGURATION%] == [DD64] ( set "DPL_INSTALLER_NAME=%DPL_INSTALLER_NAME%-DD" )
+
+: If we are not a release tag, suffix the filename
+if not [%APPVEYOR_REPO_TAG%] == [true] (
+    set "INSTALLER_SUFFIX=-%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%"
+) else (
+    set "INSTALLER_SUFFIX="
+)
+
+if not [%X86_64%] == [1] ( goto X86_INSTALL )
+
+::::::::::::::::::::::::::::::::
+: X64_INSTALL
+::::::::::::::::::::::::::::::::
+
+: Extract DLL binaries
+7z x -y "%ASSET_ARCHIVE_X64_PATH_LOCAL%" -o"assets\installer" >null
+if [%PACKAGE_PATCH_DLL_GET%] == [1] (
+    7z x -y "!ASSET_ARCHIVE_X64_PATH_LOCAL!" -o"assets\patch" >null
+)
+
+: Build the installer
+7z a -sfx7z.sfx "%DPL_INSTALLER_NAME%-x64-Installer%INSTALLER_SUFFIX%.exe" .\assets\installer\*
+
+: Build the patch
+7z a "%DPL_INSTALLER_NAME%-x64-Patch%INSTALLER_SUFFIX%.zip" .\assets\patch\*
+
+: Upload artifacts
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-x64-Installer%INSTALLER_SUFFIX%.exe"
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-x64-Patch%INSTALLER_SUFFIX%.zip"
+
+: We only do x86 OR x64, one at a time, so exit now.
+goto EOF
+
+::::::::::::::::::::::::::::::::
+: X86_INSTALL
+::::::::::::::::::::::::::::::::
+
+: Extract DLL binaries
+7z x -y "%ASSET_ARCHIVE_X86_PATH_LOCAL%" -o"assets\installer" >null
+if [%PACKAGE_PATCH_DLL_GET%] == [1] (
+    7z x -y "!ASSET_ARCHIVE_X86_PATH_LOCAL!" -o"assets\patch" >null
+)
+
+: Build the installer
+7z a -sfx7z.sfx "%DPL_INSTALLER_NAME%-Installer%INSTALLER_SUFFIX%.exe" .\assets\installer\*
+
+: Build the patch
+7z a "%DPL_INSTALLER_NAME%-Patch%INSTALLER_SUFFIX%.zip" .\assets\patch\*
+
+: Upload artifacts
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-Installer%INSTALLER_SUFFIX%.exe"
+appveyor PushArtifact "%DPL_INSTALLER_NAME%-Patch%INSTALLER_SUFFIX%.zip"
+
+: We only do x86 OR x64, one at a time, so exit now
+goto EOF
+
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+: EOF
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+
+endlocal
diff --git a/deployer/travis/deployer.sh b/deployer/travis/deployer.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c88155d217956def89f50fe5673b56a9b5fb35c6
--- /dev/null
+++ b/deployer/travis/deployer.sh
@@ -0,0 +1,157 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# Initialization
+#
+# Performs validity checks to ensure that Deployer is allowed to run
+# e.g., is an FTP hostname specified? Are we whitelisted by OSNAMES and BRANCHES?
+#
+# Set these environment variables in your Travis-CI settings, where they are stored securely.
+# See other shell scripts for more options.
+#
+# DPL_ENABLED = 1                       (leave blank to disable)
+# DPL_TAG_ENABLED = 1                   (run Deployer on all tags)
+# DPL_JOB_ENABLE_ALL = 1                (run Deployer on all jobs; leave blank to act on specific jobs, see below)
+# DPL_JOBNAMES = name1,name2            (whitelist of job names to allow uploading; leave blank to upload from all jobs)
+# DPL_OSNAMES = osx                     (whitelist of OS names to allow uploading; leave blank to upload from all OSes)
+# DPL_BRANCHES = master,branch1,branch2 (whitelist of branches to upload; leave blank to upload all branches)
+#
+# To enable Deployer on specific jobs, set _DPL_JOB_ENABLED=1 for that job. Example:
+# - matrix:
+#   - os: osx
+#     env:
+#     - _DPL_JOB_ENABLED=1
+#
+# DO NOT set __DPL_ACTIVE, because that would bypass these validity checks.
+
+# Validate Deployer state
+if [[ "$DPL_ENABLED" == "1" ]] && [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
+    # Test for base eligibility:
+    # Are we in a deployer branch? Or
+    # Are we in a release tag AND DPL_TAG_ENABLED=1?
+    if [[ $TRAVIS_BRANCH == *"deployer"* ]]; then
+        __DPL_BASE_ELIGIBLE=1;
+        __DPL_TERMINATE_EARLY_ELIGIBLE=1;
+    fi;
+
+    if [[ "$TRAVIS_TAG" != "" ]] && [[ "$DPL_TAG_ENABLED" == "1" ]]; then
+        __DPL_BASE_ELIGIBLE=1;
+        __DPL_TAG_ELIGIBLE=1;
+        __DPL_TERMINATE_EARLY_ELIGIBLE=1;
+    fi;
+
+    # Logging message for trigger word
+    if [[ "$__DPL_TAG_ELIGIBLE" != "1" ]] && [[ "$DPL_TRIGGER" != "" ]]; then
+        echo "Testing for trigger $DPL_TRIGGER, commit message: $TRAVIS_COMMIT_MESSAGE";
+        echo "[${DPL_TRIGGER}]";
+        echo "[${DPL_TRIGGER}-${_DPL_JOB_NAME}]";
+        echo "[${DPL_TRIGGER}-${TRAVIS_OS_NAME}]";
+    fi;
+
+    #
+    # Search for the trigger word
+    # Force enable if release tags are eligible
+    #
+    if [[ "$__DPL_TAG_ELIGIBLE" == "1" ]] || [[ "$DPL_TRIGGER" == "" ]] \
+    || [[ $TRAVIS_COMMIT_MESSAGE == *"[$DPL_TRIGGER]"* ]] \
+    || [[ $TRAVIS_COMMIT_MESSAGE == *"[${DPL_TRIGGER}-${_DPL_JOB_NAME}]"* ]] \
+    || [[ $TRAVIS_COMMIT_MESSAGE == *"[${DPL_TRIGGER}-${TRAVIS_OS_NAME}]"* ]]; then
+        #
+        # Whitelist by branch name
+        # Force enable if release tags are eligible
+        #
+        if [[ "$__DPL_TAG_ELIGIBLE" == "1" ]] || [[ "$DPL_BRANCHES" == "" ]] || [[ $DPL_BRANCHES == *"$TRAVIS_BRANCH"* ]]; then
+            # Set this so we only early-terminate builds when we are specifically deploying
+            # Trigger string and branch are encompassing conditions; the rest are job-specific
+            # This check only matters for deployer branches and when DPL_TERMINATE_TESTS=1,
+            # because we're filtering non-deployer jobs.
+            #
+            # __DPL_TRY_TERMINATE_EARLY is invalidated in .travis.yml if __DPL_ACTIVE=1
+            if [[ "$__DPL_TERMINATE_EARLY_ELIGIBLE" == "1" ]] && [[ "$DPL_TERMINATE_TESTS" == "1" ]]; then
+                __DPL_TRY_TERMINATE_EARLY=1;
+            fi;
+
+            #
+            # Is the job enabled for deployment?
+            #
+            if [[ "$DPL_JOB_ENABLE_ALL" == "1" ]] || [[ "$_DPL_JOB_ENABLED" == "1" ]]; then
+                #
+                # Whitelist by job names
+                #
+                if [[ "$DPL_JOBNAMES" == "" ]] || [[ "$_DPL_JOB_NAME" == "" ]] || [[ $DPL_JOBNAMES == *"$_DPL_JOB_NAME"* ]]; then
+                    #
+                    # Whitelist by OS names
+                    #
+                    if [[ "$DPL_OSNAMES" == "" ]] || [[ $DPL_OSNAMES == *"$TRAVIS_OS_NAME"* ]]; then
+                        # Base Deployer is eligible for becoming active
+
+                        # Are we building for Linux?
+                        if [[ "$_DPL_PACKAGE_BINARY" == "1" ]] || [[ "$_DPL_PACKAGE_SOURCE" == "1" ]]; then
+                            if [[ "$_DPL_PACKAGE_MAIN" == "1" ]] || [[ "$_DPL_PACKAGE_ASSET" == "1" ]]; then
+                                if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
+                                    __DPL_DEBIAN_ACTIVE=1;
+                                fi;
+                            fi;
+                        fi;
+
+                        # Now check for deployment targets
+                        if [[ "$_DPL_FTP_TARGET" == "1" ]] && [[ "$DPL_FTP_HOSTNAME" != "" ]]; then
+                            if [[ "$TRAVIS_OS_HOST" == "linux" ]] && [[ "$DPL_FTP_PROTOCOL" == "ftp" ]]; then
+                                echo "Non-secure FTP will not work on Linux Travis-CI jobs!";
+                                echo "Try SFTP or another target. Details:";
+                                echo "https://blog.travis-ci.com/2018-07-23-the-tale-of-ftp-at-travis-ci";
+                            else
+                                if [[ "$__DPL_DEBIAN_ACTIVE" == "1" ]] || [[ "$_DPL_PACKAGE_BINARY" == "1" ]] || [[ "$_DPL_BINARY" == "1" ]]; then
+                                    echo "Deployer FTP target is enabled";
+                                    __DPL_FTP_ACTIVE=1;
+                                else
+                                    echo "Deployer FTP target cannot be enabled: You must specify _DPL_PACKAGE_BINARY=1,";
+                                    echo "and/or _DPL_BINARY=1 in your job's environment variables.";
+                                fi;
+                            fi;
+                        fi;
+
+                        if [[ "$_DPL_DPUT_TARGET" == "1" ]] && [[ "$__DPL_DEBIAN_ACTIVE" == "1" ]] \
+                        && [[ "$DPL_DPUT_INCOMING" != "" ]]; then
+                            if [[ "$DPL_DPUT_METHOD" == "ftp" ]]; then
+                                echo "DPUT will not work with non-secure FTP on Linux Travis-CI jobs!";
+                                echo "Try SFTP or another method for DPUT. Details:";
+                                echo "https://blog.travis-ci.com/2018-07-23-the-tale-of-ftp-at-travis-ci";
+                            else
+                                echo "Deployer DPUT target is enabled";
+                                __DPL_DPUT_ACTIVE=1;
+                            fi;
+                        fi;
+
+                        # If any deployment targets are active, then so is the Deployer at large
+                        if [[ "$__DPL_FTP_ACTIVE" == "1" ]] || [[ "$__DPL_DPUT_ACTIVE" == "1" ]]; then
+                            __DPL_ACTIVE=1;
+                        fi;
+                    fi;
+                fi;
+            fi;
+        fi;
+    else
+        if [[ "$DPL_TRIGGER" != "" ]]; then
+            echo "Testing for global trigger [$DPL_TRIGGER, commit message: $TRAVIS_COMMIT_MESSAGE";
+        fi;
+        if [[ "$DPL_TRIGGER" != "" ]] && [[ $TRAVIS_COMMIT_MESSAGE == *"[$DPL_TRIGGER"* ]]; then
+            if [[ "$__DPL_TAG_ELIGIBLE" == "1" ]] || [[ "$DPL_BRANCHES" == "" ]] || [[ $DPL_BRANCHES == *"$TRAVIS_BRANCH"* ]]; then
+                # This check only matters for deployer branches and when DPL_TERMINATE_TESTS=1,
+                # because we're filtering non-deployer jobs.
+                if [[ "$__DPL_TERMINATE_EARLY_ELIGIBLE" == "1" ]] && [[ "$DPL_TERMINATE_TESTS" == "1" ]]; then
+                    # Assume that some job received the trigger, so mark this for early termination
+                    __DPL_TRY_TERMINATE_EARLY=1;
+                fi;
+            fi;
+        fi;
+    fi;
+fi;
+
+if [[ "$__DPL_TRY_TERMINATE_EARLY" == "1" ]] && [[ "$__DPL_ACTIVE" != "1" ]]; then
+    echo "Deployer is active in another job";
+fi;
+
+if [[ "$__DPL_TRY_TERMINATE_EARLY" != "1" ]] && [[ "$__DPL_ACTIVE" != "1" ]]; then
+    echo "Deployer is not active";
+fi;
diff --git a/deployer/travis/deployer_build.sh b/deployer/travis/deployer_build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..3817f025de4151d573222d7f980f9e1e0d758702
--- /dev/null
+++ b/deployer/travis/deployer_build.sh
@@ -0,0 +1,190 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# Build Script
+#
+# Builds the required targets depending on which sub-modules are enabled
+
+if [[ "$__DPL_FTP_ACTIVE" == "1" ]] || [[ "$__DPL_DPUT_ACTIVE" == "1" ]]; then
+	if [[ "$__DPL_DEBIAN_ACTIVE" == "1" ]]; then
+		echo "Building Debian package(s)"
+
+		sudo apt-get install devscripts debhelper fakeroot secure-delete expect;
+
+		# Build source packages first, since they zip up the entire source folder,
+		# binaries and all
+		if [[ "$_DPL_PACKAGE_MAIN" == "1" ]]; then
+			. ../debian_template.sh main;
+			OLDPWD=$PWD; # [repo]/build
+			cd ..; # repo root
+
+			if [[ "$_DPL_PACKAGE_SOURCE" == "1" ]]; then
+				echo "Building main source Debian package";
+				expect <(cat <<EOD
+spawn debuild -S -us -uc;
+expect "continue anyway? (y/n)"
+send "y\r"
+interact
+EOD
+);
+			fi;
+
+			if [[ "$_DPL_PACKAGE_BINARY" == "1" ]]; then
+				echo "Building main binary Debian package";
+				expect <(cat <<EOD
+spawn debuild -us -uc;
+expect "continue anyway? (y/n)"
+send "y\r"
+interact
+EOD
+);
+			fi;
+
+			cd $OLDPWD;
+		fi;
+
+		# Also an asset package
+		if [[ "$_DPL_PACKAGE_ASSET" == "1" ]]; then
+			. ../debian_template.sh asset;
+			OLDPWD=$PWD; # [repo]/build
+			cd ../assets;
+
+			# make sure the asset files exist, download them if they don't
+			#echo "Checking asset files for asset Debian package";
+			#debuild -T build;
+
+			if [[ "$_DPL_PACKAGE_SOURCE" == "1" ]]; then
+				echo "Building asset source Debian package";
+				expect <(cat <<EOD
+spawn debuild -S -us -uc;
+expect "continue anyway? (y/n)"
+send "y\r"
+interact
+EOD
+);
+			fi;
+
+			if [[ "$_DPL_PACKAGE_BINARY" == "1" ]]; then
+				echo "Building asset binary Debian package";
+				expect <(cat <<EOD
+spawn debuild -us -uc;
+expect "continue anyway? (y/n)"
+send "y\r"
+interact
+EOD
+);
+			fi;
+
+			cd $OLDPWD;
+		fi;
+
+		# Now sign our packages
+		if [[ "$DPL_PGP_KEY_PRIVATE" != "" ]] && [[ "$DPL_PGP_KEY_PASSPHRASE" != "" ]]; then
+			# Get the key to sign
+			# Do this AFTER debuild so that we can specify the passphrase in command line
+			echo "$DPL_PGP_KEY_PRIVATE" | base64 --decode > key.asc;
+			echo "$DPL_PGP_KEY_PASSPHRASE" > phrase.txt;
+			gpg --import key.asc;
+
+			if [[ "$_DPL_PACKAGE_MAIN" == "1" ]]; then
+				echo "Signing main package(s)";
+
+				PACKAGEFILENAME=${PACKAGE_NAME}_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				PACKAGEDBGFILENAME=${PACKAGE_NAME}-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGENIGHTLYDBGFILENAME=${PACKAGE_NAME}-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHDBGFILENAME=${PACKAGE_NAME}-patch-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHNIGHTLYDBGFILENAME=${PACKAGE_NAME}-patch-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+				PACKAGEFILENAMES=(
+					$PACKAGEFILENAME
+					$PACKAGEDBGFILENAME
+					#$PACKAGENIGHTLYFILENAME
+					#$PACKAGENIGHTLYDBGFILENAME
+					#$PACKAGEPATCHFILENAME
+					#$PACKAGEPATCHDBGFILENAME
+					#$PACKAGEPATCHNIGHTLYFILENAME
+					#$PACKAGEPATCHNIGHTLYDBGFILENAME
+				);
+
+				# Main packages are in parent of root repo folder
+				OLDPWD=$PWD; # [repo]/build
+				cd ../..; # parent of repo root
+
+				for n in ${PACKAGEFILENAMES}; do
+					for f in ./$n*.changes; do
+						debsign --no-re-sign -p"gpg --passphrase-file $OLDPWD/phrase.txt --batch" "$f";
+					done;
+				done;
+
+				cd $OLDPWD;
+			fi;
+
+			if [[ "$_DPL_PACKAGE_ASSET" == "1" ]]; then
+				echo "Signing asset package(s)";
+
+				PACKAGEFILENAME=${PACKAGE_NAME}-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+				#PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+				PACKAGEFILENAMES=(
+					$PACKAGEFILENAME
+					#$PACKAGENIGHTLYFILENAME
+					#$PACKAGEPATCHFILENAME
+					#$PACKAGEPATCHNIGHTLYFILENAME
+				)
+
+				# Asset packages are in root repo folder
+				OLDPWD=$PWD; # [repo]/build
+				cd ..; # repo root
+
+				for n in ${PACKAGEFILENAMES}; do
+					for f in ./$n*.changes; do
+						debsign --no-re-sign -p"gpg --passphrase-file $OLDPWD/phrase.txt --batch" "$f";
+					done;
+				done;
+
+				cd $OLDPWD;
+			fi;
+
+			# Delete the keys :eyes:
+			srm key.asc;
+			srm phrase.txt;
+		fi;
+	fi;
+
+	# all other OSes
+	if [[ "$TRAVIS_OS_NAME" != "linux" ]]; then
+		#
+		# Check for binary building
+		#
+		if [[ "$_DPL_BINARY" == "1" ]]; then
+			echo "Building a Binary";
+			make -k;
+		fi;
+
+		#
+		# Check for package building
+		#
+		if [[ "$_DPL_PACKAGE_BINARY" == "1" ]]; then
+			echo "Building a Package";
+
+			# Make an OSX package; superuser is required for library bundling
+			#
+			# HACK: OSX packaging can't write libraries to .app package unless we're superuser
+			# because the original library files don't have WRITE permission
+			# Bug may be sidestepped by using CHMOD_BUNDLE_ITEMS=TRUE
+			# But I don't know where this is set. Not `cmake -D...` because this var is ignored.
+			# https://cmake.org/Bug/view.php?id=9284
+			if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+				sudo make -k package;
+			else
+				# Some day, when Windows is supported, we'll just make a standard package
+				make -k package;
+			fi;
+		fi;
+	fi;
+fi;
diff --git a/deployer/travis/deployer_defaults.sh b/deployer/travis/deployer_defaults.sh
new file mode 100644
index 0000000000000000000000000000000000000000..bccb7409ac2750564d3a75433e12ff747ad21ae7
--- /dev/null
+++ b/deployer/travis/deployer_defaults.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# Default Variables
+#
+# Here are all of the user-set variables used by Deployer.
+# See the "Cross-platform deployment" page on SRB2 Wiki for documentation.
+
+# Core Parameters
+: ${DPL_ENABLED}                # Enable Deployer behavior; must be set for any deployment activity
+: ${DPL_TAG_ENABLED}            # Trigger Deployer for all tag releases
+: ${DPL_JOB_ENABLE_ALL}         # Enable all jobs for deployment
+: ${DPL_TERMINATE_TESTS}        # Terminate all build test jobs (used in .travis.yml)
+: ${DPL_TRIGGER}                # Use a [word] in the commit message to trigger Deployer
+: ${DPL_JOBNAMES}               # Trigger Deployer by job name
+: ${DPL_OSNAMES}                # Trigger Deployer by OS name (osx,linux)
+: ${DPL_BRANCHES}               # Trigger Deployer by git branch name
+
+# Job Parameters
+: ${_DPL_JOB_ENABLED}           # Enable Deployer for this specific job. DPL_ENABLED must be set too.
+: ${_DPL_JOB_NAME}              # Identifier for the job, used for logging and trigger word matching
+: ${_DPL_FTP_TARGET}            # Deploy to FTP
+: ${_DPL_DPUT_TARGET}           # Deploy to DPUT
+: ${_DPL_PACKAGE_SOURCE}        # Build packages into a Source distribution. Linux only.
+: ${_DPL_PACKAGE_BINARY}        # Build packages into a Binary distribution.
+: ${_DPL_PACKAGE_MAIN:=1}       # Build main installation package. Linux only; OS X assumes this.
+: ${_DPL_PACKAGE_ASSET}         # Build asset installation package. Linux only.
+
+# Asset File Parameters
+: ${ASSET_ARCHIVE_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-assets.7z}
+: ${ASSET_ARCHIVE_OPTIONAL_PATH:=https://github.com/mazmazz/SRB2/releases/download/SRB2_assets/SRB2-v2122-optional-assets.7z}
+: ${ASSET_FILES_HASHED:=srb2.srb zones.dta player.dta rings.dta patch.dta}
+: ${ASSET_FILES_DOCS:=README.txt LICENSE.txt LICENSE-3RD-PARTY.txt}
+: ${ASSET_FILES_OPTIONAL_GET:=0}
+
+# FTP Parameters
+: ${DPL_FTP_PROTOCOL}
+: ${DPL_FTP_USER}
+: ${DPL_FTP_PASS}
+: ${DPL_FTP_HOSTNAME}
+: ${DPL_FTP_PORT}
+: ${DPL_FTP_PATH}
+
+# DPUT Parameters
+: ${DPL_DPUT_DOMAIN:=ppa.launchpad.net}
+: ${DPL_DPUT_METHOD:=sftp}
+: ${DPL_DPUT_INCOMING}
+: ${DPL_DPUT_LOGIN:=anonymous}
+: ${DPL_SSH_KEY_PRIVATE}        # Base64-encoded private key file. Used to sign repository uploads
+: ${DPL_SSH_KEY_PASSPHRASE}     # Decodes the private key file.
+
+# Package Parameters
+: ${PACKAGE_NAME:=srb2}
+: ${PACKAGE_VERSION:=2.1.23}
+: ${PACKAGE_SUBVERSION}         # Highly recommended to set this to reflect the distro series target (e.g., ~18.04bionic)
+: ${PACKAGE_REVISION}           # Defaults to UTC timestamp
+: ${PACKAGE_INSTALL_PATH:=/usr/games/SRB2}
+: ${PACKAGE_LINK_PATH:=/usr/games}
+: ${PACKAGE_DISTRO:=trusty}
+: ${PACKAGE_URGENCY:=high}
+: ${PACKAGE_NAME_EMAIL:=Sonic Team Junior <stjr@srb2.org>}
+: ${PACKAGE_GROUP_NAME_EMAIL:=Sonic Team Junior <stjr@srb2.org>}
+: ${PACKAGE_WEBSITE:=<http://www.srb2.org>}
+
+: ${PACKAGE_ASSET_MINVERSION:=2.1.21}  # Number this the version BEFORE the actual required version, because we do a > check
+: ${PACKAGE_ASSET_MAXVERSION:=2.1.24}  # Number this the version AFTER the actual required version, because we do a < check
+
+: ${PROGRAM_NAME:=Sonic Robo Blast 2}
+: ${PROGRAM_VENDOR:=Sonic Team Junior}
+: ${PROGRAM_VERSION:=2.1.23}
+: ${PROGRAM_DESCRIPTION:=A free 3D Sonic the Hedgehog fangame closely inspired by the original Sonic games on the Sega Genesis.}
+: ${PROGRAM_FILENAME:=srb2}
+
+: ${DPL_PGP_KEY_PRIVATE}        # Base64-encoded private key file. Used to sign Debian packages
+: ${DPL_PGP_KEY_PASSPHRASE}     # Decodes the private key file.
+
+# Export Asset and Package Parameters for envsubst templating
+
+export ASSET_ARCHIVE_PATH="${ASSET_ARCHIVE_PATH}"
+export ASSET_ARCHIVE_OPTIONAL_PATH="${ASSET_ARCHIVE_OPTIONAL_PATH}"
+export ASSET_FILES_HASHED="${ASSET_FILES_HASHED}"
+export ASSET_FILES_DOCS="${ASSET_FILES_DOCS}"
+export ASSET_FILES_OPTIONAL_GET="${ASSET_FILES_OPTIONAL_GET}"
+
+export PACKAGE_NAME="${PACKAGE_NAME}"
+export PACKAGE_VERSION="${PACKAGE_VERSION}"
+export PACKAGE_SUBVERSION="${PACKAGE_SUBVERSION}" # in case we have this
+export PACKAGE_REVISION="${PACKAGE_REVISION}"
+export PACKAGE_ASSET_MINVERSION="${PACKAGE_ASSET_MINVERSION}"
+export PACKAGE_ASSET_MAXVERSION="${PACKAGE_ASSET_MAXVERSION}"
+export PACKAGE_INSTALL_PATH="${PACKAGE_INSTALL_PATH}"
+export PACKAGE_LINK_PATH="${PACKAGE_LINK_PATH}"
+export PACKAGE_DISTRO="${PACKAGE_DISTRO}"
+export PACKAGE_URGENCY="${PACKAGE_URGENCY}"
+export PACKAGE_NAME_EMAIL="${PACKAGE_NAME_EMAIL}"
+export PACKAGE_GROUP_NAME_EMAIL="${PACKAGE_GROUP_NAME_EMAIL}"
+export PACKAGE_WEBSITE="${PACKAGE_WEBSITE}"
+
+export PROGRAM_NAME="${PROGRAM_NAME}"
+export PROGRAM_VERSION="${PROGRAM_VERSION}"
+export PROGRAM_DESCRIPTION="${PROGRAM_DESCRIPTION}"
+export PROGRAM_FILENAME="${PROGRAM_FILENAME}"
+
+# This file is called in debian_template.sh, so mark our completion so we don't run it again
+__DEBIAN_PARAMETERS_INITIALIZED=1
diff --git a/deployer/travis/deployer_dput.sh b/deployer/travis/deployer_dput.sh
new file mode 100644
index 0000000000000000000000000000000000000000..863a928cdf01b7a141144f01be2f9b0b664ccf66
--- /dev/null
+++ b/deployer/travis/deployer_dput.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# DPUT uploader (e.g., Launchpad PPA)
+#
+
+if [[ "$__DPL_DPUT_ACTIVE" == "1" ]]; then
+    # Install APT dependencies
+    # paramiko required for ssh
+    sudo apt-get install python-paramiko expect dput; # python-pip
+    #pip install paramiko;
+
+    # Output the DPUT config
+    # Dput only works if you're using secure FTP, so that's what we default to.
+    cat > "./dput.cf" << EOM
+[deployer]
+fqdn = ${DPL_DPUT_DOMAIN}
+method = ${DPL_DPUT_METHOD}
+incoming = ${DPL_DPUT_INCOMING}
+login = ${DPL_DPUT_LOGIN}
+allow_unsigned_uploads = 0
+EOM
+
+    # Output SSH config
+    # Don't let SSH prompt us for untrusted hosts
+    cat >> "./ssh_config" << EOM
+
+Host *
+    StrictHostKeyChecking no
+    UserKnownHostsFile=/dev/null
+    PubKeyAuthentication yes
+    IdentityFile ${PWD}/key.private
+    IdentitiesOnly yes
+EOM
+    sudo sh -c "cat < ${PWD}/ssh_config >> /etc/ssh/ssh_config";
+
+    # Get the private key
+    echo "$DPL_SSH_KEY_PRIVATE" | base64 --decode > key.private;
+    chmod 700 ./key.private;
+
+    if [[ "$_DPL_PACKAGE_MAIN" == "1" ]]; then
+        PACKAGEFILENAME=${PACKAGE_NAME}_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        PACKAGEDBGFILENAME=${PACKAGE_NAME}-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+		#PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+		#PACKAGENIGHTLYDBGFILENAME=${PACKAGE_NAME}-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        #PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+		#PACKAGEPATCHDBGFILENAME=${PACKAGE_NAME}-patch-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        #PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+		#PACKAGEPATCHNIGHTLYDBGFILENAME=${PACKAGE_NAME}-patch-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+        PACKAGEFILENAMES=(
+            $PACKAGEFILENAME
+            $PACKAGEDBGFILENAME
+            #$PACKAGENIGHTLYFILENAME
+            #$PACKAGENIGHTLYDBGFILENAME
+            #$PACKAGEPATCHFILENAME
+            #$PACKAGEPATCHDBGFILENAME
+            #$PACKAGEPATCHNIGHTLYFILENAME
+            #$PACKAGEPATCHNIGHTLYDBGFILENAME
+        );
+
+        # Main packages are in parent of root repo folder
+        OLDPWD=$PWD; # [repo]/build
+        cd ../..;
+
+        # Enter passphrase if required
+        for n in ${PACKAGEFILENAMES}; do
+            for f in $n*.changes; do
+                # Binary builds also generate source builds, so exclude the source
+                # builds if desired
+                if [[ "$_DPL_PACKAGE_SOURCE" != "1" ]]; then
+                    if [[ "$f" == *"_source"* ]] || [[ "$f" == *".tar.xz"* ]]; then
+                        continue;
+                    fi;
+                fi;
+
+                expect <(cat <<EOD
+spawn dput -c "${OLDPWD}/dput.cf" deployer "$f";
+expect "Enter passphrase for key"
+send "${DPL_SSH_KEY_PASSPHRASE}\r"
+interact
+EOD
+);
+            done;
+        done;
+
+        # Go back to [repo]/build folder
+        cd $OLDPWD;
+    fi;
+
+    if [[ "$_DPL_PACKAGE_ASSET" == "1" ]]; then
+        PACKAGEFILENAME=${PACKAGE_NAME}-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        #PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        #PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+        #PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+        PACKAGEFILENAMES=(
+            $PACKAGEFILENAME
+            #$PACKAGENIGHTLYFILENAME
+            #$PACKAGEPATCHFILENAME
+            #$PACKAGEPATCHNIGHTLYFILENAME
+        )
+
+        # Asset packages are in root repo folder
+        OLDPWD=$PWD; # [repo]/build
+        cd ..;
+
+        # Enter passphrase if required
+        for n in ${PACKAGEFILENAMES}; do
+            for f in $n*.changes; do
+                # Binary builds also generate source builds, so exclude the source
+                # builds if desired
+                if [[ "$_DPL_PACKAGE_SOURCE" != "1" ]]; then
+                    if [[ "$f" == *"_source"* ]] || [[ "$f" == *".tar.xz"* ]]; then
+                        continue;
+                    fi;
+                fi;
+                expect <(cat <<EOD
+spawn dput -c "${OLDPWD}/dput.cf" deployer "$f";
+expect "Enter passphrase for key"
+send "${DPL_SSH_KEY_PASSPHRASE}\r"
+interact
+EOD
+);
+            done;
+        done;
+
+        # Go back to [repo]/build folder
+        cd $OLDPWD;
+    fi;
+
+    srm ./key.private;
+fi;
diff --git a/deployer/travis/deployer_ftp.sh b/deployer/travis/deployer_ftp.sh
new file mode 100644
index 0000000000000000000000000000000000000000..1f6bd08b597d3c65c545b340de63df2b82208824
--- /dev/null
+++ b/deployer/travis/deployer_ftp.sh
@@ -0,0 +1,137 @@
+#!/bin/bash
+
+# Deployer for Travis-CI
+# FTP Uploader
+#
+# Package files are uploaded to, e.g., ftp://username:password@example.com:21/path/to/upload/STJr/SRB2/master/460873812-151.1
+# With file `commit.txt` and folder(s) `bin` and `package`
+#
+# Set these environment variables in your Travis-CI settings, where they are stored securely.
+# See other shell scripts for more options.
+#
+# DPL_FTP_PROTOCOL = ftp                    (ftp or sftp or ftps or however your FTP URI begins)
+# DPL_FTP_USER = username
+# DPL_FTP_PASS = password
+# DPL_FTP_HOSTNAME = example.com
+# DPL_FTP_PORT = 21
+# DPL_FTP_PATH = path/to/upload             (do not add trailing slash)
+
+if [[ "$__DPL_FTP_ACTIVE" == "1" ]]; then
+	if [[ "$TRAVIS_JOB_NAME" != "" ]]; then
+		JOBNAME=$TRAVIS_JOB_NAME;
+	else
+		if [[ "$_DPL_JOB_NAME" != "" ]]; then
+			JOBNAME=$_DPL_JOB_NAME;
+		else
+			JOBNAME=$TRAVIS_OS_NAME;
+		fi;
+	fi;
+
+	# Generate commit.txt file
+	echo "Travis-CI Build $TRAVIS_OS_NAME - $TRAVIS_REPO_SLUG/$TRAVIS_BRANCH - $TRAVIS_JOB_NUMBER - $JOBNAME" > "commit.txt";
+	echo "Job ID $TRAVIS_JOB_ID" >> "commit.txt";
+	echo "" >> "commit.txt";
+	echo "Commit $TRAVIS_COMMIT" >> "commit.txt";
+	echo "$TRAVIS_COMMIT_MESSAGE" >> "commit.txt";
+	echo "" >> "commit.txt";
+
+	# Initialize FTP parameters
+	if [[ "$DPL_FTP_PORT" == "" ]]; then
+		DPL_FTP_PORT=21;
+	fi;
+	if [[ "$DPL_FTP_PROTOCOL" == "" ]]; then
+		DPL_FTP_PROTOCOL=ftp;
+	fi;
+	__DPL_FTP_LOCATION=$DPL_FTP_PROTOCOL://$DPL_FTP_HOSTNAME:$DPL_FTP_PORT/$DPL_FTP_PATH/$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH/$TRAVIS_JOB_ID-$TRAVIS_JOB_NUMBER-$JOBNAME;
+
+	# Upload to FTP!
+	echo "Uploading to FTP...";
+	curl --ftp-create-dirs -T "commit.txt" -u $DPL_FTP_USER:$DPL_FTP_PASS "$__DPL_FTP_LOCATION/commit.txt";
+
+	if [[ "$__DPL_DEBIAN_ACTIVE" == "1" ]]; then
+		if [[ "$_DPL_PACKAGE_MAIN" == "1" ]]; then
+			PACKAGEFILENAME=${PACKAGE_NAME}_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			PACKAGEDBGFILENAME=${PACKAGE_NAME}-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGENIGHTLYDBGFILENAME=${PACKAGE_NAME}-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHDBGFILENAME=${PACKAGE_NAME}-patch-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHNIGHTLYDBGFILENAME=${PACKAGE_NAME}-patch-nightly-dbg_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+			PACKAGEFILENAMES=(
+				$PACKAGEFILENAME
+				$PACKAGEDBGFILENAME
+				#$PACKAGENIGHTLYFILENAME
+				#$PACKAGENIGHTLYDBGFILENAME
+				#$PACKAGEPATCHFILENAME
+				#$PACKAGEPATCHDBGFILENAME
+				#$PACKAGEPATCHNIGHTLYFILENAME
+				#$PACKAGEPATCHNIGHTLYDBGFILENAME
+			);
+
+			# Main packages are in parent of root repo folder
+			OLDPWD=$PWD; # [repo]/build
+			cd ../..;
+
+			for n in ${PACKAGEFILENAMES}; do
+				for f in ./$n*; do
+					# Binary builds also generate source builds, so exclude the source
+					# builds if desired
+					if [[ "$_DPL_PACKAGE_SOURCE" != "1" ]]; then
+						if [[ "$f" == *"_source"* ]] || [[ "$f" == *".tar.xz"* ]]; then
+							continue;
+						fi;
+					fi;
+					curl --ftp-create-dirs -T "$f" -u $DPL_FTP_USER:$DPL_FTP_PASS  "$__DPL_FTP_LOCATION/package/main/$f";
+				done;
+			done;
+
+			# Go back to [repo]/build folder
+			cd $OLDPWD;
+		fi;
+
+		if [[ "$_DPL_PACKAGE_ASSET" == "1" ]]; then
+			PACKAGEFILENAME=${PACKAGE_NAME}-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGENIGHTLYFILENAME=${PACKAGE_NAME}-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHFILENAME=${PACKAGE_NAME}-patch-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+			#PACKAGEPATCHNIGHTLYFILENAME=${PACKAGE_NAME}-patch-nightly-data_${PACKAGE_VERSION}${PACKAGE_SUBVERSION}${PACKAGE_REVISION};
+
+			PACKAGEFILENAMES=(
+				$PACKAGEFILENAME
+				#$PACKAGENIGHTLYFILENAME
+				#$PACKAGEPATCHFILENAME
+				#$PACKAGEPATCHNIGHTLYFILENAME
+			)
+
+			# Asset packages are in root repo folder
+			OLDPWD=$PWD; # [repo]/build
+			cd ..;
+
+			for n in ${PACKAGEFILENAMES}; do
+				for f in ./$n*; do
+					# Binary builds also generate source builds, so exclude the source
+					# builds if desired
+					if [[ "$_DPL_PACKAGE_SOURCE" != "1" ]]; then
+						if [[ "$f" == *"_source"* ]] || [[ "$f" == *".tar.xz"* ]]; then
+							continue;
+						fi;
+					fi;
+					curl --ftp-create-dirs -T "$f" -u $DPL_FTP_USER:$DPL_FTP_PASS  "$__DPL_FTP_LOCATION/package/asset/$f";
+				done;
+			done;
+
+			# Go back to [repo]/build folder
+			cd $OLDPWD;
+		fi;
+	else
+		if [[ "$_DPL_BINARY" == "1" ]]; then
+			find bin -type f -exec curl -u $DPL_FTP_USER:$DPL_FTP_PASS --ftp-create-dirs -T {} $__DPL_FTP_LOCATION/{} \;;
+		fi;
+
+		if [[ "$_DPL_PACKAGE_BINARY" == "1" ]]; then
+			sudo rm -r package/_CPack_Packages
+			find package -type f -exec curl -u $DPL_FTP_USER:$DPL_FTP_PASS --ftp-create-dirs -T {} $__DPL_FTP_LOCATION/{} \;;
+		fi;
+	fi;
+fi
diff --git a/libs/DLL-README.txt b/libs/DLL-README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..45680c535667c5b7a32bc4f686991742efa6a551
--- /dev/null
+++ b/libs/DLL-README.txt
@@ -0,0 +1,46 @@
+# SRB2 - Which DLLs do I need to bundle?
+
+Updated 12/4/2018 (v2.1.21)
+
+Here are the required DLLs, per build. For each architecture, copy all the binaries from these folders:
+
+* libs\dll-binaries\[i686/x86_64]
+* libs\SDL2\[i686/x86_64]...\bin
+* libs\SDL2_mixer\[i686/x86_64]...\bin
+* libs\libopenmpt\[x86/x86_64]...\bin\mingw
+
+and don't forget to build r_opengl.dll for srb2dd.
+
+## srb2win, 32-bit
+
+* libs\dll-binaries\i686\exchndl.dll
+* libs\dll-binaries\i686\libgme.dll
+* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
+* libs\SDL2\i686-w64-mingw32\bin\SDL2.dll
+* libs\SDL2_mixer\i686-w64-mingw32\bin\*.dll (get everything)
+* libs\libopenmpt\x86\bin\mingw\libopenmpt.dll
+
+## srb2win, 64-bit
+
+* libs\dll-binaries\x86_64\exchndl.dll
+* libs\dll-binaries\x86_64\libgme.dll
+* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
+* libs\SDL2\x86_64-w64-mingw32\bin\SDL2.dll
+* libs\SDL2_mixer\x86_64-w64-mingw32\bin\*.dll (get everything)
+* libs\libopenmpt\x86_64\bin\mingw\libopenmpt.dll
+
+## srb2dd, 32-bit
+
+* libs\dll-binaries\i686\exchndl.dll
+* libs\dll-binaries\i686\fmodex.dll
+* libs\dll-binaries\i686\libgme.dll
+* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
+* r_opengl.dll (build this from make)
+
+## srb2dd, 64-bit
+
+* libs\dll-binaries\x86_64\exchndl.dll
+* libs\dll-binaries\x86_64\fmodex.dll
+* libs\dll-binaries\x86_64\libgme.dll
+* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
+* r_opengl.dll (build this from make)
diff --git a/libs/SDL2/COPYING.txt b/libs/SDL2/COPYING.txt
index a1c8bbe5af9caeda179d70c7476e023325d29067..694e58a09f009850b9ea7a1d617560d561a80200 100644
--- a/libs/SDL2/COPYING.txt
+++ b/libs/SDL2/COPYING.txt
@@ -1,6 +1,6 @@
 
 Simple DirectMedia Layer
-Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
   
 This software is provided 'as-is', without any express or implied
 warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/Makefile b/libs/SDL2/Makefile
index a2e6bb9ba08cca81bbcc29d53898d74c2730a737..9edfd003e83e0fc85e28622d61634d5475551dd8 100644
--- a/libs/SDL2/Makefile
+++ b/libs/SDL2/Makefile
@@ -1,12 +1,12 @@
 #
 # Makefile for installing the mingw32 version of the SDL library
 
-CROSS_PATH := /usr/local/cross-tools
+CROSS_PATH := /usr/local
 ARCHITECTURES := i686-w64-mingw32 x86_64-w64-mingw32
 
 all install:
 	@echo "Type \"make native\" to install 32-bit to /usr"
-	@echo "Type \"make cross\" to install 32-bit and 64-bit to CROSS_PATH ($(CROSS_PATH))"
+	@echo "Type \"make cross\" to install 32-bit and 64-bit to $(CROSS_PATH)"
 
 native:
 	make install-package arch=i686-w64-mingw32 prefix=/usr
@@ -22,6 +22,7 @@ install-package:
 	    sed "s|^prefix=.*|prefix=$(prefix)|" <$(arch)/bin/sdl2-config >$(prefix)/bin/sdl2-config; \
 	    chmod 755 $(prefix)/bin/sdl2-config; \
 	    sed "s|^libdir=.*|libdir=\'$(prefix)/lib\'|" <$(arch)/lib/libSDL2.la >$(prefix)/lib/libSDL2.la; \
+	    sed "s|^libdir=.*|libdir=\'$(prefix)/lib\'|" <$(arch)/lib/libSDL2main.la >$(prefix)/lib/libSDL2main.la; \
 	    sed "s|^prefix=.*|prefix=$(prefix)|" <$(arch)/lib/pkgconfig/sdl2.pc >$(prefix)/lib/pkgconfig/sdl2.pc; \
 	else \
 	    echo "*** ERROR: $(arch) or $(prefix) does not exist!"; \
diff --git a/libs/SDL2/SRB2NOTE.txt b/libs/SDL2/SRB2NOTE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b7da4c9ab31061e8961722cf92af9f0a4e7afe51
--- /dev/null
+++ b/libs/SDL2/SRB2NOTE.txt
@@ -0,0 +1,15 @@
+# SDL Development Libaries
+
+Download both the VC and MinGW packages and extract them here.
+
+https://www.libsdl.org/download-2.0.php
+
+## VC Package
+
+* include
+* lib
+
+## MinGW Package
+
+* i686-w64-mingw32
+* x86_64-w64-mingw32
diff --git a/libs/SDL2/WhatsNew.txt b/libs/SDL2/WhatsNew.txt
index 9c8d538403e8b89000e0e27f7a8bc3b3d1fca26f..9074b12ba7056cc4f81881a1e1ddf0274eab60d3 100644
--- a/libs/SDL2/WhatsNew.txt
+++ b/libs/SDL2/WhatsNew.txt
@@ -1,6 +1,91 @@
 
 This is a list of major changes in SDL's version history.
 
+---------------------------------------------------------------------------
+2.0.9:
+---------------------------------------------------------------------------
+
+General:
+* Added a new sensor API, initialized by passing SDL_INIT_SENSOR to SDL_Init(), and defined in SDL_sensor.h
+* Added an event SDL_SENSORUPDATE which is sent when a sensor is updated
+* Added SDL_GetDisplayOrientation() to return the current display orientation
+* Added an event SDL_DISPLAYEVENT which is sent when the display orientation changes
+* Added HIDAPI joystick drivers for more consistent support for Xbox, PS4 and Nintendo Switch Pro controller support across platforms. (Thanks to Valve for contributing the PS4 and Nintendo Switch Pro controller support)
+* Added support for many other popular game controllers
+* Added SDL_JoystickGetDevicePlayerIndex(), SDL_JoystickGetPlayerIndex(), and SDL_GameControllerGetPlayerIndex() to get the player index for a controller. For XInput controllers this returns the XInput index for the controller.
+* Added SDL_GameControllerRumble() and SDL_JoystickRumble() which allow simple rumble without using the haptics API
+* Added SDL_GameControllerMappingForDeviceIndex() to get the mapping for a controller before it's opened
+* Added the hint SDL_HINT_MOUSE_DOUBLE_CLICK_TIME to control the mouse double-click time
+* Added the hint SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS to control the mouse double-click radius, in pixels
+* Added SDL_HasColorKey() to return whether a surface has a colorkey active
+* Added SDL_HasAVX512F() to return whether the CPU has AVX-512F features
+* Added SDL_IsTablet() to return whether the application is running on a tablet
+* Added SDL_THREAD_PRIORITY_TIME_CRITICAL for threads that must run at the highest priority
+
+Mac OS X:
+* Fixed black screen at start on Mac OS X Mojave
+
+Linux:
+* Added SDL_LinuxSetThreadPriority() to allow adjusting the thread priority of native threads using RealtimeKit if available.
+
+iOS:
+* Fixed Asian IME input
+
+Android:
+* Updated required Android SDK to API 26, to match Google's new App Store requirements
+* Added support for wired USB Xbox, PS4, and Nintendo Switch Pro controllers
+* Added support for relative mouse mode on Android 7.0 and newer (except where it's broken, on Chromebooks and when in DeX mode with Samsung Experience 9.0)
+* Added support for custom mouse cursors on Android 7.0 and newer
+* Added the hint SDL_HINT_ANDROID_TRAP_BACK_BUTTON to control whether the back button will back out of the app (the default) or be passed to the application as SDL_SCANCODE_AC_BACK
+* Added SDL_AndroidBackButton() to trigger the Android system back button behavior when handling the back button in the application
+* Added SDL_IsChromebook() to return whether the app is running in the Chromebook Android runtime
+* Added SDL_IsDeXMode() to return whether the app is running while docked in the Samsung DeX
+
+
+---------------------------------------------------------------------------
+2.0.8:
+---------------------------------------------------------------------------
+
+General:
+* Added SDL_fmod() and SDL_log10()
+* Each of the SDL math functions now has the corresponding float version
+* Added SDL_SetYUVConversionMode() and SDL_GetYUVConversionMode() to control the formula used when converting to and from YUV colorspace. The options are JPEG, BT.601, and BT.709
+
+Windows:
+* Implemented WASAPI support on Windows UWP and removed the deprecated XAudio2 implementation
+* Added resampling support on WASAPI on Windows 7 and above
+
+Windows UWP:
+* Added SDL_WinRTGetDeviceFamily() to find out what type of device your application is running on
+
+Mac OS X:
+* Added support for the Vulkan SDK for Mac:
+  https://www.lunarg.com/lunarg-releases-vulkan-sdk-1-0-69-0-for-mac/
+* Added support for OpenGL ES using ANGLE when it's available
+
+Mac OS X / iOS / tvOS:
+* Added a Metal 2D render implementation
+* Added SDL_RenderGetMetalLayer() and SDL_RenderGetMetalCommandEncoder() to insert your own drawing into SDL rendering when using the Metal implementation
+
+iOS:
+* Added the hint SDL_HINT_IOS_HIDE_HOME_INDICATOR to control whether the home indicator bar on iPhone X should be hidden. This defaults to dimming the indicator for fullscreen applications and showing the indicator for windowed applications.
+
+iOS / Android:
+* Added the hint SDL_HINT_RETURN_KEY_HIDES_IME to control whether the return key on the software keyboard should hide the keyboard or send a key event (the default)
+
+Android:
+* SDL now supports building with Android Studio and Gradle by default, and the old Ant project is available in android-project-ant
+* SDL now requires the API 19 SDK to build, but can still target devices down to API 14 (Android 4.0.1)
+* Added SDL_IsAndroidTV() to tell whether the application is running on Android TV
+
+Android / tvOS:
+* Added the hint SDL_HINT_TV_REMOTE_AS_JOYSTICK to control whether TV remotes should be listed as joystick devices (the default) or send keyboard events.
+
+Linux:
+* Added the hint SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR to control whether the X server should skip the compositor for the SDL application. This defaults to "1"
+* Added the hint SDL_HINT_VIDEO_DOUBLE_BUFFER to control whether the Raspberry Pi and KMSDRM video drivers should use double or triple buffering (the default)
+
+
 ---------------------------------------------------------------------------
 2.0.7:
 ---------------------------------------------------------------------------
diff --git a/libs/SDL2/docs/README-android.md b/libs/SDL2/docs/README-android.md
index 62874f4355ae2c46c6aec864f370d9a258e26eb8..7f3263dc3afe769c96f3503150281c5cb3b7bd60 100644
--- a/libs/SDL2/docs/README-android.md
+++ b/libs/SDL2/docs/README-android.md
@@ -4,20 +4,24 @@ Android
 Matt Styles wrote a tutorial on building SDL for Android with Visual Studio:
 http://trederia.blogspot.de/2017/03/building-sdl2-for-android-with-visual.html
 
-The rest of this README covers the old style build process.
+The rest of this README covers the Android gradle style build process.
+
+If you are using the older ant build process, it is no longer officially
+supported, but you can use the "android-project-ant" directory as a template.
+
 
 ================================================================================
  Requirements
 ================================================================================
 
-Android SDK (version 16 or later)
+Android SDK (version 26 or later)
 https://developer.android.com/sdk/index.html
 
-Android NDK r7 or later
+Android NDK r15c or later
 https://developer.android.com/tools/sdk/ndk/index.html
 
-Minimum API level supported by SDL: 10 (Android 2.3.3)
-Joystick support is available for API level >= 12 devices.
+Minimum API level supported by SDL: 14 (Android 4.0.1)
+
 
 ================================================================================
  How the port works
@@ -31,15 +35,12 @@ Joystick support is available for API level >= 12 devices.
 - This eventually produces a standard Android .apk package
 
 The Android Java code implements an "Activity" and can be found in:
-android-project/src/org/libsdl/app/SDLActivity.java
+android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
 
 The Java code loads your game code, the SDL shared library, and
 dispatches to native functions implemented in the SDL library:
 src/core/android/SDL_android.c
 
-Your project must include some glue code that starts your main() routine:
-src/main/android/SDL_android_main.c
-
 
 ================================================================================
  Building an app
@@ -74,71 +75,30 @@ For more complex projects, follow these instructions:
     
 1. Copy the android-project directory wherever you want to keep your projects
    and rename it to the name of your project.
-2. Move or symlink this SDL directory into the "<project>/jni" directory
-3. Edit "<project>/jni/src/Android.mk" to include your source files
-4. Run 'ndk-build' (a script provided by the NDK). This compiles the C source
-
-If you want to use the Eclipse IDE, skip to the Eclipse section below.
+2. Move or symlink this SDL directory into the "<project>/app/jni" directory
+3. Edit "<project>/app/jni/src/Android.mk" to include your source files
 
-5. Create "<project>/local.properties" and use that to point to the Android SDK directory, by writing a line with the following form:
+4a. If you want to use Android Studio, simply open your <project> directory and start building.
 
-       sdk.dir=PATH_TO_ANDROID_SDK
-
-6. Run 'ant debug' in android/project. This compiles the .java and eventually 
-   creates a .apk with the native code embedded
-7. 'ant debug install' will push the apk to the device or emulator (if connected)
+4b. If you want to build manually, run './gradlew installDebug' in the project directory. This compiles the .java, creates an .apk with the native code embedded, and installs it on any connected Android device
 
 Here's an explanation of the files in the Android project, so you can customize them:
 
-    android-project/
-        AndroidManifest.xml	- package manifest. Among others, it contains the class name
-        			  of the main Activity and the package name of the application.
-        build.properties	- empty
-        build.xml		- build description file, used by ant. The actual application name
-        			  is specified here.
-        default.properties	- holds the target ABI for the application, android-10 and up
-        project.properties	- holds the target ABI for the application, android-10 and up
-        local.properties	- holds the SDK path, you should change this to the path to your SDK
+    android-project/app
+        build.gradle            - build info including the application version and SDK
+        src/main/AndroidManifest.xml	- package manifest. Among others, it contains the class name of the main Activity and the package name of the application.
         jni/			- directory holding native code
-        jni/Android.mk		- Android makefile that can call recursively the Android.mk files
-        			  in all subdirectories
+        jni/Application.mk	- Application JNI settings, including target platform and STL library
+        jni/Android.mk		- Android makefile that can call recursively the Android.mk files in all subdirectories
         jni/SDL/		- (symlink to) directory holding the SDL library files
         jni/SDL/Android.mk	- Android makefile for creating the SDL shared library
         jni/src/		- directory holding your C/C++ source
-        jni/src/Android.mk	- Android makefile that you should customize to include your 
-                                  source code and any library references
-        res/			- directory holding resources for your application
-        res/drawable-*		- directories holding icons for different phone hardware. Could be
-        			  one dir called "drawable".
-        res/layout/main.xml	- Usually contains a file main.xml, which declares the screen layout.
-        			  We don't need it because we use the SDL video output.
-        res/values/strings.xml	- strings used in your application, including the application name
-        			  shown on the phone.
-        src/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding
-        			  to SDL.  Be very careful changing this, as the SDL library relies
-        			  on this implementation.
-
-
-================================================================================
- Build an app with static linking of libSDL
-================================================================================
-
-This build uses the Android NDK module system.
-
-Instructions:
-1. Copy the android-project directory wherever you want to keep your projects
-   and rename it to the name of your project.
-2. Rename "<project>/jni/src/Android_static.mk" to "<project>/jni/src/Android.mk"
-   (overwrite the existing one)
-3. Edit "<project>/jni/src/Android.mk" to include your source files
-4. create and export an environment variable named NDK_MODULE_PATH that points
-   to the parent directory of this SDL directory. e.g.:
-
-       export NDK_MODULE_PATH="$PWD"/..
-
-5. Edit "<project>/src/org/libsdl/app/SDLActivity.java" and remove the call to
-   System.loadLibrary("SDL2").
-6. Run 'ndk-build' (a script provided by the NDK). This compiles the C source
+        jni/src/Android.mk	- Android makefile that you should customize to include your source code and any library references
+        src/main/assets/	- directory holding asset files for your application
+        src/main/res/		- directory holding resources for your application
+        src/main/res/mipmap-*	- directories holding icons for different phone hardware
+        src/main/res/values/strings.xml	- strings used in your application, including the application name
+        src/main/java/org/libsdl/app/SDLActivity.java - the Java class handling the initialization and binding to SDL. Be very careful changing this, as the SDL library relies on this implementation. You should instead subclass this for your application.
 
 
 ================================================================================
@@ -171,25 +131,23 @@ Here's an example of a minimal class file:
 Then replace "SDLActivity" in AndroidManifest.xml with the name of your
 class, .e.g. "MyGame"
 
+
 ================================================================================
  Customizing your application icon
 ================================================================================
 
 Conceptually changing your icon is just replacing the "ic_launcher.png" files in
-the drawable directories under the res directory. There are four directories for
-different screen sizes. These can be replaced with one dir called "drawable",
-containing an icon file "ic_launcher.png" with dimensions 48x48 or 72x72.
+the drawable directories under the res directory. There are several directories
+for different screen sizes.
 
-You may need to change the name of your icon in AndroidManifest.xml to match
-this icon filename.
 
 ================================================================================
  Loading assets
 ================================================================================
 
-Any files you put in the "assets" directory of your android-project directory
-will get bundled into the application package and you can load them using the
-standard functions in SDL_rwops.h.
+Any files you put in the "app/src/main/assets" directory of your project
+directory will get bundled into the application package and you can load
+them using the standard functions in SDL_rwops.h.
 
 There are also a few Android specific functions that allow you to get other
 useful paths for saving and loading data:
@@ -211,6 +169,7 @@ disable this behaviour, see for example:
     
 http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/
 
+
 ================================================================================
  Pause / Resume behaviour
 ================================================================================
@@ -231,6 +190,7 @@ a specific message, (which is not yet implemented!) and restore your textures
 manually or quit the app (which is actually the kind of behaviour you'll see
 under iOS, if the OS can not restore your GL context it will just kill your app)
 
+
 ================================================================================
  Threads and the Java VM
 ================================================================================
@@ -246,6 +206,7 @@ Android_JNI_SetupThread() before doing anything else otherwise SDL will attach
 your thread automatically anyway (when you make an SDL call), but it'll never
 detach it.
 
+
 ================================================================================
  Using STL
 ================================================================================
@@ -253,34 +214,10 @@ detach it.
 You can use STL in your project by creating an Application.mk file in the jni
 folder and adding the following line:
 
-    APP_STL := stlport_static
+    APP_STL := c++_shared
 
-For more information check out CPLUSPLUS-SUPPORT.html in the NDK documentation.
-
-================================================================================
- Additional documentation
-================================================================================
-
-The documentation in the NDK docs directory is very helpful in understanding the
-build process and how to work with native code on the Android platform.
-
-The best place to start is with docs/OVERVIEW.TXT
-
-
-================================================================================
- Using Eclipse
-================================================================================
-
-First make sure that you've installed Eclipse and the Android extensions as described here:
-	https://developer.android.com/tools/sdk/eclipse-adt.html
-
-Once you've copied the SDL android project and customized it, you can create an Eclipse project from it:
- * File -> New -> Other
- * Select the Android -> Android Project wizard and click Next
- * Enter the name you'd like your project to have
- * Select "Create project from existing source" and browse for your project directory
- * Make sure the Build Target is set to Android 3.1 (API 12)
- * Click Finish
+For more information go here:
+	https://developer.android.com/ndk/guides/cpp-support
 
 
 ================================================================================
@@ -295,13 +232,11 @@ Especially useful is the info on setting up OpenGL ES 2.0 emulation.
 Notice that this software emulator is incredibly slow and needs a lot of disk space.
 Using a real device works better.
 
+
 ================================================================================
  Troubleshooting
 ================================================================================
 
-You can create and run an emulator from the Eclipse IDE:
- * Window -> Android SDK and AVD Manager
-
 You can see if adb can see any devices with the following command:
 
     adb devices
@@ -338,7 +273,10 @@ You can see the complete command line that ndk-build is using by passing V=1 on
 
     ndk-build V=1
 
-If your application crashes in native code, you can use addr2line to convert the
+If your application crashes in native code, you can use ndk-stack to get a symbolic stack trace:
+	https://developer.android.com/ndk/guides/ndk-stack
+
+If you want to go through the process manually, you can use addr2line to convert the
 addresses in the stack trace to lines in your code.
 
 For example, if your crash looks like this:
@@ -426,36 +364,30 @@ When you're done instrumenting with valgrind, you can disable the wrapper:
 
     adb shell setprop wrap.org.libsdl.app ""
 
+
 ================================================================================
  Graphics debugging
 ================================================================================
 
 If you are developing on a compatible Tegra-based tablet, NVidia provides
-Tegra Graphics Debugger at their website.  Because SDL2 dynamically loads EGL
+Tegra Graphics Debugger at their website. Because SDL2 dynamically loads EGL
 and GLES libraries, you must follow their instructions for installing the
-interposer library on a rooted device.  The non-rooted instructions are not
+interposer library on a rooted device. The non-rooted instructions are not
 compatible with applications that use SDL2 for video.
 
 The Tegra Graphics Debugger is available from NVidia here:
 https://developer.nvidia.com/tegra-graphics-debugger
 
+
 ================================================================================
- Why is API level 10 the minimum required?
+ Why is API level 14 the minimum required?
 ================================================================================
 
-API level 10 is the minimum required level at runtime (that is, on the device) 
-because SDL requires some functionality for running not
-available on older devices. Since the incorporation of joystick support into SDL,
-the minimum SDK required to *build* SDL is version 12. Devices running API levels
-10-11 are still supported, only with the joystick functionality disabled.
-
-Support for native OpenGL ES and ES2 applications was introduced in the NDK for
-API level 4 and 8. EGL was made a stable API in the NDK for API level 9, which
-has since then been obsoleted, with the recommendation to developers to bump the
-required API level to 10.
+The latest NDK toolchain doesn't support targeting earlier than API level 14.
 As of this writing, according to https://developer.android.com/about/dashboards/index.html
-about 90% of the Android devices accessing Google Play support API level 10 or
-higher (March 2013).
+about 99% of the Android devices accessing Google Play support API level 14 or
+higher (October 2017).
+
 
 ================================================================================
  A note regarding the use of the "dirty rectangles" rendering technique
@@ -475,6 +407,7 @@ screen each frame.
 
 Reference: http://www.khronos.org/registry/egl/specs/EGLTechNote0001.html
 
+
 ================================================================================
  Known issues
 ================================================================================
diff --git a/libs/SDL2/docs/README-dynapi.md b/libs/SDL2/docs/README-dynapi.md
index bfaecb34496e5c1f142a2fadabd3b585dd4f84b3..b9a58bce1dd12db13076b927be9ee0d31861e588 100644
--- a/libs/SDL2/docs/README-dynapi.md
+++ b/libs/SDL2/docs/README-dynapi.md
@@ -64,7 +64,7 @@ level of indirection, we can do things like this:
     export SDL_DYNAMIC_API=/my/actual/libSDL-2.0.so.0
     ./MyGameThatIsStaticallyLinkedToSDL2
 
-And now, this game that is staticallly linked to SDL, can still be overridden 
+And now, this game that is statically linked to SDL, can still be overridden 
 with a newer, or better, SDL. The statically linked one will only be used as 
 far as calling into the jump table in this case. But in cases where no override
 is desired, the statically linked version will provide its own jump table, 
diff --git a/libs/SDL2/docs/README-raspberrypi.md b/libs/SDL2/docs/README-raspberrypi.md
index d64e7cb36ccf44d9dff4d940f6f667bb0d0c73f4..2c95e0649f78b4be2739271f8f45a5f16ad7fadd 100644
--- a/libs/SDL2/docs/README-raspberrypi.md
+++ b/libs/SDL2/docs/README-raspberrypi.md
@@ -27,6 +27,16 @@ OpenGL ES 2.x, it usually comes pre-installed, but in any case:
     
 sudo apt-get install libraspberrypi0 libraspberrypi-bin libraspberrypi-dev
 
+
+================================================================================
+ NEON
+================================================================================
+
+If your Pi has NEON support, make sure you add -mfpu=neon to your CFLAGS so
+that SDL will select some otherwise-disabled highly-optimized code. The
+original Pi units don't have NEON, the Pi2 probably does, and the Pi3
+definitely does.
+
 ================================================================================
  Cross compiling from x86 Linux
 ================================================================================
diff --git a/libs/SDL2/docs/README-winrt.md b/libs/SDL2/docs/README-winrt.md
index 8e993b57ad8f3b34b615ba4f5d8c7e5791eca1f5..35b7e6d872e079568c4f6b3d810517a28a68a7f7 100644
--- a/libs/SDL2/docs/README-winrt.md
+++ b/libs/SDL2/docs/README-winrt.md
@@ -23,7 +23,7 @@ Requirements
     typically do not include support for creating WinRT apps, to note.
     (The "Community" editions of Visual C++ do, however, support both
     desktop/Win32 and WinRT development).
-  - Visual Studio 2017 can be used, however it is recommented that you install
+  - Visual Studio 2017 can be used, however it is recommended that you install
     the Visual C++ 2015 build tools.  These build tools can be installed
     using VS 2017's installer.  Be sure to also install the workload for
     "Universal Windows Platform development", its optional component, the
@@ -33,7 +33,7 @@ Requirements
     earlier versions of Windows, such as Windows 7, is not always supported
     by Visual Studio, and you may get error(s) when attempting to do so.
   - Visual C++ 2012 can only build apps that target versions 8.0 of Windows,
-    or  Windows Phone.  8.0-targetted apps will run on devices running 8.1
+    or  Windows Phone.  8.0-targeted apps will run on devices running 8.1
     editions of Windows, however they will not be able to take advantage of
     8.1-specific features.
   - Visual C++ 2013 cannot create app projects that target Windows 8.0.
@@ -54,7 +54,7 @@ Requirements
 Status
 ------
 
-Here is a rough list of what works, and what doens't:
+Here is a rough list of what works, and what doesn't:
 
 * What works:
   * compilation via Visual C++ 2012 through 2015
@@ -70,7 +70,10 @@ Here is a rough list of what works, and what doens't:
     SDL_GetPerformanceFrequency(), etc.)
   * file I/O via SDL_RWops
   * mouse input  (unsupported on Windows Phone)
-  * audio, via a modified version of SDL's XAudio2 backend
+  * audio, via SDL's WASAPI backend (if you want to record, your app must 
+    have "Microphone" capabilities enabled in its manifest, and the user must 
+    not have blocked access. Otherwise, capture devices will fail to work,
+    presenting as a device disconnect shortly after opening it.)
   * .DLL file loading.  Libraries *MUST* be packaged inside applications.  Loading
     anything outside of the app is not supported.
   * system path retrieval via SDL's filesystem APIs
diff --git a/libs/SDL2/i686-w64-mingw32/bin/SDL2.dll b/libs/SDL2/i686-w64-mingw32/bin/SDL2.dll
index c21f747c2f45540809ff1fd901e2bb607ead0fff..70177904476cc7ab959c84e08a92df76cd0d748c 100755
Binary files a/libs/SDL2/i686-w64-mingw32/bin/SDL2.dll and b/libs/SDL2/i686-w64-mingw32/bin/SDL2.dll differ
diff --git a/libs/SDL2/i686-w64-mingw32/bin/sdl2-config b/libs/SDL2/i686-w64-mingw32/bin/sdl2-config
index 7fdb82515a58a2d3a0764ab51adcc072bb91d4e5..e1060b3f2d78da06616e639fb5a5316502e25694 100755
--- a/libs/SDL2/i686-w64-mingw32/bin/sdl2-config
+++ b/libs/SDL2/i686-w64-mingw32/bin/sdl2-config
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-prefix=/usr/local/i686-w64-mingw32
+prefix=/opt/local/i686-w64-mingw32
 exec_prefix=${prefix}
 exec_prefix_set=no
 libdir=${exec_prefix}/lib
@@ -39,17 +39,17 @@ while test $# -gt 0; do
       echo $exec_prefix
       ;;
     --version)
-      echo 2.0.7
+      echo 2.0.9
       ;;
     --cflags)
       echo -I${prefix}/include/SDL2  -Dmain=SDL_main
       ;;
     --libs)
-      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2  -mwindows
+      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2 -mwindows
       ;;
     --static-libs)
 #    --libs|--static-libs)
-      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2  -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc
+      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2 -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -static-libgcc
       ;;
     *)
       echo "${usage}" 1>&2
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL.h
index 366d50fa39095f0185247d490b31753351ef887c..fc35a419efd9076076aca339303a2937d5e6ada7 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,6 +51,7 @@
 #include "SDL_power.h"
 #include "SDL_render.h"
 #include "SDL_rwops.h"
+#include "SDL_sensor.h"
 #include "SDL_shape.h"
 #include "SDL_system.h"
 #include "SDL_thread.h"
@@ -80,10 +81,11 @@ extern "C" {
 #define SDL_INIT_HAPTIC         0x00001000u
 #define SDL_INIT_GAMECONTROLLER 0x00002000u  /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
 #define SDL_INIT_EVENTS         0x00004000u
+#define SDL_INIT_SENSOR         0x00008000u
 #define SDL_INIT_NOPARACHUTE    0x00100000u  /**< compatibility; this flag is ignored. */
 #define SDL_INIT_EVERYTHING ( \
                 SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
-                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \
+                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
             )
 /* @} */
 
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_assert.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_assert.h
index 90abbe39b6f1e5bd7e0bfa3d54b00954efe06e9a..b38f928ae14bb68e916bae1048a4c8648a1a20d8 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_assert.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_atomic.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_atomic.h
index 36e37f3b786840231bf87fddf563c695037c7fc5..b2287748c8f87973ef2aef5093812c0065b89725 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_atomic.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_atomic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -158,6 +158,9 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
 #if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("lwsync" : : : "memory")
 #define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("lwsync" : : : "memory")
+#elif defined(__GNUC__) && defined(__aarch64__)
+#define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
+#define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("dmb ish" : : : "memory")
 #elif defined(__GNUC__) && defined(__arm__)
 #if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__)
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_audio.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_audio.h
index f119c2b2678fc531efe1d3ecc8b1f1751169de53..d3e1bface4d406c3d6c1c3f141a36a3fce8e926b 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_audio.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_audio.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -140,7 +140,8 @@ typedef Uint16 SDL_AudioFormat;
 #define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE    0x00000001
 #define SDL_AUDIO_ALLOW_FORMAT_CHANGE       0x00000002
 #define SDL_AUDIO_ALLOW_CHANNELS_CHANGE     0x00000004
-#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
+#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE      0x00000008
+#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)
 /* @} */
 
 /* @} *//* Audio flags */
@@ -527,7 +528,7 @@ extern DECLSPEC SDL_AudioStream * SDLCALL SDL_NewAudioStream(const SDL_AudioForm
  *
  *  \param stream The stream the audio data is being added to
  *  \param buf A pointer to the audio data to add
- *  \param int The number of bytes to write to the stream
+ *  \param len The number of bytes to write to the stream
  *  \return 0 on success, or -1 on error.
  *
  *  \sa SDL_NewAudioStream
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_bits.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_bits.h
index bc28572902181c1edf67998fe3800c0269b4c451..eb8322f0d0a181b364871c28df42010ead3f17b1 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_bits.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_bits.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_blendmode.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_blendmode.h
index 9abd0bd4cf11c6d4d9931a20f21db32e2af0cdc6..36a5ea76f0c52c4364cb5f156cdf7252c0441ecc 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_blendmode.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_blendmode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_clipboard.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_clipboard.h
index 341f4ba42b17e2025a5a27ecaa3925e3f618c1ad..f28751ebb8ffe5c67f17ccab94f2a55165fea4c8 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_clipboard.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_clipboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_config.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_config.h
index 1bbb83848142162b87676b35e03d964d5bec56dd..c58be8e72ee0dbb9e73f94be9d8c2c595aa630e5 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_config.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_config.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -82,6 +82,9 @@ typedef unsigned int uintptr_t;
 #define HAVE_DSOUND_H 1
 #define HAVE_DXGI_H 1
 #define HAVE_XINPUT_H 1
+#define HAVE_MMDEVICEAPI_H 1
+#define HAVE_AUDIOCLIENT_H 1
+#define HAVE_ENDPOINTVOLUME_H 1
 
 /* This is disabled by default to avoid C runtime dependencies and manifest requirements */
 #ifdef HAVE_LIBC
@@ -109,13 +112,15 @@ typedef unsigned int uintptr_t;
 #define HAVE_MEMCMP 1
 #define HAVE_STRLEN 1
 #define HAVE__STRREV 1
-#define HAVE__STRUPR 1
-#define HAVE__STRLWR 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__STRUPR */
+/* #undef HAVE__STRLWR */
 #define HAVE_STRCHR 1
 #define HAVE_STRRCHR 1
 #define HAVE_STRSTR 1
-#define HAVE__LTOA 1
-#define HAVE__ULTOA 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__LTOA */
+/* #undef HAVE__ULTOA */
 #define HAVE_STRTOL 1
 #define HAVE_STRTOUL 1
 #define HAVE_STRTOD 1
@@ -125,30 +130,45 @@ typedef unsigned int uintptr_t;
 #define HAVE_STRNCMP 1
 #define HAVE__STRICMP 1
 #define HAVE__STRNICMP 1
-#define HAVE_ATAN 1
-#define HAVE_ATAN2 1
-#define HAVE_ACOS  1
-#define HAVE_ASIN  1
-#define HAVE_CEIL 1
-#define HAVE_COS 1
-#define HAVE_COSF 1
-#define HAVE_FABS 1
-#define HAVE_FLOOR 1
-#define HAVE_LOG 1
-#define HAVE_POW 1
-#define HAVE_SIN 1
-#define HAVE_SINF 1
-#define HAVE_SQRT 1
-#define HAVE_SQRTF 1
-#define HAVE_TAN 1
-#define HAVE_TANF 1
-#define HAVE__COPYSIGN 1
+#define HAVE_ACOS   1
+#define HAVE_ACOSF  1
+#define HAVE_ASIN   1
+#define HAVE_ASINF  1
+#define HAVE_ATAN   1
+#define HAVE_ATANF  1
+#define HAVE_ATAN2  1
+#define HAVE_ATAN2F 1
+#define HAVE_CEILF  1
+#define HAVE__COPYSIGN  1
+#define HAVE_COS    1
+#define HAVE_COSF   1
+#define HAVE_EXP    1
+#define HAVE_EXPF   1
+#define HAVE_FABS   1
+#define HAVE_FABSF  1
+#define HAVE_FLOOR  1
+#define HAVE_FLOORF 1
+#define HAVE_FMOD   1
+#define HAVE_FMODF  1
+#define HAVE_LOG    1
+#define HAVE_LOGF   1
+#define HAVE_LOG10  1
+#define HAVE_LOG10F 1
+#define HAVE_POW    1
+#define HAVE_POWF   1
+#define HAVE_SIN    1
+#define HAVE_SINF   1
+#define HAVE_SQRT   1
+#define HAVE_SQRTF  1
+#define HAVE_TAN    1
+#define HAVE_TANF   1
 #if defined(_MSC_VER)
 /* These functions were added with the VC++ 2013 C runtime library */
 #if _MSC_VER >= 1800
 #define HAVE_STRTOLL 1
 #define HAVE_VSSCANF 1
 #define HAVE_SCALBN 1
+#define HAVE_SCALBNF    1
 #endif
 /* This function is available with at least the VC++ 2008 C runtime library */
 #if _MSC_VER >= 1400
@@ -166,7 +186,6 @@ typedef unsigned int uintptr_t;
 /* Enable various audio drivers */
 #define SDL_AUDIO_DRIVER_WASAPI 1
 #define SDL_AUDIO_DRIVER_DSOUND 1
-#define SDL_AUDIO_DRIVER_XAUDIO2    0
 #define SDL_AUDIO_DRIVER_WINMM  1
 #define SDL_AUDIO_DRIVER_DISK   1
 #define SDL_AUDIO_DRIVER_DUMMY  1
@@ -174,9 +193,13 @@ typedef unsigned int uintptr_t;
 /* Enable various input drivers */
 #define SDL_JOYSTICK_DINPUT 1
 #define SDL_JOYSTICK_XINPUT 1
+#define SDL_JOYSTICK_HIDAPI 1
 #define SDL_HAPTIC_DINPUT   1
 #define SDL_HAPTIC_XINPUT   1
 
+/* Enable the dummy sensor driver */
+#define SDL_SENSOR_DUMMY  1
+
 /* Enable various shared object loading systems */
 #define SDL_LOADSO_WINDOWS  1
 
@@ -194,7 +217,7 @@ typedef unsigned int uintptr_t;
 #define SDL_VIDEO_RENDER_D3D    1
 #endif
 #ifndef SDL_VIDEO_RENDER_D3D11
-#define SDL_VIDEO_RENDER_D3D11	0
+#define SDL_VIDEO_RENDER_D3D11  0
 #endif
 
 /* Enable OpenGL support */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_cpuinfo.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_cpuinfo.h
index 94b64b03bc5dd394af7f2ccfc5968fb1c0e9dfc5..ee3a47e841242fdae7e697f6b5de18a2a47c1ecf 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_cpuinfo.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_cpuinfo.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,27 +51,35 @@
 #include <intrin.h>
 #else
 #ifdef __ALTIVEC__
-#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__)
+#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
 #include <altivec.h>
 #undef pixel
+#undef bool
 #endif
 #endif
-#ifdef __MMX__
-#include <mmintrin.h>
+#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
+#include <arm_neon.h>
 #endif
-#ifdef __3dNOW__
+#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
 #include <mm3dnow.h>
 #endif
-#ifdef __SSE__
+#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
+#include <immintrin.h>
+#else
+#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
+#include <mmintrin.h>
+#endif
+#if defined(__SSE__) && !defined(SDL_DISABLE_XMMINTRIN_H)
 #include <xmmintrin.h>
 #endif
-#ifdef __SSE2__
+#if defined(__SSE2__) && !defined(SDL_DISABLE_EMMINTRIN_H)
 #include <emmintrin.h>
 #endif
-#ifdef __SSE3__
+#if defined(__SSE3__) && !defined(SDL_DISABLE_PMMINTRIN_H)
 #include <pmmintrin.h>
 #endif
-#endif
+#endif /* HAVE_IMMINTRIN_H */
+#endif /* compiler version */
 
 #include "begin_code.h"
 /* Set up for C function definitions, even when using C++ */
@@ -154,6 +162,11 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
 
+/**
+ *  This function returns true if the CPU has AVX-512F (foundation) features.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void);
+
 /**
  *  This function returns true if the CPU has NEON (ARM SIMD) features.
  */
@@ -164,7 +177,6 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
  */
 extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
 
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_egl.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_egl.h
index e47fbe8624700058a55aff35c8cd39081a19695f..d65ed437c33fe307657aca04f192de87e0ff55e9 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_egl.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_egl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -24,7 +24,7 @@
  *
  *  This is a simple file to encapsulate the EGL API headers.
  */
-#ifndef _MSC_VER
+#if !defined(_MSC_VER) && !defined(__ANDROID__)
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_endian.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_endian.h
index 3eda7f2871144111f5cdfb24e0d57dd988c440c4..ed0bf5ba8f94888115422d06601ff4200119a9e6 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_endian.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_endian.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_error.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_error.h
index 49be9827e182ae56227e8dde300b7e8c03f8cfb8..c0e46298e8ffdbc6cf6abc4381a7ba9e2dcf1a33 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_error.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_error.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_events.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_events.h
index 0fc09bc37d795c8b5a4c34bebf9c78134a4c499f..af22eb6466661378e252d090b1504471f74833a8 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_events.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_events.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -85,6 +85,9 @@ typedef enum
                                      Called on Android in onResume()
                                 */
 
+    /* Display events */
+    SDL_DISPLAYEVENT   = 0x150,  /**< Display state change */
+
     /* Window events */
     SDL_WINDOWEVENT    = 0x200, /**< Window state change */
     SDL_SYSWMEVENT,             /**< System specific event */
@@ -144,6 +147,9 @@ typedef enum
     SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
     SDL_AUDIODEVICEREMOVED,        /**< An audio device has been removed. */
 
+    /* Sensor events */
+    SDL_SENSORUPDATE = 0x1200,     /**< A sensor was updated */
+
     /* Render events */
     SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
     SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
@@ -168,6 +174,21 @@ typedef struct SDL_CommonEvent
     Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
 } SDL_CommonEvent;
 
+/**
+ *  \brief Display state change event data (event.display.*)
+ */
+typedef struct SDL_DisplayEvent
+{
+    Uint32 type;        /**< ::SDL_DISPLAYEVENT */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Uint32 display;     /**< The associated display index */
+    Uint8 event;        /**< ::SDL_DisplayEventID */
+    Uint8 padding1;
+    Uint8 padding2;
+    Uint8 padding3;
+    Sint32 data1;       /**< event dependent data */
+} SDL_DisplayEvent;
+
 /**
  *  \brief Window state change event data (event.window.*)
  */
@@ -471,6 +492,17 @@ typedef struct SDL_DropEvent
 } SDL_DropEvent;
 
 
+/**
+ *  \brief Sensor event structure (event.sensor.*)
+ */
+typedef struct SDL_SensorEvent
+{
+    Uint32 type;        /**< ::SDL_SENSORUPDATE */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Sint32 which;       /**< The instance ID of the sensor */
+    float data[6];      /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
+} SDL_SensorEvent;
+
 /**
  *  \brief The "quit requested" event
  */
@@ -526,6 +558,7 @@ typedef union SDL_Event
 {
     Uint32 type;                    /**< Event type, shared with all events */
     SDL_CommonEvent common;         /**< Common event data */
+    SDL_DisplayEvent display;       /**< Window event data */
     SDL_WindowEvent window;         /**< Window event data */
     SDL_KeyboardEvent key;          /**< Keyboard event data */
     SDL_TextEditingEvent edit;      /**< Text editing event data */
@@ -542,6 +575,7 @@ typedef union SDL_Event
     SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
     SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
     SDL_AudioDeviceEvent adevice;   /**< Audio device event data */
+    SDL_SensorEvent sensor;         /**< Sensor event data */
     SDL_QuitEvent quit;             /**< Quit request event data */
     SDL_UserEvent user;             /**< Custom event data */
     SDL_SysWMEvent syswm;           /**< System dependent window event data */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_filesystem.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_filesystem.h
index a1c846ad0f07b71a61a60c147c709c2dc6e2c15f..fa6a1fa6e7b2ad4b9e341f6cf71c4e7fca925586 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_filesystem.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_filesystem.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gamecontroller.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gamecontroller.h
index c9215132ef50a7f214b7b4bf248d6fc73c3ff3f4..6ae9c95428907172257a93dfd6074865891f6571 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gamecontroller.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gamecontroller.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -175,6 +175,14 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index);
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index);
 
+/**
+ *  Get the mapping of a game controller.
+ *  This can be called before any controllers are opened.
+ *
+ *  \return the mapping string.  Must be freed with SDL_free().  Returns NULL if no mapping is available
+ */
+extern DECLSPEC char *SDLCALL SDL_GameControllerMappingForDeviceIndex(int joystick_index);
+
 /**
  *  Open a game controller for use.
  *  The index passed as an argument refers to the N'th game controller on the system.
@@ -196,6 +204,13 @@ extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromInstanceID(SDL
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller);
 
+/**
+ *  Get the player index of an opened game controller, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller);
+
 /**
  *  Get the USB vendor ID of an opened controller, if available.
  *  If the vendor ID isn't available this function returns 0.
@@ -345,6 +360,19 @@ SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller,
 extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
                                                           SDL_GameControllerButton button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param gamecontroller The controller to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a controller previously opened with SDL_GameControllerOpen().
  */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gesture.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gesture.h
index 2bba6a7294fa93240d5f6eefbd8d16c5773761ea..b223d80d44c3debd2de55420aaf1e907d1a08919 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gesture.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_gesture.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_haptic.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_haptic.h
index 60daefaa709bbbc0a1796bd9af6f9be073cacedd..2ea1bfc1602ee571b0ac3238b183117fcb20f25d 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_haptic.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_haptic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -117,6 +117,17 @@
 extern "C" {
 #endif /* __cplusplus */
 
+/* FIXME: For SDL 2.1, adjust all the magnitude variables to be Uint16 (0xFFFF).
+ *
+ * At the moment the magnitude variables are mixed between signed/unsigned, and
+ * it is also not made clear that ALL of those variables expect a max of 0x7FFF.
+ *
+ * Some platforms may have higher precision than that (Linux FF, Windows XInput)
+ * so we should fix the inconsistency in favor of higher possible precision,
+ * adjusting for platforms that use different scales.
+ * -flibit
+ */
+
 /**
  *  \typedef SDL_Haptic
  *
@@ -656,8 +667,8 @@ typedef struct SDL_HapticRamp
  * This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
  *
  * The Left/Right effect is used to explicitly control the large and small
- * motors, commonly found in modern game controllers. One motor is high
- * frequency, the other is low frequency.
+ * motors, commonly found in modern game controllers. The small (right) motor
+ * is high frequency, and the large (left) motor is low frequency.
  *
  * \sa SDL_HAPTIC_LEFTRIGHT
  * \sa SDL_HapticEffect
@@ -668,7 +679,7 @@ typedef struct SDL_HapticLeftRight
     Uint16 type;            /**< ::SDL_HAPTIC_LEFTRIGHT */
 
     /* Replay */
-    Uint32 length;          /**< Duration of the effect. */
+    Uint32 length;          /**< Duration of the effect in milliseconds. */
 
     /* Rumble */
     Uint16 large_magnitude; /**< Control of the large controller motor. */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_hints.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_hints.h
index 007a4bee05c0dc4ef90b1d24e88eb1137cedc945..4ee72e97d66db40a703620a9d0a162a952de1498 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_hints.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_hints.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,7 @@ extern "C" {
  *    "opengl"
  *    "opengles2"
  *    "opengles"
+ *    "metal"
  *    "software"
  *
  *  The default varies by platform, but it's the first one in the list that
@@ -210,6 +211,18 @@ extern "C" {
  */
 #define SDL_HINT_VIDEO_X11_NET_WM_PING      "SDL_VIDEO_X11_NET_WM_PING"
 
+/**
+ * \brief A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint should be used.
+ * 
+ * This variable can be set to the following values:
+ * "0" - Disable _NET_WM_BYPASS_COMPOSITOR
+ * "1" - Enable _NET_WM_BYPASS_COMPOSITOR
+ * 
+ * By default SDL will use _NET_WM_BYPASS_COMPOSITOR
+ * 
+ */
+#define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"
+
 /**
  *  \brief  A variable controlling whether the window frame and title bar are interactive when the cursor is hidden 
  *
@@ -249,6 +262,16 @@ extern "C" {
  */
 #define SDL_HINT_GRAB_KEYBOARD              "SDL_GRAB_KEYBOARD"
 
+/**
+ *  \brief  A variable setting the double click time, in milliseconds.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME    "SDL_MOUSE_DOUBLE_CLICK_TIME"
+
+/**
+ *  \brief  A variable setting the double click radius, in pixels.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS    "SDL_MOUSE_DOUBLE_CLICK_RADIUS"
+
 /**
  *  \brief  A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode
  */
@@ -316,7 +339,7 @@ extern "C" {
 #define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED"
 
 /**
- *  \brief  A variable controlling which orientations are allowed on iOS.
+ *  \brief  A variable controlling which orientations are allowed on iOS/Android.
  *
  *  In some circumstances it is necessary to be able to explicitly control
  *  which UI orientations are allowed.
@@ -354,17 +377,37 @@ extern "C" {
  */
 #define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION"
 
+/**
+ * \brief  A variable controlling whether the home indicator bar on iPhone X
+ *         should be hidden.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - The indicator bar is not hidden (default for windowed applications)
+ *    "1"       - The indicator bar is hidden and is shown when the screen is touched (useful for movie playback applications)
+ *    "2"       - The indicator bar is dim and the first swipe makes it visible and the second swipe performs the "home" action (default for fullscreen applications)
+ */
+#define SDL_HINT_IOS_HIDE_HOME_INDICATOR "SDL_IOS_HIDE_HOME_INDICATOR"
+
 /**
  *  \brief  A variable controlling whether the Android / iOS built-in
- *  accelerometer should be listed as a joystick device, rather than listing
- *  actual joysticks only.
+ *  accelerometer should be listed as a joystick device.
  *
  *  This variable can be set to the following values:
- *    "0"       - List only real joysticks and accept input from them
- *    "1"       - List real joysticks along with the accelerometer as if it were a 3 axis joystick (the default).
+ *    "0"       - The accelerometer is not listed as a joystick
+ *    "1"       - The accelerometer is available as a 3 axis joystick (the default).
  */
 #define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK"
 
+/**
+ *  \brief  A variable controlling whether the Android / tvOS remotes
+ *  should be listed as joystick devices, instead of sending keyboard events.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - Remotes send enter/escape/arrow key events
+ *    "1"       - Remotes are available as 2 axis, 2 button joysticks (the default).
+ */
+#define SDL_HINT_TV_REMOTE_AS_JOYSTICK "SDL_TV_REMOTE_AS_JOYSTICK"
+
 /**
  *  \brief  A variable that lets you disable the detection and use of Xinput gamepad devices
  *
@@ -432,6 +475,88 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
 
+/**
+ *  \brief  A variable controlling whether the HIDAPI joystick drivers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI drivers are not used
+ *    "1"       - HIDAPI drivers are used (the default)
+ *
+ *  This variable is the default for all drivers, but can be overridden by the hints for specific drivers below.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for PS4 controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
+
+/**
+ *  \brief  A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - extended reports are not enabled (the default)
+ *    "1"       - extended reports
+ *
+ *  Extended input reports allow rumble on Bluetooth PS4 controllers, but
+ *  break DirectInput handling for applications that don't use SDL.
+ *
+ *  Once extended reports are enabled, they can not be disabled without
+ *  power cycling the controller.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Steam Controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for XBox controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOX   "SDL_JOYSTICK_HIDAPI_XBOX"
+
+/**
+ *  \brief  A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
+ *
+ *  The variable can be set to the following values:
+ *    "0"       - Do not scan for Steam Controllers
+ *    "1"       - Scan for Steam Controllers (the default)
+ *
+ *  The default value is "1".  This hint must be set before initializing the joystick subsystem.
+ */
+#define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS"
+
+
 /**
  *  \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
  *      This is a debugging aid for developers and not expected to be used by end users. The default is "1"
@@ -494,6 +619,10 @@ extern "C" {
 *  This is specially useful if you build SDL against a non glibc libc library (such as musl) which
 *  provides a relatively small default thread stack size (a few kilobytes versus the default 8MB glibc uses).
 *  Support for this hint is currently available only in the pthread, Windows, and PSP backend.
+*
+*  Instead of this hint, in 2.0.9 and later, you can use
+*  SDL_CreateThreadWithStackSize(). This hint only works with the classic
+*  SDL_CreateThread().
 */
 #define SDL_HINT_THREAD_STACK_SIZE              "SDL_THREAD_STACK_SIZE"
 
@@ -719,6 +848,35 @@ extern "C" {
  */
 #define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
 
+ /**
+ * \brief A variable to control whether we trap the Android back button to handle it manually.
+ *        This is necessary for the right mouse button to work on some Android devices, or
+ *        to be able to trap the back button for use in your code reliably.  If set to true,
+ *        the back button will show up as an SDL_KEYDOWN / SDL_KEYUP pair with a keycode of 
+ *        SDL_SCANCODE_AC_BACK.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - Back button will be handled as usual for system. (default)
+ *   "1"       - Back button will be trapped, allowing you to handle the key press
+ *               manually.  (This will also let right mouse click work on systems 
+ *               where the right mouse button functions as back.)
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
+
+ /**
+ * \brief A variable to control whether the return key on the soft keyboard
+ *        should hide the soft keyboard on Android and iOS.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - The return key will be handled as a key event. This is the behaviour of SDL <= 2.0.3. (default)
+ *   "1"       - The return key will hide the keyboard.
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_RETURN_KEY_HIDES_IME "SDL_RETURN_KEY_HIDES_IME"
+
 /**
  *  \brief override the binding element for keyboard inputs for Emscripten builds
  *
@@ -752,7 +910,7 @@ extern "C" {
  *   "0"       - SDL will generate a window-close event when it sees Alt+F4.
  *   "1"       - SDL will only do normal key handling for Alt+F4.
  */
-#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4	"SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
+#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
 
 /**
  *  \brief Prevent SDL from using version 4 of the bitmap header when saving BMPs.
@@ -797,6 +955,24 @@ extern "C" {
  */
 #define SDL_HINT_RPI_VIDEO_LAYER           "SDL_RPI_VIDEO_LAYER"
 
+/**
+ * \brief Tell the video driver that we only want a double buffer.
+ *
+ * By default, most lowlevel 2D APIs will use a triple buffer scheme that 
+ * wastes no CPU time on waiting for vsync after issuing a flip, but
+ * introduces a frame of latency. On the other hand, using a double buffer
+ * scheme instead is recommended for cases where low latency is an important
+ * factor because we save a whole frame of latency.
+ * We do so by waiting for vsync immediately after issuing a flip, usually just
+ * after eglSwapBuffers call in the backend's *_SwapWindow function.
+ *
+ * Since it's driver-specific, it's only supported where possible and
+ * implemented. Currently supported the following drivers:
+ * - KMSDRM (kmsdrm)
+ * - Raspberry Pi (raspberrypi)
+ */
+#define SDL_HINT_VIDEO_DOUBLE_BUFFER      "SDL_VIDEO_DOUBLE_BUFFER"
+
 /**
  *  \brief  A variable controlling what driver to use for OpenGL ES contexts.
  *
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_joystick.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_joystick.h
index f598dc828dafa2751d3aba2286059a8298d79aa3..6e05a9c2056cf7134c22459066d253e80e9a4315 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_joystick.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_joystick.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,10 +97,10 @@ typedef enum
 typedef enum
 {
     SDL_JOYSTICK_POWER_UNKNOWN = -1,
-    SDL_JOYSTICK_POWER_EMPTY,
-    SDL_JOYSTICK_POWER_LOW,
-    SDL_JOYSTICK_POWER_MEDIUM,
-    SDL_JOYSTICK_POWER_FULL,
+    SDL_JOYSTICK_POWER_EMPTY,   /* <= 5% */
+    SDL_JOYSTICK_POWER_LOW,     /* <= 20% */
+    SDL_JOYSTICK_POWER_MEDIUM,  /* <= 70% */
+    SDL_JOYSTICK_POWER_FULL,    /* <= 100% */
     SDL_JOYSTICK_POWER_WIRED,
     SDL_JOYSTICK_POWER_MAX
 } SDL_JoystickPowerLevel;
@@ -132,6 +132,12 @@ extern DECLSPEC int SDLCALL SDL_NumJoysticks(void);
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index);
 
+/**
+ *  Get the player index of a joystick, or -1 if it's not available
+ *  This can be called before any joysticks are opened.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetDevicePlayerIndex(int device_index);
+
 /**
  *  Return the GUID for the joystick at this index
  *  This can be called before any joysticks are opened.
@@ -194,6 +200,13 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick);
 
+/**
+ *  Get the player index of an opened joystick, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetPlayerIndex(SDL_Joystick * joystick);
+
 /**
  *  Return the GUID for this opened joystick
  */
@@ -361,6 +374,19 @@ extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick,
 extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
                                                     int button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param joystick The joystick to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a joystick previously opened with SDL_JoystickOpen().
  */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keyboard.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keyboard.h
index e78ca469082c793a8190331c01766a3e3374a5ba..87482317161a6e05324bf5ab769743b1a385b436 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keyboard.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keyboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keycode.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keycode.h
index c41e45ff61b6959937f7f4e912cef24064b72e1f..d7d5b1dbcdefce3777ee48eddcc46853eba2e584 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keycode.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_keycode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_loadso.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_loadso.h
index 6ea256c6ae5813dc7b00c269101b4e866fbb5fcc..da56fb452779ee015da429caa8da746c985b0ee0 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_loadso.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_loadso.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_log.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_log.h
index 356d0e1cf7492f54d82e5829a6030587400869d1..e12b6588601712bd217578226f143941ff019cbb 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_log.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_main.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_main.h
index 2af32360f7be842213bca1253268d5198c768377..98558217fc23ea63118899e44a710b7ba934c720 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_main.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_main.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -63,10 +63,13 @@
 /* On Android SDL provides a Java class in SDLActivity.java that is the
    main activity entry point.
 
-   See README-android.md for more details on extending that class.
+   See docs/README-android.md for more details on extending that class.
  */
 #define SDL_MAIN_NEEDED
 
+/* We need to export SDL_main so it can be launched from Java */
+#define SDLMAIN_DECLSPEC    DECLSPEC
+
 #elif defined(__NACL__)
 /* On NACL we use ppapi_simple to set up the application helper code,
    then wait for the first PSE_INSTANCE_DIDCHANGEVIEW event before 
@@ -85,6 +88,10 @@
 #define C_LINKAGE
 #endif /* __cplusplus */
 
+#ifndef SDLMAIN_DECLSPEC
+#define SDLMAIN_DECLSPEC
+#endif
+
 /**
  *  \file SDL_main.h
  *
@@ -107,7 +114,7 @@
 /**
  *  The prototype for the application's main() function
  */
-extern C_LINKAGE DECLSPEC int SDL_main(int argc, char *argv[]);
+extern C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
 
 
 #include "begin_code.h"
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_messagebox.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_messagebox.h
index c326d8f01098b147e1bff015fb8e355e37f00744..b7be59d88f50ad7686b794ca13cd66f29e7ef557 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_messagebox.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_messagebox.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mouse.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mouse.h
index 6001bd46c1e80270d85d18dd72025b0c17f2722a..d3c9f6156cba36ecff91929d7a7eda0784b4af69 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mouse.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mouse.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mutex.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mutex.h
index 0272379e55e16bb25a913f9b4e4ca11809af7b56..ba4247ced8176dd98396d552536bc4646faa7841 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mutex.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_mutex.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_name.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_name.h
index 8095ed3ffd4df27fb77a15eebcd532536c3efc09..ecd863f4ca873b55249601228efc5fc578aa4fb1 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_name.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_name.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengl.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengl.h
index 314dd57eda395c3b9e7bcbaf2d3749b9aa01864d..253d9c93a96f875aa53a51a5a615d4d732d9926d 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengl.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles.h
index 800c593070a9ea28a1f01c1d2b84e0f15ad2b33c..18dd984b3ed21bf5fd3bbfcf1a8a34fe58dfca6f 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles2.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles2.h
index 102f2f3fa7075c34639c03109939460ab2e7c2cc..6ccecf216572b4c9514bded0fd0a1c54690e57d8 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles2.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_opengles2.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_pixels.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_pixels.h
index 04246689f5957e5170ad6be433e089e2483ea85a..0b4364b185231109d61663b4acb50e60e1d8fa16 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_pixels.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_pixels.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -287,7 +287,9 @@ enum
     SDL_PIXELFORMAT_NV12 =      /**< Planar mode: Y + U/V interleaved  (2 planes) */
         SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2'),
     SDL_PIXELFORMAT_NV21 =      /**< Planar mode: Y + V/U interleaved  (2 planes) */
-        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1')
+        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'),
+    SDL_PIXELFORMAT_EXTERNAL_OES =      /**< Android video texture format */
+        SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ')
 };
 
 typedef struct SDL_Color
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_platform.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_platform.h
index 9dde385fa67364dbcfcf22598e33961970c4348e..7dea4ce940fe02f1b233d15ec6a75c9452a0422c 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_platform.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_platform.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -121,7 +121,12 @@
 #if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
 /* Try to find out if we're compiling for WinRT or non-WinRT */
 #if defined(_MSC_VER) && defined(__has_include)
-#define HAVE_WINAPIFAMILY_H __has_include(<winapifamily.h>)
+#if __has_include(<winapifamily.h>)
+#define HAVE_WINAPIFAMILY_H 1
+#else
+#define HAVE_WINAPIFAMILY_H 0
+#endif
+
 /* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */
 #elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_)    /* _MSC_VER == 1700 for Visual Studio 2012 */
 #define HAVE_WINAPIFAMILY_H 1
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_power.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_power.h
index d48e8a3795dfd0d9f8a1cd9ad07ba1c54b1b0d7c..a4fe8a9359c247678ad56a47f8b8106fa465f4a9 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_power.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_power.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_quit.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_quit.h
index 9283c381050a4013097e533a1723c64bb6392938..fea56a8d88b6a0ac9e12807d693628f80991bc3e 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_quit.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_quit.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rect.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rect.h
index c4f6d74b339ddc7b71372221758dc229043362fd..543bb618694ea9d4b5383e71a7c35b666b9e754c 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rect.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rect.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_render.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_render.h
index ea6ef9ff645cef9e643162828ccb6b5173d1b732..d336192974f4cedab0497ad6ac60aaba19f3f389 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_render.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_render.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -898,6 +898,27 @@ extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw
  */
 extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture);
 
+/**
+ *  \brief Get the CAMetalLayer associated with the given Metal renderer
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return CAMetalLayer* on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalCommandEncoder()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer);
+
+/**
+ *  \brief Get the Metal command encoder for the current frame
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return id<MTLRenderCommandEncoder> on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalLayer()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_revision.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_revision.h
index 9376093ee76bd46fbb70196e87673cda9d53ccd6..92fbe67b132156d911795af5e14b335d15645c66 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_revision.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_revision.h
@@ -1,2 +1,2 @@
-#define SDL_REVISION "hg-11645:2088cd828335"
-#define SDL_REVISION_NUMBER 11645
+#define SDL_REVISION "hg-12373:8feb5da6f2fb"
+#define SDL_REVISION_NUMBER 12373
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rwops.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rwops.h
index 7f0cbdfd55d5fabdd8ff80fc5ec605a7dd70bc0c..0960699d4857bb302e9024e7263c89a725a9a276 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rwops.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_rwops.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_scancode.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_scancode.h
index 1d55212048560455d110797913a3dde07416768c..63871aa3b10beed824756b430687a3e16d7ebe44 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_scancode.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_scancode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_sensor.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_sensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac163a8cd1be73201742fe56293ddc840653f706
--- /dev/null
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_sensor.h
@@ -0,0 +1,251 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file SDL_sensor.h
+ *
+ *  Include file for SDL sensor event handling
+ *
+ */
+
+#ifndef _SDL_sensor_h
+#define _SDL_sensor_h
+
+#include "SDL_stdinc.h"
+#include "SDL_error.h"
+
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+extern "C" {
+/* *INDENT-ON* */
+#endif
+
+/**
+ *  \brief SDL_sensor.h
+ *
+ *  In order to use these functions, SDL_Init() must have been called
+ *  with the ::SDL_INIT_SENSOR flag.  This causes SDL to scan the system
+ *  for sensors, and load appropriate drivers.
+ */
+
+struct _SDL_Sensor;
+typedef struct _SDL_Sensor SDL_Sensor;
+
+/**
+ * This is a unique ID for a sensor for the time it is connected to the system,
+ * and is never reused for the lifetime of the application.
+ *
+ * The ID value starts at 0 and increments from there. The value -1 is an invalid ID.
+ */
+typedef Sint32 SDL_SensorID;
+
+/* The different sensors defined by SDL
+ *
+ * Additional sensors may be available, using platform dependent semantics.
+ *
+ * Hare are the additional Android sensors:
+ * https://developer.android.com/reference/android/hardware/SensorEvent.html#values
+ */
+typedef enum
+{
+    SDL_SENSOR_INVALID = -1,    /**< Returned for an invalid sensor */
+    SDL_SENSOR_UNKNOWN,         /**< Unknown sensor type */
+    SDL_SENSOR_ACCEL,           /**< Accelerometer */
+    SDL_SENSOR_GYRO             /**< Gyroscope */
+} SDL_SensorType;
+
+/**
+ * Accelerometer sensor
+ *
+ * The accelerometer returns the current acceleration in SI meters per
+ * second squared. This includes gravity, so a device at rest will have
+ * an acceleration of SDL_STANDARD_GRAVITY straight down.
+ *
+ * values[0]: Acceleration on the x axis
+ * values[1]: Acceleration on the y axis
+ * values[2]: Acceleration on the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+#define SDL_STANDARD_GRAVITY    9.80665f
+
+/**
+ * Gyroscope sensor
+ *
+ * The gyroscope returns the current rate of rotation in radians per second.
+ * The rotation is positive in the counter-clockwise direction. That is,
+ * an observer looking from a positive location on one of the axes would
+ * see positive rotation on that axis when it appeared to be rotating
+ * counter-clockwise.
+ *
+ * values[0]: Angular speed around the x axis
+ * values[1]: Angular speed around the y axis
+ * values[2]: Angular speed around the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+
+/* Function prototypes */
+
+/**
+ *  \brief Count the number of sensors attached to the system right now
+ */
+extern DECLSPEC int SDLCALL SDL_NumSensors(void);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ * 
+ *  \return The sensor name, or NULL if device_index is out of range.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetDeviceName(int device_index);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetDeviceType(int device_index);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if device_index is out of range.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetDeviceNonPortableType(int device_index);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetDeviceInstanceID(int device_index);
+
+/**
+ *  \brief Open a sensor for use.
+ *
+ *  The index passed as an argument refers to the N'th sensor on the system.
+ *
+ *  \return A sensor identifier, or NULL if an error occurred.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorOpen(int device_index);
+
+/**
+ * Return the SDL_Sensor associated with an instance id.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorFromInstanceID(SDL_SensorID instance_id);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  \return The sensor name, or NULL if the sensor is NULL.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetName(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetNonPortableType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor);
+
+/**
+ *  Get the current state of an opened sensor.
+ *
+ *  The number of values and interpretation of the data is sensor dependent.
+ *
+ *  \param sensor The sensor to query
+ *  \param data A pointer filled with the current sensor state
+ *  \param num_values The number of values to write to data
+ *
+ *  \return 0 or -1 if an error occurred.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values);
+
+/**
+ *  Close a sensor previously opened with SDL_SensorOpen()
+ */
+extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor * sensor);
+
+/**
+ *  Update the current state of the open sensors.
+ *
+ *  This is called automatically by the event loop if sensor events are enabled.
+ *
+ *  This needs to be called from the thread that initialized the sensor subsystem.
+ */
+extern DECLSPEC void SDLCALL SDL_SensorUpdate(void);
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+}
+/* *INDENT-ON* */
+#endif
+#include "close_code.h"
+
+#endif /* _SDL_sensor_h */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_shape.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_shape.h
index 9e492d93a4d1f6e42bcb2bbfec31fd71520cbaaa..40a6baaaec3d4d7e46c1da1fed54949123421035 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_shape.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_shape.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_stdinc.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_stdinc.h
index 72402299f2da1892dd2224bc436b1822007f9b51..e373bc380f68867e5638b8f6ea26521a66a906ee 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_stdinc.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_stdinc.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -86,6 +86,28 @@
 #ifdef HAVE_FLOAT_H
 # include <float.h>
 #endif
+#if defined(HAVE_ALLOCA) && !defined(alloca)
+# if defined(HAVE_ALLOCA_H)
+#  include <alloca.h>
+# elif defined(__GNUC__)
+#  define alloca __builtin_alloca
+# elif defined(_MSC_VER)
+#  include <malloc.h>
+#  define alloca _alloca
+# elif defined(__WATCOMC__)
+#  include <malloc.h>
+# elif defined(__BORLANDC__)
+#  include <malloc.h>
+# elif defined(__DMC__)
+#  include <stdlib.h>
+# elif defined(__AIX__)
+#pragma alloca
+# elif defined(__MRC__)
+void *alloca(unsigned);
+# else
+char *alloca();
+# endif
+#endif
 
 /**
  *  The number of elements in an array.
@@ -328,28 +350,6 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int));
 extern "C" {
 #endif
 
-#if defined(HAVE_ALLOCA) && !defined(alloca)
-# if defined(HAVE_ALLOCA_H)
-#  include <alloca.h>
-# elif defined(__GNUC__)
-#  define alloca __builtin_alloca
-# elif defined(_MSC_VER)
-#  include <malloc.h>
-#  define alloca _alloca
-# elif defined(__WATCOMC__)
-#  include <malloc.h>
-# elif defined(__BORLANDC__)
-#  include <malloc.h>
-# elif defined(__DMC__)
-#  include <stdlib.h>
-# elif defined(__AIX__)
-#pragma alloca
-# elif defined(__MRC__)
-void *alloca(unsigned);
-# else
-char *alloca();
-# endif
-#endif
 #ifdef HAVE_ALLOCA
 #define SDL_stack_alloc(type, count)    (type*)alloca(sizeof(type)*(count))
 #define SDL_stack_free(data)
@@ -445,12 +445,12 @@ SDL_FORCE_INLINE void SDL_memset4(void *dst, Uint32 val, size_t dwords)
 #endif
 }
 
-
 extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 
 extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
 
+extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
 extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
@@ -501,18 +501,35 @@ extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size
 #endif
 
 extern DECLSPEC double SDLCALL SDL_acos(double x);
+extern DECLSPEC float SDLCALL SDL_acosf(float x);
 extern DECLSPEC double SDLCALL SDL_asin(double x);
+extern DECLSPEC float SDLCALL SDL_asinf(float x);
 extern DECLSPEC double SDLCALL SDL_atan(double x);
+extern DECLSPEC float SDLCALL SDL_atanf(float x);
 extern DECLSPEC double SDLCALL SDL_atan2(double x, double y);
+extern DECLSPEC float SDLCALL SDL_atan2f(float x, float y);
 extern DECLSPEC double SDLCALL SDL_ceil(double x);
+extern DECLSPEC float SDLCALL SDL_ceilf(float x);
 extern DECLSPEC double SDLCALL SDL_copysign(double x, double y);
+extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_cos(double x);
 extern DECLSPEC float SDLCALL SDL_cosf(float x);
+extern DECLSPEC double SDLCALL SDL_exp(double x);
+extern DECLSPEC float SDLCALL SDL_expf(float x);
 extern DECLSPEC double SDLCALL SDL_fabs(double x);
+extern DECLSPEC float SDLCALL SDL_fabsf(float x);
 extern DECLSPEC double SDLCALL SDL_floor(double x);
+extern DECLSPEC float SDLCALL SDL_floorf(float x);
+extern DECLSPEC double SDLCALL SDL_fmod(double x, double y);
+extern DECLSPEC float SDLCALL SDL_fmodf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_log(double x);
+extern DECLSPEC float SDLCALL SDL_logf(float x);
+extern DECLSPEC double SDLCALL SDL_log10(double x);
+extern DECLSPEC float SDLCALL SDL_log10f(float x);
 extern DECLSPEC double SDLCALL SDL_pow(double x, double y);
+extern DECLSPEC float SDLCALL SDL_powf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_scalbn(double x, int n);
+extern DECLSPEC float SDLCALL SDL_scalbnf(float x, int n);
 extern DECLSPEC double SDLCALL SDL_sin(double x);
 extern DECLSPEC float SDLCALL SDL_sinf(float x);
 extern DECLSPEC double SDLCALL SDL_sqrt(double x);
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_surface.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_surface.h
index 510690c9cffe37fa3153d547d87c9ed0089584c2..730d49fc805ec54d5725c7369db4578d194aa56a 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_surface.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_surface.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,6 +97,17 @@ typedef struct SDL_Surface
 typedef int (SDLCALL *SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect,
                                  struct SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ * \brief The formula used for converting between YUV and RGB
+ */
+typedef enum
+{
+    SDL_YUV_CONVERSION_JPEG,        /**< Full range JPEG */
+    SDL_YUV_CONVERSION_BT601,       /**< BT.601 (the default) */
+    SDL_YUV_CONVERSION_BT709,       /**< BT.709 */
+    SDL_YUV_CONVERSION_AUTOMATIC    /**< BT.601 for SD content, BT.709 for HD content */
+} SDL_YUV_CONVERSION_MODE;
+
 /**
  *  Allocate and free an RGB surface.
  *
@@ -237,6 +248,13 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface,
 extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface,
                                             int flag, Uint32 key);
 
+/**
+ *  \brief Returns whether the surface has a color key
+ *
+ *  \return SDL_TRUE if the surface has a color key, or SDL_FALSE if the surface is NULL or has no color key
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasColorKey(SDL_Surface * surface);
+
 /**
  *  \brief Gets the color key (transparent pixel) in a blittable surface.
  *
@@ -509,6 +527,20 @@ extern DECLSPEC int SDLCALL SDL_LowerBlitScaled
     (SDL_Surface * src, SDL_Rect * srcrect,
     SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ *  \brief Set the YUV conversion mode
+ */
+extern DECLSPEC void SDLCALL SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_MODE mode);
+
+/**
+ *  \brief Get the YUV conversion mode
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionMode(void);
+
+/**
+ *  \brief Get the YUV conversion mode, returning the correct mode for the resolution when the current conversion mode is SDL_YUV_CONVERSION_AUTOMATIC
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionModeForResolution(int width, int height);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_system.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_system.h
index eb069b33d8127f026de8b57b1869ccf3297f75e6..4dc372d6b14bb7fd49f64f716d1f9f29e3a15fbc 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_system.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_system.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,18 @@ extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *a
 #endif /* __WIN32__ */
 
 
+/* Platform specific functions for Linux */
+#ifdef __LINUX__
+
+/**
+   \brief Sets the UNIX nice value for a thread, using setpriority() if possible, and RealtimeKit if available.
+
+   \return 0 on success, or -1 on error.
+ */
+extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
+ 
+#endif /* __LINUX__ */
+	
 /* Platform specific functions for iOS */
 #if defined(__IPHONEOS__) && __IPHONEOS__
 
@@ -108,6 +120,26 @@ extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(void);
  */
 extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void);
 
+/**
+   \brief Return true if the application is running on Android TV
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
+
+/**
+   \brief Return true if the application is running on a Chromebook
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
+
+/**
+  \brief Return true is the application is running on a Samsung DeX docking station
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
+
+/**
+ \brief Trigger the Android system back button behavior.
+ */
+extern DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
+
 /**
    See the official Android developer guide for more information:
    http://developer.android.com/guide/topics/data/data-storage.html
@@ -169,6 +201,25 @@ typedef enum
 } SDL_WinRT_Path;
 
 
+/**
+ *  \brief WinRT Device Family
+ */
+typedef enum
+{
+    /** \brief Unknown family  */
+    SDL_WINRT_DEVICEFAMILY_UNKNOWN,
+
+    /** \brief Desktop family*/
+    SDL_WINRT_DEVICEFAMILY_DESKTOP,
+
+    /** \brief Mobile family (for example smartphone) */
+    SDL_WINRT_DEVICEFAMILY_MOBILE,
+
+    /** \brief XBox family */
+    SDL_WINRT_DEVICEFAMILY_XBOX,
+} SDL_WinRT_DeviceFamily;
+
+
 /**
  *  \brief Retrieves a WinRT defined path on the local file system
  *
@@ -203,8 +254,20 @@ extern DECLSPEC const wchar_t * SDLCALL SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path
  */
 extern DECLSPEC const char * SDLCALL SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType);
 
+/**
+ *  \brief Detects the device family of WinRT plattform on runtime
+ *
+ *  \return Device family
+ */
+extern DECLSPEC SDL_WinRT_DeviceFamily SDLCALL SDL_WinRTGetDeviceFamily();
+
 #endif /* __WINRT__ */
 
+/**
+ \brief Return true if the current device is a tablet.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsTablet(void);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_syswm.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_syswm.h
index 2d18afb6cf90173ca289b53f74dad013a3440909..f1c4021cc8b37be22124d28a99733d1e696e71e6 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_syswm.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_syswm.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -33,12 +33,6 @@
 #include "SDL_video.h"
 #include "SDL_version.h"
 
-#include "begin_code.h"
-/* Set up for C function definitions, even when using C++ */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
 /**
  *  \file SDL_syswm.h
  *
@@ -110,6 +104,12 @@ typedef void *EGLSurface;
 #include "SDL_egl.h"
 #endif
 
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /**
  *  These are the various supported windowing subsystems
  */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test.h
index f55afcb02273929055de6bf3a8848c2025e3bda3..6cc373bf809ca34195c28d7ef465fffac49c0e89 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_assert.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_assert.h
index 11d8363942948261e7a2403c3556d7e45860ef8a..1788d7a206916e92ccc607c24d93805ec368c838 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_assert.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_common.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_common.h
index 24eeb32af1b8731d95e28084a051b219480810be..be2e6b2aab48af89d8aab4986bd15bd0fc13bd68 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_common.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_common.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_compare.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_compare.h
index 9f4c4587ade92d8afe8d8102bd216db4458930e3..c22e447d8aee297971e6c51e4c1dd2fdb3a5cd52 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_compare.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_compare.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_crc32.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_crc32.h
index add480c349fd91512e3a3229f44858407529a106..3d235d07400f10b10297e533f7d74c2cf02bc394 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_crc32.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_crc32.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_font.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_font.h
index 58c9f9d5e5f3334e23df3bbcba45cc6483635f67..59cbdcad6bcb101764c689f915d8395866b6ea6a 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_font.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_font.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_fuzzer.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
index 6cf05747ad91914fe7155322876cd3ff6e2a28d6..8fcb9ebbfa1ba58da745c08e0eb0a322e00319c6 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_harness.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_harness.h
index 68c4bcb7c44ff1fb09722fa81be6fb5013917eef..8641e0a7e3ad9945ed5ec93813920017bcc06447 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_harness.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_harness.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_images.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_images.h
index 683d2f8a689e8d49b1c750493a6c04112a19b8e3..9c4dd5b82954fc21c15ff9027372ca3419c6252e 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_images.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_images.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_log.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_log.h
index 9f993fbbc880ded0f9c4c2dd81d838fb60753ed2..ebd44fb5021c3f4fc64dcab127780ab7da89ebd2 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_log.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_md5.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_md5.h
index 67ca7d1defbb70c02e83d43eef7f11ee6cbc81e5..0e4105768fdb6bd059ad867cb90d74637e28660c 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_md5.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_md5.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_memory.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_memory.h
index 43b67f521be347be755fa725dfd2bdd6b96f6c01..4827ae6f2c372db861a635ae9faa32637f152a18 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_memory.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_memory.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_random.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_random.h
index 2b01922c3ed5b82aace74bce3c661ce7c8b62782..0eb414ff2e4db2488ac5671f9d1a91048d0f3b34 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_random.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_test_random.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_thread.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_thread.h
index d0f6575cd5ac7562cb5dba1ca84b5e94b4a3f029..554dd0b61d02481d6f665dbde0799361b1967632 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_thread.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_thread.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -54,12 +54,13 @@ typedef unsigned int SDL_TLSID;
 /**
  *  The SDL thread priority.
  *
- *  \note On many systems you require special privileges to set high priority.
+ *  \note On many systems you require special privileges to set high or time critical priority.
  */
 typedef enum {
     SDL_THREAD_PRIORITY_LOW,
     SDL_THREAD_PRIORITY_NORMAL,
-    SDL_THREAD_PRIORITY_HIGH
+    SDL_THREAD_PRIORITY_HIGH,
+    SDL_THREAD_PRIORITY_TIME_CRITICAL
 } SDL_ThreadPriority;
 
 /**
@@ -105,14 +106,24 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
 
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
+                 const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
+
+
 /**
  *  Create a thread.
  */
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #endif
 
 #elif defined(__OS2__)
@@ -132,15 +143,31 @@ extern DECLSPEC SDL_Thread *SDLCALL
 SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #endif
 
 #else
 
+/**
+ *  Create a thread with a default stack size.
+ *
+ *  This is equivalent to calling:
+ *  SDL_CreateThreadWithStackSize(fn, name, 0, data);
+ */
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+
 /**
  *  Create a thread.
  *
@@ -158,9 +185,17 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
  *   If a system imposes requirements, SDL will try to munge the string for
  *    it (truncate, etc), but the original string contents will be available
  *    from SDL_GetThreadName().
+ *
+ *   The size (in bytes) of the new stack can be specified. Zero means "use
+ *    the system default" which might be wildly different between platforms
+ *    (x86 Linux generally defaults to eight megabytes, an embedded device
+ *    might be a few kilobytes instead).
+ *
+ *   In SDL 2.1, stacksize will be folded into the original SDL_CreateThread
+ *    function.
  */
 extern DECLSPEC SDL_Thread *SDLCALL
-SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data);
 
 #endif
 
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_timer.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_timer.h
index 282625d3f0dc8dfe2b94fa48032d037cace0de3c..5600618ff4f8fbaacb2edf547ef8da1c182a0257 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_timer.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_timer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_touch.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_touch.h
index db9e25ebdccad221a3c38bbce06b02bec65c3c2c..f4075e79a5b5dd486579aa374474e3927101abe1 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_touch.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_touch.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_types.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_types.h
index 06ee80cb358963b2fca3d8218a81595345e52671..4ac248c8c577f5150c8de8ad1b36df3bd4aea854 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_types.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_types.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_version.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_version.h
index 5b4c402486d6adb4e8ebaec3cfa4bee301e48e8b..31443e149adc071cba7431bc0cd47b007f1a0e22 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_version.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_version.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -59,7 +59,7 @@ typedef struct SDL_version
 */
 #define SDL_MAJOR_VERSION   2
 #define SDL_MINOR_VERSION   0
-#define SDL_PATCHLEVEL      7
+#define SDL_PATCHLEVEL      9
 
 /**
  *  \brief Macro to determine SDL version program was compiled against.
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_video.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_video.h
index 6f6cda8b182b22160d97c0d9d5e684290419d0b9..461f13805101c9214ef4bd9b6df3143c3f093692 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_video.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_video.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -110,7 +110,9 @@ typedef enum
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported.
+                                                     On macOS NSHighResolutionCapable must be set true in the
+                                                     application's Info.plist for this to have any effect. */
     SDL_WINDOW_MOUSE_CAPTURE = 0x00004000,      /**< window has mouse captured (unrelated to INPUT_GRABBED) */
     SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000,      /**< window should always be above others */
     SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
@@ -167,6 +169,24 @@ typedef enum
     SDL_WINDOWEVENT_HIT_TEST        /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL. */
 } SDL_WindowEventID;
 
+/**
+ *  \brief Event subtype for display events
+ */
+typedef enum
+{
+    SDL_DISPLAYEVENT_NONE,          /**< Never used */
+    SDL_DISPLAYEVENT_ORIENTATION    /**< Display orientation has changed to data1 */
+} SDL_DisplayEventID;
+
+typedef enum
+{
+    SDL_ORIENTATION_UNKNOWN,            /**< The display orientation can't be determined */
+    SDL_ORIENTATION_LANDSCAPE,          /**< The display is in landscape mode, with the right side up, relative to portrait mode */
+    SDL_ORIENTATION_LANDSCAPE_FLIPPED,  /**< The display is in landscape mode, with the left side up, relative to portrait mode */
+    SDL_ORIENTATION_PORTRAIT,           /**< The display is in portrait mode */
+    SDL_ORIENTATION_PORTRAIT_FLIPPED    /**< The display is in portrait mode, upside down */
+} SDL_DisplayOrientation;
+
 /**
  *  \brief An opaque handle to an OpenGL context.
  */
@@ -314,18 +334,6 @@ extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex);
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect);
 
-/**
- *  \brief Get the dots/pixels-per-inch for a display
- *
- *  \note Diagonal, horizontal and vertical DPI can all be optionally
- *        returned if the parameter is non-NULL.
- *
- *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
- *
- *  \sa SDL_GetNumVideoDisplays()
- */
-extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
-
 /**
  *  \brief Get the usable desktop area represented by a display, with the
  *         primary display located at 0,0
@@ -345,6 +353,27 @@ extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, fl
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayUsableBounds(int displayIndex, SDL_Rect * rect);
 
+/**
+ *  \brief Get the dots/pixels-per-inch for a display
+ *
+ *  \note Diagonal, horizontal and vertical DPI can all be optionally
+ *        returned if the parameter is non-NULL.
+ *
+ *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
+
+/**
+ *  \brief Get the orientation of a display
+ *
+ *  \return The orientation of the display, or SDL_ORIENTATION_UNKNOWN if it isn't available.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC SDL_DisplayOrientation SDLCALL SDL_GetDisplayOrientation(int displayIndex);
+
 /**
  *  \brief Returns the number of available display modes.
  *
@@ -470,7 +499,7 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window);
  *  If the window is created with any of the SDL_WINDOW_OPENGL or
  *  SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function
  *  (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the
- *  corrensponding UnloadLibrary function is called by SDL_DestroyWindow().
+ *  corresponding UnloadLibrary function is called by SDL_DestroyWindow().
  *
  *  If SDL_WINDOW_VULKAN is specified and there isn't a working Vulkan driver,
  *  SDL_CreateWindow() will fail because SDL_Vulkan_LoadLibrary() will fail.
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_vulkan.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_vulkan.h
index 803b5feef3489ba257ae3664cc93afc9f8fbcf1d..972cca4d7c2658f735d96d549ccd2a5819592b16 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_vulkan.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/SDL_vulkan.h
@@ -69,30 +69,43 @@ typedef VkSurfaceKHR SDL_vulkanSurface; /* for compatibility with Tizen */
  *  \brief Dynamically load a Vulkan loader library.
  *
  *  \param [in] path The platform dependent Vulkan loader library name, or
- *              \c NULL to open the default Vulkan loader library.
+ *              \c NULL.
  *
  *  \return \c 0 on success, or \c -1 if the library couldn't be loaded.
  *
- *  This should be done after initializing the video driver, but before
+ *  If \a path is NULL SDL will use the value of the environment variable
+ *  \c SDL_VULKAN_LIBRARY, if set, otherwise it loads the default Vulkan
+ *  loader library.
+ *
+ *  This should be called after initializing the video driver, but before
  *  creating any Vulkan windows. If no Vulkan loader library is loaded, the
  *  default library will be loaded upon creation of the first Vulkan window.
  *
- *  \note If you specify a non-NULL \a path, you should retrieve all of the
- *        Vulkan functions used in your program from the dynamic library using
+ *  \note It is fairly common for Vulkan applications to link with \a libvulkan
+ *        instead of explicitly loading it at run time. This will work with
+ *        SDL provided the application links to a dynamic library and both it
+ *        and SDL use the same search path.
+ *
+ *  \note If you specify a non-NULL \c path, an application should retrieve all
+ *        of the Vulkan functions it uses from the dynamic library using
  *        \c SDL_Vulkan_GetVkGetInstanceProcAddr() unless you can guarantee
- *        \a path points to the same vulkan loader library that you linked to.
+ *        \c path points to the same vulkan loader library the application
+ *        linked to.
  *
  *  \note On Apple devices, if \a path is NULL, SDL will attempt to find
  *        the vkGetInstanceProcAddr address within all the mach-o images of
- *        the current process. This is because the currently (v0.17.0)
- *        recommended MoltenVK (Vulkan on Metal) usage is as a static library.
- *        If it is not found then SDL will attempt to load \c libMoltenVK.dylib.
- *        Applications using the dylib alternative therefore do not need to do
- *        anything special when calling SDL.
- *
- *  \note On non-Apple devices, SDL requires you to either not link to the
- *        Vulkan loader or link to a dynamic library version. This limitation
- *        may be removed in a future version of SDL.
+ *        the current process. This is because it is fairly common for Vulkan
+ *        applications to link with libvulkan (and historically MoltenVK was
+ *        provided as a static library). If it is not found then, on macOS, SDL
+ *        will attempt to load \c vulkan.framework/vulkan, \c libvulkan.1.dylib,
+ *        \c MoltenVK.framework/MoltenVK and \c libMoltenVK.dylib in that order.
+ *        On iOS SDL will attempt to load \c libMoltenVK.dylib. Applications
+ *        using a dynamic framework or .dylib must ensure it is included in its
+ *        application bundle.
+ *
+ *  \note On non-Apple devices, application linking with a static libvulkan is
+ *        not supported. Either do not link to the Vulkan loader or link to a
+ *        dynamic library version.
  *
  *  \note This function will fail if there are no working Vulkan drivers
  *        installed.
@@ -122,11 +135,11 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \brief Get the names of the Vulkan instance extensions needed to create
  *         a surface with \c SDL_Vulkan_CreateSurface().
  *
- *  \param [in]     window Window for which the required Vulkan instance
+ *  \param [in]     \c NULL or window Window for which the required Vulkan instance
  *                  extensions should be retrieved
- *  \param [in,out] count pointer to an \c unsigned related to the number of
+ *  \param [in,out] pCount pointer to an \c unsigned related to the number of
  *                  required Vulkan instance extensions
- *  \param [out]    names \c NULL or a pointer to an array to be filled with the
+ *  \param [out]    pNames \c NULL or a pointer to an array to be filled with the
  *                  required Vulkan instance extensions
  *
  *  \return \c SDL_TRUE on success, \c SDL_FALSE on error.
@@ -140,6 +153,10 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  is smaller than the number of required extensions, \c SDL_FALSE will be
  *  returned instead of \c SDL_TRUE, to indicate that not all the required
  *  extensions were returned.
+ * 
+ *  \note If \c window is not NULL, it will be checked against its creation
+ *        flags to ensure that the Vulkan flag is present. This parameter
+ *        will be removed in a future major release.
  *
  *  \note The returned list of extensions will contain \c VK_KHR_surface
  *        and zero or more platform specific extensions
@@ -147,12 +164,13 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \note The extension names queried here must be enabled when calling
  *        VkCreateInstance, otherwise surface creation will fail.
  *
- *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag.
+ *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag
+ *        or be \c NULL
  *
  *  \code
  *  unsigned int count;
  *  // get count of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, NULL))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, NULL))
  *      handle_error();
  *
  *  static const char *const additionalExtensions[] =
@@ -166,7 +184,7 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *      handle_error();
  *
  *  // get names of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, names))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, names))
  *      handle_error();
  *
  *  // copy additional extensions after required extensions
@@ -240,6 +258,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(
  * platform with high-DPI support (Apple calls this "Retina"), and not disabled
  * by the \c SDL_HINT_VIDEO_HIGHDPI_DISABLED hint.
  *
+ *  \note On macOS high-DPI support must be enabled for an application by
+ *        setting NSHighResolutionCapable to true in its Info.plist.
+ *
  *  \sa SDL_GetWindowSize()
  *  \sa SDL_CreateWindow()
  */
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/begin_code.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/begin_code.h
index 5372d6f04df418acb02bf51cd545c2170bb73849..6c2106246f2583ba711451bc7e39fc2af7e9eec7 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/begin_code.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/begin_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/include/SDL2/close_code.h b/libs/SDL2/i686-w64-mingw32/include/SDL2/close_code.h
index 2a352dbb9ec7502c62b561f30bd7433b18e581d1..b3b70a4c833d80305bbffd306c8ac099babdf419 100644
--- a/libs/SDL2/i686-w64-mingw32/include/SDL2/close_code.h
+++ b/libs/SDL2/i686-w64-mingw32/include/SDL2/close_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/i686-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake b/libs/SDL2/i686-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
index 208b7c637f7d711afa4a076e04374cacc7abd1af..ee347fcb9bbafc54f19abc1bd9dac7bfcfbb57e1 100644
--- a/libs/SDL2/i686-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
+++ b/libs/SDL2/i686-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
@@ -1,11 +1,11 @@
 # sdl2 cmake project-config input for ./configure scripts
 
-set(prefix "/usr/local/i686-w64-mingw32") 
+set(prefix "/opt/local/i686-w64-mingw32") 
 set(exec_prefix "${prefix}")
 set(libdir "${exec_prefix}/lib")
-set(SDL2_PREFIX "/usr/local/i686-w64-mingw32")
-set(SDL2_EXEC_PREFIX "/usr/local/i686-w64-mingw32")
+set(SDL2_PREFIX "/opt/local/i686-w64-mingw32")
+set(SDL2_EXEC_PREFIX "/opt/local/i686-w64-mingw32")
 set(SDL2_LIBDIR "${exec_prefix}/lib")
 set(SDL2_INCLUDE_DIRS "${prefix}/include/SDL2")
-set(SDL2_LIBRARIES "-L${SDL2_LIBDIR}  -lmingw32 -lSDL2main -lSDL2  -mwindows")
+set(SDL2_LIBRARIES "-L${SDL2_LIBDIR}  -lmingw32 -lSDL2main -lSDL2 -mwindows")
 string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES)
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2.a b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.a
index 771ee00bdb5b07b4e3d44b3b281f439f784abd23..05489a95a2b41c45ee9036080891ae5bfbf43f8b 100644
Binary files a/libs/SDL2/i686-w64-mingw32/lib/libSDL2.a and b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.a differ
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2.dll.a b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.dll.a
index 59d480738003a4f95de26aa1dbb45185d9a2ee42..b20371a131abe0dbb63bbebe22e48e7c57d8b611 100755
Binary files a/libs/SDL2/i686-w64-mingw32/lib/libSDL2.dll.a and b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.dll.a differ
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2.la b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.la
new file mode 100644
index 0000000000000000000000000000000000000000..3848c7db9b285b53d6b47f26d37e916b71ff2145
--- /dev/null
+++ b/libs/SDL2/i686-w64-mingw32/lib/libSDL2.la
@@ -0,0 +1,41 @@
+# libSDL2.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='../bin/SDL2.dll'
+
+# Names of this library.
+library_names='libSDL2.dll.a'
+
+# The name of the static archive.
+old_library='libSDL2.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=' -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2.
+current=9
+age=9
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/i686-w64-mingw32/lib'
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.a b/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.a
index 5f2cfa28f6442bb2d17a57febb97b13e027189a7..190f9e0728d3d281de92cbd305313f5c62fb3388 100644
Binary files a/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.a and b/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.a differ
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.la b/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.la
new file mode 100644
index 0000000000000000000000000000000000000000..5f96235bdbe06f364f89aa9146c3b399cb6b330b
--- /dev/null
+++ b/libs/SDL2/i686-w64-mingw32/lib/libSDL2_test.la
@@ -0,0 +1,41 @@
+# libSDL2_test.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname=''
+
+# Names of this library.
+library_names=''
+
+# The name of the static archive.
+old_library='libSDL2_test.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=''
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2_test.
+current=0
+age=0
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/i686-w64-mingw32/lib'
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.a b/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.a
index 9dddabbfcb8d76cac50bded6b9bdf2e6c5bff0fe..df7c95b95ea3a7595d4ccc18eeaabe16dce0f4e7 100644
Binary files a/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.a and b/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.a differ
diff --git a/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.la b/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.la
new file mode 100644
index 0000000000000000000000000000000000000000..d01b8a1724d3a32f2791b44c0cfa39ee7238928b
--- /dev/null
+++ b/libs/SDL2/i686-w64-mingw32/lib/libSDL2main.la
@@ -0,0 +1,41 @@
+# libSDL2main.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname=''
+
+# Names of this library.
+library_names=''
+
+# The name of the static archive.
+old_library='libSDL2main.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=''
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2main.
+current=0
+age=0
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/i686-w64-mingw32/lib'
diff --git a/libs/SDL2/i686-w64-mingw32/lib/pkgconfig/sdl2.pc b/libs/SDL2/i686-w64-mingw32/lib/pkgconfig/sdl2.pc
index 407c95c48f15c6e7579544527cfdfe4a8af25e20..25fc5928ed6be962f6ba000aee947d6aa8e97c74 100644
--- a/libs/SDL2/i686-w64-mingw32/lib/pkgconfig/sdl2.pc
+++ b/libs/SDL2/i686-w64-mingw32/lib/pkgconfig/sdl2.pc
@@ -1,15 +1,15 @@
 # sdl pkg-config source file
 
-prefix=/usr/local/i686-w64-mingw32
+prefix=/opt/local/i686-w64-mingw32
 exec_prefix=${prefix}
 libdir=${exec_prefix}/lib
 includedir=${prefix}/include
 
 Name: sdl2
 Description: Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.
-Version: 2.0.7
+Version: 2.0.9
 Requires:
 Conflicts:
-Libs: -L${libdir}  -lmingw32 -lSDL2main -lSDL2  -mwindows
-Libs.private: -lmingw32 -lSDL2main -lSDL2  -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc
+Libs: -L${libdir}  -lmingw32 -lSDL2main -lSDL2 -mwindows
+Libs.private: -lmingw32 -lSDL2main -lSDL2 -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -static-libgcc
 Cflags: -I${includedir}/SDL2  -Dmain=SDL_main
diff --git a/libs/SDL2/include/SDL.h b/libs/SDL2/include/SDL.h
old mode 100755
new mode 100644
index 366d50fa39095f0185247d490b31753351ef887c..fc35a419efd9076076aca339303a2937d5e6ada7
--- a/libs/SDL2/include/SDL.h
+++ b/libs/SDL2/include/SDL.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,6 +51,7 @@
 #include "SDL_power.h"
 #include "SDL_render.h"
 #include "SDL_rwops.h"
+#include "SDL_sensor.h"
 #include "SDL_shape.h"
 #include "SDL_system.h"
 #include "SDL_thread.h"
@@ -80,10 +81,11 @@ extern "C" {
 #define SDL_INIT_HAPTIC         0x00001000u
 #define SDL_INIT_GAMECONTROLLER 0x00002000u  /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
 #define SDL_INIT_EVENTS         0x00004000u
+#define SDL_INIT_SENSOR         0x00008000u
 #define SDL_INIT_NOPARACHUTE    0x00100000u  /**< compatibility; this flag is ignored. */
 #define SDL_INIT_EVERYTHING ( \
                 SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
-                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \
+                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
             )
 /* @} */
 
diff --git a/libs/SDL2/include/SDL_assert.h b/libs/SDL2/include/SDL_assert.h
old mode 100755
new mode 100644
index 90abbe39b6f1e5bd7e0bfa3d54b00954efe06e9a..b38f928ae14bb68e916bae1048a4c8648a1a20d8
--- a/libs/SDL2/include/SDL_assert.h
+++ b/libs/SDL2/include/SDL_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_atomic.h b/libs/SDL2/include/SDL_atomic.h
old mode 100755
new mode 100644
index 36e37f3b786840231bf87fddf563c695037c7fc5..b2287748c8f87973ef2aef5093812c0065b89725
--- a/libs/SDL2/include/SDL_atomic.h
+++ b/libs/SDL2/include/SDL_atomic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -158,6 +158,9 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
 #if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("lwsync" : : : "memory")
 #define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("lwsync" : : : "memory")
+#elif defined(__GNUC__) && defined(__aarch64__)
+#define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
+#define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("dmb ish" : : : "memory")
 #elif defined(__GNUC__) && defined(__arm__)
 #if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__)
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
diff --git a/libs/SDL2/include/SDL_audio.h b/libs/SDL2/include/SDL_audio.h
old mode 100755
new mode 100644
index f119c2b2678fc531efe1d3ecc8b1f1751169de53..d3e1bface4d406c3d6c1c3f141a36a3fce8e926b
--- a/libs/SDL2/include/SDL_audio.h
+++ b/libs/SDL2/include/SDL_audio.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -140,7 +140,8 @@ typedef Uint16 SDL_AudioFormat;
 #define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE    0x00000001
 #define SDL_AUDIO_ALLOW_FORMAT_CHANGE       0x00000002
 #define SDL_AUDIO_ALLOW_CHANNELS_CHANGE     0x00000004
-#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
+#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE      0x00000008
+#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)
 /* @} */
 
 /* @} *//* Audio flags */
@@ -527,7 +528,7 @@ extern DECLSPEC SDL_AudioStream * SDLCALL SDL_NewAudioStream(const SDL_AudioForm
  *
  *  \param stream The stream the audio data is being added to
  *  \param buf A pointer to the audio data to add
- *  \param int The number of bytes to write to the stream
+ *  \param len The number of bytes to write to the stream
  *  \return 0 on success, or -1 on error.
  *
  *  \sa SDL_NewAudioStream
diff --git a/libs/SDL2/include/SDL_bits.h b/libs/SDL2/include/SDL_bits.h
old mode 100755
new mode 100644
index bc28572902181c1edf67998fe3800c0269b4c451..eb8322f0d0a181b364871c28df42010ead3f17b1
--- a/libs/SDL2/include/SDL_bits.h
+++ b/libs/SDL2/include/SDL_bits.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_blendmode.h b/libs/SDL2/include/SDL_blendmode.h
old mode 100755
new mode 100644
index 9abd0bd4cf11c6d4d9931a20f21db32e2af0cdc6..36a5ea76f0c52c4364cb5f156cdf7252c0441ecc
--- a/libs/SDL2/include/SDL_blendmode.h
+++ b/libs/SDL2/include/SDL_blendmode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_clipboard.h b/libs/SDL2/include/SDL_clipboard.h
old mode 100755
new mode 100644
index 341f4ba42b17e2025a5a27ecaa3925e3f618c1ad..f28751ebb8ffe5c67f17ccab94f2a55165fea4c8
--- a/libs/SDL2/include/SDL_clipboard.h
+++ b/libs/SDL2/include/SDL_clipboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_config.h b/libs/SDL2/include/SDL_config.h
old mode 100755
new mode 100644
index 1bbb83848142162b87676b35e03d964d5bec56dd..c58be8e72ee0dbb9e73f94be9d8c2c595aa630e5
--- a/libs/SDL2/include/SDL_config.h
+++ b/libs/SDL2/include/SDL_config.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -82,6 +82,9 @@ typedef unsigned int uintptr_t;
 #define HAVE_DSOUND_H 1
 #define HAVE_DXGI_H 1
 #define HAVE_XINPUT_H 1
+#define HAVE_MMDEVICEAPI_H 1
+#define HAVE_AUDIOCLIENT_H 1
+#define HAVE_ENDPOINTVOLUME_H 1
 
 /* This is disabled by default to avoid C runtime dependencies and manifest requirements */
 #ifdef HAVE_LIBC
@@ -109,13 +112,15 @@ typedef unsigned int uintptr_t;
 #define HAVE_MEMCMP 1
 #define HAVE_STRLEN 1
 #define HAVE__STRREV 1
-#define HAVE__STRUPR 1
-#define HAVE__STRLWR 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__STRUPR */
+/* #undef HAVE__STRLWR */
 #define HAVE_STRCHR 1
 #define HAVE_STRRCHR 1
 #define HAVE_STRSTR 1
-#define HAVE__LTOA 1
-#define HAVE__ULTOA 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__LTOA */
+/* #undef HAVE__ULTOA */
 #define HAVE_STRTOL 1
 #define HAVE_STRTOUL 1
 #define HAVE_STRTOD 1
@@ -125,30 +130,45 @@ typedef unsigned int uintptr_t;
 #define HAVE_STRNCMP 1
 #define HAVE__STRICMP 1
 #define HAVE__STRNICMP 1
-#define HAVE_ATAN 1
-#define HAVE_ATAN2 1
-#define HAVE_ACOS  1
-#define HAVE_ASIN  1
-#define HAVE_CEIL 1
-#define HAVE_COS 1
-#define HAVE_COSF 1
-#define HAVE_FABS 1
-#define HAVE_FLOOR 1
-#define HAVE_LOG 1
-#define HAVE_POW 1
-#define HAVE_SIN 1
-#define HAVE_SINF 1
-#define HAVE_SQRT 1
-#define HAVE_SQRTF 1
-#define HAVE_TAN 1
-#define HAVE_TANF 1
-#define HAVE__COPYSIGN 1
+#define HAVE_ACOS   1
+#define HAVE_ACOSF  1
+#define HAVE_ASIN   1
+#define HAVE_ASINF  1
+#define HAVE_ATAN   1
+#define HAVE_ATANF  1
+#define HAVE_ATAN2  1
+#define HAVE_ATAN2F 1
+#define HAVE_CEILF  1
+#define HAVE__COPYSIGN  1
+#define HAVE_COS    1
+#define HAVE_COSF   1
+#define HAVE_EXP    1
+#define HAVE_EXPF   1
+#define HAVE_FABS   1
+#define HAVE_FABSF  1
+#define HAVE_FLOOR  1
+#define HAVE_FLOORF 1
+#define HAVE_FMOD   1
+#define HAVE_FMODF  1
+#define HAVE_LOG    1
+#define HAVE_LOGF   1
+#define HAVE_LOG10  1
+#define HAVE_LOG10F 1
+#define HAVE_POW    1
+#define HAVE_POWF   1
+#define HAVE_SIN    1
+#define HAVE_SINF   1
+#define HAVE_SQRT   1
+#define HAVE_SQRTF  1
+#define HAVE_TAN    1
+#define HAVE_TANF   1
 #if defined(_MSC_VER)
 /* These functions were added with the VC++ 2013 C runtime library */
 #if _MSC_VER >= 1800
 #define HAVE_STRTOLL 1
 #define HAVE_VSSCANF 1
 #define HAVE_SCALBN 1
+#define HAVE_SCALBNF    1
 #endif
 /* This function is available with at least the VC++ 2008 C runtime library */
 #if _MSC_VER >= 1400
@@ -166,7 +186,6 @@ typedef unsigned int uintptr_t;
 /* Enable various audio drivers */
 #define SDL_AUDIO_DRIVER_WASAPI 1
 #define SDL_AUDIO_DRIVER_DSOUND 1
-#define SDL_AUDIO_DRIVER_XAUDIO2    0
 #define SDL_AUDIO_DRIVER_WINMM  1
 #define SDL_AUDIO_DRIVER_DISK   1
 #define SDL_AUDIO_DRIVER_DUMMY  1
@@ -174,9 +193,13 @@ typedef unsigned int uintptr_t;
 /* Enable various input drivers */
 #define SDL_JOYSTICK_DINPUT 1
 #define SDL_JOYSTICK_XINPUT 1
+#define SDL_JOYSTICK_HIDAPI 1
 #define SDL_HAPTIC_DINPUT   1
 #define SDL_HAPTIC_XINPUT   1
 
+/* Enable the dummy sensor driver */
+#define SDL_SENSOR_DUMMY  1
+
 /* Enable various shared object loading systems */
 #define SDL_LOADSO_WINDOWS  1
 
@@ -194,7 +217,7 @@ typedef unsigned int uintptr_t;
 #define SDL_VIDEO_RENDER_D3D    1
 #endif
 #ifndef SDL_VIDEO_RENDER_D3D11
-#define SDL_VIDEO_RENDER_D3D11	0
+#define SDL_VIDEO_RENDER_D3D11  0
 #endif
 
 /* Enable OpenGL support */
diff --git a/libs/SDL2/include/SDL_config.h.cmake b/libs/SDL2/include/SDL_config.h.cmake
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config.h.in b/libs/SDL2/include/SDL_config.h.in
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_android.h b/libs/SDL2/include/SDL_config_android.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_iphoneos.h b/libs/SDL2/include/SDL_config_iphoneos.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_macosx.h b/libs/SDL2/include/SDL_config_macosx.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_macosx.h.orig b/libs/SDL2/include/SDL_config_macosx.h.orig
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_minimal.h b/libs/SDL2/include/SDL_config_minimal.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_pandora.h b/libs/SDL2/include/SDL_config_pandora.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_psp.h b/libs/SDL2/include/SDL_config_psp.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_windows.h b/libs/SDL2/include/SDL_config_windows.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_winrt.h b/libs/SDL2/include/SDL_config_winrt.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_config_wiz.h b/libs/SDL2/include/SDL_config_wiz.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_copying.h b/libs/SDL2/include/SDL_copying.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_cpuinfo.h b/libs/SDL2/include/SDL_cpuinfo.h
old mode 100755
new mode 100644
index 94b64b03bc5dd394af7f2ccfc5968fb1c0e9dfc5..ee3a47e841242fdae7e697f6b5de18a2a47c1ecf
--- a/libs/SDL2/include/SDL_cpuinfo.h
+++ b/libs/SDL2/include/SDL_cpuinfo.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,27 +51,35 @@
 #include <intrin.h>
 #else
 #ifdef __ALTIVEC__
-#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__)
+#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
 #include <altivec.h>
 #undef pixel
+#undef bool
 #endif
 #endif
-#ifdef __MMX__
-#include <mmintrin.h>
+#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
+#include <arm_neon.h>
 #endif
-#ifdef __3dNOW__
+#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
 #include <mm3dnow.h>
 #endif
-#ifdef __SSE__
+#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
+#include <immintrin.h>
+#else
+#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
+#include <mmintrin.h>
+#endif
+#if defined(__SSE__) && !defined(SDL_DISABLE_XMMINTRIN_H)
 #include <xmmintrin.h>
 #endif
-#ifdef __SSE2__
+#if defined(__SSE2__) && !defined(SDL_DISABLE_EMMINTRIN_H)
 #include <emmintrin.h>
 #endif
-#ifdef __SSE3__
+#if defined(__SSE3__) && !defined(SDL_DISABLE_PMMINTRIN_H)
 #include <pmmintrin.h>
 #endif
-#endif
+#endif /* HAVE_IMMINTRIN_H */
+#endif /* compiler version */
 
 #include "begin_code.h"
 /* Set up for C function definitions, even when using C++ */
@@ -154,6 +162,11 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
 
+/**
+ *  This function returns true if the CPU has AVX-512F (foundation) features.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void);
+
 /**
  *  This function returns true if the CPU has NEON (ARM SIMD) features.
  */
@@ -164,7 +177,6 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
  */
 extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
 
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/include/SDL_egl.h b/libs/SDL2/include/SDL_egl.h
old mode 100755
new mode 100644
index e47fbe8624700058a55aff35c8cd39081a19695f..d65ed437c33fe307657aca04f192de87e0ff55e9
--- a/libs/SDL2/include/SDL_egl.h
+++ b/libs/SDL2/include/SDL_egl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -24,7 +24,7 @@
  *
  *  This is a simple file to encapsulate the EGL API headers.
  */
-#ifndef _MSC_VER
+#if !defined(_MSC_VER) && !defined(__ANDROID__)
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
diff --git a/libs/SDL2/include/SDL_endian.h b/libs/SDL2/include/SDL_endian.h
old mode 100755
new mode 100644
index 3eda7f2871144111f5cdfb24e0d57dd988c440c4..ed0bf5ba8f94888115422d06601ff4200119a9e6
--- a/libs/SDL2/include/SDL_endian.h
+++ b/libs/SDL2/include/SDL_endian.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_error.h b/libs/SDL2/include/SDL_error.h
old mode 100755
new mode 100644
index 49be9827e182ae56227e8dde300b7e8c03f8cfb8..c0e46298e8ffdbc6cf6abc4381a7ba9e2dcf1a33
--- a/libs/SDL2/include/SDL_error.h
+++ b/libs/SDL2/include/SDL_error.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_events.h b/libs/SDL2/include/SDL_events.h
old mode 100755
new mode 100644
index 0fc09bc37d795c8b5a4c34bebf9c78134a4c499f..af22eb6466661378e252d090b1504471f74833a8
--- a/libs/SDL2/include/SDL_events.h
+++ b/libs/SDL2/include/SDL_events.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -85,6 +85,9 @@ typedef enum
                                      Called on Android in onResume()
                                 */
 
+    /* Display events */
+    SDL_DISPLAYEVENT   = 0x150,  /**< Display state change */
+
     /* Window events */
     SDL_WINDOWEVENT    = 0x200, /**< Window state change */
     SDL_SYSWMEVENT,             /**< System specific event */
@@ -144,6 +147,9 @@ typedef enum
     SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
     SDL_AUDIODEVICEREMOVED,        /**< An audio device has been removed. */
 
+    /* Sensor events */
+    SDL_SENSORUPDATE = 0x1200,     /**< A sensor was updated */
+
     /* Render events */
     SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
     SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
@@ -168,6 +174,21 @@ typedef struct SDL_CommonEvent
     Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
 } SDL_CommonEvent;
 
+/**
+ *  \brief Display state change event data (event.display.*)
+ */
+typedef struct SDL_DisplayEvent
+{
+    Uint32 type;        /**< ::SDL_DISPLAYEVENT */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Uint32 display;     /**< The associated display index */
+    Uint8 event;        /**< ::SDL_DisplayEventID */
+    Uint8 padding1;
+    Uint8 padding2;
+    Uint8 padding3;
+    Sint32 data1;       /**< event dependent data */
+} SDL_DisplayEvent;
+
 /**
  *  \brief Window state change event data (event.window.*)
  */
@@ -471,6 +492,17 @@ typedef struct SDL_DropEvent
 } SDL_DropEvent;
 
 
+/**
+ *  \brief Sensor event structure (event.sensor.*)
+ */
+typedef struct SDL_SensorEvent
+{
+    Uint32 type;        /**< ::SDL_SENSORUPDATE */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Sint32 which;       /**< The instance ID of the sensor */
+    float data[6];      /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
+} SDL_SensorEvent;
+
 /**
  *  \brief The "quit requested" event
  */
@@ -526,6 +558,7 @@ typedef union SDL_Event
 {
     Uint32 type;                    /**< Event type, shared with all events */
     SDL_CommonEvent common;         /**< Common event data */
+    SDL_DisplayEvent display;       /**< Window event data */
     SDL_WindowEvent window;         /**< Window event data */
     SDL_KeyboardEvent key;          /**< Keyboard event data */
     SDL_TextEditingEvent edit;      /**< Text editing event data */
@@ -542,6 +575,7 @@ typedef union SDL_Event
     SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
     SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
     SDL_AudioDeviceEvent adevice;   /**< Audio device event data */
+    SDL_SensorEvent sensor;         /**< Sensor event data */
     SDL_QuitEvent quit;             /**< Quit request event data */
     SDL_UserEvent user;             /**< Custom event data */
     SDL_SysWMEvent syswm;           /**< System dependent window event data */
diff --git a/libs/SDL2/include/SDL_filesystem.h b/libs/SDL2/include/SDL_filesystem.h
old mode 100755
new mode 100644
index a1c846ad0f07b71a61a60c147c709c2dc6e2c15f..fa6a1fa6e7b2ad4b9e341f6cf71c4e7fca925586
--- a/libs/SDL2/include/SDL_filesystem.h
+++ b/libs/SDL2/include/SDL_filesystem.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_gamecontroller.h b/libs/SDL2/include/SDL_gamecontroller.h
old mode 100755
new mode 100644
index c9215132ef50a7f214b7b4bf248d6fc73c3ff3f4..6ae9c95428907172257a93dfd6074865891f6571
--- a/libs/SDL2/include/SDL_gamecontroller.h
+++ b/libs/SDL2/include/SDL_gamecontroller.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -175,6 +175,14 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index);
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index);
 
+/**
+ *  Get the mapping of a game controller.
+ *  This can be called before any controllers are opened.
+ *
+ *  \return the mapping string.  Must be freed with SDL_free().  Returns NULL if no mapping is available
+ */
+extern DECLSPEC char *SDLCALL SDL_GameControllerMappingForDeviceIndex(int joystick_index);
+
 /**
  *  Open a game controller for use.
  *  The index passed as an argument refers to the N'th game controller on the system.
@@ -196,6 +204,13 @@ extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromInstanceID(SDL
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller);
 
+/**
+ *  Get the player index of an opened game controller, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller);
+
 /**
  *  Get the USB vendor ID of an opened controller, if available.
  *  If the vendor ID isn't available this function returns 0.
@@ -345,6 +360,19 @@ SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller,
 extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
                                                           SDL_GameControllerButton button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param gamecontroller The controller to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a controller previously opened with SDL_GameControllerOpen().
  */
diff --git a/libs/SDL2/include/SDL_gesture.h b/libs/SDL2/include/SDL_gesture.h
old mode 100755
new mode 100644
index 2bba6a7294fa93240d5f6eefbd8d16c5773761ea..b223d80d44c3debd2de55420aaf1e907d1a08919
--- a/libs/SDL2/include/SDL_gesture.h
+++ b/libs/SDL2/include/SDL_gesture.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_haptic.h b/libs/SDL2/include/SDL_haptic.h
old mode 100755
new mode 100644
index 60daefaa709bbbc0a1796bd9af6f9be073cacedd..2ea1bfc1602ee571b0ac3238b183117fcb20f25d
--- a/libs/SDL2/include/SDL_haptic.h
+++ b/libs/SDL2/include/SDL_haptic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -117,6 +117,17 @@
 extern "C" {
 #endif /* __cplusplus */
 
+/* FIXME: For SDL 2.1, adjust all the magnitude variables to be Uint16 (0xFFFF).
+ *
+ * At the moment the magnitude variables are mixed between signed/unsigned, and
+ * it is also not made clear that ALL of those variables expect a max of 0x7FFF.
+ *
+ * Some platforms may have higher precision than that (Linux FF, Windows XInput)
+ * so we should fix the inconsistency in favor of higher possible precision,
+ * adjusting for platforms that use different scales.
+ * -flibit
+ */
+
 /**
  *  \typedef SDL_Haptic
  *
@@ -656,8 +667,8 @@ typedef struct SDL_HapticRamp
  * This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
  *
  * The Left/Right effect is used to explicitly control the large and small
- * motors, commonly found in modern game controllers. One motor is high
- * frequency, the other is low frequency.
+ * motors, commonly found in modern game controllers. The small (right) motor
+ * is high frequency, and the large (left) motor is low frequency.
  *
  * \sa SDL_HAPTIC_LEFTRIGHT
  * \sa SDL_HapticEffect
@@ -668,7 +679,7 @@ typedef struct SDL_HapticLeftRight
     Uint16 type;            /**< ::SDL_HAPTIC_LEFTRIGHT */
 
     /* Replay */
-    Uint32 length;          /**< Duration of the effect. */
+    Uint32 length;          /**< Duration of the effect in milliseconds. */
 
     /* Rumble */
     Uint16 large_magnitude; /**< Control of the large controller motor. */
diff --git a/libs/SDL2/include/SDL_hints.h b/libs/SDL2/include/SDL_hints.h
old mode 100755
new mode 100644
index 007a4bee05c0dc4ef90b1d24e88eb1137cedc945..4ee72e97d66db40a703620a9d0a162a952de1498
--- a/libs/SDL2/include/SDL_hints.h
+++ b/libs/SDL2/include/SDL_hints.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,7 @@ extern "C" {
  *    "opengl"
  *    "opengles2"
  *    "opengles"
+ *    "metal"
  *    "software"
  *
  *  The default varies by platform, but it's the first one in the list that
@@ -210,6 +211,18 @@ extern "C" {
  */
 #define SDL_HINT_VIDEO_X11_NET_WM_PING      "SDL_VIDEO_X11_NET_WM_PING"
 
+/**
+ * \brief A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint should be used.
+ * 
+ * This variable can be set to the following values:
+ * "0" - Disable _NET_WM_BYPASS_COMPOSITOR
+ * "1" - Enable _NET_WM_BYPASS_COMPOSITOR
+ * 
+ * By default SDL will use _NET_WM_BYPASS_COMPOSITOR
+ * 
+ */
+#define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"
+
 /**
  *  \brief  A variable controlling whether the window frame and title bar are interactive when the cursor is hidden 
  *
@@ -249,6 +262,16 @@ extern "C" {
  */
 #define SDL_HINT_GRAB_KEYBOARD              "SDL_GRAB_KEYBOARD"
 
+/**
+ *  \brief  A variable setting the double click time, in milliseconds.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME    "SDL_MOUSE_DOUBLE_CLICK_TIME"
+
+/**
+ *  \brief  A variable setting the double click radius, in pixels.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS    "SDL_MOUSE_DOUBLE_CLICK_RADIUS"
+
 /**
  *  \brief  A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode
  */
@@ -316,7 +339,7 @@ extern "C" {
 #define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED"
 
 /**
- *  \brief  A variable controlling which orientations are allowed on iOS.
+ *  \brief  A variable controlling which orientations are allowed on iOS/Android.
  *
  *  In some circumstances it is necessary to be able to explicitly control
  *  which UI orientations are allowed.
@@ -354,17 +377,37 @@ extern "C" {
  */
 #define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION"
 
+/**
+ * \brief  A variable controlling whether the home indicator bar on iPhone X
+ *         should be hidden.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - The indicator bar is not hidden (default for windowed applications)
+ *    "1"       - The indicator bar is hidden and is shown when the screen is touched (useful for movie playback applications)
+ *    "2"       - The indicator bar is dim and the first swipe makes it visible and the second swipe performs the "home" action (default for fullscreen applications)
+ */
+#define SDL_HINT_IOS_HIDE_HOME_INDICATOR "SDL_IOS_HIDE_HOME_INDICATOR"
+
 /**
  *  \brief  A variable controlling whether the Android / iOS built-in
- *  accelerometer should be listed as a joystick device, rather than listing
- *  actual joysticks only.
+ *  accelerometer should be listed as a joystick device.
  *
  *  This variable can be set to the following values:
- *    "0"       - List only real joysticks and accept input from them
- *    "1"       - List real joysticks along with the accelerometer as if it were a 3 axis joystick (the default).
+ *    "0"       - The accelerometer is not listed as a joystick
+ *    "1"       - The accelerometer is available as a 3 axis joystick (the default).
  */
 #define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK"
 
+/**
+ *  \brief  A variable controlling whether the Android / tvOS remotes
+ *  should be listed as joystick devices, instead of sending keyboard events.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - Remotes send enter/escape/arrow key events
+ *    "1"       - Remotes are available as 2 axis, 2 button joysticks (the default).
+ */
+#define SDL_HINT_TV_REMOTE_AS_JOYSTICK "SDL_TV_REMOTE_AS_JOYSTICK"
+
 /**
  *  \brief  A variable that lets you disable the detection and use of Xinput gamepad devices
  *
@@ -432,6 +475,88 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
 
+/**
+ *  \brief  A variable controlling whether the HIDAPI joystick drivers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI drivers are not used
+ *    "1"       - HIDAPI drivers are used (the default)
+ *
+ *  This variable is the default for all drivers, but can be overridden by the hints for specific drivers below.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for PS4 controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
+
+/**
+ *  \brief  A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - extended reports are not enabled (the default)
+ *    "1"       - extended reports
+ *
+ *  Extended input reports allow rumble on Bluetooth PS4 controllers, but
+ *  break DirectInput handling for applications that don't use SDL.
+ *
+ *  Once extended reports are enabled, they can not be disabled without
+ *  power cycling the controller.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Steam Controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for XBox controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOX   "SDL_JOYSTICK_HIDAPI_XBOX"
+
+/**
+ *  \brief  A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
+ *
+ *  The variable can be set to the following values:
+ *    "0"       - Do not scan for Steam Controllers
+ *    "1"       - Scan for Steam Controllers (the default)
+ *
+ *  The default value is "1".  This hint must be set before initializing the joystick subsystem.
+ */
+#define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS"
+
+
 /**
  *  \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
  *      This is a debugging aid for developers and not expected to be used by end users. The default is "1"
@@ -494,6 +619,10 @@ extern "C" {
 *  This is specially useful if you build SDL against a non glibc libc library (such as musl) which
 *  provides a relatively small default thread stack size (a few kilobytes versus the default 8MB glibc uses).
 *  Support for this hint is currently available only in the pthread, Windows, and PSP backend.
+*
+*  Instead of this hint, in 2.0.9 and later, you can use
+*  SDL_CreateThreadWithStackSize(). This hint only works with the classic
+*  SDL_CreateThread().
 */
 #define SDL_HINT_THREAD_STACK_SIZE              "SDL_THREAD_STACK_SIZE"
 
@@ -719,6 +848,35 @@ extern "C" {
  */
 #define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
 
+ /**
+ * \brief A variable to control whether we trap the Android back button to handle it manually.
+ *        This is necessary for the right mouse button to work on some Android devices, or
+ *        to be able to trap the back button for use in your code reliably.  If set to true,
+ *        the back button will show up as an SDL_KEYDOWN / SDL_KEYUP pair with a keycode of 
+ *        SDL_SCANCODE_AC_BACK.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - Back button will be handled as usual for system. (default)
+ *   "1"       - Back button will be trapped, allowing you to handle the key press
+ *               manually.  (This will also let right mouse click work on systems 
+ *               where the right mouse button functions as back.)
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
+
+ /**
+ * \brief A variable to control whether the return key on the soft keyboard
+ *        should hide the soft keyboard on Android and iOS.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - The return key will be handled as a key event. This is the behaviour of SDL <= 2.0.3. (default)
+ *   "1"       - The return key will hide the keyboard.
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_RETURN_KEY_HIDES_IME "SDL_RETURN_KEY_HIDES_IME"
+
 /**
  *  \brief override the binding element for keyboard inputs for Emscripten builds
  *
@@ -752,7 +910,7 @@ extern "C" {
  *   "0"       - SDL will generate a window-close event when it sees Alt+F4.
  *   "1"       - SDL will only do normal key handling for Alt+F4.
  */
-#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4	"SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
+#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
 
 /**
  *  \brief Prevent SDL from using version 4 of the bitmap header when saving BMPs.
@@ -797,6 +955,24 @@ extern "C" {
  */
 #define SDL_HINT_RPI_VIDEO_LAYER           "SDL_RPI_VIDEO_LAYER"
 
+/**
+ * \brief Tell the video driver that we only want a double buffer.
+ *
+ * By default, most lowlevel 2D APIs will use a triple buffer scheme that 
+ * wastes no CPU time on waiting for vsync after issuing a flip, but
+ * introduces a frame of latency. On the other hand, using a double buffer
+ * scheme instead is recommended for cases where low latency is an important
+ * factor because we save a whole frame of latency.
+ * We do so by waiting for vsync immediately after issuing a flip, usually just
+ * after eglSwapBuffers call in the backend's *_SwapWindow function.
+ *
+ * Since it's driver-specific, it's only supported where possible and
+ * implemented. Currently supported the following drivers:
+ * - KMSDRM (kmsdrm)
+ * - Raspberry Pi (raspberrypi)
+ */
+#define SDL_HINT_VIDEO_DOUBLE_BUFFER      "SDL_VIDEO_DOUBLE_BUFFER"
+
 /**
  *  \brief  A variable controlling what driver to use for OpenGL ES contexts.
  *
diff --git a/libs/SDL2/include/SDL_joystick.h b/libs/SDL2/include/SDL_joystick.h
old mode 100755
new mode 100644
index f598dc828dafa2751d3aba2286059a8298d79aa3..6e05a9c2056cf7134c22459066d253e80e9a4315
--- a/libs/SDL2/include/SDL_joystick.h
+++ b/libs/SDL2/include/SDL_joystick.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,10 +97,10 @@ typedef enum
 typedef enum
 {
     SDL_JOYSTICK_POWER_UNKNOWN = -1,
-    SDL_JOYSTICK_POWER_EMPTY,
-    SDL_JOYSTICK_POWER_LOW,
-    SDL_JOYSTICK_POWER_MEDIUM,
-    SDL_JOYSTICK_POWER_FULL,
+    SDL_JOYSTICK_POWER_EMPTY,   /* <= 5% */
+    SDL_JOYSTICK_POWER_LOW,     /* <= 20% */
+    SDL_JOYSTICK_POWER_MEDIUM,  /* <= 70% */
+    SDL_JOYSTICK_POWER_FULL,    /* <= 100% */
     SDL_JOYSTICK_POWER_WIRED,
     SDL_JOYSTICK_POWER_MAX
 } SDL_JoystickPowerLevel;
@@ -132,6 +132,12 @@ extern DECLSPEC int SDLCALL SDL_NumJoysticks(void);
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index);
 
+/**
+ *  Get the player index of a joystick, or -1 if it's not available
+ *  This can be called before any joysticks are opened.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetDevicePlayerIndex(int device_index);
+
 /**
  *  Return the GUID for the joystick at this index
  *  This can be called before any joysticks are opened.
@@ -194,6 +200,13 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick);
 
+/**
+ *  Get the player index of an opened joystick, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetPlayerIndex(SDL_Joystick * joystick);
+
 /**
  *  Return the GUID for this opened joystick
  */
@@ -361,6 +374,19 @@ extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick,
 extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
                                                     int button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param joystick The joystick to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a joystick previously opened with SDL_JoystickOpen().
  */
diff --git a/libs/SDL2/include/SDL_keyboard.h b/libs/SDL2/include/SDL_keyboard.h
old mode 100755
new mode 100644
index e78ca469082c793a8190331c01766a3e3374a5ba..87482317161a6e05324bf5ab769743b1a385b436
--- a/libs/SDL2/include/SDL_keyboard.h
+++ b/libs/SDL2/include/SDL_keyboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_keycode.h b/libs/SDL2/include/SDL_keycode.h
old mode 100755
new mode 100644
index c41e45ff61b6959937f7f4e912cef24064b72e1f..d7d5b1dbcdefce3777ee48eddcc46853eba2e584
--- a/libs/SDL2/include/SDL_keycode.h
+++ b/libs/SDL2/include/SDL_keycode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_loadso.h b/libs/SDL2/include/SDL_loadso.h
old mode 100755
new mode 100644
index 6ea256c6ae5813dc7b00c269101b4e866fbb5fcc..da56fb452779ee015da429caa8da746c985b0ee0
--- a/libs/SDL2/include/SDL_loadso.h
+++ b/libs/SDL2/include/SDL_loadso.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_log.h b/libs/SDL2/include/SDL_log.h
old mode 100755
new mode 100644
index 356d0e1cf7492f54d82e5829a6030587400869d1..e12b6588601712bd217578226f143941ff019cbb
--- a/libs/SDL2/include/SDL_log.h
+++ b/libs/SDL2/include/SDL_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_main.h b/libs/SDL2/include/SDL_main.h
old mode 100755
new mode 100644
index 2af32360f7be842213bca1253268d5198c768377..98558217fc23ea63118899e44a710b7ba934c720
--- a/libs/SDL2/include/SDL_main.h
+++ b/libs/SDL2/include/SDL_main.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -63,10 +63,13 @@
 /* On Android SDL provides a Java class in SDLActivity.java that is the
    main activity entry point.
 
-   See README-android.md for more details on extending that class.
+   See docs/README-android.md for more details on extending that class.
  */
 #define SDL_MAIN_NEEDED
 
+/* We need to export SDL_main so it can be launched from Java */
+#define SDLMAIN_DECLSPEC    DECLSPEC
+
 #elif defined(__NACL__)
 /* On NACL we use ppapi_simple to set up the application helper code,
    then wait for the first PSE_INSTANCE_DIDCHANGEVIEW event before 
@@ -85,6 +88,10 @@
 #define C_LINKAGE
 #endif /* __cplusplus */
 
+#ifndef SDLMAIN_DECLSPEC
+#define SDLMAIN_DECLSPEC
+#endif
+
 /**
  *  \file SDL_main.h
  *
@@ -107,7 +114,7 @@
 /**
  *  The prototype for the application's main() function
  */
-extern C_LINKAGE DECLSPEC int SDL_main(int argc, char *argv[]);
+extern C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
 
 
 #include "begin_code.h"
diff --git a/libs/SDL2/include/SDL_messagebox.h b/libs/SDL2/include/SDL_messagebox.h
old mode 100755
new mode 100644
index c326d8f01098b147e1bff015fb8e355e37f00744..b7be59d88f50ad7686b794ca13cd66f29e7ef557
--- a/libs/SDL2/include/SDL_messagebox.h
+++ b/libs/SDL2/include/SDL_messagebox.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_mouse.h b/libs/SDL2/include/SDL_mouse.h
old mode 100755
new mode 100644
index 6001bd46c1e80270d85d18dd72025b0c17f2722a..d3c9f6156cba36ecff91929d7a7eda0784b4af69
--- a/libs/SDL2/include/SDL_mouse.h
+++ b/libs/SDL2/include/SDL_mouse.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_mutex.h b/libs/SDL2/include/SDL_mutex.h
old mode 100755
new mode 100644
index 0272379e55e16bb25a913f9b4e4ca11809af7b56..ba4247ced8176dd98396d552536bc4646faa7841
--- a/libs/SDL2/include/SDL_mutex.h
+++ b/libs/SDL2/include/SDL_mutex.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_name.h b/libs/SDL2/include/SDL_name.h
old mode 100755
new mode 100644
index 8095ed3ffd4df27fb77a15eebcd532536c3efc09..ecd863f4ca873b55249601228efc5fc578aa4fb1
--- a/libs/SDL2/include/SDL_name.h
+++ b/libs/SDL2/include/SDL_name.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_opengl.h b/libs/SDL2/include/SDL_opengl.h
old mode 100755
new mode 100644
index 314dd57eda395c3b9e7bcbaf2d3749b9aa01864d..253d9c93a96f875aa53a51a5a615d4d732d9926d
--- a/libs/SDL2/include/SDL_opengl.h
+++ b/libs/SDL2/include/SDL_opengl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_opengl_glext.h b/libs/SDL2/include/SDL_opengl_glext.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_opengles.h b/libs/SDL2/include/SDL_opengles.h
old mode 100755
new mode 100644
index 800c593070a9ea28a1f01c1d2b84e0f15ad2b33c..18dd984b3ed21bf5fd3bbfcf1a8a34fe58dfca6f
--- a/libs/SDL2/include/SDL_opengles.h
+++ b/libs/SDL2/include/SDL_opengles.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_opengles2.h b/libs/SDL2/include/SDL_opengles2.h
old mode 100755
new mode 100644
index 102f2f3fa7075c34639c03109939460ab2e7c2cc..6ccecf216572b4c9514bded0fd0a1c54690e57d8
--- a/libs/SDL2/include/SDL_opengles2.h
+++ b/libs/SDL2/include/SDL_opengles2.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_opengles2_gl2.h b/libs/SDL2/include/SDL_opengles2_gl2.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_opengles2_gl2ext.h b/libs/SDL2/include/SDL_opengles2_gl2ext.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_opengles2_gl2platform.h b/libs/SDL2/include/SDL_opengles2_gl2platform.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_opengles2_khrplatform.h b/libs/SDL2/include/SDL_opengles2_khrplatform.h
old mode 100755
new mode 100644
diff --git a/libs/SDL2/include/SDL_pixels.h b/libs/SDL2/include/SDL_pixels.h
old mode 100755
new mode 100644
index 04246689f5957e5170ad6be433e089e2483ea85a..0b4364b185231109d61663b4acb50e60e1d8fa16
--- a/libs/SDL2/include/SDL_pixels.h
+++ b/libs/SDL2/include/SDL_pixels.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -287,7 +287,9 @@ enum
     SDL_PIXELFORMAT_NV12 =      /**< Planar mode: Y + U/V interleaved  (2 planes) */
         SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2'),
     SDL_PIXELFORMAT_NV21 =      /**< Planar mode: Y + V/U interleaved  (2 planes) */
-        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1')
+        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'),
+    SDL_PIXELFORMAT_EXTERNAL_OES =      /**< Android video texture format */
+        SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ')
 };
 
 typedef struct SDL_Color
diff --git a/libs/SDL2/include/SDL_platform.h b/libs/SDL2/include/SDL_platform.h
old mode 100755
new mode 100644
index 9dde385fa67364dbcfcf22598e33961970c4348e..7dea4ce940fe02f1b233d15ec6a75c9452a0422c
--- a/libs/SDL2/include/SDL_platform.h
+++ b/libs/SDL2/include/SDL_platform.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -121,7 +121,12 @@
 #if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
 /* Try to find out if we're compiling for WinRT or non-WinRT */
 #if defined(_MSC_VER) && defined(__has_include)
-#define HAVE_WINAPIFAMILY_H __has_include(<winapifamily.h>)
+#if __has_include(<winapifamily.h>)
+#define HAVE_WINAPIFAMILY_H 1
+#else
+#define HAVE_WINAPIFAMILY_H 0
+#endif
+
 /* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */
 #elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_)    /* _MSC_VER == 1700 for Visual Studio 2012 */
 #define HAVE_WINAPIFAMILY_H 1
diff --git a/libs/SDL2/include/SDL_power.h b/libs/SDL2/include/SDL_power.h
old mode 100755
new mode 100644
index d48e8a3795dfd0d9f8a1cd9ad07ba1c54b1b0d7c..a4fe8a9359c247678ad56a47f8b8106fa465f4a9
--- a/libs/SDL2/include/SDL_power.h
+++ b/libs/SDL2/include/SDL_power.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_quit.h b/libs/SDL2/include/SDL_quit.h
old mode 100755
new mode 100644
index 9283c381050a4013097e533a1723c64bb6392938..fea56a8d88b6a0ac9e12807d693628f80991bc3e
--- a/libs/SDL2/include/SDL_quit.h
+++ b/libs/SDL2/include/SDL_quit.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_rect.h b/libs/SDL2/include/SDL_rect.h
old mode 100755
new mode 100644
index c4f6d74b339ddc7b71372221758dc229043362fd..543bb618694ea9d4b5383e71a7c35b666b9e754c
--- a/libs/SDL2/include/SDL_rect.h
+++ b/libs/SDL2/include/SDL_rect.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_render.h b/libs/SDL2/include/SDL_render.h
old mode 100755
new mode 100644
index ea6ef9ff645cef9e643162828ccb6b5173d1b732..d336192974f4cedab0497ad6ac60aaba19f3f389
--- a/libs/SDL2/include/SDL_render.h
+++ b/libs/SDL2/include/SDL_render.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -898,6 +898,27 @@ extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw
  */
 extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture);
 
+/**
+ *  \brief Get the CAMetalLayer associated with the given Metal renderer
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return CAMetalLayer* on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalCommandEncoder()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer);
+
+/**
+ *  \brief Get the Metal command encoder for the current frame
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return id<MTLRenderCommandEncoder> on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalLayer()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/include/SDL_revision.h b/libs/SDL2/include/SDL_revision.h
old mode 100755
new mode 100644
index 9376093ee76bd46fbb70196e87673cda9d53ccd6..92fbe67b132156d911795af5e14b335d15645c66
--- a/libs/SDL2/include/SDL_revision.h
+++ b/libs/SDL2/include/SDL_revision.h
@@ -1,2 +1,2 @@
-#define SDL_REVISION "hg-11645:2088cd828335"
-#define SDL_REVISION_NUMBER 11645
+#define SDL_REVISION "hg-12373:8feb5da6f2fb"
+#define SDL_REVISION_NUMBER 12373
diff --git a/libs/SDL2/include/SDL_rwops.h b/libs/SDL2/include/SDL_rwops.h
old mode 100755
new mode 100644
index 7f0cbdfd55d5fabdd8ff80fc5ec605a7dd70bc0c..0960699d4857bb302e9024e7263c89a725a9a276
--- a/libs/SDL2/include/SDL_rwops.h
+++ b/libs/SDL2/include/SDL_rwops.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_scancode.h b/libs/SDL2/include/SDL_scancode.h
old mode 100755
new mode 100644
index 1d55212048560455d110797913a3dde07416768c..63871aa3b10beed824756b430687a3e16d7ebe44
--- a/libs/SDL2/include/SDL_scancode.h
+++ b/libs/SDL2/include/SDL_scancode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_sensor.h b/libs/SDL2/include/SDL_sensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac163a8cd1be73201742fe56293ddc840653f706
--- /dev/null
+++ b/libs/SDL2/include/SDL_sensor.h
@@ -0,0 +1,251 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file SDL_sensor.h
+ *
+ *  Include file for SDL sensor event handling
+ *
+ */
+
+#ifndef _SDL_sensor_h
+#define _SDL_sensor_h
+
+#include "SDL_stdinc.h"
+#include "SDL_error.h"
+
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+extern "C" {
+/* *INDENT-ON* */
+#endif
+
+/**
+ *  \brief SDL_sensor.h
+ *
+ *  In order to use these functions, SDL_Init() must have been called
+ *  with the ::SDL_INIT_SENSOR flag.  This causes SDL to scan the system
+ *  for sensors, and load appropriate drivers.
+ */
+
+struct _SDL_Sensor;
+typedef struct _SDL_Sensor SDL_Sensor;
+
+/**
+ * This is a unique ID for a sensor for the time it is connected to the system,
+ * and is never reused for the lifetime of the application.
+ *
+ * The ID value starts at 0 and increments from there. The value -1 is an invalid ID.
+ */
+typedef Sint32 SDL_SensorID;
+
+/* The different sensors defined by SDL
+ *
+ * Additional sensors may be available, using platform dependent semantics.
+ *
+ * Hare are the additional Android sensors:
+ * https://developer.android.com/reference/android/hardware/SensorEvent.html#values
+ */
+typedef enum
+{
+    SDL_SENSOR_INVALID = -1,    /**< Returned for an invalid sensor */
+    SDL_SENSOR_UNKNOWN,         /**< Unknown sensor type */
+    SDL_SENSOR_ACCEL,           /**< Accelerometer */
+    SDL_SENSOR_GYRO             /**< Gyroscope */
+} SDL_SensorType;
+
+/**
+ * Accelerometer sensor
+ *
+ * The accelerometer returns the current acceleration in SI meters per
+ * second squared. This includes gravity, so a device at rest will have
+ * an acceleration of SDL_STANDARD_GRAVITY straight down.
+ *
+ * values[0]: Acceleration on the x axis
+ * values[1]: Acceleration on the y axis
+ * values[2]: Acceleration on the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+#define SDL_STANDARD_GRAVITY    9.80665f
+
+/**
+ * Gyroscope sensor
+ *
+ * The gyroscope returns the current rate of rotation in radians per second.
+ * The rotation is positive in the counter-clockwise direction. That is,
+ * an observer looking from a positive location on one of the axes would
+ * see positive rotation on that axis when it appeared to be rotating
+ * counter-clockwise.
+ *
+ * values[0]: Angular speed around the x axis
+ * values[1]: Angular speed around the y axis
+ * values[2]: Angular speed around the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+
+/* Function prototypes */
+
+/**
+ *  \brief Count the number of sensors attached to the system right now
+ */
+extern DECLSPEC int SDLCALL SDL_NumSensors(void);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ * 
+ *  \return The sensor name, or NULL if device_index is out of range.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetDeviceName(int device_index);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetDeviceType(int device_index);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if device_index is out of range.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetDeviceNonPortableType(int device_index);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetDeviceInstanceID(int device_index);
+
+/**
+ *  \brief Open a sensor for use.
+ *
+ *  The index passed as an argument refers to the N'th sensor on the system.
+ *
+ *  \return A sensor identifier, or NULL if an error occurred.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorOpen(int device_index);
+
+/**
+ * Return the SDL_Sensor associated with an instance id.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorFromInstanceID(SDL_SensorID instance_id);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  \return The sensor name, or NULL if the sensor is NULL.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetName(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetNonPortableType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor);
+
+/**
+ *  Get the current state of an opened sensor.
+ *
+ *  The number of values and interpretation of the data is sensor dependent.
+ *
+ *  \param sensor The sensor to query
+ *  \param data A pointer filled with the current sensor state
+ *  \param num_values The number of values to write to data
+ *
+ *  \return 0 or -1 if an error occurred.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values);
+
+/**
+ *  Close a sensor previously opened with SDL_SensorOpen()
+ */
+extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor * sensor);
+
+/**
+ *  Update the current state of the open sensors.
+ *
+ *  This is called automatically by the event loop if sensor events are enabled.
+ *
+ *  This needs to be called from the thread that initialized the sensor subsystem.
+ */
+extern DECLSPEC void SDLCALL SDL_SensorUpdate(void);
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+}
+/* *INDENT-ON* */
+#endif
+#include "close_code.h"
+
+#endif /* _SDL_sensor_h */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/include/SDL_shape.h b/libs/SDL2/include/SDL_shape.h
old mode 100755
new mode 100644
index 9e492d93a4d1f6e42bcb2bbfec31fd71520cbaaa..40a6baaaec3d4d7e46c1da1fed54949123421035
--- a/libs/SDL2/include/SDL_shape.h
+++ b/libs/SDL2/include/SDL_shape.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_stdinc.h b/libs/SDL2/include/SDL_stdinc.h
old mode 100755
new mode 100644
index 72402299f2da1892dd2224bc436b1822007f9b51..e373bc380f68867e5638b8f6ea26521a66a906ee
--- a/libs/SDL2/include/SDL_stdinc.h
+++ b/libs/SDL2/include/SDL_stdinc.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -86,6 +86,28 @@
 #ifdef HAVE_FLOAT_H
 # include <float.h>
 #endif
+#if defined(HAVE_ALLOCA) && !defined(alloca)
+# if defined(HAVE_ALLOCA_H)
+#  include <alloca.h>
+# elif defined(__GNUC__)
+#  define alloca __builtin_alloca
+# elif defined(_MSC_VER)
+#  include <malloc.h>
+#  define alloca _alloca
+# elif defined(__WATCOMC__)
+#  include <malloc.h>
+# elif defined(__BORLANDC__)
+#  include <malloc.h>
+# elif defined(__DMC__)
+#  include <stdlib.h>
+# elif defined(__AIX__)
+#pragma alloca
+# elif defined(__MRC__)
+void *alloca(unsigned);
+# else
+char *alloca();
+# endif
+#endif
 
 /**
  *  The number of elements in an array.
@@ -328,28 +350,6 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int));
 extern "C" {
 #endif
 
-#if defined(HAVE_ALLOCA) && !defined(alloca)
-# if defined(HAVE_ALLOCA_H)
-#  include <alloca.h>
-# elif defined(__GNUC__)
-#  define alloca __builtin_alloca
-# elif defined(_MSC_VER)
-#  include <malloc.h>
-#  define alloca _alloca
-# elif defined(__WATCOMC__)
-#  include <malloc.h>
-# elif defined(__BORLANDC__)
-#  include <malloc.h>
-# elif defined(__DMC__)
-#  include <stdlib.h>
-# elif defined(__AIX__)
-#pragma alloca
-# elif defined(__MRC__)
-void *alloca(unsigned);
-# else
-char *alloca();
-# endif
-#endif
 #ifdef HAVE_ALLOCA
 #define SDL_stack_alloc(type, count)    (type*)alloca(sizeof(type)*(count))
 #define SDL_stack_free(data)
@@ -445,12 +445,12 @@ SDL_FORCE_INLINE void SDL_memset4(void *dst, Uint32 val, size_t dwords)
 #endif
 }
 
-
 extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 
 extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
 
+extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
 extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
@@ -501,18 +501,35 @@ extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size
 #endif
 
 extern DECLSPEC double SDLCALL SDL_acos(double x);
+extern DECLSPEC float SDLCALL SDL_acosf(float x);
 extern DECLSPEC double SDLCALL SDL_asin(double x);
+extern DECLSPEC float SDLCALL SDL_asinf(float x);
 extern DECLSPEC double SDLCALL SDL_atan(double x);
+extern DECLSPEC float SDLCALL SDL_atanf(float x);
 extern DECLSPEC double SDLCALL SDL_atan2(double x, double y);
+extern DECLSPEC float SDLCALL SDL_atan2f(float x, float y);
 extern DECLSPEC double SDLCALL SDL_ceil(double x);
+extern DECLSPEC float SDLCALL SDL_ceilf(float x);
 extern DECLSPEC double SDLCALL SDL_copysign(double x, double y);
+extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_cos(double x);
 extern DECLSPEC float SDLCALL SDL_cosf(float x);
+extern DECLSPEC double SDLCALL SDL_exp(double x);
+extern DECLSPEC float SDLCALL SDL_expf(float x);
 extern DECLSPEC double SDLCALL SDL_fabs(double x);
+extern DECLSPEC float SDLCALL SDL_fabsf(float x);
 extern DECLSPEC double SDLCALL SDL_floor(double x);
+extern DECLSPEC float SDLCALL SDL_floorf(float x);
+extern DECLSPEC double SDLCALL SDL_fmod(double x, double y);
+extern DECLSPEC float SDLCALL SDL_fmodf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_log(double x);
+extern DECLSPEC float SDLCALL SDL_logf(float x);
+extern DECLSPEC double SDLCALL SDL_log10(double x);
+extern DECLSPEC float SDLCALL SDL_log10f(float x);
 extern DECLSPEC double SDLCALL SDL_pow(double x, double y);
+extern DECLSPEC float SDLCALL SDL_powf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_scalbn(double x, int n);
+extern DECLSPEC float SDLCALL SDL_scalbnf(float x, int n);
 extern DECLSPEC double SDLCALL SDL_sin(double x);
 extern DECLSPEC float SDLCALL SDL_sinf(float x);
 extern DECLSPEC double SDLCALL SDL_sqrt(double x);
diff --git a/libs/SDL2/include/SDL_surface.h b/libs/SDL2/include/SDL_surface.h
old mode 100755
new mode 100644
index 510690c9cffe37fa3153d547d87c9ed0089584c2..730d49fc805ec54d5725c7369db4578d194aa56a
--- a/libs/SDL2/include/SDL_surface.h
+++ b/libs/SDL2/include/SDL_surface.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,6 +97,17 @@ typedef struct SDL_Surface
 typedef int (SDLCALL *SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect,
                                  struct SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ * \brief The formula used for converting between YUV and RGB
+ */
+typedef enum
+{
+    SDL_YUV_CONVERSION_JPEG,        /**< Full range JPEG */
+    SDL_YUV_CONVERSION_BT601,       /**< BT.601 (the default) */
+    SDL_YUV_CONVERSION_BT709,       /**< BT.709 */
+    SDL_YUV_CONVERSION_AUTOMATIC    /**< BT.601 for SD content, BT.709 for HD content */
+} SDL_YUV_CONVERSION_MODE;
+
 /**
  *  Allocate and free an RGB surface.
  *
@@ -237,6 +248,13 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface,
 extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface,
                                             int flag, Uint32 key);
 
+/**
+ *  \brief Returns whether the surface has a color key
+ *
+ *  \return SDL_TRUE if the surface has a color key, or SDL_FALSE if the surface is NULL or has no color key
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasColorKey(SDL_Surface * surface);
+
 /**
  *  \brief Gets the color key (transparent pixel) in a blittable surface.
  *
@@ -509,6 +527,20 @@ extern DECLSPEC int SDLCALL SDL_LowerBlitScaled
     (SDL_Surface * src, SDL_Rect * srcrect,
     SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ *  \brief Set the YUV conversion mode
+ */
+extern DECLSPEC void SDLCALL SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_MODE mode);
+
+/**
+ *  \brief Get the YUV conversion mode
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionMode(void);
+
+/**
+ *  \brief Get the YUV conversion mode, returning the correct mode for the resolution when the current conversion mode is SDL_YUV_CONVERSION_AUTOMATIC
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionModeForResolution(int width, int height);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/include/SDL_system.h b/libs/SDL2/include/SDL_system.h
old mode 100755
new mode 100644
index eb069b33d8127f026de8b57b1869ccf3297f75e6..4dc372d6b14bb7fd49f64f716d1f9f29e3a15fbc
--- a/libs/SDL2/include/SDL_system.h
+++ b/libs/SDL2/include/SDL_system.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,18 @@ extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *a
 #endif /* __WIN32__ */
 
 
+/* Platform specific functions for Linux */
+#ifdef __LINUX__
+
+/**
+   \brief Sets the UNIX nice value for a thread, using setpriority() if possible, and RealtimeKit if available.
+
+   \return 0 on success, or -1 on error.
+ */
+extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
+ 
+#endif /* __LINUX__ */
+	
 /* Platform specific functions for iOS */
 #if defined(__IPHONEOS__) && __IPHONEOS__
 
@@ -108,6 +120,26 @@ extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(void);
  */
 extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void);
 
+/**
+   \brief Return true if the application is running on Android TV
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
+
+/**
+   \brief Return true if the application is running on a Chromebook
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
+
+/**
+  \brief Return true is the application is running on a Samsung DeX docking station
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
+
+/**
+ \brief Trigger the Android system back button behavior.
+ */
+extern DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
+
 /**
    See the official Android developer guide for more information:
    http://developer.android.com/guide/topics/data/data-storage.html
@@ -169,6 +201,25 @@ typedef enum
 } SDL_WinRT_Path;
 
 
+/**
+ *  \brief WinRT Device Family
+ */
+typedef enum
+{
+    /** \brief Unknown family  */
+    SDL_WINRT_DEVICEFAMILY_UNKNOWN,
+
+    /** \brief Desktop family*/
+    SDL_WINRT_DEVICEFAMILY_DESKTOP,
+
+    /** \brief Mobile family (for example smartphone) */
+    SDL_WINRT_DEVICEFAMILY_MOBILE,
+
+    /** \brief XBox family */
+    SDL_WINRT_DEVICEFAMILY_XBOX,
+} SDL_WinRT_DeviceFamily;
+
+
 /**
  *  \brief Retrieves a WinRT defined path on the local file system
  *
@@ -203,8 +254,20 @@ extern DECLSPEC const wchar_t * SDLCALL SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path
  */
 extern DECLSPEC const char * SDLCALL SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType);
 
+/**
+ *  \brief Detects the device family of WinRT plattform on runtime
+ *
+ *  \return Device family
+ */
+extern DECLSPEC SDL_WinRT_DeviceFamily SDLCALL SDL_WinRTGetDeviceFamily();
+
 #endif /* __WINRT__ */
 
+/**
+ \brief Return true if the current device is a tablet.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsTablet(void);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/include/SDL_syswm.h b/libs/SDL2/include/SDL_syswm.h
old mode 100755
new mode 100644
index 2d18afb6cf90173ca289b53f74dad013a3440909..f1c4021cc8b37be22124d28a99733d1e696e71e6
--- a/libs/SDL2/include/SDL_syswm.h
+++ b/libs/SDL2/include/SDL_syswm.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -33,12 +33,6 @@
 #include "SDL_video.h"
 #include "SDL_version.h"
 
-#include "begin_code.h"
-/* Set up for C function definitions, even when using C++ */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
 /**
  *  \file SDL_syswm.h
  *
@@ -110,6 +104,12 @@ typedef void *EGLSurface;
 #include "SDL_egl.h"
 #endif
 
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /**
  *  These are the various supported windowing subsystems
  */
diff --git a/libs/SDL2/include/SDL_test.h b/libs/SDL2/include/SDL_test.h
old mode 100755
new mode 100644
index f55afcb02273929055de6bf3a8848c2025e3bda3..6cc373bf809ca34195c28d7ef465fffac49c0e89
--- a/libs/SDL2/include/SDL_test.h
+++ b/libs/SDL2/include/SDL_test.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_assert.h b/libs/SDL2/include/SDL_test_assert.h
old mode 100755
new mode 100644
index 11d8363942948261e7a2403c3556d7e45860ef8a..1788d7a206916e92ccc607c24d93805ec368c838
--- a/libs/SDL2/include/SDL_test_assert.h
+++ b/libs/SDL2/include/SDL_test_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_common.h b/libs/SDL2/include/SDL_test_common.h
old mode 100755
new mode 100644
index 24eeb32af1b8731d95e28084a051b219480810be..be2e6b2aab48af89d8aab4986bd15bd0fc13bd68
--- a/libs/SDL2/include/SDL_test_common.h
+++ b/libs/SDL2/include/SDL_test_common.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_compare.h b/libs/SDL2/include/SDL_test_compare.h
old mode 100755
new mode 100644
index 9f4c4587ade92d8afe8d8102bd216db4458930e3..c22e447d8aee297971e6c51e4c1dd2fdb3a5cd52
--- a/libs/SDL2/include/SDL_test_compare.h
+++ b/libs/SDL2/include/SDL_test_compare.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_crc32.h b/libs/SDL2/include/SDL_test_crc32.h
old mode 100755
new mode 100644
index add480c349fd91512e3a3229f44858407529a106..3d235d07400f10b10297e533f7d74c2cf02bc394
--- a/libs/SDL2/include/SDL_test_crc32.h
+++ b/libs/SDL2/include/SDL_test_crc32.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_font.h b/libs/SDL2/include/SDL_test_font.h
old mode 100755
new mode 100644
index 58c9f9d5e5f3334e23df3bbcba45cc6483635f67..59cbdcad6bcb101764c689f915d8395866b6ea6a
--- a/libs/SDL2/include/SDL_test_font.h
+++ b/libs/SDL2/include/SDL_test_font.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_fuzzer.h b/libs/SDL2/include/SDL_test_fuzzer.h
old mode 100755
new mode 100644
index 6cf05747ad91914fe7155322876cd3ff6e2a28d6..8fcb9ebbfa1ba58da745c08e0eb0a322e00319c6
--- a/libs/SDL2/include/SDL_test_fuzzer.h
+++ b/libs/SDL2/include/SDL_test_fuzzer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_harness.h b/libs/SDL2/include/SDL_test_harness.h
old mode 100755
new mode 100644
index 68c4bcb7c44ff1fb09722fa81be6fb5013917eef..8641e0a7e3ad9945ed5ec93813920017bcc06447
--- a/libs/SDL2/include/SDL_test_harness.h
+++ b/libs/SDL2/include/SDL_test_harness.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_images.h b/libs/SDL2/include/SDL_test_images.h
old mode 100755
new mode 100644
index 683d2f8a689e8d49b1c750493a6c04112a19b8e3..9c4dd5b82954fc21c15ff9027372ca3419c6252e
--- a/libs/SDL2/include/SDL_test_images.h
+++ b/libs/SDL2/include/SDL_test_images.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_log.h b/libs/SDL2/include/SDL_test_log.h
old mode 100755
new mode 100644
index 9f993fbbc880ded0f9c4c2dd81d838fb60753ed2..ebd44fb5021c3f4fc64dcab127780ab7da89ebd2
--- a/libs/SDL2/include/SDL_test_log.h
+++ b/libs/SDL2/include/SDL_test_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_md5.h b/libs/SDL2/include/SDL_test_md5.h
old mode 100755
new mode 100644
index 67ca7d1defbb70c02e83d43eef7f11ee6cbc81e5..0e4105768fdb6bd059ad867cb90d74637e28660c
--- a/libs/SDL2/include/SDL_test_md5.h
+++ b/libs/SDL2/include/SDL_test_md5.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_memory.h b/libs/SDL2/include/SDL_test_memory.h
old mode 100755
new mode 100644
index 43b67f521be347be755fa725dfd2bdd6b96f6c01..4827ae6f2c372db861a635ae9faa32637f152a18
--- a/libs/SDL2/include/SDL_test_memory.h
+++ b/libs/SDL2/include/SDL_test_memory.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_test_random.h b/libs/SDL2/include/SDL_test_random.h
old mode 100755
new mode 100644
index 2b01922c3ed5b82aace74bce3c661ce7c8b62782..0eb414ff2e4db2488ac5671f9d1a91048d0f3b34
--- a/libs/SDL2/include/SDL_test_random.h
+++ b/libs/SDL2/include/SDL_test_random.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_thread.h b/libs/SDL2/include/SDL_thread.h
old mode 100755
new mode 100644
index d0f6575cd5ac7562cb5dba1ca84b5e94b4a3f029..554dd0b61d02481d6f665dbde0799361b1967632
--- a/libs/SDL2/include/SDL_thread.h
+++ b/libs/SDL2/include/SDL_thread.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -54,12 +54,13 @@ typedef unsigned int SDL_TLSID;
 /**
  *  The SDL thread priority.
  *
- *  \note On many systems you require special privileges to set high priority.
+ *  \note On many systems you require special privileges to set high or time critical priority.
  */
 typedef enum {
     SDL_THREAD_PRIORITY_LOW,
     SDL_THREAD_PRIORITY_NORMAL,
-    SDL_THREAD_PRIORITY_HIGH
+    SDL_THREAD_PRIORITY_HIGH,
+    SDL_THREAD_PRIORITY_TIME_CRITICAL
 } SDL_ThreadPriority;
 
 /**
@@ -105,14 +106,24 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
 
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
+                 const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
+
+
 /**
  *  Create a thread.
  */
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #endif
 
 #elif defined(__OS2__)
@@ -132,15 +143,31 @@ extern DECLSPEC SDL_Thread *SDLCALL
 SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #endif
 
 #else
 
+/**
+ *  Create a thread with a default stack size.
+ *
+ *  This is equivalent to calling:
+ *  SDL_CreateThreadWithStackSize(fn, name, 0, data);
+ */
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+
 /**
  *  Create a thread.
  *
@@ -158,9 +185,17 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
  *   If a system imposes requirements, SDL will try to munge the string for
  *    it (truncate, etc), but the original string contents will be available
  *    from SDL_GetThreadName().
+ *
+ *   The size (in bytes) of the new stack can be specified. Zero means "use
+ *    the system default" which might be wildly different between platforms
+ *    (x86 Linux generally defaults to eight megabytes, an embedded device
+ *    might be a few kilobytes instead).
+ *
+ *   In SDL 2.1, stacksize will be folded into the original SDL_CreateThread
+ *    function.
  */
 extern DECLSPEC SDL_Thread *SDLCALL
-SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data);
 
 #endif
 
diff --git a/libs/SDL2/include/SDL_timer.h b/libs/SDL2/include/SDL_timer.h
old mode 100755
new mode 100644
index 282625d3f0dc8dfe2b94fa48032d037cace0de3c..5600618ff4f8fbaacb2edf547ef8da1c182a0257
--- a/libs/SDL2/include/SDL_timer.h
+++ b/libs/SDL2/include/SDL_timer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_touch.h b/libs/SDL2/include/SDL_touch.h
old mode 100755
new mode 100644
index db9e25ebdccad221a3c38bbce06b02bec65c3c2c..f4075e79a5b5dd486579aa374474e3927101abe1
--- a/libs/SDL2/include/SDL_touch.h
+++ b/libs/SDL2/include/SDL_touch.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_types.h b/libs/SDL2/include/SDL_types.h
old mode 100755
new mode 100644
index 06ee80cb358963b2fca3d8218a81595345e52671..4ac248c8c577f5150c8de8ad1b36df3bd4aea854
--- a/libs/SDL2/include/SDL_types.h
+++ b/libs/SDL2/include/SDL_types.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/SDL_version.h b/libs/SDL2/include/SDL_version.h
old mode 100755
new mode 100644
index 5b4c402486d6adb4e8ebaec3cfa4bee301e48e8b..31443e149adc071cba7431bc0cd47b007f1a0e22
--- a/libs/SDL2/include/SDL_version.h
+++ b/libs/SDL2/include/SDL_version.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -59,7 +59,7 @@ typedef struct SDL_version
 */
 #define SDL_MAJOR_VERSION   2
 #define SDL_MINOR_VERSION   0
-#define SDL_PATCHLEVEL      7
+#define SDL_PATCHLEVEL      9
 
 /**
  *  \brief Macro to determine SDL version program was compiled against.
diff --git a/libs/SDL2/include/SDL_video.h b/libs/SDL2/include/SDL_video.h
old mode 100755
new mode 100644
index 6f6cda8b182b22160d97c0d9d5e684290419d0b9..461f13805101c9214ef4bd9b6df3143c3f093692
--- a/libs/SDL2/include/SDL_video.h
+++ b/libs/SDL2/include/SDL_video.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -110,7 +110,9 @@ typedef enum
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported.
+                                                     On macOS NSHighResolutionCapable must be set true in the
+                                                     application's Info.plist for this to have any effect. */
     SDL_WINDOW_MOUSE_CAPTURE = 0x00004000,      /**< window has mouse captured (unrelated to INPUT_GRABBED) */
     SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000,      /**< window should always be above others */
     SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
@@ -167,6 +169,24 @@ typedef enum
     SDL_WINDOWEVENT_HIT_TEST        /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL. */
 } SDL_WindowEventID;
 
+/**
+ *  \brief Event subtype for display events
+ */
+typedef enum
+{
+    SDL_DISPLAYEVENT_NONE,          /**< Never used */
+    SDL_DISPLAYEVENT_ORIENTATION    /**< Display orientation has changed to data1 */
+} SDL_DisplayEventID;
+
+typedef enum
+{
+    SDL_ORIENTATION_UNKNOWN,            /**< The display orientation can't be determined */
+    SDL_ORIENTATION_LANDSCAPE,          /**< The display is in landscape mode, with the right side up, relative to portrait mode */
+    SDL_ORIENTATION_LANDSCAPE_FLIPPED,  /**< The display is in landscape mode, with the left side up, relative to portrait mode */
+    SDL_ORIENTATION_PORTRAIT,           /**< The display is in portrait mode */
+    SDL_ORIENTATION_PORTRAIT_FLIPPED    /**< The display is in portrait mode, upside down */
+} SDL_DisplayOrientation;
+
 /**
  *  \brief An opaque handle to an OpenGL context.
  */
@@ -314,18 +334,6 @@ extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex);
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect);
 
-/**
- *  \brief Get the dots/pixels-per-inch for a display
- *
- *  \note Diagonal, horizontal and vertical DPI can all be optionally
- *        returned if the parameter is non-NULL.
- *
- *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
- *
- *  \sa SDL_GetNumVideoDisplays()
- */
-extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
-
 /**
  *  \brief Get the usable desktop area represented by a display, with the
  *         primary display located at 0,0
@@ -345,6 +353,27 @@ extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, fl
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayUsableBounds(int displayIndex, SDL_Rect * rect);
 
+/**
+ *  \brief Get the dots/pixels-per-inch for a display
+ *
+ *  \note Diagonal, horizontal and vertical DPI can all be optionally
+ *        returned if the parameter is non-NULL.
+ *
+ *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
+
+/**
+ *  \brief Get the orientation of a display
+ *
+ *  \return The orientation of the display, or SDL_ORIENTATION_UNKNOWN if it isn't available.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC SDL_DisplayOrientation SDLCALL SDL_GetDisplayOrientation(int displayIndex);
+
 /**
  *  \brief Returns the number of available display modes.
  *
@@ -470,7 +499,7 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window);
  *  If the window is created with any of the SDL_WINDOW_OPENGL or
  *  SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function
  *  (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the
- *  corrensponding UnloadLibrary function is called by SDL_DestroyWindow().
+ *  corresponding UnloadLibrary function is called by SDL_DestroyWindow().
  *
  *  If SDL_WINDOW_VULKAN is specified and there isn't a working Vulkan driver,
  *  SDL_CreateWindow() will fail because SDL_Vulkan_LoadLibrary() will fail.
diff --git a/libs/SDL2/include/SDL_vulkan.h b/libs/SDL2/include/SDL_vulkan.h
old mode 100755
new mode 100644
index 803b5feef3489ba257ae3664cc93afc9f8fbcf1d..972cca4d7c2658f735d96d549ccd2a5819592b16
--- a/libs/SDL2/include/SDL_vulkan.h
+++ b/libs/SDL2/include/SDL_vulkan.h
@@ -69,30 +69,43 @@ typedef VkSurfaceKHR SDL_vulkanSurface; /* for compatibility with Tizen */
  *  \brief Dynamically load a Vulkan loader library.
  *
  *  \param [in] path The platform dependent Vulkan loader library name, or
- *              \c NULL to open the default Vulkan loader library.
+ *              \c NULL.
  *
  *  \return \c 0 on success, or \c -1 if the library couldn't be loaded.
  *
- *  This should be done after initializing the video driver, but before
+ *  If \a path is NULL SDL will use the value of the environment variable
+ *  \c SDL_VULKAN_LIBRARY, if set, otherwise it loads the default Vulkan
+ *  loader library.
+ *
+ *  This should be called after initializing the video driver, but before
  *  creating any Vulkan windows. If no Vulkan loader library is loaded, the
  *  default library will be loaded upon creation of the first Vulkan window.
  *
- *  \note If you specify a non-NULL \a path, you should retrieve all of the
- *        Vulkan functions used in your program from the dynamic library using
+ *  \note It is fairly common for Vulkan applications to link with \a libvulkan
+ *        instead of explicitly loading it at run time. This will work with
+ *        SDL provided the application links to a dynamic library and both it
+ *        and SDL use the same search path.
+ *
+ *  \note If you specify a non-NULL \c path, an application should retrieve all
+ *        of the Vulkan functions it uses from the dynamic library using
  *        \c SDL_Vulkan_GetVkGetInstanceProcAddr() unless you can guarantee
- *        \a path points to the same vulkan loader library that you linked to.
+ *        \c path points to the same vulkan loader library the application
+ *        linked to.
  *
  *  \note On Apple devices, if \a path is NULL, SDL will attempt to find
  *        the vkGetInstanceProcAddr address within all the mach-o images of
- *        the current process. This is because the currently (v0.17.0)
- *        recommended MoltenVK (Vulkan on Metal) usage is as a static library.
- *        If it is not found then SDL will attempt to load \c libMoltenVK.dylib.
- *        Applications using the dylib alternative therefore do not need to do
- *        anything special when calling SDL.
- *
- *  \note On non-Apple devices, SDL requires you to either not link to the
- *        Vulkan loader or link to a dynamic library version. This limitation
- *        may be removed in a future version of SDL.
+ *        the current process. This is because it is fairly common for Vulkan
+ *        applications to link with libvulkan (and historically MoltenVK was
+ *        provided as a static library). If it is not found then, on macOS, SDL
+ *        will attempt to load \c vulkan.framework/vulkan, \c libvulkan.1.dylib,
+ *        \c MoltenVK.framework/MoltenVK and \c libMoltenVK.dylib in that order.
+ *        On iOS SDL will attempt to load \c libMoltenVK.dylib. Applications
+ *        using a dynamic framework or .dylib must ensure it is included in its
+ *        application bundle.
+ *
+ *  \note On non-Apple devices, application linking with a static libvulkan is
+ *        not supported. Either do not link to the Vulkan loader or link to a
+ *        dynamic library version.
  *
  *  \note This function will fail if there are no working Vulkan drivers
  *        installed.
@@ -122,11 +135,11 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \brief Get the names of the Vulkan instance extensions needed to create
  *         a surface with \c SDL_Vulkan_CreateSurface().
  *
- *  \param [in]     window Window for which the required Vulkan instance
+ *  \param [in]     \c NULL or window Window for which the required Vulkan instance
  *                  extensions should be retrieved
- *  \param [in,out] count pointer to an \c unsigned related to the number of
+ *  \param [in,out] pCount pointer to an \c unsigned related to the number of
  *                  required Vulkan instance extensions
- *  \param [out]    names \c NULL or a pointer to an array to be filled with the
+ *  \param [out]    pNames \c NULL or a pointer to an array to be filled with the
  *                  required Vulkan instance extensions
  *
  *  \return \c SDL_TRUE on success, \c SDL_FALSE on error.
@@ -140,6 +153,10 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  is smaller than the number of required extensions, \c SDL_FALSE will be
  *  returned instead of \c SDL_TRUE, to indicate that not all the required
  *  extensions were returned.
+ * 
+ *  \note If \c window is not NULL, it will be checked against its creation
+ *        flags to ensure that the Vulkan flag is present. This parameter
+ *        will be removed in a future major release.
  *
  *  \note The returned list of extensions will contain \c VK_KHR_surface
  *        and zero or more platform specific extensions
@@ -147,12 +164,13 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \note The extension names queried here must be enabled when calling
  *        VkCreateInstance, otherwise surface creation will fail.
  *
- *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag.
+ *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag
+ *        or be \c NULL
  *
  *  \code
  *  unsigned int count;
  *  // get count of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, NULL))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, NULL))
  *      handle_error();
  *
  *  static const char *const additionalExtensions[] =
@@ -166,7 +184,7 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *      handle_error();
  *
  *  // get names of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, names))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, names))
  *      handle_error();
  *
  *  // copy additional extensions after required extensions
@@ -240,6 +258,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(
  * platform with high-DPI support (Apple calls this "Retina"), and not disabled
  * by the \c SDL_HINT_VIDEO_HIGHDPI_DISABLED hint.
  *
+ *  \note On macOS high-DPI support must be enabled for an application by
+ *        setting NSHighResolutionCapable to true in its Info.plist.
+ *
  *  \sa SDL_GetWindowSize()
  *  \sa SDL_CreateWindow()
  */
diff --git a/libs/SDL2/include/begin_code.h b/libs/SDL2/include/begin_code.h
old mode 100755
new mode 100644
index 5372d6f04df418acb02bf51cd545c2170bb73849..6c2106246f2583ba711451bc7e39fc2af7e9eec7
--- a/libs/SDL2/include/begin_code.h
+++ b/libs/SDL2/include/begin_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/include/close_code.h b/libs/SDL2/include/close_code.h
old mode 100755
new mode 100644
index 2a352dbb9ec7502c62b561f30bd7433b18e581d1..b3b70a4c833d80305bbffd306c8ac099babdf419
--- a/libs/SDL2/include/close_code.h
+++ b/libs/SDL2/include/close_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/lib/ARM/SDL2.lib b/libs/SDL2/lib/ARM/SDL2.lib
new file mode 100644
index 0000000000000000000000000000000000000000..be9d8694983e4ca6b97440b9fbc0b8db2f04a5e9
Binary files /dev/null and b/libs/SDL2/lib/ARM/SDL2.lib differ
diff --git a/libs/SDL2/lib/ARM64/SDL2.dll b/libs/SDL2/lib/ARM64/SDL2.dll
new file mode 100644
index 0000000000000000000000000000000000000000..f9eac1c0b6765981e468ec00873ed9677e8959bc
Binary files /dev/null and b/libs/SDL2/lib/ARM64/SDL2.dll differ
diff --git a/libs/SDL2/lib/ARM64/SDL2.lib b/libs/SDL2/lib/ARM64/SDL2.lib
new file mode 100644
index 0000000000000000000000000000000000000000..40904c308236581069948476cb2c8a62779ebf97
Binary files /dev/null and b/libs/SDL2/lib/ARM64/SDL2.lib differ
diff --git a/libs/SDL2/lib/x64/SDL2.dll b/libs/SDL2/lib/x64/SDL2.dll
old mode 100755
new mode 100644
index 6776cdd891ea4479c662519a1667ddc08ca5a8ef..934f8091c7d59b5d479026cfab1ec45d9077fff1
Binary files a/libs/SDL2/lib/x64/SDL2.dll and b/libs/SDL2/lib/x64/SDL2.dll differ
diff --git a/libs/SDL2/lib/x64/SDL2.lib b/libs/SDL2/lib/x64/SDL2.lib
old mode 100755
new mode 100644
index 42e5ee7d0d205716ff111f3142617a81a0e8979b..f4941ae0e34edcfb71f31cdf289afeae0b3c2305
Binary files a/libs/SDL2/lib/x64/SDL2.lib and b/libs/SDL2/lib/x64/SDL2.lib differ
diff --git a/libs/SDL2/lib/x64/SDL2main.lib b/libs/SDL2/lib/x64/SDL2main.lib
old mode 100755
new mode 100644
index 1f409a2499d5306887d22041f9520cbb6c3a28f7..6129946574aa807aad3da264518444cc115f51f4
Binary files a/libs/SDL2/lib/x64/SDL2main.lib and b/libs/SDL2/lib/x64/SDL2main.lib differ
diff --git a/libs/SDL2/lib/x64/SDL2test.lib b/libs/SDL2/lib/x64/SDL2test.lib
old mode 100755
new mode 100644
index b54347ddc4cfbcdacf2110d9391ed9f60a47decf..5b0328280a7dffe20df128d6685f235b10d77459
Binary files a/libs/SDL2/lib/x64/SDL2test.lib and b/libs/SDL2/lib/x64/SDL2test.lib differ
diff --git a/libs/SDL2/lib/x86/SDL2.dll b/libs/SDL2/lib/x86/SDL2.dll
old mode 100755
new mode 100644
index c21f747c2f45540809ff1fd901e2bb607ead0fff..70177904476cc7ab959c84e08a92df76cd0d748c
Binary files a/libs/SDL2/lib/x86/SDL2.dll and b/libs/SDL2/lib/x86/SDL2.dll differ
diff --git a/libs/SDL2/lib/x86/SDL2.lib b/libs/SDL2/lib/x86/SDL2.lib
old mode 100755
new mode 100644
index a36fab5a3bed78ec4a6ad5903ab40c58054318b0..cef79017ce2858e313eac2c6fccda4e7014bd77e
Binary files a/libs/SDL2/lib/x86/SDL2.lib and b/libs/SDL2/lib/x86/SDL2.lib differ
diff --git a/libs/SDL2/lib/x86/SDL2main.lib b/libs/SDL2/lib/x86/SDL2main.lib
old mode 100755
new mode 100644
index 2080d5db5f3bec958f02a2c35c17ec69fad0dd6f..55c40c7a0757d13e7b178f76267b507c958b6626
Binary files a/libs/SDL2/lib/x86/SDL2main.lib and b/libs/SDL2/lib/x86/SDL2main.lib differ
diff --git a/libs/SDL2/lib/x86/SDL2test.lib b/libs/SDL2/lib/x86/SDL2test.lib
old mode 100755
new mode 100644
index c26c50f612086cf4ed28fe3288f6974567893f2a..d7a395ff1dd03a19bb4624e023c0856454e789e6
Binary files a/libs/SDL2/lib/x86/SDL2test.lib and b/libs/SDL2/lib/x86/SDL2test.lib differ
diff --git a/libs/SDL2/test/CMakeLists.txt b/libs/SDL2/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3c25c5c7c98790944cc22c408cec58152544b0e6
--- /dev/null
+++ b/libs/SDL2/test/CMakeLists.txt
@@ -0,0 +1,122 @@
+cmake_minimum_required(VERSION 2.8.11)
+project(SDL2 C)
+
+# Global settings for all of the test targets
+# FIXME: is this wrong?
+remove_definitions(-DUSING_GENERATED_CONFIG_H)
+link_libraries(SDL2_test SDL2-static)
+
+# FIXME: Parent directory CMakeLists.txt only sets these for mingw/cygwin,
+# but we need them for VS as well.
+if(WINDOWS)
+    link_libraries(SDL2main)
+    add_definitions(-Dmain=SDL_main)
+endif()
+
+add_executable(checkkeys checkkeys.c)
+add_executable(loopwave loopwave.c)
+add_executable(loopwavequeue loopwavequeue.c)
+add_executable(testresample testresample.c)
+add_executable(testaudioinfo testaudioinfo.c)
+
+file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c)
+add_executable(testautomation ${TESTAUTOMATION_SOURCE_FILES})
+
+add_executable(testmultiaudio testmultiaudio.c)
+add_executable(testaudiohotplug testaudiohotplug.c)
+add_executable(testaudiocapture testaudiocapture.c)
+add_executable(testatomic testatomic.c)
+add_executable(testintersections testintersections.c)
+add_executable(testrelative testrelative.c)
+add_executable(testhittesting testhittesting.c)
+add_executable(testdraw2 testdraw2.c)
+add_executable(testdrawchessboard testdrawchessboard.c)
+add_executable(testdropfile testdropfile.c)
+add_executable(testerror testerror.c)
+add_executable(testfile testfile.c)
+add_executable(testgamecontroller testgamecontroller.c)
+add_executable(testgesture testgesture.c)
+add_executable(testgl2 testgl2.c)
+add_executable(testgles testgles.c)
+add_executable(testgles2 testgles2.c)
+add_executable(testhaptic testhaptic.c)
+add_executable(testhotplug testhotplug.c)
+add_executable(testrumble testrumble.c)
+add_executable(testthread testthread.c)
+add_executable(testiconv testiconv.c)
+add_executable(testime testime.c)
+add_executable(testjoystick testjoystick.c)
+add_executable(testkeys testkeys.c)
+add_executable(testloadso testloadso.c)
+add_executable(testlock testlock.c)
+
+if(APPLE)
+    add_executable(testnative testnative.c
+                              testnativecocoa.m
+                              testnativex11.c)
+elseif(WINDOWS)
+    add_executable(testnative testnative.c testnativew32.c)
+elseif(UNIX)
+    add_executable(testnative testnative.c testnativex11.c)
+endif()
+
+add_executable(testoverlay2 testoverlay2.c testyuv_cvt.c)
+add_executable(testplatform testplatform.c)
+add_executable(testpower testpower.c)
+add_executable(testfilesystem testfilesystem.c)
+add_executable(testrendertarget testrendertarget.c)
+add_executable(testscale testscale.c)
+add_executable(testsem testsem.c)
+add_executable(testshader testshader.c)
+add_executable(testshape testshape.c)
+add_executable(testsprite2 testsprite2.c)
+add_executable(testspriteminimal testspriteminimal.c)
+add_executable(teststreaming teststreaming.c)
+add_executable(testtimer testtimer.c)
+add_executable(testver testver.c)
+add_executable(testviewport testviewport.c)
+add_executable(testwm2 testwm2.c)
+add_executable(testyuv testyuv.c testyuv_cvt.c)
+add_executable(torturethread torturethread.c)
+add_executable(testrendercopyex testrendercopyex.c)
+add_executable(testmessage testmessage.c)
+add_executable(testdisplayinfo testdisplayinfo.c)
+add_executable(testqsort testqsort.c)
+add_executable(testbounds testbounds.c)
+add_executable(testcustomcursor testcustomcursor.c)
+add_executable(controllermap controllermap.c)
+add_executable(testvulkan testvulkan.c)
+
+# HACK: Dummy target to cause the resource files to be copied to the build directory.
+# Need to make it an executable so we can use the TARGET_FILE_DIR generator expression.
+# This is needed so they get copied to the correct Debug/Release subdirectory in Xcode.
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/resources_dummy.c "int main(int argc, const char **argv){ return 1; }\n")
+add_executable(SDL2_test_resoureces ${CMAKE_CURRENT_BINARY_DIR}/resources_dummy.c)
+
+file(GLOB RESOURCE_FILES *.bmp *.wav)
+foreach(RESOURCE_FILE ${RESOURCE_FILES})
+    add_custom_command(TARGET SDL2_test_resoureces POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${RESOURCE_FILE} $<TARGET_FILE_DIR:SDL2_test_resoureces>)
+endforeach(RESOURCE_FILE)
+
+file(COPY ${RESOURCE_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+# TODO: Might be easier to make all targets depend on the resources...?
+add_dependencies(testscale SDL2_test_resoureces)
+add_dependencies(testrendercopyex SDL2_test_resoureces)
+add_dependencies(controllermap SDL2_test_resoureces)
+add_dependencies(testyuv SDL2_test_resoureces)
+add_dependencies(testgamecontroller SDL2_test_resoureces)
+add_dependencies(testshape SDL2_test_resoureces)
+add_dependencies(testshader SDL2_test_resoureces)
+add_dependencies(testnative SDL2_test_resoureces)
+add_dependencies(testspriteminimal SDL2_test_resoureces)
+add_dependencies(testautomation SDL2_test_resoureces)
+add_dependencies(testcustomcursor SDL2_test_resoureces)
+add_dependencies(testrendertarget SDL2_test_resoureces)
+add_dependencies(testsprite2 SDL2_test_resoureces)
+
+add_dependencies(loopwave SDL2_test_resoureces)
+add_dependencies(loopwavequeue SDL2_test_resoureces)
+add_dependencies(testresample SDL2_test_resoureces)
+add_dependencies(testaudiohotplug SDL2_test_resoureces)
+add_dependencies(testmultiaudio SDL2_test_resoureces)
diff --git a/libs/SDL2/test/Makefile.in b/libs/SDL2/test/Makefile.in
index 0ac70e923be6800d88b1ff7a226546a13d3a6baa..9ddd6ffe7785e606f2c6ba4bcbfda8aca65f37f8 100644
--- a/libs/SDL2/test/Makefile.in
+++ b/libs/SDL2/test/Makefile.in
@@ -9,19 +9,23 @@ LIBS	= @LIBS@
 
 TARGETS = \
 	checkkeys$(EXE) \
+	controllermap$(EXE) \
 	loopwave$(EXE) \
 	loopwavequeue$(EXE) \
 	testatomic$(EXE) \
-	testaudioinfo$(EXE) \
 	testaudiocapture$(EXE) \
+	testaudiohotplug$(EXE) \
+	testaudioinfo$(EXE) \
 	testautomation$(EXE) \
 	testbounds$(EXE) \
 	testcustomcursor$(EXE) \
+	testdisplayinfo$(EXE) \
 	testdraw2$(EXE) \
 	testdrawchessboard$(EXE) \
 	testdropfile$(EXE) \
 	testerror$(EXE) \
 	testfile$(EXE) \
+	testfilesystem$(EXE) \
 	testgamecontroller$(EXE) \
 	testgesture$(EXE) \
 	testgl2$(EXE) \
@@ -29,44 +33,42 @@ TARGETS = \
 	testgles2$(EXE) \
 	testhaptic$(EXE) \
 	testhittesting$(EXE) \
-	testrumble$(EXE) \
 	testhotplug$(EXE) \
-	testthread$(EXE) \
 	testiconv$(EXE) \
 	testime$(EXE) \
 	testintersections$(EXE) \
-	testrelative$(EXE) \
 	testjoystick$(EXE) \
 	testkeys$(EXE) \
 	testloadso$(EXE) \
 	testlock$(EXE) \
+	testmessage$(EXE) \
 	testmultiaudio$(EXE) \
-	testaudiohotplug$(EXE) \
 	testnative$(EXE) \
 	testoverlay2$(EXE) \
 	testplatform$(EXE) \
 	testpower$(EXE) \
-	testfilesystem$(EXE) \
+	testqsort$(EXE) \
+	testrelative$(EXE) \
+	testrendercopyex$(EXE) \
 	testrendertarget$(EXE) \
 	testresample$(EXE) \
+	testrumble$(EXE) \
 	testscale$(EXE) \
 	testsem$(EXE) \
+	testsensor$(EXE) \
 	testshader$(EXE) \
 	testshape$(EXE) \
 	testsprite2$(EXE) \
 	testspriteminimal$(EXE) \
 	teststreaming$(EXE) \
+	testthread$(EXE) \
 	testtimer$(EXE) \
 	testver$(EXE) \
 	testviewport$(EXE) \
+	testvulkan$(EXE) \
 	testwm2$(EXE) \
+	testyuv$(EXE) \
 	torturethread$(EXE) \
-	testrendercopyex$(EXE) \
-	testmessage$(EXE) \
-	testdisplayinfo$(EXE) \
-	testqsort$(EXE) \
-	controllermap$(EXE) \
-	testvulkan$(EXE) \
 	
 all: Makefile $(TARGETS) copydatafiles
 
@@ -218,7 +220,7 @@ endif
 endif
 endif
 
-testoverlay2$(EXE): $(srcdir)/testoverlay2.c
+testoverlay2$(EXE): $(srcdir)/testoverlay2.c $(srcdir)/testyuv_cvt.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
 testplatform$(EXE): $(srcdir)/testplatform.c
@@ -239,6 +241,9 @@ testscale$(EXE): $(srcdir)/testscale.c
 testsem$(EXE): $(srcdir)/testsem.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
+testsensor$(EXE): $(srcdir)/testsensor.c
+	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
+
 testshader$(EXE): $(srcdir)/testshader.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS) @GLLIB@ @MATHLIB@
 
@@ -266,6 +271,9 @@ testviewport$(EXE): $(srcdir)/testviewport.c
 testwm2$(EXE): $(srcdir)/testwm2.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
+testyuv$(EXE): $(srcdir)/testyuv.c $(srcdir)/testyuv_cvt.c
+	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
+
 torturethread$(EXE): $(srcdir)/torturethread.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
@@ -309,7 +317,10 @@ distclean: clean
 %.wav: $(srcdir)/%.wav
 	cp $< $@
 
-copydatafiles: copybmpfiles copywavfiles
+%.dat: $(srcdir)/%.dat
+	cp $< $@
+
+copydatafiles: copybmpfiles copywavfiles copydatfiles
 .PHONY : copydatafiles
 
 copybmpfiles: $(foreach bmp,$(wildcard $(srcdir)/*.bmp),$(notdir $(bmp)))
@@ -318,3 +329,6 @@ copybmpfiles: $(foreach bmp,$(wildcard $(srcdir)/*.bmp),$(notdir $(bmp)))
 copywavfiles: $(foreach wav,$(wildcard $(srcdir)/*.wav),$(notdir $(wav)))
 .PHONY : copywavfiles
 
+copydatfiles: $(foreach dat,$(wildcard $(srcdir)/*.dat),$(notdir $(dat)))
+.PHONY : copydatfiles
+
diff --git a/libs/SDL2/test/checkkeys.c b/libs/SDL2/test/checkkeys.c
index 066c786d4392db048109551a6ab9b96ce63be8eb..4452acaba5220484a12a4a74382161490f58dc98 100644
--- a/libs/SDL2/test/checkkeys.c
+++ b/libs/SDL2/test/checkkeys.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/controllermap.c b/libs/SDL2/test/controllermap.c
index c4fe5929c3ade4870cee18a5d9c18c6b9b9b53b0..2ca53518a7e6d3b9b16e15c1377d6f929437cdc3 100644
--- a/libs/SDL2/test/controllermap.c
+++ b/libs/SDL2/test/controllermap.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/loopwave.c b/libs/SDL2/test/loopwave.c
index 63e19961105559b647f339147e33a30184b4c66a..88d8fc87181e493f6ad61f07d28c203497d2cb61 100644
--- a/libs/SDL2/test/loopwave.c
+++ b/libs/SDL2/test/loopwave.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/loopwavequeue.c b/libs/SDL2/test/loopwavequeue.c
index 3dc7792f6d6ecf1559c95da5a09e231e19b64488..3f0a69e1594fabb7e301879f6837ce2cf714eabe 100644
--- a/libs/SDL2/test/loopwavequeue.c
+++ b/libs/SDL2/test/loopwavequeue.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testatomic.c b/libs/SDL2/test/testatomic.c
index f8dca91785ca6f108851b6a6ace0e1e007dc2c48..6af9d4bf62830033495e5dd781c25a96a42da040 100644
--- a/libs/SDL2/test/testatomic.c
+++ b/libs/SDL2/test/testatomic.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testaudiocapture.c b/libs/SDL2/test/testaudiocapture.c
index 3b7ea0b790b90f5a00eacbbed68d4285bea82cd2..a418d123c4bf35f1e27cc5b0df13d92dcb206b78 100644
--- a/libs/SDL2/test/testaudiocapture.c
+++ b/libs/SDL2/test/testaudiocapture.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testaudiohotplug.c b/libs/SDL2/test/testaudiohotplug.c
index a72f91745e49d8996a79f3930c87917e01773dc4..374cbb27b74a3ae8bce6dcf3a41df30cc10f2435 100644
--- a/libs/SDL2/test/testaudiohotplug.c
+++ b/libs/SDL2/test/testaudiohotplug.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testaudioinfo.c b/libs/SDL2/test/testaudioinfo.c
index c70b86b5e07e326bb57fb9a5993b98a9b55251e3..adecce9b7249608c68077d34c34cd5b14f48b859 100644
--- a/libs/SDL2/test/testaudioinfo.c
+++ b/libs/SDL2/test/testaudioinfo.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testautomation.c b/libs/SDL2/test/testautomation.c
index 9fd22be072409110c87d52966e111366ac8d6e8e..bb799ea43b60e34143132a6292b6b8b7592c9281 100644
--- a/libs/SDL2/test/testautomation.c
+++ b/libs/SDL2/test/testautomation.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testbounds.c b/libs/SDL2/test/testbounds.c
index bc46cb823fee8528ced0bfeea6686daeb3aa8cc3..764809446a29e3620ba3d41d8744b9d4fc76f651 100644
--- a/libs/SDL2/test/testbounds.c
+++ b/libs/SDL2/test/testbounds.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testcustomcursor.c b/libs/SDL2/test/testcustomcursor.c
index 59c6409db01f58dfa61b964690c1fe77a8660934..469449818559b285a185ef460b83e0bda4c8ec63 100644
--- a/libs/SDL2/test/testcustomcursor.c
+++ b/libs/SDL2/test/testcustomcursor.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -73,6 +73,24 @@ init_color_cursor(const char *file)
     SDL_Cursor *cursor = NULL;
     SDL_Surface *surface = SDL_LoadBMP(file);
     if (surface) {
+        if (surface->format->palette) {
+            SDL_SetColorKey(surface, 1, *(Uint8 *) surface->pixels);
+        } else {
+            switch (surface->format->BitsPerPixel) {
+            case 15:
+                SDL_SetColorKey(surface, 1, (*(Uint16 *)surface->pixels) & 0x00007FFF);
+                break;
+            case 16:
+                SDL_SetColorKey(surface, 1, *(Uint16 *)surface->pixels);
+                break;
+            case 24:
+                SDL_SetColorKey(surface, 1, (*(Uint32 *)surface->pixels) & 0x00FFFFFF);
+                break;
+            case 32:
+                SDL_SetColorKey(surface, 1, *(Uint32 *)surface->pixels);
+                break;
+            }
+        }
         cursor = SDL_CreateColorCursor(surface, 0, 0);
         SDL_FreeSurface(surface);
     }
@@ -116,7 +134,9 @@ init_system_cursor(const char *image[])
 
 static SDLTest_CommonState *state;
 int done;
-SDL_Cursor *cursor = NULL;
+static SDL_Cursor *cursors[1+SDL_NUM_SYSTEM_CURSORS];
+static int current_cursor;
+static int show_cursor;
 
 /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
 static void
@@ -134,6 +154,18 @@ loop()
     /* Check for events */
     while (SDL_PollEvent(&event)) {
         SDLTest_CommonEvent(state, &event, &done);
+        if (event.type == SDL_MOUSEBUTTONDOWN) {
+            if (event.button.button == SDL_BUTTON_LEFT) {
+                ++current_cursor;
+                if (current_cursor == SDL_arraysize(cursors)) {
+                    current_cursor = 0;
+                }
+                SDL_SetCursor(cursors[current_cursor]);
+            } else {
+                show_cursor = !show_cursor;
+                SDL_ShowCursor(show_cursor);
+            }
+        }
     }
     
     for (i = 0; i < state->num_windows; ++i) {
@@ -188,15 +220,22 @@ main(int argc, char *argv[])
     }
 
     if (color_cursor) {
-        cursor = init_color_cursor(color_cursor);
+        cursors[0] = init_color_cursor(color_cursor);
     } else {
-        cursor = init_system_cursor(arrow);
+        cursors[0] = init_system_cursor(arrow);
     }
-    if (!cursor) {
+    if (!cursors[0]) {
         SDL_Log("Error, couldn't create cursor\n");
         quit(2);
     }
-    SDL_SetCursor(cursor);
+    for (i = 0; i < SDL_NUM_SYSTEM_CURSORS; ++i) {
+        cursors[1+i] = SDL_CreateSystemCursor((SDL_SystemCursor)i);
+        if (!cursors[1+i]) {
+            SDL_Log("Error, couldn't create system cursor %d\n", i);
+            quit(2);
+        }
+    }
+    SDL_SetCursor(cursors[0]);
 
     /* Main render loop */
     done = 0;
@@ -208,9 +247,13 @@ main(int argc, char *argv[])
     }
 #endif
 
-    SDL_FreeCursor(cursor);
+    for (i = 0; i < SDL_arraysize(cursors); ++i) {
+        SDL_FreeCursor(cursors[i]);
+    }
     quit(0);
 
     /* keep the compiler happy ... */
     return(0);
 }
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/test/testdisplayinfo.c b/libs/SDL2/test/testdisplayinfo.c
index 51c19b010be3044b8a66308a7aaa2a70fa936a78..0cc5fbdd74bdc8aed1b008c22d5862419c6c7d5c 100644
--- a/libs/SDL2/test/testdisplayinfo.c
+++ b/libs/SDL2/test/testdisplayinfo.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testdraw2.c b/libs/SDL2/test/testdraw2.c
index 1472a3863e16ed5f426ce39fd25d5eaa68f0c014..91ee7eea2c765c98555ca1632f3eba4499951e9e 100644
--- a/libs/SDL2/test/testdraw2.c
+++ b/libs/SDL2/test/testdraw2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testdrawchessboard.c b/libs/SDL2/test/testdrawchessboard.c
index cef8621fca8e7eb9b5dc64ea02e346d73426d59c..3dd78e1ac662302174e5dfacf7de6e07d22e6d2c 100644
--- a/libs/SDL2/test/testdrawchessboard.c
+++ b/libs/SDL2/test/testdrawchessboard.c
@@ -1,5 +1,5 @@
 /*
-   Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testdropfile.c b/libs/SDL2/test/testdropfile.c
index b3cb17d9bff69f9bfeb0d86803cb366ab2bc7c75..1c2a3f0e64197330515b5e370f9c297e7aaac401 100644
--- a/libs/SDL2/test/testdropfile.c
+++ b/libs/SDL2/test/testdropfile.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testerror.c b/libs/SDL2/test/testerror.c
index 468f8a7fb8ae789a7417b564a2733513669a3bec..87fcab21b3e5e98b05404d1f7748a93f80e3f9d1 100644
--- a/libs/SDL2/test/testerror.c
+++ b/libs/SDL2/test/testerror.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testfile.c b/libs/SDL2/test/testfile.c
index 23215ea277a258bee07f75ac4fad4ff779b593c1..e563d77bee96cd27a34a985b94e8716c42035dfc 100644
--- a/libs/SDL2/test/testfile.c
+++ b/libs/SDL2/test/testfile.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testfilesystem.c b/libs/SDL2/test/testfilesystem.c
index e8a3b3c36d29b9038b0bd0ee7911b2dd45e93902..ada4e864ced90d9d5c14a9d6bccf44c6476438d4 100644
--- a/libs/SDL2/test/testfilesystem.c
+++ b/libs/SDL2/test/testfilesystem.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testgamecontroller.c b/libs/SDL2/test/testgamecontroller.c
index f586bfe220e90efdc6f50c5b732a82f9d2a373bb..c8616d7cd21abae4ce857e31426eb5ef9f5c65bd 100644
--- a/libs/SDL2/test/testgamecontroller.c
+++ b/libs/SDL2/test/testgamecontroller.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -114,6 +114,11 @@ loop(void *arg)
         case SDL_CONTROLLERBUTTONDOWN:
         case SDL_CONTROLLERBUTTONUP:
             SDL_Log("Controller button %s %s\n", SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
+            /* First button triggers a 0.5 second full strength rumble */
+            if (event.type == SDL_CONTROLLERBUTTONDOWN &&
+                event.cbutton.button == SDL_CONTROLLER_BUTTON_A) {
+                SDL_GameControllerRumble(gamecontroller, 0xFFFF, 0xFFFF, 500);
+            }
             break;
         case SDL_KEYDOWN:
             if (event.key.keysym.sym != SDLK_ESCAPE) {
diff --git a/libs/SDL2/test/testgesture.c b/libs/SDL2/test/testgesture.c
index f7db94fbeb36108d0e6847c8ab94033dc28af135..f4b254a65b7b777782047a44e063533c6bcfd4c4 100644
--- a/libs/SDL2/test/testgesture.c
+++ b/libs/SDL2/test/testgesture.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testgl2.c b/libs/SDL2/test/testgl2.c
index 114c14105ebecea81e9f27db9951021d5940340c..dd01d0e291ae34cef950068fb5416d21b8fa6181 100644
--- a/libs/SDL2/test/testgl2.c
+++ b/libs/SDL2/test/testgl2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testgles.c b/libs/SDL2/test/testgles.c
index cb1da1407e30df9ff77861e94293984b7b2bafeb..96895da0f7992d48fc6af3886846efee73233b2e 100644
--- a/libs/SDL2/test/testgles.c
+++ b/libs/SDL2/test/testgles.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testgles2.c b/libs/SDL2/test/testgles2.c
index 282e0b611e70e4e70afbacf79f4a18e1a148d2d1..c4578a5cc7987b558b3b4a713fbfa704b6bf76d3 100644
--- a/libs/SDL2/test/testgles2.c
+++ b/libs/SDL2/test/testgles2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testhotplug.c b/libs/SDL2/test/testhotplug.c
index 765ce2639499a6d1fda317f38844f7c8343cd0c2..72c90e8d97dbfdc1ac60afa32e1ca4398b171b30 100644
--- a/libs/SDL2/test/testhotplug.c
+++ b/libs/SDL2/test/testhotplug.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testiconv.c b/libs/SDL2/test/testiconv.c
index ff3b28c93c7eadfe401cf216c387532dba3fea87..47e8c377acb5b33229cf8c1f270ab88c95662744 100644
--- a/libs/SDL2/test/testiconv.c
+++ b/libs/SDL2/test/testiconv.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testime.c b/libs/SDL2/test/testime.c
index 32f80cd6bd63cc75efd800197de707b439d1d7e4..77bb86962869ea03970e2ee881c372dc6b530207 100644
--- a/libs/SDL2/test/testime.c
+++ b/libs/SDL2/test/testime.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testintersections.c b/libs/SDL2/test/testintersections.c
index dcf1bc172f5578262092599cbc73652b4400a15d..619df0640e1599ed200887872bc71ae0f5322df5 100644
--- a/libs/SDL2/test/testintersections.c
+++ b/libs/SDL2/test/testintersections.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testjoystick.c b/libs/SDL2/test/testjoystick.c
index 50e5cc96ef17618fbabe1a9312cb7fffed111226..bca74924401048fba60712304d7db4fbe7da3756 100644
--- a/libs/SDL2/test/testjoystick.c
+++ b/libs/SDL2/test/testjoystick.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -90,6 +90,10 @@ loop(void *arg)
             case SDL_JOYBUTTONDOWN:
                 SDL_Log("Joystick %d button %d down\n",
                        event.jbutton.which, event.jbutton.button);
+                /* First button triggers a 0.5 second full strength rumble */
+                if (event.jbutton.button == 0) {
+                    SDL_JoystickRumble(joystick, 0xFFFF, 0xFFFF, 500);
+                }
                 break;
             case SDL_JOYBUTTONUP:
                 SDL_Log("Joystick %d button %d up\n",
diff --git a/libs/SDL2/test/testkeys.c b/libs/SDL2/test/testkeys.c
index 33649d22928d3d2648619e330437f8d81d752a56..73f880e13acefbc6c2475cb950b250ed930dc744 100644
--- a/libs/SDL2/test/testkeys.c
+++ b/libs/SDL2/test/testkeys.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testloadso.c b/libs/SDL2/test/testloadso.c
index 2dce11d6a52f79e0b93a4a56361eaade8f648bba..c6fa33106ff6cda7d172aa6fd57d8abb66f86892 100644
--- a/libs/SDL2/test/testloadso.c
+++ b/libs/SDL2/test/testloadso.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testlock.c b/libs/SDL2/test/testlock.c
index e79fcda966cfd50dc54c8dda2427b5c35f221420..8299a9a675decc8ef63715526ec42491b65d6ad0 100644
--- a/libs/SDL2/test/testlock.c
+++ b/libs/SDL2/test/testlock.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testmessage.c b/libs/SDL2/test/testmessage.c
index 07d0b0309794b0a6e68fea24a9e041f8f5c6d831..8488d8eda61ac4c2ad7d8275d2d819e8b9e33276 100644
--- a/libs/SDL2/test/testmessage.c
+++ b/libs/SDL2/test/testmessage.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testmultiaudio.c b/libs/SDL2/test/testmultiaudio.c
index 4bae3859cb0e1efe7fa21ad6ccdd9102947ecac5..52a4cac7d96150c41e0d8b532e5675b7fe982adc 100644
--- a/libs/SDL2/test/testmultiaudio.c
+++ b/libs/SDL2/test/testmultiaudio.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testnative.c b/libs/SDL2/test/testnative.c
index 83bc495dfeccdbdb072048092e469bc80c8ebff6..674d9d3b4d8520a5a86424b230d9e845d1d00f55 100644
--- a/libs/SDL2/test/testnative.c
+++ b/libs/SDL2/test/testnative.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testnative.h b/libs/SDL2/test/testnative.h
index b2ee5fa44b43605a25159a74aedcca2a0b5a9981..29d85fb325b923d97de0c0cd1e1b28f7d8b7ccc5 100644
--- a/libs/SDL2/test/testnative.h
+++ b/libs/SDL2/test/testnative.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testnativew32.c b/libs/SDL2/test/testnativew32.c
index 7a27b127ae63fca311ce28f7bf2f96c9deaf7fbd..7e96bc697a2b6d8429c6b596df72e5b0c41d91e6 100644
--- a/libs/SDL2/test/testnativew32.c
+++ b/libs/SDL2/test/testnativew32.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testnativex11.c b/libs/SDL2/test/testnativex11.c
index 8908d22ffd2039b0b8f8e7437d89c739a45e319a..386c25e9ec423a119a388aa71529ab9d0a55a027 100644
--- a/libs/SDL2/test/testnativex11.c
+++ b/libs/SDL2/test/testnativex11.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testoverlay2.c b/libs/SDL2/test/testoverlay2.c
index 2145c4d36b045d4b106c3d9ac88d2b6d8ace1b0b..daf07d3e941fc723f06e0c00a8e116d989c17baa 100644
--- a/libs/SDL2/test/testoverlay2.c
+++ b/libs/SDL2/test/testoverlay2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -16,16 +16,14 @@
  *                                                                              *
  ********************************************************************************/
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
 #ifdef __EMSCRIPTEN__
 #include <emscripten/emscripten.h>
 #endif
 
 #include "SDL.h"
 
+#include "testyuv_cvt.h"
+
 #define MOOSEPIC_W 64
 #define MOOSEPIC_H 88
 
@@ -149,7 +147,6 @@ SDL_Renderer *renderer;
 int paused = 0;
 int i;
 SDL_bool done = SDL_FALSE;
-Uint32 pixel_format = SDL_PIXELFORMAT_YV12;
 static int fpsdelay;
 
 /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
@@ -160,91 +157,6 @@ quit(int rc)
     exit(rc);
 }
 
-/* All RGB2YUV conversion code and some other parts of code has been taken from testoverlay.c */
-
-/* NOTE: These RGB conversion functions are not intended for speed,
-         only as examples.
-*/
-
-void
-RGBtoYUV(Uint8 * rgb, int *yuv, int monochrome, int luminance)
-{
-    if (monochrome) {
-#if 1                           /* these are the two formulas that I found on the FourCC site... */
-        yuv[0] = (int)(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
-        yuv[1] = 128;
-        yuv[2] = 128;
-#else
-        yuv[0] = (int)(0.257 * rgb[0]) + (0.504 * rgb[1]) + (0.098 * rgb[2]) + 16;
-        yuv[1] = 128;
-        yuv[2] = 128;
-#endif
-    } else {
-#if 1                           /* these are the two formulas that I found on the FourCC site... */
-        yuv[0] = (int)(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
-        yuv[1] = (int)((rgb[2] - yuv[0]) * 0.565 + 128);
-        yuv[2] = (int)((rgb[0] - yuv[0]) * 0.713 + 128);
-#else
-        yuv[0] = (0.257 * rgb[0]) + (0.504 * rgb[1]) + (0.098 * rgb[2]) + 16;
-        yuv[1] = 128 - (0.148 * rgb[0]) - (0.291 * rgb[1]) + (0.439 * rgb[2]);
-        yuv[2] = 128 + (0.439 * rgb[0]) - (0.368 * rgb[1]) - (0.071 * rgb[2]);
-#endif
-    }
-
-    if (luminance != 100) {
-        yuv[0] = yuv[0] * luminance / 100;
-        if (yuv[0] > 255)
-            yuv[0] = 255;
-    }
-}
-
-void
-ConvertRGBtoYV12(Uint8 *rgb, Uint8 *out, int w, int h,
-                 int monochrome, int luminance)
-{
-    int x, y;
-    int yuv[3];
-    Uint8 *op[3];
-
-    op[0] = out;
-    op[1] = op[0] + w*h;
-    op[2] = op[1] + w*h/4;
-    for (y = 0; y < h; ++y) {
-        for (x = 0; x < w; ++x) {
-            RGBtoYUV(rgb, yuv, monochrome, luminance);
-            *(op[0]++) = yuv[0];
-            if (x % 2 == 0 && y % 2 == 0) {
-                *(op[1]++) = yuv[2];
-                *(op[2]++) = yuv[1];
-            }
-            rgb += 3;
-        }
-    }
-}
-
-void
-ConvertRGBtoNV12(Uint8 *rgb, Uint8 *out, int w, int h,
-                 int monochrome, int luminance)
-{
-    int x, y;
-    int yuv[3];
-    Uint8 *op[2];
-
-    op[0] = out;
-    op[1] = op[0] + w*h;
-    for (y = 0; y < h; ++y) {
-        for (x = 0; x < w; ++x) {
-            RGBtoYUV(rgb, yuv, monochrome, luminance);
-            *(op[0]++) = yuv[0];
-            if (x % 2 == 0 && y % 2 == 0) {
-                *(op[1]++) = yuv[1];
-                *(op[1]++) = yuv[2];
-            }
-            rgb += 3;
-        }
-    }
-}
-
 static void
 PrintUsage(char *argv0)
 {
@@ -307,7 +219,7 @@ loop()
     if (!paused) {
         i = (i + 1) % MOOSEFRAMES_COUNT;
 
-        SDL_UpdateTexture(MooseTexture, NULL, MooseFrame[i], MOOSEPIC_W*SDL_BYTESPERPIXEL(pixel_format));
+        SDL_UpdateTexture(MooseTexture, NULL, MooseFrame[i], MOOSEPIC_W);
     }
     SDL_RenderClear(renderer);
     SDL_RenderCopy(renderer, MooseTexture, NULL, &displayrect);
@@ -329,11 +241,6 @@ main(int argc, char **argv)
     int j;
     int fps = 12;
     int nodelay = 0;
-#ifdef TEST_NV12
-    Uint32 pixel_format = SDL_PIXELFORMAT_NV12;
-#else
-    Uint32 pixel_format = SDL_PIXELFORMAT_YV12;
-#endif
     int scale = 5;
 
     /* Enable standard application logging */
@@ -347,7 +254,7 @@ main(int argc, char **argv)
     while (argc > 1) {
         if (strcmp(argv[1], "-fps") == 0) {
             if (argv[2]) {
-                fps = atoi(argv[2]);
+                fps = SDL_atoi(argv[2]);
                 if (fps == 0) {
                     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                             "The -fps option requires an argument [from 1 to 1000], default is 12.\n");
@@ -371,7 +278,7 @@ main(int argc, char **argv)
             argc -= 1;
         } else if (strcmp(argv[1], "-scale") == 0) {
             if (argv[2]) {
-                scale = atoi(argv[2]);
+                scale = SDL_atoi(argv[2]);
                 if (scale == 0) {
                     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                             "The -scale option requires an argument [from 1 to 50], default is 5.\n");
@@ -439,7 +346,7 @@ main(int argc, char **argv)
         quit(4);
     }
 
-    MooseTexture = SDL_CreateTexture(renderer, pixel_format, SDL_TEXTUREACCESS_STREAMING, MOOSEPIC_W, MOOSEPIC_H);
+    MooseTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, MOOSEPIC_W, MOOSEPIC_H);
     if (!MooseTexture) {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError());
         free(RawMooseData);
@@ -461,17 +368,9 @@ main(int argc, char **argv)
             rgb[2] = MooseColors[frame[j]].b;
             rgb += 3;
         }
-        switch (pixel_format) {
-        case SDL_PIXELFORMAT_YV12:
-            ConvertRGBtoYV12(MooseFrameRGB, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H, 0, 100);
-            break;
-        case SDL_PIXELFORMAT_NV12:
-            ConvertRGBtoNV12(MooseFrameRGB, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H, 0, 100);
-            break;
-        default:
-            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unsupported pixel format\n");
-            break;
-        }
+        ConvertRGBtoYUV(SDL_PIXELFORMAT_YV12, MooseFrameRGB, MOOSEPIC_W*3, MooseFrame[i], MOOSEPIC_W, MOOSEPIC_H,
+            SDL_GetYUVConversionModeForResolution(MOOSEPIC_W, MOOSEPIC_H),
+            0, 100);
     }
 
     free(RawMooseData);
diff --git a/libs/SDL2/test/testplatform.c b/libs/SDL2/test/testplatform.c
index 0cba8fe76c31a50082c173cf810312555389f9e1..1c1d2dcd83b3078d8c067facd0ca45edede0938d 100644
--- a/libs/SDL2/test/testplatform.c
+++ b/libs/SDL2/test/testplatform.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -380,6 +380,7 @@ TestCPUInfo(SDL_bool verbose)
         SDL_Log("SSE4.2 %s\n", SDL_HasSSE42()? "detected" : "not detected");
         SDL_Log("AVX %s\n", SDL_HasAVX()? "detected" : "not detected");
         SDL_Log("AVX2 %s\n", SDL_HasAVX2()? "detected" : "not detected");
+        SDL_Log("AVX-512F %s\n", SDL_HasAVX512F()? "detected" : "not detected");
         SDL_Log("NEON %s\n", SDL_HasNEON()? "detected" : "not detected");
         SDL_Log("System RAM %d MB\n", SDL_GetSystemRAM());
     }
diff --git a/libs/SDL2/test/testpower.c b/libs/SDL2/test/testpower.c
index 48d64f094eab10f0183f9f0090d552463369c77b..adb58832accf6ba1dd0166b597c07670c6299d88 100644
--- a/libs/SDL2/test/testpower.c
+++ b/libs/SDL2/test/testpower.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testqsort.c b/libs/SDL2/test/testqsort.c
index 8867539364df747395c7050d3bf547586f90841a..e83b0731e5896effbc5ce68cdac16fd24e8382c6 100644
--- a/libs/SDL2/test/testqsort.c
+++ b/libs/SDL2/test/testqsort.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testrelative.c b/libs/SDL2/test/testrelative.c
index 5d2ab5d93f344e3887b6bc9ad09bd2ecf649874b..816329ff567b98be5d4a1c6b66e4e8d69c2bd1b5 100644
--- a/libs/SDL2/test/testrelative.c
+++ b/libs/SDL2/test/testrelative.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testrendercopyex.c b/libs/SDL2/test/testrendercopyex.c
index 72a54e649ae60babfbf492131b332a738c042858..209a351576458de0190e1fe14ee8c80eefca4a8b 100644
--- a/libs/SDL2/test/testrendercopyex.c
+++ b/libs/SDL2/test/testrendercopyex.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testrendertarget.c b/libs/SDL2/test/testrendertarget.c
index ec21f2e4301f1fcb8996fae54e8188a6a024519f..7d24d8aeaebc3f21ad74b3987e6b0ace82bbd80e 100644
--- a/libs/SDL2/test/testrendertarget.c
+++ b/libs/SDL2/test/testrendertarget.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testresample.c b/libs/SDL2/test/testresample.c
index a54a47dcb02b342d8d48ebbbba57b7e0e2b8ffeb..4234d9e4549359dcbe23e5206728d7dea9d76ea9 100644
--- a/libs/SDL2/test/testresample.c
+++ b/libs/SDL2/test/testresample.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -93,7 +93,7 @@ main(int argc, char **argv)
     SDL_WriteLE32(io, 0x45564157);      /* WAVE */
     SDL_WriteLE32(io, 0x20746D66);      /* fmt */
     SDL_WriteLE32(io, 16);      /* chunk size */
-    SDL_WriteLE16(io, 1);       /* uncompressed */
+    SDL_WriteLE16(io, SDL_AUDIO_ISFLOAT(spec.format) ? 3 : 1);       /* uncompressed */
     SDL_WriteLE16(io, cvtchans);   /* channels */
     SDL_WriteLE32(io, cvtfreq); /* sample rate */
     SDL_WriteLE32(io, avgbytes);        /* average bytes per second */
diff --git a/libs/SDL2/test/testrumble.c b/libs/SDL2/test/testrumble.c
index a22c52ec2a24ab7dec4c55839be202d792173966..908a6aafb4dc71ddfb4a6a3c8b45dd47bc88c5a2 100644
--- a/libs/SDL2/test/testrumble.c
+++ b/libs/SDL2/test/testrumble.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testscale.c b/libs/SDL2/test/testscale.c
index 34451d6fc482e423ec0554fa81ebf954aa290e7d..e1b46fcc8ded92377ec91e4aacd7927352b9c66d 100644
--- a/libs/SDL2/test/testscale.c
+++ b/libs/SDL2/test/testscale.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testsem.c b/libs/SDL2/test/testsem.c
index 13438b1b6ca9036ab494262b74bed04626165137..884763e6a3adeeebf7ce6dedee4c56a14161c66f 100644
--- a/libs/SDL2/test/testsem.c
+++ b/libs/SDL2/test/testsem.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testsensor.c b/libs/SDL2/test/testsensor.c
new file mode 100644
index 0000000000000000000000000000000000000000..00bfd137deaa2a2a155f63ccae1d20caa919e6f4
--- /dev/null
+++ b/libs/SDL2/test/testsensor.c
@@ -0,0 +1,117 @@
+/*
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+
+/* Simple test of the SDL sensor code */
+
+#include "SDL.h"
+
+static const char *GetSensorTypeString(SDL_SensorType type)
+{
+    static char unknown_type[64];
+
+    switch (type)
+    {
+    case SDL_SENSOR_INVALID:
+        return "SDL_SENSOR_INVALID";
+    case SDL_SENSOR_UNKNOWN:
+        return "SDL_SENSOR_UNKNOWN";
+    case SDL_SENSOR_ACCEL:
+        return "SDL_SENSOR_ACCEL";
+    case SDL_SENSOR_GYRO:
+        return "SDL_SENSOR_GYRO";
+    default:
+        SDL_snprintf(unknown_type, sizeof(unknown_type), "UNKNOWN (%d)", type);
+        return unknown_type;
+    }
+}
+
+static void HandleSensorEvent(SDL_SensorEvent *event)
+{
+    SDL_Sensor *sensor = SDL_SensorFromInstanceID(event->which);
+    if (!sensor) {
+        SDL_Log("Couldn't get sensor for sensor event\n");
+        return;
+    }
+
+    switch (SDL_SensorGetType(sensor)) {
+    case SDL_SENSOR_ACCEL:
+        SDL_Log("Accelerometer update: %.2f, %.2f, %.2f\n", event->data[0], event->data[1], event->data[2]);
+        break;
+    case SDL_SENSOR_GYRO:
+        SDL_Log("Gyro update: %.2f, %.2f, %.2f\n", event->data[0], event->data[1], event->data[2]);
+        break;
+    default:
+        SDL_Log("Sensor update for sensor type %s\n", GetSensorTypeString(SDL_SensorGetType(sensor)));
+        break;
+    }
+}
+
+int
+main(int argc, char **argv)
+{
+    int i;
+    int num_sensors, num_opened;
+
+    /* Load the SDL library */
+    if (SDL_Init(SDL_INIT_SENSOR) < 0) {
+        SDL_Log("Couldn't initialize SDL: %s\n", SDL_GetError());
+        return (1);
+    }
+
+    num_sensors = SDL_NumSensors();
+    num_opened = 0;
+
+    SDL_Log("There are %d sensors available\n", num_sensors);
+    for (i = 0; i < num_sensors; ++i) {
+        SDL_Log("Sensor %d: %s, type %s, platform type %d\n",
+            SDL_SensorGetDeviceInstanceID(i),
+            SDL_SensorGetDeviceName(i),
+            GetSensorTypeString(SDL_SensorGetDeviceType(i)),
+            SDL_SensorGetDeviceNonPortableType(i));
+
+        if (SDL_SensorGetDeviceType(i) != SDL_SENSOR_UNKNOWN) {
+            SDL_Sensor *sensor = SDL_SensorOpen(i);
+            if (sensor == NULL) {
+                SDL_Log("Couldn't open sensor %d: %s\n", SDL_SensorGetDeviceInstanceID(i), SDL_GetError());
+            } else {
+                ++num_opened;
+            }
+        }
+    }
+    SDL_Log("Opened %d sensors\n", num_opened);
+
+    if (num_opened > 0) {
+        SDL_bool done = SDL_FALSE;
+        SDL_Event event;
+
+        SDL_CreateWindow("Sensor Test", 0, 0, 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
+        while (!done) {
+            while (SDL_PollEvent(&event) > 0) {
+                switch (event.type) {
+                case SDL_SENSORUPDATE:
+                    HandleSensorEvent(&event.sensor);
+                    break;
+                case SDL_MOUSEBUTTONUP:
+                case SDL_KEYUP:
+                case SDL_QUIT:
+                    done = SDL_TRUE;
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+    }
+
+    SDL_Quit();
+    return (0);
+}
diff --git a/libs/SDL2/test/testshader.c b/libs/SDL2/test/testshader.c
index 1700da1ccc1d6c9ee83eda6b92a476c4357f09c9..ee0ccdad200a3c5a13ea036ecccee853407e8e58 100644
--- a/libs/SDL2/test/testshader.c
+++ b/libs/SDL2/test/testshader.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testshape.c b/libs/SDL2/test/testshape.c
index d54cc2265a1527a91975c0c5d12ee17a7111637d..7e451e667b232dbdbeb5e41d204aa9172dafebb0 100644
--- a/libs/SDL2/test/testshape.c
+++ b/libs/SDL2/test/testshape.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testsprite2.c b/libs/SDL2/test/testsprite2.c
index 14258f74cd2efdc05f891dc30cb0991086f864ef..b0348f85d7c3927e597f9440d41d2b3d191adcad 100644
--- a/libs/SDL2/test/testsprite2.c
+++ b/libs/SDL2/test/testsprite2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testspriteminimal.c b/libs/SDL2/test/testspriteminimal.c
index 478c5849677173ae8c8bd6ccf96bd4426ec6dc93..92560002ac441d29dad1d6b01bd68f568127ab87 100644
--- a/libs/SDL2/test/testspriteminimal.c
+++ b/libs/SDL2/test/testspriteminimal.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/teststreaming.c b/libs/SDL2/test/teststreaming.c
index 76a9fed18888fc576631c4566d38398198f519ed..7ec6891062676d6eb3de8fb18dd93a112db5612a 100644
--- a/libs/SDL2/test/teststreaming.c
+++ b/libs/SDL2/test/teststreaming.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testthread.c b/libs/SDL2/test/testthread.c
index 6557b2fc742289143d776fb84c95f942fe72d0fe..4555a1e9457477ddf0c39123d4c3618cafa8ca1a 100644
--- a/libs/SDL2/test/testthread.c
+++ b/libs/SDL2/test/testthread.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -20,6 +20,7 @@
 
 static SDL_TLSID tls;
 static int alive = 0;
+static int testprio = 0;
 
 /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
 static void
@@ -29,14 +30,37 @@ quit(int rc)
     exit(rc);
 }
 
+static const char *
+getprioritystr(SDL_ThreadPriority priority)
+{
+    switch(priority)
+    {
+    case SDL_THREAD_PRIORITY_LOW: return "SDL_THREAD_PRIORITY_LOW";
+    case SDL_THREAD_PRIORITY_NORMAL: return "SDL_THREAD_PRIORITY_NORMAL";
+    case SDL_THREAD_PRIORITY_HIGH: return "SDL_THREAD_PRIORITY_HIGH";
+    case SDL_THREAD_PRIORITY_TIME_CRITICAL: return "SDL_THREAD_PRIORITY_TIME_CRITICAL";
+    }
+
+    return "???";
+}
+
 int SDLCALL
 ThreadFunc(void *data)
 {
+    SDL_ThreadPriority prio = SDL_THREAD_PRIORITY_NORMAL;
+
     SDL_TLSSet(tls, "baby thread", NULL);
     SDL_Log("Started thread %s: My thread id is %lu, thread data = %s\n",
            (char *) data, SDL_ThreadID(), (const char *)SDL_TLSGet(tls));
     while (alive) {
         SDL_Log("Thread '%s' is alive!\n", (char *) data);
+
+        if (testprio) {
+            SDL_Log("SDL_SetThreadPriority(%s):%d\n", getprioritystr(prio), SDL_SetThreadPriority(prio));
+            if (++prio > SDL_THREAD_PRIORITY_TIME_CRITICAL)
+                prio = SDL_THREAD_PRIORITY_LOW;
+        }
+
         SDL_Delay(1 * 1000);
     }
     SDL_Log("Thread '%s' exiting!\n", (char *) data);
@@ -55,6 +79,7 @@ killed(int sig)
 int
 main(int argc, char *argv[])
 {
+    int arg = 1;
     SDL_Thread *thread;
 
     /* Enable standard application logging */
@@ -66,6 +91,13 @@ main(int argc, char *argv[])
         return (1);
     }
 
+    while (argv[arg] && *argv[arg] == '-') {
+        if (SDL_strcmp(argv[arg], "--prio") == 0) {
+            testprio = 1;
+        }
+        ++arg;
+    }
+
     tls = SDL_TLSCreate();
     SDL_assert(tls);
     SDL_TLSSet(tls, "main thread", NULL);
diff --git a/libs/SDL2/test/testtimer.c b/libs/SDL2/test/testtimer.c
index 261497be74da6e54b874b759f6a4112fcb29cfba..34764c1c96b6e30dc2dd13ec4d2ce73c51929984 100644
--- a/libs/SDL2/test/testtimer.c
+++ b/libs/SDL2/test/testtimer.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testver.c b/libs/SDL2/test/testver.c
index 04a7a9010e5197d7e4b549e4792528a9520ae7fa..1ae0083457bbc9b9083c8dfdb4ab424914d49c00 100644
--- a/libs/SDL2/test/testver.c
+++ b/libs/SDL2/test/testver.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testviewport.c b/libs/SDL2/test/testviewport.c
index f9d0bd5aa60f708102a5ecc71aeda3fed24c0144..4b8d20e76d77dc12f73def864b1102515005a62a 100644
--- a/libs/SDL2/test/testviewport.c
+++ b/libs/SDL2/test/testviewport.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/test/testvulkan.c b/libs/SDL2/test/testvulkan.c
index 95cbec8039ecf658853eabde379f5a428ea65bba..cd682af1f6019a804a59adad2f91c0e50195b983 100644
--- a/libs/SDL2/test/testvulkan.c
+++ b/libs/SDL2/test/testvulkan.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -255,7 +255,7 @@ static void createInstance(void)
     appInfo.apiVersion = VK_API_VERSION_1_0;
     instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
     instanceCreateInfo.pApplicationInfo = &appInfo;
-    if(!SDL_Vulkan_GetInstanceExtensions(state->windows[0], &extensionCount, NULL))
+    if(!SDL_Vulkan_GetInstanceExtensions(NULL, &extensionCount, NULL))
     {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                      "SDL_Vulkan_GetInstanceExtensions(): %s\n",
@@ -268,7 +268,7 @@ static void createInstance(void)
         SDL_OutOfMemory();
         quit(2);
     }
-    if(!SDL_Vulkan_GetInstanceExtensions(state->windows[0], &extensionCount, extensions))
+    if(!SDL_Vulkan_GetInstanceExtensions(NULL, &extensionCount, extensions))
     {
         SDL_free((void*)extensions);
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
diff --git a/libs/SDL2/test/testwm2.c b/libs/SDL2/test/testwm2.c
index ab3eea2f5bb2a2b90dfed4120fc91ea07f396cb5..5da38730947cfef3f973a90d1eab219d97dd5d75 100644
--- a/libs/SDL2/test/testwm2.c
+++ b/libs/SDL2/test/testwm2.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -146,6 +146,9 @@ main(int argc, char *argv[])
         quit(2);
     }
 
+    SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
+    SDL_EventState(SDL_DROPTEXT, SDL_ENABLE);
+
     for (i = 0; i < state->num_windows; ++i) {
         SDL_Renderer *renderer = state->renderers[i];
         SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF);
diff --git a/libs/SDL2/test/testyuv.bmp b/libs/SDL2/test/testyuv.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..af32034b6b5b858be470f9ef3bb023bc199bf96e
Binary files /dev/null and b/libs/SDL2/test/testyuv.bmp differ
diff --git a/libs/SDL2/test/testyuv.c b/libs/SDL2/test/testyuv.c
new file mode 100644
index 0000000000000000000000000000000000000000..f52ab9f72ec81e70e671dcdbf87aba773ae9e10e
--- /dev/null
+++ b/libs/SDL2/test/testyuv.c
@@ -0,0 +1,455 @@
+/*
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "SDL.h"
+#include "SDL_test_font.h"
+#include "testyuv_cvt.h"
+
+
+/* 422 (YUY2, etc) formats are the largest */
+#define MAX_YUV_SURFACE_SIZE(W, H, P)  (H*4*(W+P+1)/2)
+
+
+/* Return true if the YUV format is packed pixels */
+static SDL_bool is_packed_yuv_format(Uint32 format)
+{
+    return (format == SDL_PIXELFORMAT_YUY2 ||
+            format == SDL_PIXELFORMAT_UYVY ||
+            format == SDL_PIXELFORMAT_YVYU);
+}
+
+/* Create a surface with a good pattern for verifying YUV conversion */
+static SDL_Surface *generate_test_pattern(int pattern_size)
+{
+    SDL_Surface *pattern = SDL_CreateRGBSurfaceWithFormat(0, pattern_size, pattern_size, 0, SDL_PIXELFORMAT_RGB24);
+
+    if (pattern) {
+        int i, x, y;
+        Uint8 *p, c;
+        const int thickness = 2;    /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */
+
+        /* R, G, B in alternating horizontal bands */
+        for (y = 0; y < pattern->h; y += thickness) {
+            for (i = 0; i < thickness; ++i) {
+                p = (Uint8 *)pattern->pixels + (y + i) * pattern->pitch + ((y/thickness) % 3);
+                for (x = 0; x < pattern->w; ++x) {
+                    *p = 0xFF;
+                    p += 3;
+                }
+            }
+        }
+
+        /* Black and white in alternating vertical bands */
+        c = 0xFF;
+        for (x = 1*thickness; x < pattern->w; x += 2*thickness) {
+            for (i = 0; i < thickness; ++i) {
+                p = (Uint8 *)pattern->pixels + (x + i)*3;
+                for (y = 0; y < pattern->h; ++y) {
+                    SDL_memset(p, c, 3);
+                    p += pattern->pitch;
+                }
+            }
+            if (c) {
+                c = 0x00;
+            } else {
+                c = 0xFF;
+            }
+        }
+    }
+    return pattern;
+}
+
+static SDL_bool verify_yuv_data(Uint32 format, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface)
+{
+    const int tolerance = 20;
+    const int size = (surface->h * surface->pitch);
+    Uint8 *rgb;
+    SDL_bool result = SDL_FALSE;
+
+    rgb = (Uint8 *)SDL_malloc(size);
+    if (!rgb) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory");
+        return SDL_FALSE;
+    }
+
+    if (SDL_ConvertPixels(surface->w, surface->h, format, yuv, yuv_pitch, surface->format->format, rgb, surface->pitch) == 0) {
+        int x, y;
+        result = SDL_TRUE;
+        for (y = 0; y < surface->h; ++y) {
+            const Uint8 *actual = rgb + y * surface->pitch;
+            const Uint8 *expected = (const Uint8 *)surface->pixels + y * surface->pitch;
+            for (x = 0; x < surface->w; ++x) {
+                int deltaR = (int)actual[0] - expected[0];
+                int deltaG = (int)actual[1] - expected[1];
+                int deltaB = (int)actual[2] - expected[2];
+                int distance = (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
+                if (distance > tolerance) {
+                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Pixel at %d,%d was 0x%.2x,0x%.2x,0x%.2x, expected 0x%.2x,0x%.2x,0x%.2x, distance = %d\n", x, y, actual[0], actual[1], actual[2], expected[0], expected[1], expected[2], distance);
+                    result = SDL_FALSE;
+                }
+                actual += 3;
+                expected += 3;
+            }
+        }
+    } else {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format->format), SDL_GetError());
+    }
+    SDL_free(rgb);
+
+    return result;
+}
+
+static int run_automated_tests(int pattern_size, int extra_pitch)
+{
+    const Uint32 formats[] = {
+        SDL_PIXELFORMAT_YV12,
+        SDL_PIXELFORMAT_IYUV,
+        SDL_PIXELFORMAT_NV12,
+        SDL_PIXELFORMAT_NV21,
+        SDL_PIXELFORMAT_YUY2,
+        SDL_PIXELFORMAT_UYVY,
+        SDL_PIXELFORMAT_YVYU
+    };
+    int i, j;
+    SDL_Surface *pattern = generate_test_pattern(pattern_size);
+    const int yuv_len = MAX_YUV_SURFACE_SIZE(pattern->w, pattern->h, extra_pitch);
+    Uint8 *yuv1 = (Uint8 *)SDL_malloc(yuv_len);
+    Uint8 *yuv2 = (Uint8 *)SDL_malloc(yuv_len);
+    int yuv1_pitch, yuv2_pitch;
+    int result = -1;
+    
+    if (!pattern || !yuv1 || !yuv2) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces");
+        goto done;
+    }
+
+    /* Verify conversion from YUV formats */
+    for (i = 0; i < SDL_arraysize(formats); ++i) {
+        if (!ConvertRGBtoYUV(formats[i], pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, SDL_GetYUVConversionModeForResolution(pattern->w, pattern->h), 0, 100)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s\n", SDL_GetPixelFormatName(formats[i]));
+            goto done;
+        }
+        yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w);
+        if (!verify_yuv_data(formats[i], yuv1, yuv1_pitch, pattern)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", SDL_GetPixelFormatName(formats[i]));
+            goto done;
+        }
+    }
+
+    /* Verify conversion to YUV formats */
+    for (i = 0; i < SDL_arraysize(formats); ++i) {
+        yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
+        if (SDL_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
+            goto done;
+        }
+        if (!verify_yuv_data(formats[i], yuv1, yuv1_pitch, pattern)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", SDL_GetPixelFormatName(formats[i]));
+            goto done;
+        }
+    }
+
+    /* Verify conversion between YUV formats */
+    for (i = 0; i < SDL_arraysize(formats); ++i) {
+        for (j = 0; j < SDL_arraysize(formats); ++j) {
+            yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
+            yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
+            if (SDL_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
+                goto done;
+            }
+            if (SDL_ConvertPixels(pattern->w, pattern->h, formats[i], yuv1, yuv1_pitch, formats[j], yuv2, yuv2_pitch) < 0) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
+                goto done;
+            }
+            if (!verify_yuv_data(formats[j], yuv2, yuv2_pitch, pattern)) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
+                goto done;
+            }
+        }
+    }
+
+    /* Verify conversion between YUV formats in-place */
+    for (i = 0; i < SDL_arraysize(formats); ++i) {
+        for (j = 0; j < SDL_arraysize(formats); ++j) {
+            if (is_packed_yuv_format(formats[i]) != is_packed_yuv_format(formats[j])) {
+                /* Can't change plane vs packed pixel layout in-place */
+                continue;
+            }
+
+            yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch;
+            yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch;
+            if (SDL_ConvertPixels(pattern->w, pattern->h, pattern->format->format, pattern->pixels, pattern->pitch, formats[i], yuv1, yuv1_pitch) < 0) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError());
+                goto done;
+            }
+            if (SDL_ConvertPixels(pattern->w, pattern->h, formats[i], yuv1, yuv1_pitch, formats[j], yuv1, yuv2_pitch) < 0) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError());
+                goto done;
+            }
+            if (!verify_yuv_data(formats[j], yuv1, yuv2_pitch, pattern)) {
+                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]));
+                goto done;
+            }
+        }
+    }
+
+
+    result = 0;
+
+done:
+    SDL_free(yuv1);
+    SDL_free(yuv2);
+    SDL_FreeSurface(pattern);
+    return result;
+}
+
+int
+main(int argc, char **argv)
+{
+    struct {
+        SDL_bool enable_intrinsics;
+        int pattern_size;
+        int extra_pitch;
+    } automated_test_params[] = {
+        /* Test: even width and height */
+        { SDL_FALSE, 2, 0 },
+        { SDL_FALSE, 4, 0 },
+        /* Test: odd width and height */
+        { SDL_FALSE, 1, 0 },
+        { SDL_FALSE, 3, 0 },
+        /* Test: even width and height, extra pitch */
+        { SDL_FALSE, 2, 3 },
+        { SDL_FALSE, 4, 3 },
+        /* Test: odd width and height, extra pitch */
+        { SDL_FALSE, 1, 3 },
+        { SDL_FALSE, 3, 3 },
+        /* Test: even width and height with intrinsics */
+        { SDL_TRUE, 32, 0 },
+        /* Test: odd width and height with intrinsics */
+        { SDL_TRUE, 33, 0 },
+        { SDL_TRUE, 37, 0 },
+        /* Test: even width and height with intrinsics, extra pitch */
+        { SDL_TRUE, 32, 3 },
+        /* Test: odd width and height with intrinsics, extra pitch */
+        { SDL_TRUE, 33, 3 },
+        { SDL_TRUE, 37, 3 },
+    };
+    int arg = 1;
+    const char *filename;
+    SDL_Surface *original;
+    SDL_Surface *converted;
+    SDL_Window *window;
+    SDL_Renderer *renderer;
+    SDL_Texture *output[3];
+    const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" };
+    char title[128];
+    const char *yuv_name;
+    const char *yuv_mode;
+    Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888;
+    Uint32 yuv_format = SDL_PIXELFORMAT_YV12;
+    int current = 0;
+    int pitch;
+    Uint8 *raw_yuv;
+    Uint32 then, now, i, iterations = 100;
+    SDL_bool should_run_automated_tests = SDL_FALSE;
+
+    while (argv[arg] && *argv[arg] == '-') {
+        if (SDL_strcmp(argv[arg], "--jpeg") == 0) {
+            SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_JPEG);
+        } else if (SDL_strcmp(argv[arg], "--bt601") == 0) {
+            SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
+        } else if (SDL_strcmp(argv[arg], "--bt709") == 0) {
+            SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
+        } else if (SDL_strcmp(argv[arg], "--auto") == 0) {
+            SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_AUTOMATIC);
+        } else if (SDL_strcmp(argv[arg], "--yv12") == 0) {
+            yuv_format = SDL_PIXELFORMAT_YV12;
+        } else if (SDL_strcmp(argv[arg], "--iyuv") == 0) {
+            yuv_format = SDL_PIXELFORMAT_IYUV;
+        } else if (SDL_strcmp(argv[arg], "--yuy2") == 0) {
+            yuv_format = SDL_PIXELFORMAT_YUY2;
+        } else if (SDL_strcmp(argv[arg], "--uyvy") == 0) {
+            yuv_format = SDL_PIXELFORMAT_UYVY;
+        } else if (SDL_strcmp(argv[arg], "--yvyu") == 0) {
+            yuv_format = SDL_PIXELFORMAT_YVYU;
+        } else if (SDL_strcmp(argv[arg], "--nv12") == 0) {
+            yuv_format = SDL_PIXELFORMAT_NV12;
+        } else if (SDL_strcmp(argv[arg], "--nv21") == 0) {
+            yuv_format = SDL_PIXELFORMAT_NV21;
+        } else if (SDL_strcmp(argv[arg], "--rgb555") == 0) {
+            rgb_format = SDL_PIXELFORMAT_RGB555;
+        } else if (SDL_strcmp(argv[arg], "--rgb565") == 0) {
+            rgb_format = SDL_PIXELFORMAT_RGB565;
+        } else if (SDL_strcmp(argv[arg], "--rgb24") == 0) {
+            rgb_format = SDL_PIXELFORMAT_RGB24;
+        } else if (SDL_strcmp(argv[arg], "--argb") == 0) {
+            rgb_format = SDL_PIXELFORMAT_ARGB8888;
+        } else if (SDL_strcmp(argv[arg], "--abgr") == 0) {
+            rgb_format = SDL_PIXELFORMAT_ABGR8888;
+        } else if (SDL_strcmp(argv[arg], "--rgba") == 0) {
+            rgb_format = SDL_PIXELFORMAT_RGBA8888;
+        } else if (SDL_strcmp(argv[arg], "--bgra") == 0) {
+            rgb_format = SDL_PIXELFORMAT_BGRA8888;
+        } else if (SDL_strcmp(argv[arg], "--automated") == 0) {
+            should_run_automated_tests = SDL_TRUE;
+        } else {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Usage: %s [--jpeg|--bt601|-bt709|--auto] [--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21] [--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra] [image_filename]\n", argv[0]);
+            return 1;
+        }
+        ++arg;
+    }
+
+    /* Run automated tests */
+    if (should_run_automated_tests) {
+        for (i = 0; i < SDL_arraysize(automated_test_params); ++i) {
+            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s\n", 
+                automated_test_params[i].pattern_size,
+                automated_test_params[i].extra_pitch,
+                automated_test_params[i].enable_intrinsics ? "enabled" : "disabled");
+            if (run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch) < 0) {
+                return 2;
+            }
+        }
+        return 0;
+    }
+
+    if (argv[arg]) {
+        filename = argv[arg];
+    } else {
+        filename = "testyuv.bmp";
+    }
+    original = SDL_ConvertSurfaceFormat(SDL_LoadBMP(filename), SDL_PIXELFORMAT_RGB24, 0);
+    if (!original) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
+        return 3;
+    }
+
+    raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0));
+    ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h,
+        SDL_GetYUVConversionModeForResolution(original->w, original->h),
+        0, 100);
+    pitch = CalculateYUVPitch(yuv_format, original->w);
+
+    converted = SDL_CreateRGBSurfaceWithFormat(0, original->w, original->h, 0, rgb_format);
+    if (!converted) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s\n", SDL_GetError());
+        return 3;
+    }
+
+    then = SDL_GetTicks();
+    for ( i = 0; i < iterations; ++i ) {
+        SDL_ConvertPixels(original->w, original->h, yuv_format, raw_yuv, pitch, rgb_format, converted->pixels, converted->pitch);
+    }
+    now = SDL_GetTicks();
+    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %d ms, %.2fms each\n", iterations, (now - then), (float)(now - then)/iterations);
+
+    window = SDL_CreateWindow("YUV test",
+                              SDL_WINDOWPOS_UNDEFINED,
+                              SDL_WINDOWPOS_UNDEFINED,
+                              original->w, original->h,
+                              0);
+    if (!window) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
+        return 4;
+    }
+
+    renderer = SDL_CreateRenderer(window, -1, 0);
+    if (!renderer) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
+        return 4;
+    }
+
+    output[0] = SDL_CreateTextureFromSurface(renderer, original);
+    output[1] = SDL_CreateTextureFromSurface(renderer, converted);
+    output[2] = SDL_CreateTexture(renderer, yuv_format, SDL_TEXTUREACCESS_STREAMING, original->w, original->h);
+    if (!output[0] || !output[1] || !output[2]) {
+        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s\n", SDL_GetError());
+        return 5;
+    }
+    SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch);
+    
+    yuv_name = SDL_GetPixelFormatName(yuv_format);
+    if (SDL_strncmp(yuv_name, "SDL_PIXELFORMAT_", 16) == 0) {
+        yuv_name += 16;
+    }
+
+    switch (SDL_GetYUVConversionModeForResolution(original->w, original->h)) {
+    case SDL_YUV_CONVERSION_JPEG:
+        yuv_mode = "JPEG";
+        break;
+    case SDL_YUV_CONVERSION_BT601:
+        yuv_mode = "BT.601";
+        break;
+    case SDL_YUV_CONVERSION_BT709:
+        yuv_mode = "BT.709";
+        break;
+    default:
+        yuv_mode = "UNKNOWN";
+        break;
+    }
+
+    { int done = 0;
+        while ( !done )
+        {
+            SDL_Event event;
+            while (SDL_PollEvent(&event) > 0) {
+                if (event.type == SDL_QUIT) {
+                    done = 1;
+                }
+                if (event.type == SDL_KEYDOWN) {
+                    if (event.key.keysym.sym == SDLK_ESCAPE) {
+                        done = 1;
+                    } else if (event.key.keysym.sym == SDLK_LEFT) {
+                        --current;
+                    } else if (event.key.keysym.sym == SDLK_RIGHT) {
+                        ++current;
+                    }
+                }
+                if (event.type == SDL_MOUSEBUTTONDOWN) {
+                    if (event.button.x < (original->w/2)) {
+                        --current;
+                    } else {
+                        ++current;
+                    }
+                }
+            }
+
+            /* Handle wrapping */
+            if (current < 0) {
+                current += SDL_arraysize(output);
+            }
+            if (current >= SDL_arraysize(output)) {
+                current -= SDL_arraysize(output);
+            }
+
+            SDL_RenderClear(renderer);
+            SDL_RenderCopy(renderer, output[current], NULL, NULL);
+            SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
+            if (current == 0) {
+                SDLTest_DrawString(renderer, 4, 4, titles[current]);
+            } else {
+                SDL_snprintf(title, sizeof(title), "%s %s %s", titles[current], yuv_name, yuv_mode);
+                SDLTest_DrawString(renderer, 4, 4, title);
+            }
+            SDL_RenderPresent(renderer);
+            SDL_Delay(10);
+        }
+    }
+    SDL_Quit();
+    return 0;
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/test/testyuv_cvt.c b/libs/SDL2/test/testyuv_cvt.c
new file mode 100644
index 0000000000000000000000000000000000000000..553a3fa185de6647db5f9386a4fbe3a8695c63b9
--- /dev/null
+++ b/libs/SDL2/test/testyuv_cvt.c
@@ -0,0 +1,300 @@
+/*
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+
+#include "SDL.h"
+
+#include "testyuv_cvt.h"
+
+
+static float clip3(float x, float y, float z)
+{
+    return ((z < x) ? x : ((z > y) ? y : z));
+}
+
+static void RGBtoYUV(Uint8 * rgb, int *yuv, SDL_YUV_CONVERSION_MODE mode, int monochrome, int luminance)
+{
+    if (mode == SDL_YUV_CONVERSION_JPEG) {
+        /* Full range YUV */
+        yuv[0] = (int)(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
+        yuv[1] = (int)((rgb[2] - yuv[0]) * 0.565 + 128);
+        yuv[2] = (int)((rgb[0] - yuv[0]) * 0.713 + 128);
+    } else {
+        // This formula is from Microsoft's documentation:
+        // https://msdn.microsoft.com/en-us/library/windows/desktop/dd206750(v=vs.85).aspx
+        // L = Kr * R + Kb * B + (1 - Kr - Kb) * G
+        // Y =                   floor(2^(M-8) * (219*(L-Z)/S + 16) + 0.5);
+        // U = clip3(0, (2^M)-1, floor(2^(M-8) * (112*(B-L) / ((1-Kb)*S) + 128) + 0.5));
+        // V = clip3(0, (2^M)-1, floor(2^(M-8) * (112*(R-L) / ((1-Kr)*S) + 128) + 0.5));
+        float S, Z, R, G, B, L, Kr, Kb, Y, U, V;
+
+        if (mode == SDL_YUV_CONVERSION_BT709) {
+            /* BT.709 */
+            Kr = 0.2126f;
+            Kb = 0.0722f;
+        } else {
+            /* BT.601 */
+            Kr = 0.299f;
+            Kb = 0.114f;
+        }
+
+        S = 255.0f;
+        Z = 0.0f;
+        R = rgb[0];
+        G = rgb[1];
+        B = rgb[2];
+        L = Kr * R + Kb * B + (1 - Kr - Kb) * G;
+        Y = (Uint8)SDL_floorf((219*(L-Z)/S + 16) + 0.5f);
+        U = (Uint8)clip3(0, 255, SDL_floorf((112.0f*(B-L) / ((1.0f-Kb)*S) + 128) + 0.5f));
+        V = (Uint8)clip3(0, 255, SDL_floorf((112.0f*(R-L) / ((1.0f-Kr)*S) + 128) + 0.5f));
+
+        yuv[0] = (Uint8)Y;
+        yuv[1] = (Uint8)U;
+        yuv[2] = (Uint8)V;
+    }
+
+    if (monochrome) {
+        yuv[1] = 128;
+        yuv[2] = 128;
+    }
+
+    if (luminance != 100) {
+        yuv[0] = yuv[0] * luminance / 100;
+        if (yuv[0] > 255)
+            yuv[0] = 255;
+    }
+}
+
+static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, SDL_YUV_CONVERSION_MODE mode, int monochrome, int luminance)
+{
+    int x, y;
+    int yuv[4][3];
+    Uint8 *Y1, *Y2, *U, *V;
+    Uint8 *rgb1, *rgb2;
+    int rgb_row_advance = (pitch - w*3) + pitch;
+    int UV_advance;
+
+    rgb1 = src;
+    rgb2 = src + pitch;
+
+    Y1 = out;
+    Y2 = Y1 + w;
+    switch (format) {
+    case SDL_PIXELFORMAT_YV12:
+        V = (Y1 + h * w);
+        U = V + ((h + 1)/2)*((w + 1)/2);
+        UV_advance = 1;
+        break;
+    case SDL_PIXELFORMAT_IYUV:
+        U = (Y1 + h * w);
+        V = U + ((h + 1)/2)*((w + 1)/2);
+        UV_advance = 1;
+        break;
+    case SDL_PIXELFORMAT_NV12:
+        U = (Y1 + h * w);
+        V = U + 1;
+        UV_advance = 2;
+        break;
+    case SDL_PIXELFORMAT_NV21:
+        V = (Y1 + h * w);
+        U = V + 1;
+        UV_advance = 2;
+        break;
+    default:
+        SDL_assert(!"Unsupported planar YUV format");
+        return;
+    }
+
+    for (y = 0; y < (h - 1); y += 2) {
+        for (x = 0; x < (w - 1); x += 2) {
+            RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance);
+            rgb1 += 3;
+            *Y1++ = (Uint8)yuv[0][0];
+
+            RGBtoYUV(rgb1, yuv[1], mode, monochrome, luminance);
+            rgb1 += 3;
+            *Y1++ = (Uint8)yuv[1][0];
+
+            RGBtoYUV(rgb2, yuv[2], mode, monochrome, luminance);
+            rgb2 += 3;
+            *Y2++ = (Uint8)yuv[2][0];
+
+            RGBtoYUV(rgb2, yuv[3], mode, monochrome, luminance);
+            rgb2 += 3;
+            *Y2++ = (Uint8)yuv[3][0];
+
+            *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1])/4.0f + 0.5f);
+            U += UV_advance;
+
+            *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2])/4.0f + 0.5f);
+            V += UV_advance;
+        }
+        /* Last column */
+        if (x == (w - 1)) {
+            RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance);
+            rgb1 += 3;
+            *Y1++ = (Uint8)yuv[0][0];
+
+            RGBtoYUV(rgb2, yuv[2], mode, monochrome, luminance);
+            rgb2 += 3;
+            *Y2++ = (Uint8)yuv[2][0];
+
+            *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[2][1])/2.0f + 0.5f);
+            U += UV_advance;
+
+            *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[2][2])/2.0f + 0.5f);
+            V += UV_advance;
+        }
+        Y1 += w;
+        Y2 += w;
+        rgb1 += rgb_row_advance;
+        rgb2 += rgb_row_advance;
+    }
+    /* Last row */
+    if (y == (h - 1)) {
+        for (x = 0; x < (w - 1); x += 2) {
+            RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance);
+            rgb1 += 3;
+            *Y1++ = (Uint8)yuv[0][0];
+
+            RGBtoYUV(rgb1, yuv[1], mode, monochrome, luminance);
+            rgb1 += 3;
+            *Y1++ = (Uint8)yuv[1][0];
+
+            *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1])/2.0f + 0.5f);
+            U += UV_advance;
+
+            *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2])/2.0f + 0.5f);
+            V += UV_advance;
+        }
+        /* Last column */
+        if (x == (w - 1)) {
+            RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance);
+            *Y1++ = (Uint8)yuv[0][0];
+
+            *U = (Uint8)yuv[0][1];
+            U += UV_advance;
+
+            *V = (Uint8)yuv[0][2];
+            V += UV_advance;
+        }
+    }
+}
+
+static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, SDL_YUV_CONVERSION_MODE mode, int monochrome, int luminance)
+{
+    int x, y;
+    int yuv[2][3];
+    Uint8 *Y1, *Y2, *U, *V;
+    Uint8 *rgb;
+    int rgb_row_advance = (pitch - w*3);
+
+    rgb = src;
+
+    switch (format) {
+    case SDL_PIXELFORMAT_YUY2:
+        Y1 = out;
+        U = out+1;
+        Y2 = out+2;
+        V = out+3;
+        break;
+    case SDL_PIXELFORMAT_UYVY:
+        U = out;
+        Y1 = out+1;
+        V = out+2;
+        Y2 = out+3;
+        break;
+    case SDL_PIXELFORMAT_YVYU:
+        Y1 = out;
+        V = out+1;
+        Y2 = out+2;
+        U = out+3;
+        break;
+    default:
+        SDL_assert(!"Unsupported packed YUV format");
+        return;
+    }
+
+    for (y = 0; y < h; ++y) {
+        for (x = 0; x < (w - 1); x += 2) {
+            RGBtoYUV(rgb, yuv[0], mode, monochrome, luminance);
+            rgb += 3;
+            *Y1 = (Uint8)yuv[0][0];
+            Y1 += 4;
+
+            RGBtoYUV(rgb, yuv[1], mode, monochrome, luminance);
+            rgb += 3;
+            *Y2 = (Uint8)yuv[1][0];
+            Y2 += 4;
+
+            *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1])/2.0f + 0.5f);
+            U += 4;
+
+            *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2])/2.0f + 0.5f);
+            V += 4;
+        }
+        /* Last column */
+        if (x == (w - 1)) {
+            RGBtoYUV(rgb, yuv[0], mode, monochrome, luminance);
+            rgb += 3;
+            *Y2 = *Y1 = (Uint8)yuv[0][0];
+            Y1 += 4;
+            Y2 += 4;
+
+            *U = (Uint8)yuv[0][1];
+            U += 4;
+
+            *V = (Uint8)yuv[0][2];
+            V += 4;
+        }
+        rgb += rgb_row_advance;
+    }
+}
+
+SDL_bool ConvertRGBtoYUV(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, SDL_YUV_CONVERSION_MODE mode, int monochrome, int luminance)
+{
+    switch (format)
+    {
+    case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+        ConvertRGBtoPlanar2x2(format, src, pitch, out, w, h, mode, monochrome, luminance);
+        return SDL_TRUE;
+    case SDL_PIXELFORMAT_YUY2:
+    case SDL_PIXELFORMAT_UYVY:
+    case SDL_PIXELFORMAT_YVYU:
+        ConvertRGBtoPacked4(format, src, pitch, out, w, h, mode, monochrome, luminance);
+        return SDL_TRUE;
+    default:
+        return SDL_FALSE;
+    }
+}
+
+int CalculateYUVPitch(Uint32 format, int width)
+{
+    switch (format)
+    {
+    case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+        return width;
+    case SDL_PIXELFORMAT_YUY2:
+    case SDL_PIXELFORMAT_UYVY:
+    case SDL_PIXELFORMAT_YVYU:
+        return 4*((width + 1)/2);
+    default:
+        return 0;
+    }
+}
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/test/testyuv_cvt.h b/libs/SDL2/test/testyuv_cvt.h
new file mode 100644
index 0000000000000000000000000000000000000000..bd845878f4607518d2a32746c61e28e2555d82b8
--- /dev/null
+++ b/libs/SDL2/test/testyuv_cvt.h
@@ -0,0 +1,16 @@
+/*
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely.
+*/
+
+/* These functions are designed for testing correctness, not for speed */
+
+extern SDL_bool ConvertRGBtoYUV(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, SDL_YUV_CONVERSION_MODE mode, int monochrome, int luminance);
+extern int CalculateYUVPitch(Uint32 format, int width);
diff --git a/libs/SDL2/test/torturethread.c b/libs/SDL2/test/torturethread.c
index 4c2679b8ebd749f729918965cacea63754e5a328..9b1a407eea2dfe167ef5e662079f1bce9f624468 100644
--- a/libs/SDL2/test/torturethread.c
+++ b/libs/SDL2/test/torturethread.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/bin/SDL2.dll b/libs/SDL2/x86_64-w64-mingw32/bin/SDL2.dll
index 6776cdd891ea4479c662519a1667ddc08ca5a8ef..934f8091c7d59b5d479026cfab1ec45d9077fff1 100755
Binary files a/libs/SDL2/x86_64-w64-mingw32/bin/SDL2.dll and b/libs/SDL2/x86_64-w64-mingw32/bin/SDL2.dll differ
diff --git a/libs/SDL2/x86_64-w64-mingw32/bin/sdl2-config b/libs/SDL2/x86_64-w64-mingw32/bin/sdl2-config
index 34d3cb87742057ac1bbc25d362470cc998ff7ee7..9809f6002224622f26ee76a054673fc9cadd7249 100755
--- a/libs/SDL2/x86_64-w64-mingw32/bin/sdl2-config
+++ b/libs/SDL2/x86_64-w64-mingw32/bin/sdl2-config
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-prefix=/usr/local/x86_64-w64-mingw32
+prefix=/opt/local/x86_64-w64-mingw32
 exec_prefix=${prefix}
 exec_prefix_set=no
 libdir=${exec_prefix}/lib
@@ -39,17 +39,17 @@ while test $# -gt 0; do
       echo $exec_prefix
       ;;
     --version)
-      echo 2.0.7
+      echo 2.0.9
       ;;
     --cflags)
       echo -I${prefix}/include/SDL2  -Dmain=SDL_main
       ;;
     --libs)
-      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2  -mwindows
+      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2 -mwindows
       ;;
     --static-libs)
 #    --libs|--static-libs)
-      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2  -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc
+      echo -L${exec_prefix}/lib  -lmingw32 -lSDL2main -lSDL2 -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -static-libgcc
       ;;
     *)
       echo "${usage}" 1>&2
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL.h
index 366d50fa39095f0185247d490b31753351ef887c..fc35a419efd9076076aca339303a2937d5e6ada7 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,6 +51,7 @@
 #include "SDL_power.h"
 #include "SDL_render.h"
 #include "SDL_rwops.h"
+#include "SDL_sensor.h"
 #include "SDL_shape.h"
 #include "SDL_system.h"
 #include "SDL_thread.h"
@@ -80,10 +81,11 @@ extern "C" {
 #define SDL_INIT_HAPTIC         0x00001000u
 #define SDL_INIT_GAMECONTROLLER 0x00002000u  /**< SDL_INIT_GAMECONTROLLER implies SDL_INIT_JOYSTICK */
 #define SDL_INIT_EVENTS         0x00004000u
+#define SDL_INIT_SENSOR         0x00008000u
 #define SDL_INIT_NOPARACHUTE    0x00100000u  /**< compatibility; this flag is ignored. */
 #define SDL_INIT_EVERYTHING ( \
                 SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \
-                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \
+                SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
             )
 /* @} */
 
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_assert.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_assert.h
index 90abbe39b6f1e5bd7e0bfa3d54b00954efe06e9a..b38f928ae14bb68e916bae1048a4c8648a1a20d8 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_assert.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_atomic.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_atomic.h
index 36e37f3b786840231bf87fddf563c695037c7fc5..b2287748c8f87973ef2aef5093812c0065b89725 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_atomic.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_atomic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -158,6 +158,9 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
 #if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__))
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("lwsync" : : : "memory")
 #define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("lwsync" : : : "memory")
+#elif defined(__GNUC__) && defined(__aarch64__)
+#define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
+#define SDL_MemoryBarrierAcquire()   __asm__ __volatile__ ("dmb ish" : : : "memory")
 #elif defined(__GNUC__) && defined(__arm__)
 #if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__)
 #define SDL_MemoryBarrierRelease()   __asm__ __volatile__ ("dmb ish" : : : "memory")
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_audio.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_audio.h
index f119c2b2678fc531efe1d3ecc8b1f1751169de53..d3e1bface4d406c3d6c1c3f141a36a3fce8e926b 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_audio.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_audio.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -140,7 +140,8 @@ typedef Uint16 SDL_AudioFormat;
 #define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE    0x00000001
 #define SDL_AUDIO_ALLOW_FORMAT_CHANGE       0x00000002
 #define SDL_AUDIO_ALLOW_CHANNELS_CHANGE     0x00000004
-#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)
+#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE      0x00000008
+#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)
 /* @} */
 
 /* @} *//* Audio flags */
@@ -527,7 +528,7 @@ extern DECLSPEC SDL_AudioStream * SDLCALL SDL_NewAudioStream(const SDL_AudioForm
  *
  *  \param stream The stream the audio data is being added to
  *  \param buf A pointer to the audio data to add
- *  \param int The number of bytes to write to the stream
+ *  \param len The number of bytes to write to the stream
  *  \return 0 on success, or -1 on error.
  *
  *  \sa SDL_NewAudioStream
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_bits.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_bits.h
index bc28572902181c1edf67998fe3800c0269b4c451..eb8322f0d0a181b364871c28df42010ead3f17b1 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_bits.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_bits.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_blendmode.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_blendmode.h
index 9abd0bd4cf11c6d4d9931a20f21db32e2af0cdc6..36a5ea76f0c52c4364cb5f156cdf7252c0441ecc 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_blendmode.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_blendmode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_clipboard.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_clipboard.h
index 341f4ba42b17e2025a5a27ecaa3925e3f618c1ad..f28751ebb8ffe5c67f17ccab94f2a55165fea4c8 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_clipboard.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_clipboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_config.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_config.h
index 1bbb83848142162b87676b35e03d964d5bec56dd..c58be8e72ee0dbb9e73f94be9d8c2c595aa630e5 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_config.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_config.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -82,6 +82,9 @@ typedef unsigned int uintptr_t;
 #define HAVE_DSOUND_H 1
 #define HAVE_DXGI_H 1
 #define HAVE_XINPUT_H 1
+#define HAVE_MMDEVICEAPI_H 1
+#define HAVE_AUDIOCLIENT_H 1
+#define HAVE_ENDPOINTVOLUME_H 1
 
 /* This is disabled by default to avoid C runtime dependencies and manifest requirements */
 #ifdef HAVE_LIBC
@@ -109,13 +112,15 @@ typedef unsigned int uintptr_t;
 #define HAVE_MEMCMP 1
 #define HAVE_STRLEN 1
 #define HAVE__STRREV 1
-#define HAVE__STRUPR 1
-#define HAVE__STRLWR 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__STRUPR */
+/* #undef HAVE__STRLWR */
 #define HAVE_STRCHR 1
 #define HAVE_STRRCHR 1
 #define HAVE_STRSTR 1
-#define HAVE__LTOA 1
-#define HAVE__ULTOA 1
+/* These functions have security warnings, so we won't use them */
+/* #undef HAVE__LTOA */
+/* #undef HAVE__ULTOA */
 #define HAVE_STRTOL 1
 #define HAVE_STRTOUL 1
 #define HAVE_STRTOD 1
@@ -125,30 +130,45 @@ typedef unsigned int uintptr_t;
 #define HAVE_STRNCMP 1
 #define HAVE__STRICMP 1
 #define HAVE__STRNICMP 1
-#define HAVE_ATAN 1
-#define HAVE_ATAN2 1
-#define HAVE_ACOS  1
-#define HAVE_ASIN  1
-#define HAVE_CEIL 1
-#define HAVE_COS 1
-#define HAVE_COSF 1
-#define HAVE_FABS 1
-#define HAVE_FLOOR 1
-#define HAVE_LOG 1
-#define HAVE_POW 1
-#define HAVE_SIN 1
-#define HAVE_SINF 1
-#define HAVE_SQRT 1
-#define HAVE_SQRTF 1
-#define HAVE_TAN 1
-#define HAVE_TANF 1
-#define HAVE__COPYSIGN 1
+#define HAVE_ACOS   1
+#define HAVE_ACOSF  1
+#define HAVE_ASIN   1
+#define HAVE_ASINF  1
+#define HAVE_ATAN   1
+#define HAVE_ATANF  1
+#define HAVE_ATAN2  1
+#define HAVE_ATAN2F 1
+#define HAVE_CEILF  1
+#define HAVE__COPYSIGN  1
+#define HAVE_COS    1
+#define HAVE_COSF   1
+#define HAVE_EXP    1
+#define HAVE_EXPF   1
+#define HAVE_FABS   1
+#define HAVE_FABSF  1
+#define HAVE_FLOOR  1
+#define HAVE_FLOORF 1
+#define HAVE_FMOD   1
+#define HAVE_FMODF  1
+#define HAVE_LOG    1
+#define HAVE_LOGF   1
+#define HAVE_LOG10  1
+#define HAVE_LOG10F 1
+#define HAVE_POW    1
+#define HAVE_POWF   1
+#define HAVE_SIN    1
+#define HAVE_SINF   1
+#define HAVE_SQRT   1
+#define HAVE_SQRTF  1
+#define HAVE_TAN    1
+#define HAVE_TANF   1
 #if defined(_MSC_VER)
 /* These functions were added with the VC++ 2013 C runtime library */
 #if _MSC_VER >= 1800
 #define HAVE_STRTOLL 1
 #define HAVE_VSSCANF 1
 #define HAVE_SCALBN 1
+#define HAVE_SCALBNF    1
 #endif
 /* This function is available with at least the VC++ 2008 C runtime library */
 #if _MSC_VER >= 1400
@@ -166,7 +186,6 @@ typedef unsigned int uintptr_t;
 /* Enable various audio drivers */
 #define SDL_AUDIO_DRIVER_WASAPI 1
 #define SDL_AUDIO_DRIVER_DSOUND 1
-#define SDL_AUDIO_DRIVER_XAUDIO2    0
 #define SDL_AUDIO_DRIVER_WINMM  1
 #define SDL_AUDIO_DRIVER_DISK   1
 #define SDL_AUDIO_DRIVER_DUMMY  1
@@ -174,9 +193,13 @@ typedef unsigned int uintptr_t;
 /* Enable various input drivers */
 #define SDL_JOYSTICK_DINPUT 1
 #define SDL_JOYSTICK_XINPUT 1
+#define SDL_JOYSTICK_HIDAPI 1
 #define SDL_HAPTIC_DINPUT   1
 #define SDL_HAPTIC_XINPUT   1
 
+/* Enable the dummy sensor driver */
+#define SDL_SENSOR_DUMMY  1
+
 /* Enable various shared object loading systems */
 #define SDL_LOADSO_WINDOWS  1
 
@@ -194,7 +217,7 @@ typedef unsigned int uintptr_t;
 #define SDL_VIDEO_RENDER_D3D    1
 #endif
 #ifndef SDL_VIDEO_RENDER_D3D11
-#define SDL_VIDEO_RENDER_D3D11	0
+#define SDL_VIDEO_RENDER_D3D11  0
 #endif
 
 /* Enable OpenGL support */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_cpuinfo.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_cpuinfo.h
index 94b64b03bc5dd394af7f2ccfc5968fb1c0e9dfc5..ee3a47e841242fdae7e697f6b5de18a2a47c1ecf 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_cpuinfo.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_cpuinfo.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -51,27 +51,35 @@
 #include <intrin.h>
 #else
 #ifdef __ALTIVEC__
-#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__)
+#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
 #include <altivec.h>
 #undef pixel
+#undef bool
 #endif
 #endif
-#ifdef __MMX__
-#include <mmintrin.h>
+#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
+#include <arm_neon.h>
 #endif
-#ifdef __3dNOW__
+#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
 #include <mm3dnow.h>
 #endif
-#ifdef __SSE__
+#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
+#include <immintrin.h>
+#else
+#if defined(__MMX__) && !defined(SDL_DISABLE_MMINTRIN_H)
+#include <mmintrin.h>
+#endif
+#if defined(__SSE__) && !defined(SDL_DISABLE_XMMINTRIN_H)
 #include <xmmintrin.h>
 #endif
-#ifdef __SSE2__
+#if defined(__SSE2__) && !defined(SDL_DISABLE_EMMINTRIN_H)
 #include <emmintrin.h>
 #endif
-#ifdef __SSE3__
+#if defined(__SSE3__) && !defined(SDL_DISABLE_PMMINTRIN_H)
 #include <pmmintrin.h>
 #endif
-#endif
+#endif /* HAVE_IMMINTRIN_H */
+#endif /* compiler version */
 
 #include "begin_code.h"
 /* Set up for C function definitions, even when using C++ */
@@ -154,6 +162,11 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void);
  */
 extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX2(void);
 
+/**
+ *  This function returns true if the CPU has AVX-512F (foundation) features.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX512F(void);
+
 /**
  *  This function returns true if the CPU has NEON (ARM SIMD) features.
  */
@@ -164,7 +177,6 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
  */
 extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
 
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_egl.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_egl.h
index e47fbe8624700058a55aff35c8cd39081a19695f..d65ed437c33fe307657aca04f192de87e0ff55e9 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_egl.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_egl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -24,7 +24,7 @@
  *
  *  This is a simple file to encapsulate the EGL API headers.
  */
-#ifndef _MSC_VER
+#if !defined(_MSC_VER) && !defined(__ANDROID__)
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_endian.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_endian.h
index 3eda7f2871144111f5cdfb24e0d57dd988c440c4..ed0bf5ba8f94888115422d06601ff4200119a9e6 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_endian.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_endian.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_error.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_error.h
index 49be9827e182ae56227e8dde300b7e8c03f8cfb8..c0e46298e8ffdbc6cf6abc4381a7ba9e2dcf1a33 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_error.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_error.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_events.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_events.h
index 0fc09bc37d795c8b5a4c34bebf9c78134a4c499f..af22eb6466661378e252d090b1504471f74833a8 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_events.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_events.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -85,6 +85,9 @@ typedef enum
                                      Called on Android in onResume()
                                 */
 
+    /* Display events */
+    SDL_DISPLAYEVENT   = 0x150,  /**< Display state change */
+
     /* Window events */
     SDL_WINDOWEVENT    = 0x200, /**< Window state change */
     SDL_SYSWMEVENT,             /**< System specific event */
@@ -144,6 +147,9 @@ typedef enum
     SDL_AUDIODEVICEADDED = 0x1100, /**< A new audio device is available */
     SDL_AUDIODEVICEREMOVED,        /**< An audio device has been removed. */
 
+    /* Sensor events */
+    SDL_SENSORUPDATE = 0x1200,     /**< A sensor was updated */
+
     /* Render events */
     SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset and their contents need to be updated */
     SDL_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */
@@ -168,6 +174,21 @@ typedef struct SDL_CommonEvent
     Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
 } SDL_CommonEvent;
 
+/**
+ *  \brief Display state change event data (event.display.*)
+ */
+typedef struct SDL_DisplayEvent
+{
+    Uint32 type;        /**< ::SDL_DISPLAYEVENT */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Uint32 display;     /**< The associated display index */
+    Uint8 event;        /**< ::SDL_DisplayEventID */
+    Uint8 padding1;
+    Uint8 padding2;
+    Uint8 padding3;
+    Sint32 data1;       /**< event dependent data */
+} SDL_DisplayEvent;
+
 /**
  *  \brief Window state change event data (event.window.*)
  */
@@ -471,6 +492,17 @@ typedef struct SDL_DropEvent
 } SDL_DropEvent;
 
 
+/**
+ *  \brief Sensor event structure (event.sensor.*)
+ */
+typedef struct SDL_SensorEvent
+{
+    Uint32 type;        /**< ::SDL_SENSORUPDATE */
+    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
+    Sint32 which;       /**< The instance ID of the sensor */
+    float data[6];      /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
+} SDL_SensorEvent;
+
 /**
  *  \brief The "quit requested" event
  */
@@ -526,6 +558,7 @@ typedef union SDL_Event
 {
     Uint32 type;                    /**< Event type, shared with all events */
     SDL_CommonEvent common;         /**< Common event data */
+    SDL_DisplayEvent display;       /**< Window event data */
     SDL_WindowEvent window;         /**< Window event data */
     SDL_KeyboardEvent key;          /**< Keyboard event data */
     SDL_TextEditingEvent edit;      /**< Text editing event data */
@@ -542,6 +575,7 @@ typedef union SDL_Event
     SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
     SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
     SDL_AudioDeviceEvent adevice;   /**< Audio device event data */
+    SDL_SensorEvent sensor;         /**< Sensor event data */
     SDL_QuitEvent quit;             /**< Quit request event data */
     SDL_UserEvent user;             /**< Custom event data */
     SDL_SysWMEvent syswm;           /**< System dependent window event data */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_filesystem.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_filesystem.h
index a1c846ad0f07b71a61a60c147c709c2dc6e2c15f..fa6a1fa6e7b2ad4b9e341f6cf71c4e7fca925586 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_filesystem.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_filesystem.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gamecontroller.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gamecontroller.h
index c9215132ef50a7f214b7b4bf248d6fc73c3ff3f4..6ae9c95428907172257a93dfd6074865891f6571 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gamecontroller.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gamecontroller.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -175,6 +175,14 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index);
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index);
 
+/**
+ *  Get the mapping of a game controller.
+ *  This can be called before any controllers are opened.
+ *
+ *  \return the mapping string.  Must be freed with SDL_free().  Returns NULL if no mapping is available
+ */
+extern DECLSPEC char *SDLCALL SDL_GameControllerMappingForDeviceIndex(int joystick_index);
+
 /**
  *  Open a game controller for use.
  *  The index passed as an argument refers to the N'th game controller on the system.
@@ -196,6 +204,13 @@ extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerFromInstanceID(SDL
  */
 extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller);
 
+/**
+ *  Get the player index of an opened game controller, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller);
+
 /**
  *  Get the USB vendor ID of an opened controller, if available.
  *  If the vendor ID isn't available this function returns 0.
@@ -345,6 +360,19 @@ SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller,
 extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
                                                           SDL_GameControllerButton button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param gamecontroller The controller to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a controller previously opened with SDL_GameControllerOpen().
  */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gesture.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gesture.h
index 2bba6a7294fa93240d5f6eefbd8d16c5773761ea..b223d80d44c3debd2de55420aaf1e907d1a08919 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gesture.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_gesture.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_haptic.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_haptic.h
index 60daefaa709bbbc0a1796bd9af6f9be073cacedd..2ea1bfc1602ee571b0ac3238b183117fcb20f25d 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_haptic.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_haptic.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -117,6 +117,17 @@
 extern "C" {
 #endif /* __cplusplus */
 
+/* FIXME: For SDL 2.1, adjust all the magnitude variables to be Uint16 (0xFFFF).
+ *
+ * At the moment the magnitude variables are mixed between signed/unsigned, and
+ * it is also not made clear that ALL of those variables expect a max of 0x7FFF.
+ *
+ * Some platforms may have higher precision than that (Linux FF, Windows XInput)
+ * so we should fix the inconsistency in favor of higher possible precision,
+ * adjusting for platforms that use different scales.
+ * -flibit
+ */
+
 /**
  *  \typedef SDL_Haptic
  *
@@ -656,8 +667,8 @@ typedef struct SDL_HapticRamp
  * This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
  *
  * The Left/Right effect is used to explicitly control the large and small
- * motors, commonly found in modern game controllers. One motor is high
- * frequency, the other is low frequency.
+ * motors, commonly found in modern game controllers. The small (right) motor
+ * is high frequency, and the large (left) motor is low frequency.
  *
  * \sa SDL_HAPTIC_LEFTRIGHT
  * \sa SDL_HapticEffect
@@ -668,7 +679,7 @@ typedef struct SDL_HapticLeftRight
     Uint16 type;            /**< ::SDL_HAPTIC_LEFTRIGHT */
 
     /* Replay */
-    Uint32 length;          /**< Duration of the effect. */
+    Uint32 length;          /**< Duration of the effect in milliseconds. */
 
     /* Rumble */
     Uint16 large_magnitude; /**< Control of the large controller motor. */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_hints.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_hints.h
index 007a4bee05c0dc4ef90b1d24e88eb1137cedc945..4ee72e97d66db40a703620a9d0a162a952de1498 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_hints.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_hints.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,7 @@ extern "C" {
  *    "opengl"
  *    "opengles2"
  *    "opengles"
+ *    "metal"
  *    "software"
  *
  *  The default varies by platform, but it's the first one in the list that
@@ -210,6 +211,18 @@ extern "C" {
  */
 #define SDL_HINT_VIDEO_X11_NET_WM_PING      "SDL_VIDEO_X11_NET_WM_PING"
 
+/**
+ * \brief A variable controlling whether the X11 _NET_WM_BYPASS_COMPOSITOR hint should be used.
+ * 
+ * This variable can be set to the following values:
+ * "0" - Disable _NET_WM_BYPASS_COMPOSITOR
+ * "1" - Enable _NET_WM_BYPASS_COMPOSITOR
+ * 
+ * By default SDL will use _NET_WM_BYPASS_COMPOSITOR
+ * 
+ */
+#define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"
+
 /**
  *  \brief  A variable controlling whether the window frame and title bar are interactive when the cursor is hidden 
  *
@@ -249,6 +262,16 @@ extern "C" {
  */
 #define SDL_HINT_GRAB_KEYBOARD              "SDL_GRAB_KEYBOARD"
 
+/**
+ *  \brief  A variable setting the double click time, in milliseconds.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_TIME    "SDL_MOUSE_DOUBLE_CLICK_TIME"
+
+/**
+ *  \brief  A variable setting the double click radius, in pixels.
+ */
+#define SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS    "SDL_MOUSE_DOUBLE_CLICK_RADIUS"
+
 /**
  *  \brief  A variable setting the speed scale for mouse motion, in floating point, when the mouse is not in relative mode
  */
@@ -316,7 +339,7 @@ extern "C" {
 #define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED"
 
 /**
- *  \brief  A variable controlling which orientations are allowed on iOS.
+ *  \brief  A variable controlling which orientations are allowed on iOS/Android.
  *
  *  In some circumstances it is necessary to be able to explicitly control
  *  which UI orientations are allowed.
@@ -354,17 +377,37 @@ extern "C" {
  */
 #define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION"
 
+/**
+ * \brief  A variable controlling whether the home indicator bar on iPhone X
+ *         should be hidden.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - The indicator bar is not hidden (default for windowed applications)
+ *    "1"       - The indicator bar is hidden and is shown when the screen is touched (useful for movie playback applications)
+ *    "2"       - The indicator bar is dim and the first swipe makes it visible and the second swipe performs the "home" action (default for fullscreen applications)
+ */
+#define SDL_HINT_IOS_HIDE_HOME_INDICATOR "SDL_IOS_HIDE_HOME_INDICATOR"
+
 /**
  *  \brief  A variable controlling whether the Android / iOS built-in
- *  accelerometer should be listed as a joystick device, rather than listing
- *  actual joysticks only.
+ *  accelerometer should be listed as a joystick device.
  *
  *  This variable can be set to the following values:
- *    "0"       - List only real joysticks and accept input from them
- *    "1"       - List real joysticks along with the accelerometer as if it were a 3 axis joystick (the default).
+ *    "0"       - The accelerometer is not listed as a joystick
+ *    "1"       - The accelerometer is available as a 3 axis joystick (the default).
  */
 #define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK"
 
+/**
+ *  \brief  A variable controlling whether the Android / tvOS remotes
+ *  should be listed as joystick devices, instead of sending keyboard events.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - Remotes send enter/escape/arrow key events
+ *    "1"       - Remotes are available as 2 axis, 2 button joysticks (the default).
+ */
+#define SDL_HINT_TV_REMOTE_AS_JOYSTICK "SDL_TV_REMOTE_AS_JOYSTICK"
+
 /**
  *  \brief  A variable that lets you disable the detection and use of Xinput gamepad devices
  *
@@ -432,6 +475,88 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
 
+/**
+ *  \brief  A variable controlling whether the HIDAPI joystick drivers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI drivers are not used
+ *    "1"       - HIDAPI drivers are used (the default)
+ *
+ *  This variable is the default for all drivers, but can be overridden by the hints for specific drivers below.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for PS4 controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
+
+/**
+ *  \brief  A variable controlling whether extended input reports should be used for PS4 controllers when using the HIDAPI driver.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - extended reports are not enabled (the default)
+ *    "1"       - extended reports
+ *
+ *  Extended input reports allow rumble on Bluetooth PS4 controllers, but
+ *  break DirectInput handling for applications that don't use SDL.
+ *
+ *  Once extended reports are enabled, they can not be disabled without
+ *  power cycling the controller.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Steam Controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for XBox controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOX   "SDL_JOYSTICK_HIDAPI_XBOX"
+
+/**
+ *  \brief  A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
+ *
+ *  The variable can be set to the following values:
+ *    "0"       - Do not scan for Steam Controllers
+ *    "1"       - Scan for Steam Controllers (the default)
+ *
+ *  The default value is "1".  This hint must be set before initializing the joystick subsystem.
+ */
+#define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS"
+
+
 /**
  *  \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
  *      This is a debugging aid for developers and not expected to be used by end users. The default is "1"
@@ -494,6 +619,10 @@ extern "C" {
 *  This is specially useful if you build SDL against a non glibc libc library (such as musl) which
 *  provides a relatively small default thread stack size (a few kilobytes versus the default 8MB glibc uses).
 *  Support for this hint is currently available only in the pthread, Windows, and PSP backend.
+*
+*  Instead of this hint, in 2.0.9 and later, you can use
+*  SDL_CreateThreadWithStackSize(). This hint only works with the classic
+*  SDL_CreateThread().
 */
 #define SDL_HINT_THREAD_STACK_SIZE              "SDL_THREAD_STACK_SIZE"
 
@@ -719,6 +848,35 @@ extern "C" {
  */
 #define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
 
+ /**
+ * \brief A variable to control whether we trap the Android back button to handle it manually.
+ *        This is necessary for the right mouse button to work on some Android devices, or
+ *        to be able to trap the back button for use in your code reliably.  If set to true,
+ *        the back button will show up as an SDL_KEYDOWN / SDL_KEYUP pair with a keycode of 
+ *        SDL_SCANCODE_AC_BACK.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - Back button will be handled as usual for system. (default)
+ *   "1"       - Back button will be trapped, allowing you to handle the key press
+ *               manually.  (This will also let right mouse click work on systems 
+ *               where the right mouse button functions as back.)
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
+
+ /**
+ * \brief A variable to control whether the return key on the soft keyboard
+ *        should hide the soft keyboard on Android and iOS.
+ *
+ * The variable can be set to the following values:
+ *   "0"       - The return key will be handled as a key event. This is the behaviour of SDL <= 2.0.3. (default)
+ *   "1"       - The return key will hide the keyboard.
+ *
+ * The value of this hint is used at runtime, so it can be changed at any time.
+ */
+#define SDL_HINT_RETURN_KEY_HIDES_IME "SDL_RETURN_KEY_HIDES_IME"
+
 /**
  *  \brief override the binding element for keyboard inputs for Emscripten builds
  *
@@ -752,7 +910,7 @@ extern "C" {
  *   "0"       - SDL will generate a window-close event when it sees Alt+F4.
  *   "1"       - SDL will only do normal key handling for Alt+F4.
  */
-#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4	"SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
+#define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4"
 
 /**
  *  \brief Prevent SDL from using version 4 of the bitmap header when saving BMPs.
@@ -797,6 +955,24 @@ extern "C" {
  */
 #define SDL_HINT_RPI_VIDEO_LAYER           "SDL_RPI_VIDEO_LAYER"
 
+/**
+ * \brief Tell the video driver that we only want a double buffer.
+ *
+ * By default, most lowlevel 2D APIs will use a triple buffer scheme that 
+ * wastes no CPU time on waiting for vsync after issuing a flip, but
+ * introduces a frame of latency. On the other hand, using a double buffer
+ * scheme instead is recommended for cases where low latency is an important
+ * factor because we save a whole frame of latency.
+ * We do so by waiting for vsync immediately after issuing a flip, usually just
+ * after eglSwapBuffers call in the backend's *_SwapWindow function.
+ *
+ * Since it's driver-specific, it's only supported where possible and
+ * implemented. Currently supported the following drivers:
+ * - KMSDRM (kmsdrm)
+ * - Raspberry Pi (raspberrypi)
+ */
+#define SDL_HINT_VIDEO_DOUBLE_BUFFER      "SDL_VIDEO_DOUBLE_BUFFER"
+
 /**
  *  \brief  A variable controlling what driver to use for OpenGL ES contexts.
  *
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_joystick.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_joystick.h
index f598dc828dafa2751d3aba2286059a8298d79aa3..6e05a9c2056cf7134c22459066d253e80e9a4315 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_joystick.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_joystick.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,10 +97,10 @@ typedef enum
 typedef enum
 {
     SDL_JOYSTICK_POWER_UNKNOWN = -1,
-    SDL_JOYSTICK_POWER_EMPTY,
-    SDL_JOYSTICK_POWER_LOW,
-    SDL_JOYSTICK_POWER_MEDIUM,
-    SDL_JOYSTICK_POWER_FULL,
+    SDL_JOYSTICK_POWER_EMPTY,   /* <= 5% */
+    SDL_JOYSTICK_POWER_LOW,     /* <= 20% */
+    SDL_JOYSTICK_POWER_MEDIUM,  /* <= 70% */
+    SDL_JOYSTICK_POWER_FULL,    /* <= 100% */
     SDL_JOYSTICK_POWER_WIRED,
     SDL_JOYSTICK_POWER_MAX
 } SDL_JoystickPowerLevel;
@@ -132,6 +132,12 @@ extern DECLSPEC int SDLCALL SDL_NumJoysticks(void);
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index);
 
+/**
+ *  Get the player index of a joystick, or -1 if it's not available
+ *  This can be called before any joysticks are opened.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetDevicePlayerIndex(int device_index);
+
 /**
  *  Return the GUID for the joystick at this index
  *  This can be called before any joysticks are opened.
@@ -194,6 +200,13 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID
  */
 extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick);
 
+/**
+ *  Get the player index of an opened joystick, or -1 if it's not available
+ *
+ *  For XInput controllers this returns the XInput user index.
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickGetPlayerIndex(SDL_Joystick * joystick);
+
 /**
  *  Return the GUID for this opened joystick
  */
@@ -361,6 +374,19 @@ extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick,
 extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
                                                     int button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param joystick The joystick to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a joystick previously opened with SDL_JoystickOpen().
  */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keyboard.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keyboard.h
index e78ca469082c793a8190331c01766a3e3374a5ba..87482317161a6e05324bf5ab769743b1a385b436 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keyboard.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keyboard.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keycode.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keycode.h
index c41e45ff61b6959937f7f4e912cef24064b72e1f..d7d5b1dbcdefce3777ee48eddcc46853eba2e584 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keycode.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_keycode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_loadso.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_loadso.h
index 6ea256c6ae5813dc7b00c269101b4e866fbb5fcc..da56fb452779ee015da429caa8da746c985b0ee0 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_loadso.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_loadso.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_log.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_log.h
index 356d0e1cf7492f54d82e5829a6030587400869d1..e12b6588601712bd217578226f143941ff019cbb 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_log.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_main.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_main.h
index 2af32360f7be842213bca1253268d5198c768377..98558217fc23ea63118899e44a710b7ba934c720 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_main.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_main.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -63,10 +63,13 @@
 /* On Android SDL provides a Java class in SDLActivity.java that is the
    main activity entry point.
 
-   See README-android.md for more details on extending that class.
+   See docs/README-android.md for more details on extending that class.
  */
 #define SDL_MAIN_NEEDED
 
+/* We need to export SDL_main so it can be launched from Java */
+#define SDLMAIN_DECLSPEC    DECLSPEC
+
 #elif defined(__NACL__)
 /* On NACL we use ppapi_simple to set up the application helper code,
    then wait for the first PSE_INSTANCE_DIDCHANGEVIEW event before 
@@ -85,6 +88,10 @@
 #define C_LINKAGE
 #endif /* __cplusplus */
 
+#ifndef SDLMAIN_DECLSPEC
+#define SDLMAIN_DECLSPEC
+#endif
+
 /**
  *  \file SDL_main.h
  *
@@ -107,7 +114,7 @@
 /**
  *  The prototype for the application's main() function
  */
-extern C_LINKAGE DECLSPEC int SDL_main(int argc, char *argv[]);
+extern C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
 
 
 #include "begin_code.h"
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_messagebox.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_messagebox.h
index c326d8f01098b147e1bff015fb8e355e37f00744..b7be59d88f50ad7686b794ca13cd66f29e7ef557 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_messagebox.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_messagebox.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mouse.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mouse.h
index 6001bd46c1e80270d85d18dd72025b0c17f2722a..d3c9f6156cba36ecff91929d7a7eda0784b4af69 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mouse.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mouse.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mutex.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mutex.h
index 0272379e55e16bb25a913f9b4e4ca11809af7b56..ba4247ced8176dd98396d552536bc4646faa7841 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mutex.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_mutex.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_name.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_name.h
index 8095ed3ffd4df27fb77a15eebcd532536c3efc09..ecd863f4ca873b55249601228efc5fc578aa4fb1 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_name.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_name.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengl.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengl.h
index 314dd57eda395c3b9e7bcbaf2d3749b9aa01864d..253d9c93a96f875aa53a51a5a615d4d732d9926d 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengl.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengl.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles.h
index 800c593070a9ea28a1f01c1d2b84e0f15ad2b33c..18dd984b3ed21bf5fd3bbfcf1a8a34fe58dfca6f 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles2.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles2.h
index 102f2f3fa7075c34639c03109939460ab2e7c2cc..6ccecf216572b4c9514bded0fd0a1c54690e57d8 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles2.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_opengles2.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_pixels.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_pixels.h
index 04246689f5957e5170ad6be433e089e2483ea85a..0b4364b185231109d61663b4acb50e60e1d8fa16 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_pixels.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_pixels.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -287,7 +287,9 @@ enum
     SDL_PIXELFORMAT_NV12 =      /**< Planar mode: Y + U/V interleaved  (2 planes) */
         SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2'),
     SDL_PIXELFORMAT_NV21 =      /**< Planar mode: Y + V/U interleaved  (2 planes) */
-        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1')
+        SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'),
+    SDL_PIXELFORMAT_EXTERNAL_OES =      /**< Android video texture format */
+        SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ')
 };
 
 typedef struct SDL_Color
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_platform.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_platform.h
index 9dde385fa67364dbcfcf22598e33961970c4348e..7dea4ce940fe02f1b233d15ec6a75c9452a0422c 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_platform.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_platform.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -121,7 +121,12 @@
 #if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
 /* Try to find out if we're compiling for WinRT or non-WinRT */
 #if defined(_MSC_VER) && defined(__has_include)
-#define HAVE_WINAPIFAMILY_H __has_include(<winapifamily.h>)
+#if __has_include(<winapifamily.h>)
+#define HAVE_WINAPIFAMILY_H 1
+#else
+#define HAVE_WINAPIFAMILY_H 0
+#endif
+
 /* If _USING_V110_SDK71_ is defined it means we are using the Windows XP toolset. */
 #elif defined(_MSC_VER) && (_MSC_VER >= 1700 && !_USING_V110_SDK71_)    /* _MSC_VER == 1700 for Visual Studio 2012 */
 #define HAVE_WINAPIFAMILY_H 1
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_power.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_power.h
index d48e8a3795dfd0d9f8a1cd9ad07ba1c54b1b0d7c..a4fe8a9359c247678ad56a47f8b8106fa465f4a9 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_power.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_power.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_quit.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_quit.h
index 9283c381050a4013097e533a1723c64bb6392938..fea56a8d88b6a0ac9e12807d693628f80991bc3e 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_quit.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_quit.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rect.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rect.h
index c4f6d74b339ddc7b71372221758dc229043362fd..543bb618694ea9d4b5383e71a7c35b666b9e754c 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rect.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rect.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_render.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_render.h
index ea6ef9ff645cef9e643162828ccb6b5173d1b732..d336192974f4cedab0497ad6ac60aaba19f3f389 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_render.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_render.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -898,6 +898,27 @@ extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw
  */
 extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture);
 
+/**
+ *  \brief Get the CAMetalLayer associated with the given Metal renderer
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return CAMetalLayer* on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalCommandEncoder()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer);
+
+/**
+ *  \brief Get the Metal command encoder for the current frame
+ *
+ *  \param renderer The renderer to query
+ *
+ *  \return id<MTLRenderCommandEncoder> on success, or NULL if the renderer isn't a Metal renderer
+ *
+ *  \sa SDL_RenderGetMetalLayer()
+ */
+extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_revision.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_revision.h
index 9376093ee76bd46fbb70196e87673cda9d53ccd6..92fbe67b132156d911795af5e14b335d15645c66 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_revision.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_revision.h
@@ -1,2 +1,2 @@
-#define SDL_REVISION "hg-11645:2088cd828335"
-#define SDL_REVISION_NUMBER 11645
+#define SDL_REVISION "hg-12373:8feb5da6f2fb"
+#define SDL_REVISION_NUMBER 12373
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rwops.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rwops.h
index 7f0cbdfd55d5fabdd8ff80fc5ec605a7dd70bc0c..0960699d4857bb302e9024e7263c89a725a9a276 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rwops.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_rwops.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_scancode.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_scancode.h
index 1d55212048560455d110797913a3dde07416768c..63871aa3b10beed824756b430687a3e16d7ebe44 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_scancode.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_scancode.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_sensor.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_sensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac163a8cd1be73201742fe56293ddc840653f706
--- /dev/null
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_sensor.h
@@ -0,0 +1,251 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file SDL_sensor.h
+ *
+ *  Include file for SDL sensor event handling
+ *
+ */
+
+#ifndef _SDL_sensor_h
+#define _SDL_sensor_h
+
+#include "SDL_stdinc.h"
+#include "SDL_error.h"
+
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+extern "C" {
+/* *INDENT-ON* */
+#endif
+
+/**
+ *  \brief SDL_sensor.h
+ *
+ *  In order to use these functions, SDL_Init() must have been called
+ *  with the ::SDL_INIT_SENSOR flag.  This causes SDL to scan the system
+ *  for sensors, and load appropriate drivers.
+ */
+
+struct _SDL_Sensor;
+typedef struct _SDL_Sensor SDL_Sensor;
+
+/**
+ * This is a unique ID for a sensor for the time it is connected to the system,
+ * and is never reused for the lifetime of the application.
+ *
+ * The ID value starts at 0 and increments from there. The value -1 is an invalid ID.
+ */
+typedef Sint32 SDL_SensorID;
+
+/* The different sensors defined by SDL
+ *
+ * Additional sensors may be available, using platform dependent semantics.
+ *
+ * Hare are the additional Android sensors:
+ * https://developer.android.com/reference/android/hardware/SensorEvent.html#values
+ */
+typedef enum
+{
+    SDL_SENSOR_INVALID = -1,    /**< Returned for an invalid sensor */
+    SDL_SENSOR_UNKNOWN,         /**< Unknown sensor type */
+    SDL_SENSOR_ACCEL,           /**< Accelerometer */
+    SDL_SENSOR_GYRO             /**< Gyroscope */
+} SDL_SensorType;
+
+/**
+ * Accelerometer sensor
+ *
+ * The accelerometer returns the current acceleration in SI meters per
+ * second squared. This includes gravity, so a device at rest will have
+ * an acceleration of SDL_STANDARD_GRAVITY straight down.
+ *
+ * values[0]: Acceleration on the x axis
+ * values[1]: Acceleration on the y axis
+ * values[2]: Acceleration on the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+#define SDL_STANDARD_GRAVITY    9.80665f
+
+/**
+ * Gyroscope sensor
+ *
+ * The gyroscope returns the current rate of rotation in radians per second.
+ * The rotation is positive in the counter-clockwise direction. That is,
+ * an observer looking from a positive location on one of the axes would
+ * see positive rotation on that axis when it appeared to be rotating
+ * counter-clockwise.
+ *
+ * values[0]: Angular speed around the x axis
+ * values[1]: Angular speed around the y axis
+ * values[2]: Angular speed around the z axis
+ *
+ * For phones held in portrait mode, the axes are defined as follows:
+ * -X ... +X : left ... right
+ * -Y ... +Y : bottom ... top
+ * -Z ... +Z : farther ... closer
+ * 
+ * The axis data is not changed when the phone is rotated.
+ *
+ * \sa SDL_GetDisplayOrientation()
+ */
+
+/* Function prototypes */
+
+/**
+ *  \brief Count the number of sensors attached to the system right now
+ */
+extern DECLSPEC int SDLCALL SDL_NumSensors(void);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ * 
+ *  \return The sensor name, or NULL if device_index is out of range.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetDeviceName(int device_index);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetDeviceType(int device_index);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if device_index is out of range.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetDeviceNonPortableType(int device_index);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if device_index is out of range.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetDeviceInstanceID(int device_index);
+
+/**
+ *  \brief Open a sensor for use.
+ *
+ *  The index passed as an argument refers to the N'th sensor on the system.
+ *
+ *  \return A sensor identifier, or NULL if an error occurred.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorOpen(int device_index);
+
+/**
+ * Return the SDL_Sensor associated with an instance id.
+ */
+extern DECLSPEC SDL_Sensor *SDLCALL SDL_SensorFromInstanceID(SDL_SensorID instance_id);
+
+/**
+ *  \brief Get the implementation dependent name of a sensor.
+ *
+ *  \return The sensor name, or NULL if the sensor is NULL.
+ */
+extern DECLSPEC const char *SDLCALL SDL_SensorGetName(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor type, or SDL_SENSOR_INVALID if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorType SDLCALL SDL_SensorGetType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the platform dependent type of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor platform dependent type, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetNonPortableType(SDL_Sensor *sensor);
+
+/**
+ *  \brief Get the instance ID of a sensor.
+ *
+ *  This can be called before any sensors are opened.
+ *
+ *  \return The sensor instance ID, or -1 if the sensor is NULL.
+ */
+extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor);
+
+/**
+ *  Get the current state of an opened sensor.
+ *
+ *  The number of values and interpretation of the data is sensor dependent.
+ *
+ *  \param sensor The sensor to query
+ *  \param data A pointer filled with the current sensor state
+ *  \param num_values The number of values to write to data
+ *
+ *  \return 0 or -1 if an error occurred.
+ */
+extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values);
+
+/**
+ *  Close a sensor previously opened with SDL_SensorOpen()
+ */
+extern DECLSPEC void SDLCALL SDL_SensorClose(SDL_Sensor * sensor);
+
+/**
+ *  Update the current state of the open sensors.
+ *
+ *  This is called automatically by the event loop if sensor events are enabled.
+ *
+ *  This needs to be called from the thread that initialized the sensor subsystem.
+ */
+extern DECLSPEC void SDLCALL SDL_SensorUpdate(void);
+
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+/* *INDENT-OFF* */
+}
+/* *INDENT-ON* */
+#endif
+#include "close_code.h"
+
+#endif /* _SDL_sensor_h */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_shape.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_shape.h
index 9e492d93a4d1f6e42bcb2bbfec31fd71520cbaaa..40a6baaaec3d4d7e46c1da1fed54949123421035 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_shape.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_shape.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_stdinc.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_stdinc.h
index 72402299f2da1892dd2224bc436b1822007f9b51..e373bc380f68867e5638b8f6ea26521a66a906ee 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_stdinc.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_stdinc.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -86,6 +86,28 @@
 #ifdef HAVE_FLOAT_H
 # include <float.h>
 #endif
+#if defined(HAVE_ALLOCA) && !defined(alloca)
+# if defined(HAVE_ALLOCA_H)
+#  include <alloca.h>
+# elif defined(__GNUC__)
+#  define alloca __builtin_alloca
+# elif defined(_MSC_VER)
+#  include <malloc.h>
+#  define alloca _alloca
+# elif defined(__WATCOMC__)
+#  include <malloc.h>
+# elif defined(__BORLANDC__)
+#  include <malloc.h>
+# elif defined(__DMC__)
+#  include <stdlib.h>
+# elif defined(__AIX__)
+#pragma alloca
+# elif defined(__MRC__)
+void *alloca(unsigned);
+# else
+char *alloca();
+# endif
+#endif
 
 /**
  *  The number of elements in an array.
@@ -328,28 +350,6 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int));
 extern "C" {
 #endif
 
-#if defined(HAVE_ALLOCA) && !defined(alloca)
-# if defined(HAVE_ALLOCA_H)
-#  include <alloca.h>
-# elif defined(__GNUC__)
-#  define alloca __builtin_alloca
-# elif defined(_MSC_VER)
-#  include <malloc.h>
-#  define alloca _alloca
-# elif defined(__WATCOMC__)
-#  include <malloc.h>
-# elif defined(__BORLANDC__)
-#  include <malloc.h>
-# elif defined(__DMC__)
-#  include <stdlib.h>
-# elif defined(__AIX__)
-#pragma alloca
-# elif defined(__MRC__)
-void *alloca(unsigned);
-# else
-char *alloca();
-# endif
-#endif
 #ifdef HAVE_ALLOCA
 #define SDL_stack_alloc(type, count)    (type*)alloca(sizeof(type)*(count))
 #define SDL_stack_free(data)
@@ -445,12 +445,12 @@ SDL_FORCE_INLINE void SDL_memset4(void *dst, Uint32 val, size_t dwords)
 #endif
 }
 
-
 extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 
 extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
 
+extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
 extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
@@ -501,18 +501,35 @@ extern DECLSPEC int SDLCALL SDL_vsnprintf(SDL_OUT_Z_CAP(maxlen) char *text, size
 #endif
 
 extern DECLSPEC double SDLCALL SDL_acos(double x);
+extern DECLSPEC float SDLCALL SDL_acosf(float x);
 extern DECLSPEC double SDLCALL SDL_asin(double x);
+extern DECLSPEC float SDLCALL SDL_asinf(float x);
 extern DECLSPEC double SDLCALL SDL_atan(double x);
+extern DECLSPEC float SDLCALL SDL_atanf(float x);
 extern DECLSPEC double SDLCALL SDL_atan2(double x, double y);
+extern DECLSPEC float SDLCALL SDL_atan2f(float x, float y);
 extern DECLSPEC double SDLCALL SDL_ceil(double x);
+extern DECLSPEC float SDLCALL SDL_ceilf(float x);
 extern DECLSPEC double SDLCALL SDL_copysign(double x, double y);
+extern DECLSPEC float SDLCALL SDL_copysignf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_cos(double x);
 extern DECLSPEC float SDLCALL SDL_cosf(float x);
+extern DECLSPEC double SDLCALL SDL_exp(double x);
+extern DECLSPEC float SDLCALL SDL_expf(float x);
 extern DECLSPEC double SDLCALL SDL_fabs(double x);
+extern DECLSPEC float SDLCALL SDL_fabsf(float x);
 extern DECLSPEC double SDLCALL SDL_floor(double x);
+extern DECLSPEC float SDLCALL SDL_floorf(float x);
+extern DECLSPEC double SDLCALL SDL_fmod(double x, double y);
+extern DECLSPEC float SDLCALL SDL_fmodf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_log(double x);
+extern DECLSPEC float SDLCALL SDL_logf(float x);
+extern DECLSPEC double SDLCALL SDL_log10(double x);
+extern DECLSPEC float SDLCALL SDL_log10f(float x);
 extern DECLSPEC double SDLCALL SDL_pow(double x, double y);
+extern DECLSPEC float SDLCALL SDL_powf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_scalbn(double x, int n);
+extern DECLSPEC float SDLCALL SDL_scalbnf(float x, int n);
 extern DECLSPEC double SDLCALL SDL_sin(double x);
 extern DECLSPEC float SDLCALL SDL_sinf(float x);
 extern DECLSPEC double SDLCALL SDL_sqrt(double x);
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_surface.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_surface.h
index 510690c9cffe37fa3153d547d87c9ed0089584c2..730d49fc805ec54d5725c7369db4578d194aa56a 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_surface.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_surface.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -97,6 +97,17 @@ typedef struct SDL_Surface
 typedef int (SDLCALL *SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect,
                                  struct SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ * \brief The formula used for converting between YUV and RGB
+ */
+typedef enum
+{
+    SDL_YUV_CONVERSION_JPEG,        /**< Full range JPEG */
+    SDL_YUV_CONVERSION_BT601,       /**< BT.601 (the default) */
+    SDL_YUV_CONVERSION_BT709,       /**< BT.709 */
+    SDL_YUV_CONVERSION_AUTOMATIC    /**< BT.601 for SD content, BT.709 for HD content */
+} SDL_YUV_CONVERSION_MODE;
+
 /**
  *  Allocate and free an RGB surface.
  *
@@ -237,6 +248,13 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface,
 extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface,
                                             int flag, Uint32 key);
 
+/**
+ *  \brief Returns whether the surface has a color key
+ *
+ *  \return SDL_TRUE if the surface has a color key, or SDL_FALSE if the surface is NULL or has no color key
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasColorKey(SDL_Surface * surface);
+
 /**
  *  \brief Gets the color key (transparent pixel) in a blittable surface.
  *
@@ -509,6 +527,20 @@ extern DECLSPEC int SDLCALL SDL_LowerBlitScaled
     (SDL_Surface * src, SDL_Rect * srcrect,
     SDL_Surface * dst, SDL_Rect * dstrect);
 
+/**
+ *  \brief Set the YUV conversion mode
+ */
+extern DECLSPEC void SDLCALL SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_MODE mode);
+
+/**
+ *  \brief Get the YUV conversion mode
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionMode(void);
+
+/**
+ *  \brief Get the YUV conversion mode, returning the correct mode for the resolution when the current conversion mode is SDL_YUV_CONVERSION_AUTOMATIC
+ */
+extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionModeForResolution(int width, int height);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_system.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_system.h
index eb069b33d8127f026de8b57b1869ccf3297f75e6..4dc372d6b14bb7fd49f64f716d1f9f29e3a15fbc 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_system.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_system.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -76,6 +76,18 @@ extern DECLSPEC SDL_bool SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *a
 #endif /* __WIN32__ */
 
 
+/* Platform specific functions for Linux */
+#ifdef __LINUX__
+
+/**
+   \brief Sets the UNIX nice value for a thread, using setpriority() if possible, and RealtimeKit if available.
+
+   \return 0 on success, or -1 on error.
+ */
+extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
+ 
+#endif /* __LINUX__ */
+	
 /* Platform specific functions for iOS */
 #if defined(__IPHONEOS__) && __IPHONEOS__
 
@@ -108,6 +120,26 @@ extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(void);
  */
 extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(void);
 
+/**
+   \brief Return true if the application is running on Android TV
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
+
+/**
+   \brief Return true if the application is running on a Chromebook
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
+
+/**
+  \brief Return true is the application is running on a Samsung DeX docking station
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
+
+/**
+ \brief Trigger the Android system back button behavior.
+ */
+extern DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
+
 /**
    See the official Android developer guide for more information:
    http://developer.android.com/guide/topics/data/data-storage.html
@@ -169,6 +201,25 @@ typedef enum
 } SDL_WinRT_Path;
 
 
+/**
+ *  \brief WinRT Device Family
+ */
+typedef enum
+{
+    /** \brief Unknown family  */
+    SDL_WINRT_DEVICEFAMILY_UNKNOWN,
+
+    /** \brief Desktop family*/
+    SDL_WINRT_DEVICEFAMILY_DESKTOP,
+
+    /** \brief Mobile family (for example smartphone) */
+    SDL_WINRT_DEVICEFAMILY_MOBILE,
+
+    /** \brief XBox family */
+    SDL_WINRT_DEVICEFAMILY_XBOX,
+} SDL_WinRT_DeviceFamily;
+
+
 /**
  *  \brief Retrieves a WinRT defined path on the local file system
  *
@@ -203,8 +254,20 @@ extern DECLSPEC const wchar_t * SDLCALL SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path
  */
 extern DECLSPEC const char * SDLCALL SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType);
 
+/**
+ *  \brief Detects the device family of WinRT plattform on runtime
+ *
+ *  \return Device family
+ */
+extern DECLSPEC SDL_WinRT_DeviceFamily SDLCALL SDL_WinRTGetDeviceFamily();
+
 #endif /* __WINRT__ */
 
+/**
+ \brief Return true if the current device is a tablet.
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_IsTablet(void);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_syswm.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_syswm.h
index 2d18afb6cf90173ca289b53f74dad013a3440909..f1c4021cc8b37be22124d28a99733d1e696e71e6 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_syswm.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_syswm.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -33,12 +33,6 @@
 #include "SDL_video.h"
 #include "SDL_version.h"
 
-#include "begin_code.h"
-/* Set up for C function definitions, even when using C++ */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
 /**
  *  \file SDL_syswm.h
  *
@@ -110,6 +104,12 @@ typedef void *EGLSurface;
 #include "SDL_egl.h"
 #endif
 
+#include "begin_code.h"
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /**
  *  These are the various supported windowing subsystems
  */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test.h
index f55afcb02273929055de6bf3a8848c2025e3bda3..6cc373bf809ca34195c28d7ef465fffac49c0e89 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_assert.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_assert.h
index 11d8363942948261e7a2403c3556d7e45860ef8a..1788d7a206916e92ccc607c24d93805ec368c838 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_assert.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_assert.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_common.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_common.h
index 24eeb32af1b8731d95e28084a051b219480810be..be2e6b2aab48af89d8aab4986bd15bd0fc13bd68 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_common.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_common.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_compare.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_compare.h
index 9f4c4587ade92d8afe8d8102bd216db4458930e3..c22e447d8aee297971e6c51e4c1dd2fdb3a5cd52 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_compare.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_compare.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_crc32.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_crc32.h
index add480c349fd91512e3a3229f44858407529a106..3d235d07400f10b10297e533f7d74c2cf02bc394 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_crc32.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_crc32.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_font.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_font.h
index 58c9f9d5e5f3334e23df3bbcba45cc6483635f67..59cbdcad6bcb101764c689f915d8395866b6ea6a 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_font.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_font.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_fuzzer.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
index 6cf05747ad91914fe7155322876cd3ff6e2a28d6..8fcb9ebbfa1ba58da745c08e0eb0a322e00319c6 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_fuzzer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_harness.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_harness.h
index 68c4bcb7c44ff1fb09722fa81be6fb5013917eef..8641e0a7e3ad9945ed5ec93813920017bcc06447 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_harness.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_harness.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_images.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_images.h
index 683d2f8a689e8d49b1c750493a6c04112a19b8e3..9c4dd5b82954fc21c15ff9027372ca3419c6252e 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_images.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_images.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_log.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_log.h
index 9f993fbbc880ded0f9c4c2dd81d838fb60753ed2..ebd44fb5021c3f4fc64dcab127780ab7da89ebd2 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_log.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_log.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_md5.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_md5.h
index 67ca7d1defbb70c02e83d43eef7f11ee6cbc81e5..0e4105768fdb6bd059ad867cb90d74637e28660c 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_md5.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_md5.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_memory.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_memory.h
index 43b67f521be347be755fa725dfd2bdd6b96f6c01..4827ae6f2c372db861a635ae9faa32637f152a18 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_memory.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_memory.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_random.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_random.h
index 2b01922c3ed5b82aace74bce3c661ce7c8b62782..0eb414ff2e4db2488ac5671f9d1a91048d0f3b34 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_random.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_test_random.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_thread.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_thread.h
index d0f6575cd5ac7562cb5dba1ca84b5e94b4a3f029..554dd0b61d02481d6f665dbde0799361b1967632 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_thread.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_thread.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -54,12 +54,13 @@ typedef unsigned int SDL_TLSID;
 /**
  *  The SDL thread priority.
  *
- *  \note On many systems you require special privileges to set high priority.
+ *  \note On many systems you require special privileges to set high or time critical priority.
  */
 typedef enum {
     SDL_THREAD_PRIORITY_LOW,
     SDL_THREAD_PRIORITY_NORMAL,
-    SDL_THREAD_PRIORITY_HIGH
+    SDL_THREAD_PRIORITY_HIGH,
+    SDL_THREAD_PRIORITY_TIME_CRITICAL
 } SDL_ThreadPriority;
 
 /**
@@ -105,14 +106,24 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
 
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
+                 const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
+
+
 /**
  *  Create a thread.
  */
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex)
 #endif
 
 #elif defined(__OS2__)
@@ -132,15 +143,31 @@ extern DECLSPEC SDL_Thread *SDLCALL
 SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
                  pfnSDL_CurrentBeginThread pfnBeginThread,
                  pfnSDL_CurrentEndThread pfnEndThread);
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data,
+                 pfnSDL_CurrentBeginThread pfnBeginThread,
+                 pfnSDL_CurrentEndThread pfnEndThread);
 #if defined(SDL_CreateThread) && SDL_DYNAMIC_API
 #undef SDL_CreateThread
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#undef SDL_CreateThreadWithStackSize
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #else
 #define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
+#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSize(fn, name, stacksize, data, (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread)
 #endif
 
 #else
 
+/**
+ *  Create a thread with a default stack size.
+ *
+ *  This is equivalent to calling:
+ *  SDL_CreateThreadWithStackSize(fn, name, 0, data);
+ */
+extern DECLSPEC SDL_Thread *SDLCALL
+SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+
 /**
  *  Create a thread.
  *
@@ -158,9 +185,17 @@ SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data,
  *   If a system imposes requirements, SDL will try to munge the string for
  *    it (truncate, etc), but the original string contents will be available
  *    from SDL_GetThreadName().
+ *
+ *   The size (in bytes) of the new stack can be specified. Zero means "use
+ *    the system default" which might be wildly different between platforms
+ *    (x86 Linux generally defaults to eight megabytes, an embedded device
+ *    might be a few kilobytes instead).
+ *
+ *   In SDL 2.1, stacksize will be folded into the original SDL_CreateThread
+ *    function.
  */
 extern DECLSPEC SDL_Thread *SDLCALL
-SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
+SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data);
 
 #endif
 
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_timer.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_timer.h
index 282625d3f0dc8dfe2b94fa48032d037cace0de3c..5600618ff4f8fbaacb2edf547ef8da1c182a0257 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_timer.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_timer.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_touch.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_touch.h
index db9e25ebdccad221a3c38bbce06b02bec65c3c2c..f4075e79a5b5dd486579aa374474e3927101abe1 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_touch.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_touch.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_types.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_types.h
index 06ee80cb358963b2fca3d8218a81595345e52671..4ac248c8c577f5150c8de8ad1b36df3bd4aea854 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_types.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_types.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_version.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_version.h
index 5b4c402486d6adb4e8ebaec3cfa4bee301e48e8b..31443e149adc071cba7431bc0cd47b007f1a0e22 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_version.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_version.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -59,7 +59,7 @@ typedef struct SDL_version
 */
 #define SDL_MAJOR_VERSION   2
 #define SDL_MINOR_VERSION   0
-#define SDL_PATCHLEVEL      7
+#define SDL_PATCHLEVEL      9
 
 /**
  *  \brief Macro to determine SDL version program was compiled against.
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_video.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_video.h
index 6f6cda8b182b22160d97c0d9d5e684290419d0b9..461f13805101c9214ef4bd9b6df3143c3f093692 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_video.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_video.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -110,7 +110,9 @@ typedef enum
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported.
+                                                     On macOS NSHighResolutionCapable must be set true in the
+                                                     application's Info.plist for this to have any effect. */
     SDL_WINDOW_MOUSE_CAPTURE = 0x00004000,      /**< window has mouse captured (unrelated to INPUT_GRABBED) */
     SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000,      /**< window should always be above others */
     SDL_WINDOW_SKIP_TASKBAR  = 0x00010000,      /**< window should not be added to the taskbar */
@@ -167,6 +169,24 @@ typedef enum
     SDL_WINDOWEVENT_HIT_TEST        /**< Window had a hit test that wasn't SDL_HITTEST_NORMAL. */
 } SDL_WindowEventID;
 
+/**
+ *  \brief Event subtype for display events
+ */
+typedef enum
+{
+    SDL_DISPLAYEVENT_NONE,          /**< Never used */
+    SDL_DISPLAYEVENT_ORIENTATION    /**< Display orientation has changed to data1 */
+} SDL_DisplayEventID;
+
+typedef enum
+{
+    SDL_ORIENTATION_UNKNOWN,            /**< The display orientation can't be determined */
+    SDL_ORIENTATION_LANDSCAPE,          /**< The display is in landscape mode, with the right side up, relative to portrait mode */
+    SDL_ORIENTATION_LANDSCAPE_FLIPPED,  /**< The display is in landscape mode, with the left side up, relative to portrait mode */
+    SDL_ORIENTATION_PORTRAIT,           /**< The display is in portrait mode */
+    SDL_ORIENTATION_PORTRAIT_FLIPPED    /**< The display is in portrait mode, upside down */
+} SDL_DisplayOrientation;
+
 /**
  *  \brief An opaque handle to an OpenGL context.
  */
@@ -314,18 +334,6 @@ extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex);
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect);
 
-/**
- *  \brief Get the dots/pixels-per-inch for a display
- *
- *  \note Diagonal, horizontal and vertical DPI can all be optionally
- *        returned if the parameter is non-NULL.
- *
- *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
- *
- *  \sa SDL_GetNumVideoDisplays()
- */
-extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
-
 /**
  *  \brief Get the usable desktop area represented by a display, with the
  *         primary display located at 0,0
@@ -345,6 +353,27 @@ extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, fl
  */
 extern DECLSPEC int SDLCALL SDL_GetDisplayUsableBounds(int displayIndex, SDL_Rect * rect);
 
+/**
+ *  \brief Get the dots/pixels-per-inch for a display
+ *
+ *  \note Diagonal, horizontal and vertical DPI can all be optionally
+ *        returned if the parameter is non-NULL.
+ *
+ *  \return 0 on success, or -1 if no DPI information is available or the index is out of range.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC int SDLCALL SDL_GetDisplayDPI(int displayIndex, float * ddpi, float * hdpi, float * vdpi);
+
+/**
+ *  \brief Get the orientation of a display
+ *
+ *  \return The orientation of the display, or SDL_ORIENTATION_UNKNOWN if it isn't available.
+ *
+ *  \sa SDL_GetNumVideoDisplays()
+ */
+extern DECLSPEC SDL_DisplayOrientation SDLCALL SDL_GetDisplayOrientation(int displayIndex);
+
 /**
  *  \brief Returns the number of available display modes.
  *
@@ -470,7 +499,7 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window);
  *  If the window is created with any of the SDL_WINDOW_OPENGL or
  *  SDL_WINDOW_VULKAN flags, then the corresponding LoadLibrary function
  *  (SDL_GL_LoadLibrary or SDL_Vulkan_LoadLibrary) is called and the
- *  corrensponding UnloadLibrary function is called by SDL_DestroyWindow().
+ *  corresponding UnloadLibrary function is called by SDL_DestroyWindow().
  *
  *  If SDL_WINDOW_VULKAN is specified and there isn't a working Vulkan driver,
  *  SDL_CreateWindow() will fail because SDL_Vulkan_LoadLibrary() will fail.
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_vulkan.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_vulkan.h
index 803b5feef3489ba257ae3664cc93afc9f8fbcf1d..972cca4d7c2658f735d96d549ccd2a5819592b16 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_vulkan.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/SDL_vulkan.h
@@ -69,30 +69,43 @@ typedef VkSurfaceKHR SDL_vulkanSurface; /* for compatibility with Tizen */
  *  \brief Dynamically load a Vulkan loader library.
  *
  *  \param [in] path The platform dependent Vulkan loader library name, or
- *              \c NULL to open the default Vulkan loader library.
+ *              \c NULL.
  *
  *  \return \c 0 on success, or \c -1 if the library couldn't be loaded.
  *
- *  This should be done after initializing the video driver, but before
+ *  If \a path is NULL SDL will use the value of the environment variable
+ *  \c SDL_VULKAN_LIBRARY, if set, otherwise it loads the default Vulkan
+ *  loader library.
+ *
+ *  This should be called after initializing the video driver, but before
  *  creating any Vulkan windows. If no Vulkan loader library is loaded, the
  *  default library will be loaded upon creation of the first Vulkan window.
  *
- *  \note If you specify a non-NULL \a path, you should retrieve all of the
- *        Vulkan functions used in your program from the dynamic library using
+ *  \note It is fairly common for Vulkan applications to link with \a libvulkan
+ *        instead of explicitly loading it at run time. This will work with
+ *        SDL provided the application links to a dynamic library and both it
+ *        and SDL use the same search path.
+ *
+ *  \note If you specify a non-NULL \c path, an application should retrieve all
+ *        of the Vulkan functions it uses from the dynamic library using
  *        \c SDL_Vulkan_GetVkGetInstanceProcAddr() unless you can guarantee
- *        \a path points to the same vulkan loader library that you linked to.
+ *        \c path points to the same vulkan loader library the application
+ *        linked to.
  *
  *  \note On Apple devices, if \a path is NULL, SDL will attempt to find
  *        the vkGetInstanceProcAddr address within all the mach-o images of
- *        the current process. This is because the currently (v0.17.0)
- *        recommended MoltenVK (Vulkan on Metal) usage is as a static library.
- *        If it is not found then SDL will attempt to load \c libMoltenVK.dylib.
- *        Applications using the dylib alternative therefore do not need to do
- *        anything special when calling SDL.
- *
- *  \note On non-Apple devices, SDL requires you to either not link to the
- *        Vulkan loader or link to a dynamic library version. This limitation
- *        may be removed in a future version of SDL.
+ *        the current process. This is because it is fairly common for Vulkan
+ *        applications to link with libvulkan (and historically MoltenVK was
+ *        provided as a static library). If it is not found then, on macOS, SDL
+ *        will attempt to load \c vulkan.framework/vulkan, \c libvulkan.1.dylib,
+ *        \c MoltenVK.framework/MoltenVK and \c libMoltenVK.dylib in that order.
+ *        On iOS SDL will attempt to load \c libMoltenVK.dylib. Applications
+ *        using a dynamic framework or .dylib must ensure it is included in its
+ *        application bundle.
+ *
+ *  \note On non-Apple devices, application linking with a static libvulkan is
+ *        not supported. Either do not link to the Vulkan loader or link to a
+ *        dynamic library version.
  *
  *  \note This function will fail if there are no working Vulkan drivers
  *        installed.
@@ -122,11 +135,11 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \brief Get the names of the Vulkan instance extensions needed to create
  *         a surface with \c SDL_Vulkan_CreateSurface().
  *
- *  \param [in]     window Window for which the required Vulkan instance
+ *  \param [in]     \c NULL or window Window for which the required Vulkan instance
  *                  extensions should be retrieved
- *  \param [in,out] count pointer to an \c unsigned related to the number of
+ *  \param [in,out] pCount pointer to an \c unsigned related to the number of
  *                  required Vulkan instance extensions
- *  \param [out]    names \c NULL or a pointer to an array to be filled with the
+ *  \param [out]    pNames \c NULL or a pointer to an array to be filled with the
  *                  required Vulkan instance extensions
  *
  *  \return \c SDL_TRUE on success, \c SDL_FALSE on error.
@@ -140,6 +153,10 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  is smaller than the number of required extensions, \c SDL_FALSE will be
  *  returned instead of \c SDL_TRUE, to indicate that not all the required
  *  extensions were returned.
+ * 
+ *  \note If \c window is not NULL, it will be checked against its creation
+ *        flags to ensure that the Vulkan flag is present. This parameter
+ *        will be removed in a future major release.
  *
  *  \note The returned list of extensions will contain \c VK_KHR_surface
  *        and zero or more platform specific extensions
@@ -147,12 +164,13 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *  \note The extension names queried here must be enabled when calling
  *        VkCreateInstance, otherwise surface creation will fail.
  *
- *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag.
+ *  \note \c window should have been created with the \c SDL_WINDOW_VULKAN flag
+ *        or be \c NULL
  *
  *  \code
  *  unsigned int count;
  *  // get count of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, NULL))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, NULL))
  *      handle_error();
  *
  *  static const char *const additionalExtensions[] =
@@ -166,7 +184,7 @@ extern DECLSPEC void SDLCALL SDL_Vulkan_UnloadLibrary(void);
  *      handle_error();
  *
  *  // get names of required extensions
- *  if(!SDL_Vulkan_GetInstanceExtensions(window, &count, names))
+ *  if(!SDL_Vulkan_GetInstanceExtensions(NULL, &count, names))
  *      handle_error();
  *
  *  // copy additional extensions after required extensions
@@ -240,6 +258,9 @@ extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_CreateSurface(
  * platform with high-DPI support (Apple calls this "Retina"), and not disabled
  * by the \c SDL_HINT_VIDEO_HIGHDPI_DISABLED hint.
  *
+ *  \note On macOS high-DPI support must be enabled for an application by
+ *        setting NSHighResolutionCapable to true in its Info.plist.
+ *
  *  \sa SDL_GetWindowSize()
  *  \sa SDL_CreateWindow()
  */
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/begin_code.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/begin_code.h
index 5372d6f04df418acb02bf51cd545c2170bb73849..6c2106246f2583ba711451bc7e39fc2af7e9eec7 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/begin_code.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/begin_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/close_code.h b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/close_code.h
index 2a352dbb9ec7502c62b561f30bd7433b18e581d1..b3b70a4c833d80305bbffd306c8ac099babdf419 100644
--- a/libs/SDL2/x86_64-w64-mingw32/include/SDL2/close_code.h
+++ b/libs/SDL2/x86_64-w64-mingw32/include/SDL2/close_code.h
@@ -1,6 +1,6 @@
 /*
   Simple DirectMedia Layer
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake b/libs/SDL2/x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
index f5bfc3b797c7bc03dc127c9d096c781d8a5218c3..455d8ed55aa4860f8c85b7be139afe131a36c4fd 100644
--- a/libs/SDL2/x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
+++ b/libs/SDL2/x86_64-w64-mingw32/lib/cmake/SDL2/sdl2-config.cmake
@@ -1,11 +1,11 @@
 # sdl2 cmake project-config input for ./configure scripts
 
-set(prefix "/usr/local/x86_64-w64-mingw32") 
+set(prefix "/opt/local/x86_64-w64-mingw32") 
 set(exec_prefix "${prefix}")
 set(libdir "${exec_prefix}/lib")
-set(SDL2_PREFIX "/usr/local/x86_64-w64-mingw32")
-set(SDL2_EXEC_PREFIX "/usr/local/x86_64-w64-mingw32")
+set(SDL2_PREFIX "/opt/local/x86_64-w64-mingw32")
+set(SDL2_EXEC_PREFIX "/opt/local/x86_64-w64-mingw32")
 set(SDL2_LIBDIR "${exec_prefix}/lib")
 set(SDL2_INCLUDE_DIRS "${prefix}/include/SDL2")
-set(SDL2_LIBRARIES "-L${SDL2_LIBDIR}  -lmingw32 -lSDL2main -lSDL2  -mwindows")
+set(SDL2_LIBRARIES "-L${SDL2_LIBDIR}  -lmingw32 -lSDL2main -lSDL2 -mwindows")
 string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES)
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.a b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.a
index d81a9cbcfdb9bfbc18bf3f0808272b47ba616fc8..8279506827fce6c54f19e0d1376ff5d03abe5da3 100644
Binary files a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.a and b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.a differ
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.dll.a b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.dll.a
index 65648749ce65704ec31fc18ad33e2cb5f7b99bca..669b6a6378b8c870c6da55d554d54a6d12061bb5 100755
Binary files a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.dll.a and b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.dll.a differ
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.la b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.la
new file mode 100644
index 0000000000000000000000000000000000000000..d5295eec8bed2925b2151577d8518dc995fb6000
--- /dev/null
+++ b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2.la
@@ -0,0 +1,41 @@
+# libSDL2.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='../bin/SDL2.dll'
+
+# Names of this library.
+library_names='libSDL2.dll.a'
+
+# The name of the static archive.
+old_library='libSDL2.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=' -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2.
+current=9
+age=9
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/x86_64-w64-mingw32/lib'
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.a b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.a
index af32f8689c25e98815a79601e725008161b1374e..fcbdbbf76211f12f77fb4a95bcd0eac68e1aca42 100644
Binary files a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.a and b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.a differ
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.la b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.la
new file mode 100644
index 0000000000000000000000000000000000000000..76ad43f0f49d5e79da26057c43edc4ee4fc00dd5
--- /dev/null
+++ b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2_test.la
@@ -0,0 +1,41 @@
+# libSDL2_test.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname=''
+
+# Names of this library.
+library_names=''
+
+# The name of the static archive.
+old_library='libSDL2_test.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=''
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2_test.
+current=0
+age=0
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/x86_64-w64-mingw32/lib'
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.a b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.a
index decdd5a0f008f7cb03f17d9137ffbe5c5ad29702..3a26e430db838e098490e496082a80eed9e75679 100644
Binary files a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.a and b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.a differ
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.la b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.la
new file mode 100644
index 0000000000000000000000000000000000000000..5aa4693331f8406263ae893d2a7d8499049b8e50
--- /dev/null
+++ b/libs/SDL2/x86_64-w64-mingw32/lib/libSDL2main.la
@@ -0,0 +1,41 @@
+# libSDL2main.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.2
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname=''
+
+# Names of this library.
+library_names=''
+
+# The name of the static archive.
+old_library='libSDL2main.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=''
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2main.
+current=0
+age=0
+revision=0
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL/SDL2-2.0.9/x86_64-w64-mingw32/lib'
diff --git a/libs/SDL2/x86_64-w64-mingw32/lib/pkgconfig/sdl2.pc b/libs/SDL2/x86_64-w64-mingw32/lib/pkgconfig/sdl2.pc
index d06fc29cd57d3d44c9c4eade58eeb900b89127b1..ae83be160346141441a84d33d2ea33c2e9ce0b7e 100644
--- a/libs/SDL2/x86_64-w64-mingw32/lib/pkgconfig/sdl2.pc
+++ b/libs/SDL2/x86_64-w64-mingw32/lib/pkgconfig/sdl2.pc
@@ -1,15 +1,15 @@
 # sdl pkg-config source file
 
-prefix=/usr/local/x86_64-w64-mingw32
+prefix=/opt/local/x86_64-w64-mingw32
 exec_prefix=${prefix}
 libdir=${exec_prefix}/lib
 includedir=${prefix}/include
 
 Name: sdl2
 Description: Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.
-Version: 2.0.7
+Version: 2.0.9
 Requires:
 Conflicts:
-Libs: -L${libdir}  -lmingw32 -lSDL2main -lSDL2  -mwindows
-Libs.private: -lmingw32 -lSDL2main -lSDL2  -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc
+Libs: -L${libdir}  -lmingw32 -lSDL2main -lSDL2 -mwindows
+Libs.private: -lmingw32 -lSDL2main -lSDL2 -mwindows  -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -static-libgcc
 Cflags: -I${includedir}/SDL2  -Dmain=SDL_main
diff --git a/libs/SDL2_mixer/CHANGES.txt b/libs/SDL2_mixer/CHANGES.txt
index 1c99deb8ed1f16397e8a03ced510c176d5cd34db..7760ddfe33b4abb709da995ab957bece3522850c 100644
--- a/libs/SDL2_mixer/CHANGES.txt
+++ b/libs/SDL2_mixer/CHANGES.txt
@@ -1,3 +1,15 @@
+2.0.4:
+Ozkan Sezer - Wed, 10 Oct 2018 14:56:10
+ * Removed smpeg support for mp3 music, now that it's replaced by libmpg123
+Ozkan Sezer - Sun, 07 Oct 2018 08:50:02
+ * Fixed mp3 mad decoder to skip tags, which otherwise would lead to crashes
+Ozkan Sezer - Fri, 15 Jun 2018 05:32:56
+ * Added support for Opus music playback using opusfile library
+
+2.0.3:
+Sam Lantinga - Thu, Mar  1, 2018  9:06:58 AM
+ * Fixed regression where Mix_Init() would return 0 for available music formats
+
 2.0.2:
 Sam Lantinga - Fri Oct 20 22:04:50 PDT 2017
  * Implemented 24-bit and surround sound support for FLAC audio files
diff --git a/libs/SDL2_mixer/COPYING.txt b/libs/SDL2_mixer/COPYING.txt
index 056d0f35256b07c9f108233e1fa28c187dd9998f..234a5a5efd51fd6fca18282cabff68d893cda6ab 100644
--- a/libs/SDL2_mixer/COPYING.txt
+++ b/libs/SDL2_mixer/COPYING.txt
@@ -1,6 +1,6 @@
 /*
   SDL_mixer:  An audio mixer library based on the SDL library
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
diff --git a/libs/SDL2_mixer/README.txt b/libs/SDL2_mixer/README.txt
index 14de6e002567608d39d64f0ac268fe40af44158e..a639c37bc6f09c7ce2ce6ea16376e6ff4a5dfd42 100644
--- a/libs/SDL2_mixer/README.txt
+++ b/libs/SDL2_mixer/README.txt
@@ -14,7 +14,7 @@ for documentation on this mixer library.
 The mixer can currently load Microsoft WAVE files and Creative Labs VOC
 files as audio samples, it can load FLAC files with libFLAC, it can load
 Ogg Vorbis files with Ogg Vorbis or Tremor libraries, it can load MP3 files
-using mpg123, SMPEG or libmad, and it can load MIDI files with Timidity,
+using mpg123 or libmad, and it can load MIDI files with Timidity,
 FluidSynth, and natively on Windows, Mac OSX, and Linux, and finally it can
 load the following file formats via ModPlug or MikMod: .MOD .S3M .IT .XM.
 
diff --git a/libs/SDL2_mixer/SRB2NOTE.txt b/libs/SDL2_mixer/SRB2NOTE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7053c44f66de850cde4d6448797dc6a443e29f4b
--- /dev/null
+++ b/libs/SDL2_mixer/SRB2NOTE.txt
@@ -0,0 +1,15 @@
+# SDL Mixer Development Libaries
+
+Download both the VC and MinGW packages and extract them here.
+
+https://www.libsdl.org/projects/SDL_mixer/
+
+## VC Package
+
+* include
+* lib
+
+## MinGW Package
+
+* i686-w64-mingw32
+* x86_64-w64-mingw32
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.FLAC.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.FLAC.txt
index 94fd3aa692d3d4759fec1b13522d5db404e4508b..2e67b77cbb6ed89bd8c78b5d6a7f8e43974bf6f2 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.FLAC.txt
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.FLAC.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_mixer/file/default/external
 ---
 
 Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007  Josh Coalson
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.modplug.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.modplug.txt
index 59fbf826c3b3a602bec0a23e347202be0f618aba..a89532db83cd3b7fa8575b38ce08065e92132856 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.modplug.txt
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.modplug.txt
@@ -1 +1,5 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 ModPlug-XMMS and libmodplug are now in the public domain.
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.mpg123.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.mpg123.txt
index d7bb85fc3be6d8a00bc8e875569d30deb9f0dceb..2111cd79fafac286df4728c833cc0268c960e328 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.mpg123.txt
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.mpg123.txt
@@ -1,3 +1,7 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 This is the file that contains the terms of use, copying, etc. for the mpg123 distribution package.
 
 Main message, to include in "About ..." boxes, etc:
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.ogg-vorbis.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
index c2cb6d56eb51b788c61183f83133283f22383926..6456dc4ed17fb4de76e4f19318de3c0021b63684 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_image/file/default/external
 ---
 
 Copyright (c) 2002-2008 Xiph.org Foundation
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opus.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opus.txt
new file mode 100644
index 0000000000000000000000000000000000000000..adb56a854b6eff6d2afa846d13d38128e62c40c6
--- /dev/null
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opus.txt
@@ -0,0 +1,48 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+                    Jean-Marc Valin, Timothy B. Terriberry,
+                    CSIRO, Gregory Maxwell, Mark Borgerding,
+                    Erik de Castro Lopo
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of Internet Society, IETF or IETF Trust, nor the 
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Opus is subject to the royalty-free patent licenses which are
+specified at:
+
+Xiph.Org Foundation:
+https://datatracker.ietf.org/ipr/1524/
+
+Microsoft Corporation:
+https://datatracker.ietf.org/ipr/1914/
+
+Broadcom Corporation:
+https://datatracker.ietf.org/ipr/1526/
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opusfile.txt b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opusfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfc9681fcf54ba50bc0ee265bdc1ed9386840846
--- /dev/null
+++ b/libs/SDL2_mixer/i686-w64-mingw32/bin/LICENSE.opusfile.txt
@@ -0,0 +1,32 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.Org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/SDL2_mixer.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/SDL2_mixer.dll
index 54ebbb1d48d0fbc7300678d5368ebb57ad493795..96abfa1ab759933e80342a202f1b80928d14858d 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/SDL2_mixer.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/SDL2_mixer.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libFLAC-8.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libFLAC-8.dll
index 1c55ad4966a52c673e54be638bf97196ad7bf2fa..b56ea2a8c3d9fea131c88d0d3f6c3419b8e01166 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libFLAC-8.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libFLAC-8.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libmodplug-1.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libmodplug-1.dll
index a2cba0d6b5cf20d7fa854a1c698dfc1b2b45d714..d66a317ddfe8d56309ff2e63fe66d62edb68a710 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libmodplug-1.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libmodplug-1.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libmpg123-0.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libmpg123-0.dll
index ca7de300d7db8875d19fc379dd36a37504bd70ca..240298bbfc04835998085ab5054ee5111e83c702 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libmpg123-0.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libmpg123-0.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libogg-0.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libogg-0.dll
index 3abe6ebfca1a9a96b2b679aa971302dcb5f66743..233c0f42a8379b75b3aeb681436519379079fa8e 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libogg-0.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libogg-0.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libopus-0.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libopus-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..94ea541d283adca154b853671e78101ad2625c90
Binary files /dev/null and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libopus-0.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libopusfile-0.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libopusfile-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..260796b6aa5a7da89c729a465141d8ff7036695b
Binary files /dev/null and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libopusfile-0.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbis-0.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbis-0.dll
index 4e44ef0886511997ef9f14a2a3a49b3b5291c336..4e4aaa543b55575edb1bda0d6c1c5dd341a7727f 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbis-0.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbis-0.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbisfile-3.dll b/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbisfile-3.dll
index e757eb6ded42f8a14b0e3cdb1ad4b76a4fac6f12..5d90a235c988cb15b0f9dc2deb7b065a25b47f2e 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbisfile-3.dll and b/libs/SDL2_mixer/i686-w64-mingw32/bin/libvorbisfile-3.dll differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/include/SDL2/SDL_mixer.h b/libs/SDL2_mixer/i686-w64-mingw32/include/SDL2/SDL_mixer.h
index cbb8ae6b65ad8325c961f3fc664e6fd8a1bbfd6f..009d8a2da6725c58c87a11b09e08ee5bc8e6a2da 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/include/SDL2/SDL_mixer.h
+++ b/libs/SDL2_mixer/i686-w64-mingw32/include/SDL2/SDL_mixer.h
@@ -1,6 +1,6 @@
 /*
   SDL_mixer:  An audio mixer library based on the SDL library
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -38,7 +38,7 @@ extern "C" {
 */
 #define SDL_MIXER_MAJOR_VERSION 2
 #define SDL_MIXER_MINOR_VERSION 0
-#define SDL_MIXER_PATCHLEVEL    2
+#define SDL_MIXER_PATCHLEVEL    4
 
 /* This macro can be used to fill a version structure with the compile-time
  * version of the SDL_mixer library.
@@ -80,7 +80,8 @@ typedef enum
     MIX_INIT_MOD    = 0x00000002,
     MIX_INIT_MP3    = 0x00000008,
     MIX_INIT_OGG    = 0x00000010,
-    MIX_INIT_MID    = 0x00000020
+    MIX_INIT_MID    = 0x00000020,
+    MIX_INIT_OPUS   = 0x00000040
 } MIX_InitFlags;
 
 /* Loads dynamic libraries and prepares them for use.  Flags should be
@@ -134,7 +135,8 @@ typedef enum {
     MUS_MP3,
     MUS_MP3_MAD_UNUSED,
     MUS_FLAC,
-    MUS_MODPLUG_UNUSED
+    MUS_MODPLUG_UNUSED,
+    MUS_OPUS
 } Mix_MusicType;
 
 /* The internal format for a music chunk interpreted via mikmod */
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.a b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.a
index 9cc3ed19c47a87da0addd390ee03268bb186407d..5c780be5148ee4805f223a3c96d636098608e65c 100644
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.a and b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.a differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.dll.a b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.dll.a
index 6b63de0ee7b7ddfb5f3e716f6340c08546957219..ac90388f2af7c0bc6ff1008b2bc3d210517539fb 100755
Binary files a/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.dll.a and b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.dll.a differ
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.la b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.la
new file mode 100644
index 0000000000000000000000000000000000000000..f8179ea8cd40a65ca0e80c40dd8565a53a7e1af4
--- /dev/null
+++ b/libs/SDL2_mixer/i686-w64-mingw32/lib/libSDL2_mixer.la
@@ -0,0 +1,41 @@
+# libSDL2_mixer.la - a libtool library file
+# Generated by ltmain.sh (GNU libtool) 2.2.6
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='../bin/SDL2_mixer.dll'
+
+# Names of this library.
+library_names='libSDL2_mixer.dll.a'
+
+# The name of the static archive.
+old_library='libSDL2_mixer.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=' -L/opt/local/i686-w64-mingw32/lib -lmingw32 /opt/local/i686-w64-mingw32/lib/libSDL2.la -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -lwinmm'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2_mixer.
+current=2
+age=2
+revision=2
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL_mixer/SDL2_mixer-2.0.4/i686-w64-mingw32/lib'
diff --git a/libs/SDL2_mixer/i686-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc b/libs/SDL2_mixer/i686-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
index 76ba57fab61143f0ca31ff68d1193522b44239cf..0b8410993f73f82ff7d6f8f93f1c8e2721e049fb 100644
--- a/libs/SDL2_mixer/i686-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
+++ b/libs/SDL2_mixer/i686-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
@@ -1,11 +1,11 @@
-prefix=/usr/local/i686-w64-mingw32
+prefix=/opt/local/i686-w64-mingw32
 exec_prefix=${prefix}
 libdir=${exec_prefix}/lib
 includedir=${prefix}/include
 
 Name: SDL2_mixer
 Description: mixer library for Simple DirectMedia Layer
-Version: 2.0.2
+Version: 2.0.4
 Requires: sdl2 >= 2.0.7
 Libs: -L${libdir} -lSDL2_mixer
 Cflags: -I${includedir}/SDL2
diff --git a/libs/SDL2_mixer/include/SDL_mixer.h b/libs/SDL2_mixer/include/SDL_mixer.h
index cbb8ae6b65ad8325c961f3fc664e6fd8a1bbfd6f..009d8a2da6725c58c87a11b09e08ee5bc8e6a2da 100644
--- a/libs/SDL2_mixer/include/SDL_mixer.h
+++ b/libs/SDL2_mixer/include/SDL_mixer.h
@@ -1,6 +1,6 @@
 /*
   SDL_mixer:  An audio mixer library based on the SDL library
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -38,7 +38,7 @@ extern "C" {
 */
 #define SDL_MIXER_MAJOR_VERSION 2
 #define SDL_MIXER_MINOR_VERSION 0
-#define SDL_MIXER_PATCHLEVEL    2
+#define SDL_MIXER_PATCHLEVEL    4
 
 /* This macro can be used to fill a version structure with the compile-time
  * version of the SDL_mixer library.
@@ -80,7 +80,8 @@ typedef enum
     MIX_INIT_MOD    = 0x00000002,
     MIX_INIT_MP3    = 0x00000008,
     MIX_INIT_OGG    = 0x00000010,
-    MIX_INIT_MID    = 0x00000020
+    MIX_INIT_MID    = 0x00000020,
+    MIX_INIT_OPUS   = 0x00000040
 } MIX_InitFlags;
 
 /* Loads dynamic libraries and prepares them for use.  Flags should be
@@ -134,7 +135,8 @@ typedef enum {
     MUS_MP3,
     MUS_MP3_MAD_UNUSED,
     MUS_FLAC,
-    MUS_MODPLUG_UNUSED
+    MUS_MODPLUG_UNUSED,
+    MUS_OPUS
 } Mix_MusicType;
 
 /* The internal format for a music chunk interpreted via mikmod */
diff --git a/libs/SDL2_mixer/lib/ARM/SDL2_mixer.lib b/libs/SDL2_mixer/lib/ARM/SDL2_mixer.lib
new file mode 100644
index 0000000000000000000000000000000000000000..3886f3aa9457f01e8f91e69d42d9b975fce3e8c0
Binary files /dev/null and b/libs/SDL2_mixer/lib/ARM/SDL2_mixer.lib differ
diff --git a/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.dll b/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.dll
new file mode 100644
index 0000000000000000000000000000000000000000..d5650b0d2096a7ef18b90e04d409f2da4b3c0493
Binary files /dev/null and b/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.dll differ
diff --git a/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.lib b/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.lib
new file mode 100644
index 0000000000000000000000000000000000000000..58c3e696633183fe342fd20c55717d3263b1813c
Binary files /dev/null and b/libs/SDL2_mixer/lib/ARM64/SDL2_mixer.lib differ
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.FLAC.txt b/libs/SDL2_mixer/lib/x64/LICENSE.FLAC.txt
index 94fd3aa692d3d4759fec1b13522d5db404e4508b..2e67b77cbb6ed89bd8c78b5d6a7f8e43974bf6f2 100644
--- a/libs/SDL2_mixer/lib/x64/LICENSE.FLAC.txt
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.FLAC.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_mixer/file/default/external
 ---
 
 Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007  Josh Coalson
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.modplug.txt b/libs/SDL2_mixer/lib/x64/LICENSE.modplug.txt
index 59fbf826c3b3a602bec0a23e347202be0f618aba..a89532db83cd3b7fa8575b38ce08065e92132856 100644
--- a/libs/SDL2_mixer/lib/x64/LICENSE.modplug.txt
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.modplug.txt
@@ -1 +1,5 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 ModPlug-XMMS and libmodplug are now in the public domain.
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.mpg123.txt b/libs/SDL2_mixer/lib/x64/LICENSE.mpg123.txt
index d7bb85fc3be6d8a00bc8e875569d30deb9f0dceb..2111cd79fafac286df4728c833cc0268c960e328 100644
--- a/libs/SDL2_mixer/lib/x64/LICENSE.mpg123.txt
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.mpg123.txt
@@ -1,3 +1,7 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 This is the file that contains the terms of use, copying, etc. for the mpg123 distribution package.
 
 Main message, to include in "About ..." boxes, etc:
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.ogg-vorbis.txt b/libs/SDL2_mixer/lib/x64/LICENSE.ogg-vorbis.txt
index c2cb6d56eb51b788c61183f83133283f22383926..6456dc4ed17fb4de76e4f19318de3c0021b63684 100644
--- a/libs/SDL2_mixer/lib/x64/LICENSE.ogg-vorbis.txt
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.ogg-vorbis.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_image/file/default/external
 ---
 
 Copyright (c) 2002-2008 Xiph.org Foundation
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.opus.txt b/libs/SDL2_mixer/lib/x64/LICENSE.opus.txt
new file mode 100644
index 0000000000000000000000000000000000000000..adb56a854b6eff6d2afa846d13d38128e62c40c6
--- /dev/null
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.opus.txt
@@ -0,0 +1,48 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+                    Jean-Marc Valin, Timothy B. Terriberry,
+                    CSIRO, Gregory Maxwell, Mark Borgerding,
+                    Erik de Castro Lopo
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of Internet Society, IETF or IETF Trust, nor the 
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Opus is subject to the royalty-free patent licenses which are
+specified at:
+
+Xiph.Org Foundation:
+https://datatracker.ietf.org/ipr/1524/
+
+Microsoft Corporation:
+https://datatracker.ietf.org/ipr/1914/
+
+Broadcom Corporation:
+https://datatracker.ietf.org/ipr/1526/
diff --git a/libs/SDL2_mixer/lib/x64/LICENSE.opusfile.txt b/libs/SDL2_mixer/lib/x64/LICENSE.opusfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfc9681fcf54ba50bc0ee265bdc1ed9386840846
--- /dev/null
+++ b/libs/SDL2_mixer/lib/x64/LICENSE.opusfile.txt
@@ -0,0 +1,32 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.Org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/SDL2_mixer/lib/x64/SDL2_mixer.dll b/libs/SDL2_mixer/lib/x64/SDL2_mixer.dll
old mode 100755
new mode 100644
index 116bb1df6f93e2afddac4d97269348f4e1531528..40bb1c14638d0cd2e0cd3b00ceee416326fcd3ed
Binary files a/libs/SDL2_mixer/lib/x64/SDL2_mixer.dll and b/libs/SDL2_mixer/lib/x64/SDL2_mixer.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/SDL2_mixer.lib b/libs/SDL2_mixer/lib/x64/SDL2_mixer.lib
old mode 100755
new mode 100644
diff --git a/libs/SDL2_mixer/lib/x64/libFLAC-8.dll b/libs/SDL2_mixer/lib/x64/libFLAC-8.dll
old mode 100755
new mode 100644
index b5603902d5ff26bc57d0f0025757b6f0d28a06a4..71f2e19d9cac86400bcfb3bb68a659b9773925ad
Binary files a/libs/SDL2_mixer/lib/x64/libFLAC-8.dll and b/libs/SDL2_mixer/lib/x64/libFLAC-8.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libmodplug-1.dll b/libs/SDL2_mixer/lib/x64/libmodplug-1.dll
old mode 100755
new mode 100644
index 31509378b5078d6b24c0b0a435a8d8dba7a94b2c..7c0512674e8e43019777928ca42aca50081a8795
Binary files a/libs/SDL2_mixer/lib/x64/libmodplug-1.dll and b/libs/SDL2_mixer/lib/x64/libmodplug-1.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libmpg123-0.dll b/libs/SDL2_mixer/lib/x64/libmpg123-0.dll
old mode 100755
new mode 100644
index d222d2dc500d2ab30b18a8dfa139ad71fa3dccfe..c7809b163f5e1d305341bba3e65874efac205792
Binary files a/libs/SDL2_mixer/lib/x64/libmpg123-0.dll and b/libs/SDL2_mixer/lib/x64/libmpg123-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libogg-0.dll b/libs/SDL2_mixer/lib/x64/libogg-0.dll
old mode 100755
new mode 100644
index 7b94e60325918c92b4b764cb738dd8f5c0c3379a..51334815979b8fe3a74fa39c3cc54eb4cd70c6ce
Binary files a/libs/SDL2_mixer/lib/x64/libogg-0.dll and b/libs/SDL2_mixer/lib/x64/libogg-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libopus-0.dll b/libs/SDL2_mixer/lib/x64/libopus-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..9ba6c3860c6daa71ae9646f01b43003dac3798f0
Binary files /dev/null and b/libs/SDL2_mixer/lib/x64/libopus-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libopusfile-0.dll b/libs/SDL2_mixer/lib/x64/libopusfile-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..97a88b6108980d2450acd66cb36193e4cfbaa30a
Binary files /dev/null and b/libs/SDL2_mixer/lib/x64/libopusfile-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libvorbis-0.dll b/libs/SDL2_mixer/lib/x64/libvorbis-0.dll
old mode 100755
new mode 100644
index 8d8d6537b1fc243e1926ec03a7be842b6a5d6fed..f5ae1bf754671625ec44f9992efb55e0ad5c2dec
Binary files a/libs/SDL2_mixer/lib/x64/libvorbis-0.dll and b/libs/SDL2_mixer/lib/x64/libvorbis-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x64/libvorbisfile-3.dll b/libs/SDL2_mixer/lib/x64/libvorbisfile-3.dll
old mode 100755
new mode 100644
index 08f8f7aa82ede72a588bb10daef73cd94dad137b..d0787368a3e30fd363c1324423c78d7e06add0be
Binary files a/libs/SDL2_mixer/lib/x64/libvorbisfile-3.dll and b/libs/SDL2_mixer/lib/x64/libvorbisfile-3.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.FLAC.txt b/libs/SDL2_mixer/lib/x86/LICENSE.FLAC.txt
index 94fd3aa692d3d4759fec1b13522d5db404e4508b..2e67b77cbb6ed89bd8c78b5d6a7f8e43974bf6f2 100644
--- a/libs/SDL2_mixer/lib/x86/LICENSE.FLAC.txt
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.FLAC.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_mixer/file/default/external
 ---
 
 Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007  Josh Coalson
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.modplug.txt b/libs/SDL2_mixer/lib/x86/LICENSE.modplug.txt
index 59fbf826c3b3a602bec0a23e347202be0f618aba..a89532db83cd3b7fa8575b38ce08065e92132856 100644
--- a/libs/SDL2_mixer/lib/x86/LICENSE.modplug.txt
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.modplug.txt
@@ -1 +1,5 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 ModPlug-XMMS and libmodplug are now in the public domain.
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.mpg123.txt b/libs/SDL2_mixer/lib/x86/LICENSE.mpg123.txt
index d7bb85fc3be6d8a00bc8e875569d30deb9f0dceb..2111cd79fafac286df4728c833cc0268c960e328 100644
--- a/libs/SDL2_mixer/lib/x86/LICENSE.mpg123.txt
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.mpg123.txt
@@ -1,3 +1,7 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 This is the file that contains the terms of use, copying, etc. for the mpg123 distribution package.
 
 Main message, to include in "About ..." boxes, etc:
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.ogg-vorbis.txt b/libs/SDL2_mixer/lib/x86/LICENSE.ogg-vorbis.txt
index c2cb6d56eb51b788c61183f83133283f22383926..6456dc4ed17fb4de76e4f19318de3c0021b63684 100644
--- a/libs/SDL2_mixer/lib/x86/LICENSE.ogg-vorbis.txt
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.ogg-vorbis.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_image/file/default/external
 ---
 
 Copyright (c) 2002-2008 Xiph.org Foundation
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.opus.txt b/libs/SDL2_mixer/lib/x86/LICENSE.opus.txt
new file mode 100644
index 0000000000000000000000000000000000000000..adb56a854b6eff6d2afa846d13d38128e62c40c6
--- /dev/null
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.opus.txt
@@ -0,0 +1,48 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+                    Jean-Marc Valin, Timothy B. Terriberry,
+                    CSIRO, Gregory Maxwell, Mark Borgerding,
+                    Erik de Castro Lopo
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of Internet Society, IETF or IETF Trust, nor the 
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Opus is subject to the royalty-free patent licenses which are
+specified at:
+
+Xiph.Org Foundation:
+https://datatracker.ietf.org/ipr/1524/
+
+Microsoft Corporation:
+https://datatracker.ietf.org/ipr/1914/
+
+Broadcom Corporation:
+https://datatracker.ietf.org/ipr/1526/
diff --git a/libs/SDL2_mixer/lib/x86/LICENSE.opusfile.txt b/libs/SDL2_mixer/lib/x86/LICENSE.opusfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfc9681fcf54ba50bc0ee265bdc1ed9386840846
--- /dev/null
+++ b/libs/SDL2_mixer/lib/x86/LICENSE.opusfile.txt
@@ -0,0 +1,32 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.Org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/SDL2_mixer/lib/x86/SDL2_mixer.dll b/libs/SDL2_mixer/lib/x86/SDL2_mixer.dll
old mode 100755
new mode 100644
index 54ebbb1d48d0fbc7300678d5368ebb57ad493795..96abfa1ab759933e80342a202f1b80928d14858d
Binary files a/libs/SDL2_mixer/lib/x86/SDL2_mixer.dll and b/libs/SDL2_mixer/lib/x86/SDL2_mixer.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/SDL2_mixer.lib b/libs/SDL2_mixer/lib/x86/SDL2_mixer.lib
old mode 100755
new mode 100644
diff --git a/libs/SDL2_mixer/lib/x86/libFLAC-8.dll b/libs/SDL2_mixer/lib/x86/libFLAC-8.dll
old mode 100755
new mode 100644
index 1c55ad4966a52c673e54be638bf97196ad7bf2fa..b56ea2a8c3d9fea131c88d0d3f6c3419b8e01166
Binary files a/libs/SDL2_mixer/lib/x86/libFLAC-8.dll and b/libs/SDL2_mixer/lib/x86/libFLAC-8.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libmodplug-1.dll b/libs/SDL2_mixer/lib/x86/libmodplug-1.dll
old mode 100755
new mode 100644
index a2cba0d6b5cf20d7fa854a1c698dfc1b2b45d714..d66a317ddfe8d56309ff2e63fe66d62edb68a710
Binary files a/libs/SDL2_mixer/lib/x86/libmodplug-1.dll and b/libs/SDL2_mixer/lib/x86/libmodplug-1.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libmpg123-0.dll b/libs/SDL2_mixer/lib/x86/libmpg123-0.dll
old mode 100755
new mode 100644
index ca7de300d7db8875d19fc379dd36a37504bd70ca..240298bbfc04835998085ab5054ee5111e83c702
Binary files a/libs/SDL2_mixer/lib/x86/libmpg123-0.dll and b/libs/SDL2_mixer/lib/x86/libmpg123-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libogg-0.dll b/libs/SDL2_mixer/lib/x86/libogg-0.dll
old mode 100755
new mode 100644
index 3abe6ebfca1a9a96b2b679aa971302dcb5f66743..233c0f42a8379b75b3aeb681436519379079fa8e
Binary files a/libs/SDL2_mixer/lib/x86/libogg-0.dll and b/libs/SDL2_mixer/lib/x86/libogg-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libopus-0.dll b/libs/SDL2_mixer/lib/x86/libopus-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..94ea541d283adca154b853671e78101ad2625c90
Binary files /dev/null and b/libs/SDL2_mixer/lib/x86/libopus-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libopusfile-0.dll b/libs/SDL2_mixer/lib/x86/libopusfile-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..260796b6aa5a7da89c729a465141d8ff7036695b
Binary files /dev/null and b/libs/SDL2_mixer/lib/x86/libopusfile-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libvorbis-0.dll b/libs/SDL2_mixer/lib/x86/libvorbis-0.dll
old mode 100755
new mode 100644
index 4e44ef0886511997ef9f14a2a3a49b3b5291c336..4e4aaa543b55575edb1bda0d6c1c5dd341a7727f
Binary files a/libs/SDL2_mixer/lib/x86/libvorbis-0.dll and b/libs/SDL2_mixer/lib/x86/libvorbis-0.dll differ
diff --git a/libs/SDL2_mixer/lib/x86/libvorbisfile-3.dll b/libs/SDL2_mixer/lib/x86/libvorbisfile-3.dll
old mode 100755
new mode 100644
index e757eb6ded42f8a14b0e3cdb1ad4b76a4fac6f12..5d90a235c988cb15b0f9dc2deb7b065a25b47f2e
Binary files a/libs/SDL2_mixer/lib/x86/libvorbisfile-3.dll and b/libs/SDL2_mixer/lib/x86/libvorbisfile-3.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.FLAC.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.FLAC.txt
index 94fd3aa692d3d4759fec1b13522d5db404e4508b..2e67b77cbb6ed89bd8c78b5d6a7f8e43974bf6f2 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.FLAC.txt
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.FLAC.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_mixer/file/default/external
 ---
 
 Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007  Josh Coalson
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.modplug.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.modplug.txt
index 59fbf826c3b3a602bec0a23e347202be0f618aba..a89532db83cd3b7fa8575b38ce08065e92132856 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.modplug.txt
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.modplug.txt
@@ -1 +1,5 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 ModPlug-XMMS and libmodplug are now in the public domain.
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.mpg123.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.mpg123.txt
index d7bb85fc3be6d8a00bc8e875569d30deb9f0dceb..2111cd79fafac286df4728c833cc0268c960e328 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.mpg123.txt
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.mpg123.txt
@@ -1,3 +1,7 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
 This is the file that contains the terms of use, copying, etc. for the mpg123 distribution package.
 
 Main message, to include in "About ..." boxes, etc:
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.ogg-vorbis.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
index c2cb6d56eb51b788c61183f83133283f22383926..6456dc4ed17fb4de76e4f19318de3c0021b63684 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.ogg-vorbis.txt
@@ -1,5 +1,5 @@
 The source code to this library used with SDL_mixer can be found here:
-http://www.libsdl.org/projects/SDL_mixer/libs/
+https://hg.libsdl.org/SDL_image/file/default/external
 ---
 
 Copyright (c) 2002-2008 Xiph.org Foundation
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opus.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opus.txt
new file mode 100644
index 0000000000000000000000000000000000000000..adb56a854b6eff6d2afa846d13d38128e62c40c6
--- /dev/null
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opus.txt
@@ -0,0 +1,48 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+                    Jean-Marc Valin, Timothy B. Terriberry,
+                    CSIRO, Gregory Maxwell, Mark Borgerding,
+                    Erik de Castro Lopo
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of Internet Society, IETF or IETF Trust, nor the 
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Opus is subject to the royalty-free patent licenses which are
+specified at:
+
+Xiph.Org Foundation:
+https://datatracker.ietf.org/ipr/1524/
+
+Microsoft Corporation:
+https://datatracker.ietf.org/ipr/1914/
+
+Broadcom Corporation:
+https://datatracker.ietf.org/ipr/1526/
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opusfile.txt b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opusfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfc9681fcf54ba50bc0ee265bdc1ed9386840846
--- /dev/null
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/LICENSE.opusfile.txt
@@ -0,0 +1,32 @@
+The source code to this library used with SDL_mixer can be found here:
+https://hg.libsdl.org/SDL_mixer/file/default/external
+---
+
+Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.Org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/SDL2_mixer.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/SDL2_mixer.dll
index 116bb1df6f93e2afddac4d97269348f4e1531528..40bb1c14638d0cd2e0cd3b00ceee416326fcd3ed 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/SDL2_mixer.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/SDL2_mixer.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libFLAC-8.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libFLAC-8.dll
index b5603902d5ff26bc57d0f0025757b6f0d28a06a4..71f2e19d9cac86400bcfb3bb68a659b9773925ad 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libFLAC-8.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libFLAC-8.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmodplug-1.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmodplug-1.dll
index 31509378b5078d6b24c0b0a435a8d8dba7a94b2c..7c0512674e8e43019777928ca42aca50081a8795 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmodplug-1.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmodplug-1.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmpg123-0.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmpg123-0.dll
index d222d2dc500d2ab30b18a8dfa139ad71fa3dccfe..c7809b163f5e1d305341bba3e65874efac205792 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmpg123-0.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libmpg123-0.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libogg-0.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libogg-0.dll
index 7b94e60325918c92b4b764cb738dd8f5c0c3379a..51334815979b8fe3a74fa39c3cc54eb4cd70c6ce 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libogg-0.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libogg-0.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopus-0.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopus-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..9ba6c3860c6daa71ae9646f01b43003dac3798f0
Binary files /dev/null and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopus-0.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopusfile-0.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopusfile-0.dll
new file mode 100644
index 0000000000000000000000000000000000000000..97a88b6108980d2450acd66cb36193e4cfbaa30a
Binary files /dev/null and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libopusfile-0.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbis-0.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbis-0.dll
index 8d8d6537b1fc243e1926ec03a7be842b6a5d6fed..f5ae1bf754671625ec44f9992efb55e0ad5c2dec 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbis-0.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbis-0.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbisfile-3.dll b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbisfile-3.dll
index 08f8f7aa82ede72a588bb10daef73cd94dad137b..d0787368a3e30fd363c1324423c78d7e06add0be 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbisfile-3.dll and b/libs/SDL2_mixer/x86_64-w64-mingw32/bin/libvorbisfile-3.dll differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2/SDL_mixer.h b/libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2/SDL_mixer.h
index cbb8ae6b65ad8325c961f3fc664e6fd8a1bbfd6f..009d8a2da6725c58c87a11b09e08ee5bc8e6a2da 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2/SDL_mixer.h
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2/SDL_mixer.h
@@ -1,6 +1,6 @@
 /*
   SDL_mixer:  An audio mixer library based on the SDL library
-  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -38,7 +38,7 @@ extern "C" {
 */
 #define SDL_MIXER_MAJOR_VERSION 2
 #define SDL_MIXER_MINOR_VERSION 0
-#define SDL_MIXER_PATCHLEVEL    2
+#define SDL_MIXER_PATCHLEVEL    4
 
 /* This macro can be used to fill a version structure with the compile-time
  * version of the SDL_mixer library.
@@ -80,7 +80,8 @@ typedef enum
     MIX_INIT_MOD    = 0x00000002,
     MIX_INIT_MP3    = 0x00000008,
     MIX_INIT_OGG    = 0x00000010,
-    MIX_INIT_MID    = 0x00000020
+    MIX_INIT_MID    = 0x00000020,
+    MIX_INIT_OPUS   = 0x00000040
 } MIX_InitFlags;
 
 /* Loads dynamic libraries and prepares them for use.  Flags should be
@@ -134,7 +135,8 @@ typedef enum {
     MUS_MP3,
     MUS_MP3_MAD_UNUSED,
     MUS_FLAC,
-    MUS_MODPLUG_UNUSED
+    MUS_MODPLUG_UNUSED,
+    MUS_OPUS
 } Mix_MusicType;
 
 /* The internal format for a music chunk interpreted via mikmod */
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.a b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.a
index 7d39a1f6360a2221f689c06bf4245694d7649031..b6483716ebd9dc29a36426d08b618c6f4caa563b 100644
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.a and b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.a differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.dll.a b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.dll.a
index d812f5c85acfe1a72901963c0a030e48f202071b..82a4bef080902ad394f7bed9b2506c1cf6e3906a 100755
Binary files a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.dll.a and b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.dll.a differ
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.la b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.la
new file mode 100644
index 0000000000000000000000000000000000000000..dba212c5a1ce6f6e270904d2d5f62a3914dc903e
--- /dev/null
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/libSDL2_mixer.la
@@ -0,0 +1,41 @@
+# libSDL2_mixer.la - a libtool library file
+# Generated by ltmain.sh (GNU libtool) 2.2.6
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='../bin/SDL2_mixer.dll'
+
+# Names of this library.
+library_names='libSDL2_mixer.dll.a'
+
+# The name of the static archive.
+old_library='libSDL2_mixer.a'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags=''
+
+# Libraries that this one depends upon.
+dependency_libs=' -L/opt/local/x86_64-w64-mingw32/lib -lmingw32 /opt/local/x86_64-w64-mingw32/lib/libSDL2.la -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -limm32 -lole32 -loleaut32 -lshell32 -lsetupapi -lversion -luuid -lwinmm'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libSDL2_mixer.
+current=2
+age=2
+revision=2
+
+# Is this an already installed library?
+installed=yes
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=no
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/Users/valve/release/SDL_mixer/SDL2_mixer-2.0.4/x86_64-w64-mingw32/lib'
diff --git a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
index 56c30ccfe773f6ed0c7ff160a3f59037a3e3f3ba..b7984169ff65c983662c4d618d79227a01740795 100644
--- a/libs/SDL2_mixer/x86_64-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
+++ b/libs/SDL2_mixer/x86_64-w64-mingw32/lib/pkgconfig/SDL2_mixer.pc
@@ -1,11 +1,11 @@
-prefix=/usr/local/x86_64-w64-mingw32
+prefix=/opt/local/x86_64-w64-mingw32
 exec_prefix=${prefix}
 libdir=${exec_prefix}/lib
 includedir=${prefix}/include
 
 Name: SDL2_mixer
 Description: mixer library for Simple DirectMedia Layer
-Version: 2.0.2
+Version: 2.0.4
 Requires: sdl2 >= 2.0.7
 Libs: -L${libdir} -lSDL2_mixer
 Cflags: -I${includedir}/SDL2
diff --git a/libs/SDLMixerX/CHANGES.X.txt b/libs/SDLMixerX/CHANGES.X.txt
new file mode 100644
index 0000000000000000000000000000000000000000..17b43d9af555fc8efb3c8b074db6631fe2dd6bd9
--- /dev/null
+++ b/libs/SDLMixerX/CHANGES.X.txt
@@ -0,0 +1,48 @@
+2.1.0:
+Vitaly Novichkov - Sat, Jan 20,  2018  18:06:00
+ * SDL Mixer X has been re-created from original again as official SDL Mixer had a big and incompatible rework
+ * Custom resampler is no more needed as SDL2 now finally uses new better resampler
+ * Added CMake build
+ * Added an ability to toggle MIDI playing libraries (NativeMidi, Timidity or FluidSynth)
+ * Added a full seekability with "tell" and "length" commands
+ * Added a basic support for Meta-Tags to show Title, Artist, Album and Copyright captured from music files.
+ * Added an extra arguments in music path string to support song individual settings per some codecs (for example, play different MIDI files with different MIDI playing libraries)
+ * Timidity: Added ability to set a config search pach, for example, inside of application directory API.
+ * Added OPNMIDI MIDI sequencer based on YM2612 chip emulation. Includes a complete General-MIDI embedded bank.
+ * Added functions to use custom bank files for ADL/OPN-MIDI sequencers (WOPL for ADLMIDI and WOPN for OPNMIDI)
+ * WAVE: Added support for PCM24, PCM32, Float32, Float64m, ALAW, and MULAW sample formats,
+ * AIFF: Added support for AIFF-C, also support for PCM24, PCM32, Float32, Float64, ALAW, and MULAW formats
+ * WAVE & AIFF: Added full seekability support (seek, tell, length)
+ * MP3: More accurate MP3 file detection as some weird MP3 files are can't be detected by existing code; Added usage lf libID3tag library to fetch ID3 tags and also detect begin offset inside of MP3 files as some MP3 files are causing libMAD play ID3 data as actual encoded sound data that causes weird playback and later crash. To avoid this, ID3 parse is needed to detect actual begin of audio data.
+ * Fixed: SMPEG: Fix a broken build
+ * Fixed: OGG: Fixed support for pre-defined count of loops when loop tags are presented
+ * Warning fixes
+ * Some C90 compatibility fixes
+ * Internally: Added MIX_UNUSED() macro to mute warnings on various compilers
+
+1.0.3:
+Vitaly Novichkov - 2016
+ * Implemented own resampler to don't use buggy SDL Audio's resampler
+ * Added support of path arguments to allow dynamic configuring of GME and MIDI playing per every file
+ * ModPlug now is part of SDL Mixer X in same project
+ * Link all dependent libraries (libOGG, libVorbis, libFLAC, libMAD) statically
+
+1.0.2:
+Vitaly Novichkov - 2015
+ * Added ADLMIDI MIDI sequencer which is completely independent MIDI sequencer which requires no extra bank files to play MIDI.
+ * Added ability to dynamically switch MIDI device (ADLMIDI, Timidity, Native MIDI, FluidSynth)
+ * Added meta-tag getting functions: Mix_GetMusicTitle, Mix_GetMusicTitleTag, Mix_GetMusicArtistTag, Mix_GetMusicAlbumTag, Mix_GetMusicCopyrightTag.
+ * Added ability to build VB6-Compatible version to use it as standalone audio library
+
+1.0.1:
+Vitaly Novichkov - 2015
+ * Added GME library to support many game music formats
+ * Added support of loop tags for OGG Vorbis files (based on vorbis comments "LOOPSTART" and "LOOPEND" (also supported "LOOPLENGTH" for RPG-Maker compatibility))
+ * Added MIX_Timidity_addToPathList(const char *path) to customize Timidity path
+
+1.0.0:
+Vitaly Novichkov - 2015
+ * Added support of SPC playback
+ * Attempt to fix resampling
+ * Using QMake instead of autotools to build library
+
diff --git a/libs/SDLMixerX/CHANGES.txt b/libs/SDLMixerX/CHANGES.txt
new file mode 100644
index 0000000000000000000000000000000000000000..50c0fee0bd03eef09f8039afe8dcf47750e283c6
--- /dev/null
+++ b/libs/SDLMixerX/CHANGES.txt
@@ -0,0 +1,184 @@
+2.0.3:
+Sam Lantinga - Thu, Mar  1, 2018  9:06:58 AM
+ * Fixed regression where Mix_Init() would return 0 for available music formats
+
+2.0.2:
+Sam Lantinga - Fri Oct 20 22:04:50 PDT 2017
+ * Implemented 24-bit and surround sound support for FLAC audio files
+Sam Lantinga - Thu Oct 12 21:32:44 PDT 2017
+ * Updated external libraries flac-1.3.2, libmodplug-0.8.9.0, libogg-1.3.2 and libvorbis-1.3.5
+Ryan Gordon - Thu Oct 12 21:29:59 PDT 2017
+ * Updated for SDL 2.0.6 and newer
+Franc[e]sco - Thu Jul 20 22:03:19 2017 +0200
+ * Added support for MP3 playback using mpg123
+David Ludwig - Sun Apr 10 22:35:38 2016
+ * Added support for UWP / Windows 10 apps
+Juha Kuikka - Fri Jan 29 12:44:01 PST 2016
+ * Added Mix_OpenAudioDevice() so you can specify the audio device to open
+
+2.0.1:
+Sam Lantinga - Tue Jul  7 11:40:33 PDT 2015
+ * Added support for 'smpl' format loop points in music WAV files
+Sam Lantinga - Sat Aug 23 10:57:26 2014
+ * Fixed floating point exception in Mix_Volume()
+David Ludwig - Mon Apr 14 22:15:36 2014
+ * Added support for building for Windows RT and Windows Phone
+Isaac Burns - Sun Sep 15 21:50:27 PDT 2013
+ * Added support for loading MP3 files as sound chunks
+
+2.0.0:
+Sam Lantinga - Sun Jun  9 14:45:30 PDT 2013
+ * Made libmodplug the default MOD player as it is now in the public domain
+Sam Lantinga - Sat Jun  1 19:11:08 PDT 2013
+ * Updated for SDL 2.0 release
+ * SDL_LoadMUS_RW() now takes an argument telling whether or not the data source should be freed when done.
+
+1.2.13:
+Paul P Komkoff Jr - Sun Jul 22 16:12:28 PDT 2012
+ * Fixed malloc/free mismatch in the MikMod driver
+
+1.2.12:
+Sam Lantinga - Sat Jan 14 22:00:29 2012 -0500
+ * Fixed seek offset with SMPEG (was relative, should be absolute)
+Sam Lantinga - Fri Jan 13 03:04:27 EST 2012
+ * Fixed memory crash loading Ogg Vorbis files on Windows
+Sam Lantinga - Thu Jan 05 22:51:54 2012 -0500
+ * Added an Xcode project for iOS
+Nikos Chantziaras - 2012-01-02 17:37:36 PST
+ * Added Mix_LoadMUSType_RW() so you can tell SDL_mixer what type the music is
+Sam Lantinga - Sun Jan 01 16:45:58 2012 -0500
+ * Fixed looping native MIDI on Mac OS X and Windows
+Sam Lantinga - Sun Jan 01 01:00:51 2012 -0500
+ * Added /usr/local/share/timidity to the timidity data path
+Sam Lantinga - Sat Dec 31 21:26:46 2011 -0500
+ * Fixed timidity loading of some MIDI files
+Sam Lantinga - Sat Dec 31 19:11:59 EST 2011
+ * Fixed dropping audio in the FLAC audio decoding
+Sam Lantinga - Sat Dec 31 18:32:05 EST 2011
+ * Fixed memory leak in SDL_LoadMUS()
+Sam Lantinga - Sat Dec 31 10:22:05 EST 2011
+ * Removed GPL native MIDI code for new licensing
+Sam Lantinga - Sat Dec 31 10:22:05 EST 2011
+ * SDL_mixer is now under the zlib license
+Manuel Montezelo - 2011-12-28 11:42:44 PST
+ * Fixed drums playing on MIDI channel 16 with timidity
+Ryan C. Gordon - Wed Jun 15 03:41:31 2011 -0400
+ * The music-finished hook can start a track immediately
+James Le Cuirot - Mon Mar 21 16:54:11 PDT 2011
+ * Added support for FluidSynth
+Egor Suvorov - Tue Jan 18 11:06:47 PST 2011
+ * Added support for native MIDI on Haiku
+Sam Lantinga - Tue Jan 11 01:29:19 2011 -0800
+ * Added Android.mk to build on the Android platform
+Jon Atkins - Sat Nov 14 13:00:18 PST 2009
+ * Added support for libmodplug (disabled by default)
+
+1.2.11:
+Sam Lantinga - Sat Nov 14 12:38:01 PST 2009
+ * Fixed initialization error and crashes if MikMod library isn't available
+Sam Lantinga - Sat Nov 14 11:22:14 PST 2009
+ * Fixed bug loading multiple music files
+
+1.2.10:
+Sam Lantinga - Sun Nov  8 08:34:48 PST 2009
+ * Added Mix_Init()/Mix_Quit() to prevent constantly loading and unloading DLLs
+Mike Frysinger - 2009-11-05 09:11:43 PST
+ * Check for fork/vfork on any platform, don't just assume it on UNIX
+Jon Atkins - Thu Nov 5 00:02:50 2009 UTC
+ * Fixed export of Mix_GetNumChunkDecoders() and Mix_GetNumMusicDecoders()
+C.W. Betts - 2009-11-02 00:16:21 PST
+ * Use newer MIDI API on Mac OS X 10.5+
+
+1.2.9:
+Ryan Gordon - Sun Oct 18 11:42:31 PDT 2009
+ * Updated native MIDI support on Mac OS X for 10.6
+Ryan Gordon - Sun Oct 11 05:29:55 2009 UTC
+ * Reset channel volumes after a fade out interrupts a fade in.
+Ryan Gordon - Sun Oct 11 02:59:12 2009 UTC
+ * Fixed crash race condition with position audio functions
+Ryan Gordon - Sat Oct 10 17:05:45 2009 UTC
+ * Fixed stereo panning in 8-bit mode
+Sam Lantinga - Sat Oct 10 11:07:15 2009 UTC
+ * Added /usr/share/timidity to the default timidity.cfg locations
+Sam Lantinga - Sat Oct  3 13:33:36 PDT 2009
+ * MOD support uses libmikmod and is dynamically loaded by default
+ * A patched version of libmikmod is included in libmikmod-3.1.12.zip
+ * The libmikmod patches fix security issues CVE-2007-6720 and CVE-2009-0179.
+Sam Lantinga - Sat Oct  3 02:49:41 PDT 2009
+ * Added TIMIDITY_CFG environment variable to fully locate timidity.cfg
+Sam Lantinga - Fri Oct  2 07:15:35 PDT 2009
+ * Implemented seamless looping for music playback
+Forrest Voight - 2009-06-13 20:31:38 PDT
+ * ID3 files are now recognized as MP3 format
+Steven Noonan - 2008-05-13 13:31:36 PDT
+ * Fixed native MIDI crash on 64-bit Windows
+Ryan Gordon - Fri Jun 5 16:07:08 2009 UTC
+ * Added decoder enumeration API:
+	Mix_GetNumChunkDecoders(), Mix_GetChunkDecoder(),
+	Mix_GetNumMusicDecoders(), Mix_GetMusicDecoder()
+Austen Dicken - Tue Feb 26 23:28:27 PST 2008
+ * Added support for FLAC audio both as chunks and streaming
+Tilman Sauerbeck - Tue Feb 26 03:44:47 PST 2008
+ * Added support for streaming WAV files with Mix_LoadMUS_RW()
+Ryan Gordon - Mon Feb 4 17:10:08 UTC 2008
+ * Fixed crash caused by not resetting position_channels
+
+1.2.8:
+Sam Lantinga - Wed Jul 18 09:45:54 PDT 2007
+ * Improved detection of Ogg Vorbis and Tremor libraries
+Ryan Gordon - Sun Jul 15 12:03:54 EDT 2007
+ * Fixed memory leaks in Effects API.
+David Rose - Sat Jul 14 22:16:09 PDT 2007
+ * Added support for MP3 playback with libmad (for GPL projects only!)
+Sam Lantinga - Sat Jul 14 21:39:30 PDT 2007
+ * Fixed the final loop of audio samples of a certain size
+Sam Lantinga - Sat Jul 14 21:05:09 PDT 2007
+ * Fixed opening Ogg Vorbis files using different C runtimes on Windows
+Philippe Simons - Sat Jul 14 20:33:17 PDT 2007
+ * Added support for Ogg Vorbis playback with Tremor (an integer decoder)
+Sam Lantinga - Sat Jul 14 07:02:09 PDT 2007
+ * Fixed memory corruption in timidity resampling code
+Ryan Gordon - Tue Jul 3 10:44:29 2007 UTC
+ * Fixed building SDL_mixer with SDL 1.3 pre-release
+Ryan Gordon - Tue Feb 13 08:11:54 2007 UTC
+ * Fixed compiling both timidity and native midi in the same build
+Hans de Goede - Sun Aug 20 23:25:46 2006 UTC
+ * Added volume control to playmus
+Jonathan Atkins - Thu Aug 10 15:06:40 2006 UTC
+ * Fixed linking with system libmikmod
+David Ergo - Fri Jun 23 09:07:19 2006 UTC
+ * Corrected no-op conditions in SetDistance(), SetPanning() and SetPosition()
+ * Fixed copy/paste errors in channel amplitudes
+
+1.2.7:
+Sam Lantinga - Fri May 12 00:04:32 PDT 2006
+ * Added support for dynamically loading SMPEG library
+Sam Lantinga - Thu May 11 22:22:43 PDT 2006
+ * Added support for dynamically loading Ogg Vorbis library
+Sam Lantinga - Sun Apr 30 09:01:44 PDT 2006
+ * Removed automake dependency, to allow Universal binaries on Mac OS X
+ * Added gcc-fat.sh for generating Universal binaries on Mac OS X
+Sam Lantinga - Sun Apr 30 01:48:40 PDT 2006
+ * Updated libtool support to version 1.5.22
+Patrice Mandin - Sat Jul 16 16:43:24 UTC 2005
+ * Use SDL_RWops also for native midi mac and win32
+Patrice Mandin - Sat Jul 9 14:40:09 UTC 2005
+ * Use SDL_RWops also for native midi gpl (todo: mac and win32)
+Ryan C. Gordon - Sat Jul  9 01:54:03 EDT 2005
+ * Tweaked Mix_Chunk's definition to make predeclaration easier.
+Patrice Mandin - Mon Jul 4 19:45:40 UTC 2005
+ * Search timidity.cfg also in /etc
+ * Fix memory leaks in timidity player
+ * Use also SDL_RWops to read midifiles for timidity
+Ryan C. Gordon - Mon Jun 13 18:18:12 EDT 2005
+ * Patch from Eric Wing to fix native midi compiling on MacOS/x86.
+Sam Lantinga - Wed Dec 22 17:14:32 PST 2004
+ * Disabled support for the system version of libmikmod by default
+Sam Lantinga - Tue Dec 21 09:51:29 PST 2004
+ * Fixed building mikmod support on UNIX
+ * Always build SDL_RWops music support
+ * Added SDL_RWops support for reading MP3 files
+
+1.2.6:
+Jonathan Atkins - Wed, 15 Sep 2004 23:26:42 -0500
+ * Added support for using the system version of libmikmod
diff --git a/libs/SDLMixerX/CMakeLists.txt b/libs/SDLMixerX/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..af55b026510046cebfa88ebe0d1cb20cc6f1f4c6
--- /dev/null
+++ b/libs/SDLMixerX/CMakeLists.txt
@@ -0,0 +1,818 @@
+cmake_minimum_required (VERSION 2.8.11)
+project(SDLMixerX C)
+
+include(FindPkgConfig)
+include(CheckIncludeFiles)
+include(CheckFunctionExists)
+include(CheckLibraryExists)
+#include(CheckCSourceRuns)
+#include(CheckCCompilerFlag)
+
+if(NOT CMAKE_VERSION VERSION_LESS 2.8.12)
+    set(CMAKE_MACOSX_RPATH 0)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+    message("== Using default build configuration which is a Release!")
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    set(MIX_DEBUG_SUFFIX "d")
+else()
+    set(MIX_DEBUG_SUFFIX "")
+endif()
+
+if(WIN32)
+    set(CMAKE_SHARED_LIBRARY_PREFIX "")
+endif()
+
+if(POLICY CMP0058)
+    cmake_policy(SET CMP0058 NEW)
+endif()
+
+option(SSEMATH             "Allow GCC to use SSE floating point math" ${OPT_DEF_SSEMATH})
+option(MMX                 "Use MMX assembly routines" ${OPT_DEF_ASM})
+option(3DNOW               "Use 3Dnow! MMX assembly routines" ${OPT_DEF_ASM})
+option(SSE                 "Use SSE assembly routines" ${OPT_DEF_ASM})
+option(SSE2                "Use SSE2 assembly routines" ${OPT_DEF_SSEMATH})
+option(SSE3                "Use SSE3 assembly routines" ${OPT_DEF_SSEMATH})
+
+# Settings
+option(SDL_MIXER_X_STATIC   "Build static library of SDL Mixer X" ON)
+option(SDL_MIXER_X_SHARED   "Build shared library of SDL Mixer X" ON)
+
+option(DOWNLOAD_AUDIO_CODECS_DEPENDENCY "Downloads and compiles AudioCodecs dependencies pack directly" OFF)
+set(AUDIO_CODECS_REPO_PATH "" CACHE PATH "Path to the AudioCodecs dependencies pack")
+set(AUDIO_CODECS_INSTALL_PATH "" CACHE PATH "Path to installed AudioCodecs prefix")
+
+if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
+    # Turn on warnings and legacy C/C++ standards to support more compilers
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c90")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -std=c++98")
+    # Deny undefined symbols
+    if(NOT APPLE)
+        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" )
+        set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined" )
+    endif()
+endif()
+
+set(SDL_MIXER_INCLUDE_PATHS)
+
+if(NOT AUDIO_CODECS_REPO_PATH AND NOT AUDIO_CODECS_INSTALL_PATH)
+    # Try to resolve sqlite dependency
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+        # Download and configure AudioCodecs dependency
+        set(SDL2_REPO_PATH ${CMAKE_INSTALL_PREFIX})
+        set(SDL2_INCLUDE_PATH ${CMAKE_INSTALL_PREFIX}/include/SDL2)
+        set(SDL2_LIBS_PATH ${CMAKE_INSTALL_PREFIX}/lib)
+        include(cmake/DownloadAudioCodecs.cmake)
+        set(AUDIO_CODECS_REPO_PATH ${AUDIO_CODECS_REPOSITORY_PATH})
+        set(AUDIO_CODECS_INSTALL_PATH ${AUDIO_CODECS_INSTALL_DIR})
+    endif()
+endif()
+
+set(FIND_PREFER_STATIC
+    "-static${MIX_DEBUG_SUFFIX}.a"
+    "-static${MIX_DEBUG_SUFFIX}.lib"
+    "${MIX_DEBUG_SUFFIX}.a"
+    "${MIX_DEBUG_SUFFIX}.lib"
+    "-static.a"
+    "-static.lib"
+    ".a"
+    ".lib"
+    "${MIX_DEBUG_SUFFIX}.dll.a"
+    "${MIX_DEBUG_SUFFIX}.dll.lib"
+    ".dll.a"
+    ".dll.lib"
+    "${MIX_DEBUG_SUFFIX}.so"
+    "${MIX_DEBUG_SUFFIX}.dylib"
+    ".so"
+    ".dylib"
+)
+
+set(FIND_PREFER_SHARED
+    "${MIX_DEBUG_SUFFIX}.dll.a"
+    "${MIX_DEBUG_SUFFIX}.dll.lib"
+    ".dll.a"
+    ".dll.lib"
+    "${MIX_DEBUG_SUFFIX}.so"
+    "${MIX_DEBUG_SUFFIX}.dylib"
+    ".so"
+    ".dylib"
+    "-static${MIX_DEBUG_SUFFIX}.a"
+    "-static${MIX_DEBUG_SUFFIX}.lib"
+    "${MIX_DEBUG_SUFFIX}.a"
+    "${MIX_DEBUG_SUFFIX}.lib"
+    "-static.a"
+    "-static.lib"
+    ".a"
+    ".lib"
+)
+
+if(AUDIO_CODECS_REPO_PATH OR AUDIO_CODECS_INSTALL_PATH)
+    set(AUDIO_CODECS_PATH ${AUDIO_CODECS_REPO_PATH})
+    set(AUDIO_CODECS_INSTALL_DIR ${AUDIO_CODECS_INSTALL_PATH})
+    set(SDL2_REPO_PATH ${AUDIO_CODECS_INSTALL_DIR})
+    set(SDL2_INCLUDE_PATH ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2)
+    link_directories(${AUDIO_CODECS_INSTALL_DIR}/lib)
+    list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_INSTALL_DIR}/include ${SDL2_INCLUDE_PATH})
+    message("AudioCodecs will be used. No needed extra libraries installed in the system.")
+    set(USE_SYSTEM_AUDIO_LIBRARIES 0)
+    set(CMAKE_FIND_LIBRARY_SUFFIXES ${FIND_PREFER_STATIC})
+else()
+    message("AudioCodecs will NOT be used. Libraries will be recognized in the system.")
+    set(USE_SYSTEM_AUDIO_LIBRARIES 1)
+endif()
+
+add_definitions(-DHAVE_INTTYPES_H -DHAVE_SETENV -DHAVE_SINF)
+add_definitions(-Dmain=SDL_main -DPIC -D_REENTRANT -D_USE_MATH_DEFINES)
+
+set(SDLMixerX_SOURCES)
+set(SDLMixerX_LINK_LIBS)
+
+# Recognize SDL2 library from the system
+if(USE_SYSTEM_AUDIO_LIBRARIES)
+    find_package(PkgConfig)
+    pkg_check_modules(SDL2 REQUIRED sdl2)
+    list(APPEND SDL_MIXER_INCLUDE_PATHS ${SDL2_INCLUDE_DIRS})
+    list(APPEND SDLMixerX_LINK_LIBS ${SDL2_LIBRARIES})
+    message("== SDL2: ${SDL2_INCLUDE_DIRS} ${SDL2_LIBRARIES} ==")
+endif()
+
+option(USE_WAV             "Build with WAV codec" ON)
+if(USE_WAV)
+    add_definitions(-DMUSIC_WAV)
+    list(APPEND SDLMixerX_SOURCES
+        ${SDLMixerX_SOURCE_DIR}/src/codecs/load_aiff.c
+        ${SDLMixerX_SOURCE_DIR}/src/codecs/load_voc.c
+        ${SDLMixerX_SOURCE_DIR}/src/codecs/music_wav.c)
+endif()
+
+option(USE_OGG_VORBIS      "Build with OGG Vorbis codec" ON)
+if(USE_OGG_VORBIS)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(vorbisfile ov_open "vorbis/vorbisfile.h" FOUND_VORBIS)
+        find_path(LIBVORBIS_INCLUDE_DIR vorbis/codec.h)
+        find_library(LIBVORBISFILE_LIB NAMES vorbisfile)
+        find_library(LIBVORBIS_LIB NAMES vorbis)
+        message("Vorbis: ${LIBVORBIS_INCLUDE_DIR} ${LIBVORBIS_LIB}")
+    else()
+        set(FOUND_VORBIS 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBVORBISFILE_LIB vorbisfile)
+            set(LIBVORBIS_LIB vorbis)
+        else()
+            find_library(LIBVORBISFILE_LIB NAMES vorbisfile
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            find_library(LIBVORBIS_LIB NAMES vorbis
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_VORBIS)
+        message("== using Vorbis ==")
+        add_definitions(-DMUSIC_OGG)
+        list(APPEND SDL_MIXER_INCLUDE_PATHS
+            ${AUDIO_CODECS_PATH}/libogg/include
+            ${AUDIO_CODECS_PATH}/libvorbis/include
+        )
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBVORBISFILE_LIB} ${LIBVORBIS_LIB})
+        set(LIBOGG_NEEDED ON)
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_ogg.c)
+    endif()
+endif()
+
+option(USE_OPUS      "Build with OPUS codec" ON)
+if(USE_OPUS)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(opusfile op_open_file "opus/opusfile.h" FOUND_OPUS)
+        find_path(LIBOPUS_INCLUDE_DIR opus/opusfile.h)
+        find_library(LIBOPUSFILE_LIB NAMES opusfile)
+        find_library(LIBOPUS_LIB NAMES opusfile)
+        message("Opus: ${LIBOPUS_INCLUDE_DIR} ${LIBOPUSFILE_LIB} ${LIBOPUS_LIB}")
+    else()
+        set(FOUND_OPUS 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBOPUSFILE_LIB opusfile)
+            set(LIBOPUS_LIB opus)
+        else()
+            find_library(LIBOPUSFILE_LIB NAMES opusfile
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            find_library(LIBOPUS_LIB NAMES opus
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_OPUS)
+        message("== using Opus ==")
+        add_definitions(-DMUSIC_OPUS)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS
+                ${AUDIO_CODECS_PATH}/libogg/include
+                ${AUDIO_CODECS_PATH}/libopus/include
+                ${AUDIO_CODECS_PATH}/libopusfile/include
+            )
+        endif()
+        if(AUDIO_CODECS_INSTALL_DIR)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_INSTALL_DIR}/include/opus)
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBOPUSFILE_LIB} ${LIBOPUS_LIB})
+        set(LIBOGG_NEEDED ON)
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_opus.c)
+    endif()
+endif()
+
+option(USE_FLAC            "Build with FLAC codec" ON)
+if(USE_FLAC)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(FLAC FLAC__format_sample_rate_is_valid "FLAC/format.h" FOUND_FLAC)
+        find_path(LIBFLAC_INCLUDE_DIR "FLAC/all.h")
+        find_library(LIBFLAC_LIB NAMES FLAC)
+        message("FLAC: ${LIBFLAC_INCLUDE_DIR} ${LIBFLAC_LIB}")
+    else()
+        set(FOUND_FLAC 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBFLAC_LIB FLAC)
+        else()
+            find_library(LIBFLAC_LIB NAMES FLAC
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_FLAC)
+        message("== using FLAC ==")
+        add_definitions(-DMUSIC_FLAC -DFLAC__NO_DLL)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS
+                ${AUDIO_CODECS_PATH}/libogg/include
+                ${AUDIO_CODECS_PATH}/libFLAC/include
+            )
+        endif()
+        set(LIBOGG_NEEDED ON)
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBFLAC_LIB})
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_flac.c)
+    endif()
+endif()
+
+if(LIBOGG_NEEDED)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        find_library(LIBOGG_LIB NAMES ogg)
+    else()
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBOGG_LIB ogg)
+        else()
+            find_library(LIBOGG_LIB NAMES ogg
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    list(APPEND SDLMixerX_LINK_LIBS ${LIBOGG_LIB})
+endif()
+
+option(USE_MP3_ID3TAG   "Build with MP3 Meta tags support provided by libID3Tag library" ON)
+if(USE_MP3_ID3TAG AND NOT USE_SYSTEM_AUDIO_LIBRARIES)
+    message("== using ID3Tag (custom) ==")
+    add_definitions(-DMUSIC_MP3_ID3TAG)
+    if(AUDIO_CODECS_REPO_PATH)
+        list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libid3tag/include)
+    endif()
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+        set(LIBID3TAG_LIB id3tag)
+    else()
+        find_library(LIBID3TAG_LIB NAMES id3tag
+                     HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+    endif()
+    list(APPEND SDLMixerX_LINK_LIBS ${LIBID3TAG_LIB})
+endif()
+
+option(USE_MP3_MAD      "Build with MAD MP3 codec" ON)
+option(USE_MP3_MAD_GPL_DITHERING "Enable GPL-Licensed dithering functions for MAD library" ON)
+if(USE_MP3_MAD)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(mad mad_frame_init "mad.h" FOUND_MAD)
+        find_path(LIBMAD_INCLUDE_DIR "FLAC/all.h")
+        find_library(LIBMAD_LIB NAMES mad)
+        message("MAD: ${LIBMAD_INCLUDE_DIR} ${LIBMAD_LIB}")
+    else()
+        set(FOUND_MAD 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBMAD_LIB mad)
+        else()
+            find_library(LIBMAD_LIB NAMES mad
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_MAD)
+        message("== using MAD ==")
+        add_definitions(-DMUSIC_MP3_MAD)
+        if(USE_MP3_MAD_GPL_DITHERING)
+            add_definitions(-DMUSIC_MP3_MAD_GPL_DITHERING)
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBMAD_LIB})
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libmad/include)
+        endif()
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_mad.c)
+    endif()
+endif()
+
+option(USE_MP3_SMPEG       "Build with SMPEG MP3 codec" OFF)
+if(USE_MP3_SMPEG)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(smpeg SMPEG_new "smpeg/smpeg.h" FOUND_SMPEG)
+        find_path(LIBSMPEG_INCLUDE_DIR "smpeg/smpeg.h")
+        find_library(LIBSMPEG_LIB NAMES smpeg)
+        message("SMPEG: ${LIBSMPEG_INCLUDE_DIR} ${LIBSMPEG_LIB}")
+    else()
+        set(FOUND_SMPEG 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBSMPEG_LIB smpeg)
+        else()
+            find_library(LIBSMPEG_LIB NAMES smpeg
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_SMPEG)
+        message("== using SMPEG ==")
+        add_definitions(-DMUSIC_MP3_SMPEG)
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBSMPEG_LIB})
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/smpeg/include)
+        endif()
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_smpeg.c)
+    endif()
+endif()
+
+option(USE_MP3_MPG123     "[WIP, DON'T USE IT] Build with MPG123 MP3 codec" OFF)
+if(USE_MP3_MPG123)
+    add_definitions(-DMUSIC_MP3_MPG123)
+
+    message(WARNING "MPG123 SUPPORT IS WIP, DON'T USE IT IN PRODUCTION!!!")
+    # ======= Until AudioCodecs will receive buildable mpg123, detect it externally =======
+    include(cmake/FindMpg123.cmake)
+
+    if(NOT MPG123_FOUND)
+        message(FATAL_ERROR "MPG123 is not found!")
+    else()
+        message("MPG123 found in ${MPG123_INCLUDE_DIR} folder")
+    endif()
+
+    list(APPEND SDL_MIXER_INCLUDE_PATHS ${MPG123_INCLUDE_DIRS})
+    set(LIBS ${LIBS} ${MPG123_LIBRARIES})
+    list(APPEND SDLMixerX_LINK_LIBS mpg123)
+    list(APPEND SDLMixerX_SOURCES
+        ${SDLMixerX_SOURCE_DIR}/src/codecs/music_mpg123.c)
+endif()
+
+if(USE_MP3_MAD OR USE_MP3_MPG123 OR USE_MP3_SMPEG)
+    list(APPEND SDLMixerX_SOURCES
+        ${SDLMixerX_SOURCE_DIR}/src/codecs/music_id3tag.c)
+endif()
+
+option(USE_MODPLUG         "Build with ModPlug library" ON)
+if(USE_MODPLUG)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(modplug ModPlug_Load "libmodplug/modplug.h" FOUND_MODPLUG)
+        find_path(LIBMODPLUG_INCLUDE_DIR libmodplug/modplug.h)
+        find_library(LIBMODPLUG_LIB NAMES modplug)
+        message("ModPlug: ${LIBMODPLUG_INCLUDE_DIR} ${LIBMODPLUG_LIB}")
+    else()
+        set(FOUND_MODPLUG 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBMODPLUG_LIB modplug)
+        else()
+            find_library(LIBMODPLUG_LIB NAMES modplug
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_MODPLUG)
+        message("== using libModPlug ==")
+        add_definitions(-DMUSIC_MOD_MODPLUG -DMODPLUG_STATIC)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libmodplug/include)
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBMODPLUG_LIB})
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_modplug.c)
+    endif()
+endif()
+
+option(USE_MIKMOD          "Build with MikMod library" OFF)
+if(USE_MIKMOD)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(mikmod Player_Load "mikmod.h" FOUND_MIKMOD)
+        find_path(LIBMIKMOD_INCLUDE_DIR "mikmod.h")
+        find_library(LIBMIKMOD_LIB NAMES mikmod)
+        message("MikMod: ${LIBMIKMOD_INCLUDE_DIR} ${LIBMIKMOD_LIB}")
+    else()
+        set(FOUND_MIKMOD 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBMIKMOD_LIB mikmod)
+        else()
+            find_library(LIBMIKMOD_LIB NAMES mikmod
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_MIKMOD)
+        message("== using MikMod ==")
+        add_definitions(-DMUSIC_MOD_MIKMOD)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libmikmod/include)
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBMIKMOD_LIB})
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_mikmod.c)
+    endif()
+endif()
+
+option(USE_GME             "Build with Game Music Emulators library" ON)
+if(USE_GME)
+    if(USE_SYSTEM_AUDIO_LIBRARIES)
+        check_library_exists(gme gme_load_file "gme.h" FOUND_GME)
+        find_path(LIBGME_INCLUDE_DIR "gme.h")
+        find_library(LIBGME_LIB NAMES gme)
+        find_library(LIBZLIB_LIB NAMES z zlib)
+        message("GME: ${LIBGME_INCLUDE_DIR} ${LIBGME_LIB}")
+    else()
+        set(FOUND_GME 1)
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBGME_LIB gme)
+            set(LIBZLIB_LIB zlib)
+        else()
+            find_library(LIBGME_LIB NAMES gme
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            find_library(LIBZLIB_LIB NAMES zlib z
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+    endif()
+    if(FOUND_GME)
+        message("== using GME ==")
+        add_definitions(-DMUSIC_GME)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libgme/include)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/zlib/include)
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBGME_LIB} ${LIBZLIB_LIB})
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_gme.c)
+    endif()
+endif()
+
+if(NOT WIN32) # CMD Music is not supported on Windows
+    option(USE_CMD             "Build with CMD music player support" ON)
+    if(USE_CMD)
+        message("== using CMD Music ==")
+        add_definitions(-DMUSIC_CMD -D_POSIX_C_SOURCE=1)
+        CHECK_FUNCTION_EXISTS(fork HAVE_FORK)
+        if(HAVE_FORK)
+            add_definitions(-DHAVE_FORK)
+        endif()
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_cmd.c)
+    endif()
+endif()
+
+option(USE_MIDI            "Build with MIDI support" ON)
+if(USE_MIDI)
+
+    option(USE_MIDI_ADLMIDI    "Build with libADLMIDI OPL3 Emulator based MIDI sequencer support" ON)
+    if(USE_MIDI_ADLMIDI)
+        if(USE_SYSTEM_AUDIO_LIBRARIES)
+            check_library_exists(ADLMIDI adl_init "adlmidi.h" FOUND_ADLMIDI)
+            find_path(LIBADLMIDI_INCLUDE_DIR "adlmidi.h")
+            find_library(LIBADLMIDI_LIB NAMES ADLMIDI)
+            message("ADLMIDI: ${LIBADLMIDI_INCLUDE_DIR} ${LIBADLMIDI_LIB}")
+        else()
+            set(FOUND_ADLMIDI 1)
+            if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+                set(LIBADLMIDI_LIB ADLMIDI)
+            else()
+                find_library(LIBADLMIDI_LIB NAMES ADLMIDI
+                             HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            endif()
+        endif()
+        if(FOUND_ADLMIDI)
+            message("== using ADLMIDI ==")
+            add_definitions(-DMUSIC_MID_ADLMIDI)
+            if(AUDIO_CODECS_REPO_PATH)
+                list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libADLMIDI/include)
+            endif()
+            set(LIBMATH_NEEDED 1)
+            list(APPEND SDLMixerX_LINK_LIBS ${LIBADLMIDI_LIB})
+            list(APPEND SDLMixerX_SOURCES
+                ${SDLMixerX_SOURCE_DIR}/src/codecs/music_midi_adl.c)
+        endif()
+    endif()
+
+    option(USE_MIDI_OPNMIDI    "Build with libOPNMIDI OPN2 Emulator based MIDI sequencer support" ON)
+    if(USE_MIDI_OPNMIDI)
+        if(USE_SYSTEM_AUDIO_LIBRARIES)
+            check_library_exists(OPNMIDI opn_init "opnmidi.h" FOUND_OPNMIDI)
+            find_path(LIBOPNMIDI_INCLUDE_DIR "opnmidi.h")
+            find_library(LIBOPNMIDI_LIB NAMES OPNMIDI)
+            message("OPNMIDI: ${LIBOPNMIDI_INCLUDE_DIR} ${LIBOPNMIDI_LIB}")
+        else()
+            set(FOUND_OPNMIDI 1)
+            if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+                set(LIBOPNMIDI_LIB OPNMIDI)
+            else()
+                find_library(LIBOPNMIDI_LIB NAMES OPNMIDI
+                             HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            endif()
+        endif()
+        if(FOUND_OPNMIDI)
+            message("== using OPNMIDI ==")
+            add_definitions(-DMUSIC_MID_OPNMIDI)
+            if(AUDIO_CODECS_REPO_PATH)
+                list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libOPNMIDI/include)
+            endif()
+            set(LIBMATH_NEEDED 1)
+            list(APPEND SDLMixerX_LINK_LIBS ${LIBOPNMIDI_LIB})
+            list(APPEND SDLMixerX_SOURCES
+                ${SDLMixerX_SOURCE_DIR}/src/codecs/music_midi_opn.c)
+        endif()
+    endif()
+
+    option(USE_MIDI_TIMIDITY   "Build with Timidity wave table MIDI sequencer support" ON)
+    if(USE_MIDI_TIMIDITY AND NOT USE_SYSTEM_AUDIO_LIBRARIES)
+        message("== using Timidity (custom) ==")
+        add_definitions(-DMUSIC_MID_TIMIDITY)
+        if(AUDIO_CODECS_REPO_PATH)
+            list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/libtimidity/include)
+        endif()
+        if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+            set(LIBTIMIDITY_LIB timidity)
+        else()
+            find_library(LIBTIMIDITY_LIB NAMES timidity
+                         HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+        endif()
+        list(APPEND SDLMixerX_LINK_LIBS ${LIBTIMIDITY_LIB})
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_timidity.c)
+    endif()
+
+    option(USE_MIDI_FLUIDSYNTH "Build with FluidSynth wave table MIDI sequencer support" OFF)
+    if(USE_MIDI_FLUIDSYNTH)
+        if(USE_SYSTEM_AUDIO_LIBRARIES)
+            check_library_exists(fluidsynth new_fluid_midi_router "fluidsynth.h" FOUND_FLUIDSYNTH)
+            find_path(LIBFLUIDSYNTH_INCLUDE_DIR "fluidsynth.h")
+            find_library(LIBFLUIDSYNTH_LIB NAMES fluidsynth)
+            message("FluidSynth: ${LIBFLUIDSYNTH_INCLUDE_DIR} ${LIBFLUIDSYNTH_LIB}")
+        else()
+            set(FOUND_FLUIDSYNTH 1)
+            if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+                set(LIBFLUIDSYNTH_LIB fluidsynth)
+            else()
+                find_library(LIBFLUIDSYNTH_LIB NAMES fluidsynth
+                             HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+            endif()
+        endif()
+        if(FOUND_FLUIDSYNTH)
+            message("== using FluidSynth ==")
+            add_definitions(-DMUSIC_MID_FLUIDSYNTH)
+            if(AUDIO_CODECS_REPO_PATH)
+                list(APPEND SDL_MIXER_INCLUDE_PATHS ${AUDIO_CODECS_PATH}/FluidLite/include)
+            endif()
+            list(APPEND SDLMixerX_LINK_LIBS ${LIBFLUIDSYNTH_LIB})
+            list(APPEND SDLMixerX_SOURCES
+                ${SDLMixerX_SOURCE_DIR}/src/codecs/music_fluidsynth.c)
+        endif()
+    endif()
+
+     # Native MIDI correctly works on Windows and macOS only.
+    if(WIN32 OR APPLE)
+        set(NATIVE_MIDI_SUPPORTED ON)
+    else()
+        set(NATIVE_MIDI_SUPPORTED OFF)
+    endif()
+
+    option(USE_MIDI_NATIVE     "Build with operating system native MIDI output support" ${NATIVE_MIDI_SUPPORTED})
+    if(USE_MIDI_NATIVE)
+        add_definitions(-DMUSIC_MID_NATIVE)
+        list(APPEND SDLMixerX_SOURCES
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/music_nativemidi.c
+            ${SDLMixerX_SOURCE_DIR}/src/codecs/native_midi/native_midi_common.c)
+        if(WIN32)
+            list(APPEND SDLMixerX_SOURCES
+                ${SDLMixerX_SOURCE_DIR}/src/codecs/native_midi/native_midi_win32.c)
+            list(APPEND SDLMixerX_LINK_LIBS winmm)
+        endif()
+        if(APPLE)
+            list(APPEND SDLMixerX_SOURCES
+                ${SDLMixerX_SOURCE_DIR}/src/codecs/native_midi/native_midi_macosx.c)
+        endif()
+    endif()
+    if(NOT USE_MIDI_ADLMIDI AND
+       NOT USE_MIDI_OPNMIDI AND
+       NOT USE_MIDI_TIMIDITY AND
+       NOT USE_MIDI_FLUIDSYNTH AND
+       NOT USE_MIDI_NATIVE)
+        message_error("To have MIDI support you must enable at least one MIDI sequencer library")
+    endif()
+
+endif()
+
+if(USE_SYSTEM_AUDIO_LIBRARIES)
+    find_library(LIBSDL2CUSTOM_LIB NAMES SDL2)
+else()
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+        set(LIBSDL2CUSTOM_LIB SDL2$<$<CONFIG:Debug>:d>)
+    else()
+        set(CMAKE_FIND_LIBRARY_SUFFIXES ${FIND_PREFER_SHARED})
+        find_library(LIBSDL2CUSTOM_LIB NAMES SDL2
+                     HINTS "${AUDIO_CODECS_INSTALL_PATH}/lib")
+    endif()
+endif()
+list(APPEND SDLMixerX_LINK_LIBS ${LIBSDL2CUSTOM_LIB})
+
+if(LIBMATH_NEEDED AND NOT MSVC)
+    list(APPEND SDLMixerX_LINK_LIBS m)
+endif()
+
+if(WIN32)
+    option(BUILD_AS_VB6_BINDING "Compile library into solid module compatible with VisualBasic 6" OFF)
+    if(BUILD_AS_VB6_BINDING)
+        message("== SDL Mixer X will be built as solid module that is ready for MS-VB6 binding! ==")
+        list(APPEND SDLMixerX_SOURCES ${SDLMixerX_SOURCE_DIR}/src/vb6_binding/vb6_sdl_binds.c)
+        list(REMOVE_ITEM SDLMixerX_LINK_LIBS SDL2)
+        list(REMOVE_ITEM SDLMixerX_LINK_LIBS SDL2$<$<CONFIG:Debug>:d>)
+        list(APPEND SDLMixerX_LINK_LIBS
+                    SDL2-static
+                    uuid winmm ole32 imm32 version oleaut32 user32 gdi32)
+        if(NOT MSVC)
+            list(APPEND SDLMixerX_LINK_LIBS stdc++ gcc pthread)
+        endif()
+    endif()
+endif()
+
+if(APPLE)
+    find_library(APPLE_CORE_AUDIO CoreAudio)
+    find_library(APPLE_IOKIT IOKit)
+    find_library(APPLE_COCOA Cocoa)
+    find_library(APPLE_AUDIOTOOLBOX AudioToolbox)
+    find_library(APPLE_CORE_VIDEO CoreVideo)
+    find_library(APPLE_CORE_FOUNDATION CoreFoundation)
+    find_library(APPLE_CARBON Carbon)
+    find_library(APPLE_FORCE_FEEDBACK ForceFeedback)
+    find_library(ICONV_LIB iconv)
+    list(APPEND SDLMixerX_LINK_LIBS
+            ${APPLE_CORE_AUDIO}
+            ${APPLE_COCOA}
+            ${APPLE_CORE_VIDEO}
+            ${APPLE_CORE_FOUNDATION}
+            ${APPLE_FORCE_FEEDBACK}
+            ${APPLE_IOKIT}
+            ${APPLE_CARBON}
+            ${APPLE_AUDIOTOOLBOX}
+            ${ICONV_LIB})
+    list(APPEND SDLMixerX_LINK_LIBS objc)
+endif()
+
+if(NOT MSVC)
+    list(APPEND SDLMixerX_LINK_LIBS stdc++)
+endif()
+
+list(APPEND SDLMixerX_SOURCES
+        ${SDLMixerX_SOURCE_DIR}/src/effect_position.c
+        ${SDLMixerX_SOURCE_DIR}/src/effects_internal.c
+        ${SDLMixerX_SOURCE_DIR}/src/effect_stereoreverse.c
+        ${SDLMixerX_SOURCE_DIR}/src/mixer.c
+        ${SDLMixerX_SOURCE_DIR}/src/music.c
+        ${SDLMixerX_SOURCE_DIR}/src/mixer_x_deprecated.c
+    )
+
+file(GLOB SDLMixerX_SOURCES ${SDLMixerX_SOURCES})
+
+set(SDLMixerX_INSTALLS)
+
+if(SDL_MIXER_X_STATIC AND NOT BUILD_AS_VB6_BINDING)
+    add_library(SDL2_mixer_ext_Static STATIC ${SDLMixerX_SOURCES})
+    if(WIN32)
+        set_target_properties(SDL2_mixer_ext_Static PROPERTIES OUTPUT_NAME SDL2_mixer_ext-static)
+    else()
+        set_target_properties(SDL2_mixer_ext_Static PROPERTIES OUTPUT_NAME SDL2_mixer_ext)
+    endif()
+    target_include_directories(SDL2_mixer_ext_Static PRIVATE
+        ${SDLMixerX_SOURCE_DIR}/include/SDL_mixer_ext
+        ${SDLMixerX_SOURCE_DIR}/src
+        ${SDLMixerX_SOURCE_DIR}/src/codecs
+        ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2
+    )
+    target_include_directories(SDL2_mixer_ext_Static PUBLIC
+        ${SDLMixerX_SOURCE_DIR}/include
+        ${SDL_MIXER_INCLUDE_PATHS}
+    )
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY AND AUDIO_CODECS_INSTALL_PATH)
+        add_dependencies(SDL2_mixer_ext_Static AudioCodecs)
+    endif()
+    set_target_properties(SDL2_mixer_ext_Static
+        PROPERTIES
+        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+    )
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+        add_custom_command(
+            TARGET SDL2_mixer_ext_Static POST_BUILD
+            COMMAND ${CMAKE_COMMAND} -E copy
+                    ${CMAKE_SOURCE_DIR}/include/SDL_mixer_ext/SDL_mixer_ext.h
+                    ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2)
+    endif()
+    list(APPEND SDLMixerX_INSTALLS SDL2_mixer_ext_Static)
+endif()
+
+if(WIN32)
+    list(APPEND SDLMixerX_SOURCES ${SDLMixerX_SOURCE_DIR}/version.rc)
+endif()
+
+if(SDL_MIXER_X_SHARED AND NOT BUILD_AS_VB6_BINDING)
+    add_library(SDL2_mixer_ext SHARED ${SDLMixerX_SOURCES})
+    target_link_libraries(SDL2_mixer_ext ${SDLMixerX_LINK_LIBS})
+    target_include_directories(SDL2_mixer_ext PRIVATE
+        ${SDLMixerX_SOURCE_DIR}/include/SDL_mixer_ext
+        ${SDLMixerX_SOURCE_DIR}/src
+        ${SDLMixerX_SOURCE_DIR}/src/codecs
+        ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2
+    )
+    target_include_directories(SDL2_mixer_ext PUBLIC
+        ${SDLMixerX_SOURCE_DIR}/include
+        ${SDL_MIXER_INCLUDE_PATHS}
+    )
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY AND AUDIO_CODECS_INSTALL_PATH)
+        add_dependencies(SDL2_mixer_ext AudioCodecs)
+    endif()
+    set_target_properties(SDL2_mixer_ext
+        PROPERTIES
+        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+    )
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+        add_custom_command(
+            TARGET SDL2_mixer_ext POST_BUILD
+            COMMAND ${CMAKE_COMMAND} -E copy
+                    ${CMAKE_SOURCE_DIR}/include/SDL_mixer_ext/SDL_mixer_ext.h
+                    ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2)
+    endif()
+    list(APPEND SDLMixerX_INSTALLS SDL2_mixer_ext)
+endif()
+
+if(BUILD_AS_VB6_BINDING)
+    add_library(SDL2_mixer_ext_VB6 MODULE ${SDLMixerX_SOURCES})
+    set_target_properties(SDL2_mixer_ext_VB6 PROPERTIES OUTPUT_NAME SDL2MixerVB)
+    set_target_properties(SDL2_mixer_ext_VB6 PROPERTIES PREFIX "")
+    target_compile_definitions(SDL2_mixer_ext_VB6 PRIVATE -DFORCE_STDCALLS)
+    target_link_libraries(SDL2_mixer_ext_VB6 ${SDLMixerX_LINK_LIBS})
+    target_include_directories(SDL2_mixer_ext_VB6 PUBLIC
+        ${SDLMixerX_SOURCE_DIR}/include/SDL_mixer_ext
+        ${SDLMixerX_SOURCE_DIR}/src
+        ${SDLMixerX_SOURCE_DIR}/src/codecs
+        ${AUDIO_CODECS_INSTALL_DIR}/include/SDL2
+    )
+    target_include_directories(SDL2_mixer_ext_VB6 PUBLIC
+        ${SDLMixerX_SOURCE_DIR}/include
+        ${SDL_MIXER_INCLUDE_PATHS}
+    )
+    set_target_properties(SDL2_mixer_ext_VB6 PROPERTIES LINK_FLAGS
+        "-Wl,--add-stdcall-alias -static-libgcc -static-libstdc++ -static -lpthread")
+    if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY AND AUDIO_CODECS_INSTALL_PATH)
+        add_dependencies(SDL2_mixer_ext_VB6 AudioCodecs)
+    endif()
+    set_target_properties(SDL2_mixer_ext_VB6
+        PROPERTIES
+        ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sdl-mixer-vb6"
+        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sdl-mixer-vb6"
+        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sdl-mixer-vb6"
+    )
+    add_custom_command(
+        TARGET SDL2_mixer_ext_VB6 POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E copy
+                ${CMAKE_SOURCE_DIR}/VB6_Wrapper/modSDL2_mixer_ext_vb6.bas
+                ${CMAKE_BINARY_DIR}/sdl-mixer-vb6)
+    list(APPEND SDLMixerX_INSTALLS SDL2_mixer_ext_VB6)
+endif()
+
+install(TARGETS ${SDLMixerX_INSTALLS}
+        RUNTIME DESTINATION "bin"
+        LIBRARY DESTINATION "lib"
+        ARCHIVE DESTINATION "lib"
+        FRAMEWORK DESTINATION "lib"
+        INCLUDES DESTINATION "include")
+
+if(DOWNLOAD_AUDIO_CODECS_DEPENDENCY)
+    install(DIRECTORY ${AUDIO_CODECS_INSTALL_DIR}/bin
+            DESTINATION .)
+    install(DIRECTORY ${AUDIO_CODECS_INSTALL_DIR}/lib
+            DESTINATION .)
+    install(DIRECTORY ${AUDIO_CODECS_INSTALL_DIR}/include
+            DESTINATION .)
+endif()
+
+install(FILES
+        include/SDL_mixer_ext/begin_code.h
+        include/SDL_mixer_ext/close_code.h
+        include/SDL_mixer_ext/SDL_mixer_ext.h
+        DESTINATION include/SDL2)
+
diff --git a/libs/SDLMixerX/COPYING.txt b/libs/SDLMixerX/COPYING.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e8f8643f27c30c0497fc8084203fc7903389f96b
--- /dev/null
+++ b/libs/SDLMixerX/COPYING.txt
@@ -0,0 +1,24 @@
+/*
+  SDL Mixer X:  An extended audio mixer library, forked from SDL_mixer
+  Copyright (C) 2014-2018 Vitaly Novichkov <admin@wohlnet.ru>
+
+  SDL_mixer:  An audio mixer library based on the SDL library
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+*/
diff --git a/libs/SDLMixerX/GPLv2.txt b/libs/SDLMixerX/GPLv2.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1f963da0d1ca40ea60730c03befcbbc6771740c3
--- /dev/null
+++ b/libs/SDLMixerX/GPLv2.txt
@@ -0,0 +1,340 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/libs/SDLMixerX/GPLv3.txt b/libs/SDLMixerX/GPLv3.txt
new file mode 100644
index 0000000000000000000000000000000000000000..10926e87f113fb026c366866d6fa466061562870
--- /dev/null
+++ b/libs/SDLMixerX/GPLv3.txt
@@ -0,0 +1,675 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/libs/SDLMixerX/README.txt b/libs/SDLMixerX/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..998e0d9d3a97065fa10538a12b6ac1fe12988d20
--- /dev/null
+++ b/libs/SDLMixerX/README.txt
@@ -0,0 +1,248 @@
+SDL Mixer X (aka SDL Mixer 2.0 Modded or SDL_mixer_ext),
+by Vitaly Novichkov <Wohlstand>,
+forked from SDL Mixer 2.0 by Sam Lantinga <slouken@libsdl.org>
+
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+WARNING: The licenses for libmad, ADLMIDI, OPNMIDI, and GME is GPL,
+         which means that in order to use it your application must
+         also be GPL!
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The latest original version of this library is available from:
+http://www.libsdl.org/projects/SDL_mixer/
+
+Sources of modified library version is available in the PGE Project's sources:
+https://github.com/Wohlhabend-Networks/PGE-Project/
+    in the folder: _Libs/SDL2_mixer_modified/
+or:
+https://bitbucket.org/Wohlstand/pge-project
+    in the folder: _Libs/SDL2_mixer_modified/
+
+=============================================================================
+Difference between original and this library:
+-----------------------------------------------------------------------------
++ Added new codecs:
+  - Game Music Emulators (LGPL v2.1) which adds support of chip tunes
+        like NSF, VGM, SPC, HES, etc.
+  - libADLMIDI (GPL v3, LGPL v3) (remake from ADLMIDI) to play MIDI with
+        emulated OPL3 synthesiser, also supports loop points
+        "loopStart" and "loopEnd"
+  - libOPNMIDI (GPL v3, LGPL v3) to play MIDI with
+        emulated OPN2 synthesiser, also supports loop points
+        "loopStart" and "loopEnd"
++ Added some new functions
++ Added support of loop points for OGG files (via "LOOPSTART" and "LOOPEND"
+  (or "LOOPLENGTH" to be compatible with RPG Maker) vorbis comments)
++ Reorganized music codecs processing system
++ Added support to get current position and track lenght for a seekable codecs
+
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+IMPORTANT: To choice a track number of NSF, GBM, HES, etc file,
+           you must append "|xxx" to end of file path for
+           Mix_LoadMUS function.
+           Where xxx - actual number of chip track, (from 0 to N-1)
+           Examples: "file.nsf|12", "file.hes|2"
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+Arguments are passing like argument for a GME-based files (NSF, HES, etc.):
+Syntax for MIDI is:
+        myfile.mid|xyy;xyy;xyy;...;
+    where x - parameter type
+    where y - value (every value must be ended with semicolon!)
+
+available parameters:
+        s - use a specific synthesiser
+            0 - ADLMIDI
+            1 - Native MIDI [Win32/OSX/Haiku only]
+            2 - Timidity
+            3 - Fluidsynth
+        b - value from 0 to 66 - number of ADLMIDI bank
+        t - (0 or 1) enable deep tremolo on ADLMIDI
+        v - (0 or 1) enable deep vibrato on ADLMIDI
+        m - (0 or 1) enable scalable modulation on ADLMIDI
+        a - (0 or 1) enable AdLib mode of percussion on ADLMIDI
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+=============================================================================
+Added music codecs:
+-----------------------------------------------------------------------------
+General:
+- MUS_GME - Game Music Emulatirs
+
+MIDI Playing:
+- ADLMIDI - A software synthesizer is based on Yamaha OPL3 (YMF262) chip emulator
+- OPNMIDI - A software synthesizer is based on Yamaha OPL3 (YM2612) chip emulator
+
+=============================================================================
+Added functions:
+-----------------------------------------------------------------------------
+/*
+    Allows you to set up custom path for Timidify patches
+*/
+void MIX_Timidity_addToPathList(const char *path);
+
+/*
+    Get music title from meta-tag if possible. If title tag is empty, filename will be returned
+*/
+const char* Mix_GetMusicTitle(const Mix_Music *music);
+
+/*
+    Get music title from meta-tag if possible
+*/
+const char* Mix_GetMusicTitleTag(const Mix_Music *music);
+
+/*
+    Get music artist from meta-tag if possible
+*/
+const char* Mix_GetMusicArtistTag(const Mix_Music *music);
+
+/*
+    Get music album from meta-tag if possible
+*/
+const char* Mix_GetMusicAlbumTag(const Mix_Music *music);
+
+/*
+    Get music copyright from meta-tag if possible
+*/
+const char* Mix_GetMusicCopyrightTag(const Mix_Music *music);
+
+/*
+    Load music from memory with passing of extra arguments
+*/
+Mix_Music * SDLCALLCC Mix_LoadMUS_RW_ARG(SDL_RWops *src, int freesrc, char *args)
+
+/*
+    Load music from memory with passing NSF/HES/etc. track number (accepts integer unlike SDLCALLCC Mix_LoadMUS_RW_ARG)
+*/
+Mix_Music * SDLCALLCC Mix_LoadMUS_RW_GME(SDL_RWops *src, int freesrc, int trackID)
+
+typedef enum
+{
+    MIDI_ADLMIDI,
+    MIDI_Native,
+    MIDI_Timidity,
+    MIDI_OPNMIDI,
+    MIDI_Fluidsynth,
+    MIDI_KnuwnDevices   /* Count of MIDI device types */
+} Mix_MIDI_Device;
+
+/*
+  Allows you to toggle MIDI Devices!
+   (change will be applied on re-opening of MIDI file)
+   Attempt to toggle unsupported MIDI device takes no effect
+   (for case when library built without linking of required library)
+*/
+int Mix_SetMidiDevice(int device);
+
+/*
+    Returns current ADLMIDI bank number
+*/
+int  MIX_ADLMIDI_getBankID();
+
+/*
+    Changes ADLMIDI bank number (changes applying on MIDI file reopen)
+*/
+void MIX_ADLMIDI_setBankID(int bnk);
+
+/*
+    Returns current state of ADLMIDI deep tremolo flag
+*/
+int  MIX_ADLMIDI_getTremolo();
+
+/*
+    Changes ADLMIDI deep tremolo flag (changes applying on MIDI file reopen)
+*/
+void MIX_ADLMIDI_setTremolo(int tr);
+
+/*
+    Returns current state of ADLMIDI deep vibrato flag
+*/
+int  MIX_ADLMIDI_getVibrato();
+
+/*
+    Changes ADLMIDI deep vibrato flag (changes applying on MIDI file reopen)
+*/
+void MIX_ADLMIDI_setVibrato(int vib);
+
+/*
+    Returns current state of ADLMIDI deep scaling modulation flag
+*/
+int  MIX_ADLMIDI_getScaleMod();
+
+/*
+    Changes ADLMIDI scaling modulation flag (changes applying on MIDI file reopen)
+*/
+void MIX_ADLMIDI_setScaleMod(int sc);
+
+/*
+    Resets ADLMIDI flags and settings to default state
+*/
+void MIX_ADLMIDI_setSetDefaults();
+
+/*
+    Get the time current position of music stream
+    returns -1.0 if this feature is not supported for some codec
+*/
+double Mix_GetMusicPosition(Mix_Music *music);
+/*
+    Get the total time length of music stream
+    returns -1.0 if this feature is not supported for some codec
+ */
+double Mix_GetMusicTotalTime(Mix_Music *music);
+
+/*
+   Get the loop start time position of music stream
+   returns -1.0 if this feature is not used for this music or not supported for some codec
+*/
+double Mix_GetMusicLoopStartTime(Mix_Music *music);
+/*
+    Get the loop end time position of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+*/
+double Mix_GetMusicLoopEndTime(Mix_Music *music);
+/*
+    Get the loop time length of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+*/
+double Mix_GetMusicLoopLengthTime(Mix_Music *music);
+
+=============================================================================
+
+Due to popular demand, here is a simple multi-channel audio mixer.
+It supports 8 channels of 16 bit stereo audio, plus a single channel
+of music, mixed by the Modplug MOD, Timidity MIDI, ADLMIDI, GME
+and LibMAD MP3 libraries.
+
+See the header file SDL_mixer_ext.h for documentation on this mixer library.
+
+The mixer can currently load Microsoft WAVE files and Creative Labs VOC
+files as audio samples, and can load MIDI files via Timidity and the
+following music formats via MikMod:  .MOD .S3M .IT .XM. It can load
+Ogg Vorbis streams as music if built with Ogg Vorbis or Tremor libraries,
+and finally it can load MP3 music using the SMPEG or libmad libraries.
+
+Tremor decoding is disabled by default; you can enable it by passing
+	--enable-music-ogg-tremor
+to configure, or by defining OGG_MUSIC and OGG_USE_TREMOR.
+
+libmad decoding is disabled by default; you can enable it by passing
+	--enable-music-mp3-mad
+to configure, or by defining MP3_MAD_MUSIC
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+WARNING: The license for libmad is GPL, which means that in order to
+         use it your application must also be GPL!
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The process of mixing MIDI files to wave output is very CPU intensive,
+so if playing regular WAVE files sound great, but playing MIDI files
+sound choppy on slow computers, try using 8-bit audio, mono audio,
+or lower frequencies.
+
+To play MIDI files via Timidity, you'll need to get a complete set of GUS patches
+from:
+http://www.libsdl.org/projects/mixer/timidity/timidity.tar.gz
+and unpack them in /usr/local/lib under UNIX, and C:\ under Win32.
+
+This library is under the zlib license, see the file "COPYING.txt" for details.
+
+
+
diff --git a/libs/SDLMixerX/SDL2_mixer_ext.License.txt b/libs/SDLMixerX/SDL2_mixer_ext.License.txt
new file mode 100644
index 0000000000000000000000000000000000000000..58806358d64626d12c874c3e9dbee8f5b30f9fd0
--- /dev/null
+++ b/libs/SDLMixerX/SDL2_mixer_ext.License.txt
@@ -0,0 +1,104 @@
+========================================================================
+SDL_mixer_ext {aka SDL Mixer X, SDL Mixer 2.0 Modded}, by Vitaly Novichkov <admin@wohlnet.ru>
+forked from SDL Mixer 2.0 by Sam Lantinga <slouken@libsdl.org>
+========================================================================
+
+SDL_mixer_ext:  An extended audio mixer library, forked from SDL_mixer
+Copyright (C) 2014-2018 Vitaly Novichkov <admin@wohlnet.ru>
+
+SDL_mixer:  An audio mixer library based on the SDL library
+Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+Sources of modified library version is available on GitHub:
+https://github.com/WohlSoft/SDL-Mixer-X
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+========================================================================
+Statically linked libraries:
+========================================================================
+------------------------------------------------------------------------
+libADLMIDI: is a free MIDI to WAV conversion library with OPL3 emulation
+
+Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
+ADLMIDI Library API:   Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
+
+Library is based on the ADLMIDI, a MIDI player for Linux and Windows
+with OPL3 emulation:
+http://iki.fi/bisqwit/source/adlmidi.html
+
+Source code: https://github.com/Wohlstand/libADLMIDI
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+------------------------------------------------------------------------
+
+Game Music Emulators: is a free chuptunes to WAV conversion library.
+
+Source code: https://bitbucket.org/mpyne/game-music-emu
+
+Copyright (C) 2003-2006 Shay Green.
+
+This module is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published
+by the Free Software Foundation; either version 2.1 of the License,
+or (at your option) any later version.
+
+This module is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this module;
+If not, see <http://www.gnu.org/licenses/>.
+
+------------------------------------------------------------------------
+
+libmad: MPEG audio decoder library
+
+Copyright (C) 2000-2004 Underbit Technologies, Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+If you would like to negotiate alternate licensing terms, you may do
+so by contacting: Underbit Technologies, Inc. <info@underbit.com>
+
+------------------------------------------------------------------------
diff --git a/libs/SDLMixerX/SRB2Note.txt b/libs/SDLMixerX/SRB2Note.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6036d394e2d9742be9489492af34db4283c83e27
--- /dev/null
+++ b/libs/SDLMixerX/SRB2Note.txt
@@ -0,0 +1,7 @@
+i686-w64-mingw32/bin/libfluidsynth-2.dll was grabbed from GZDoom v3.5.1, because this build does not have glib, gthread, or sndfile external dependencies.
+
+sdl_mixer_ext2.dll requires libgcc and libstdc++ external dependencies. While passing to the linker "-Bstatic -lgcc" works fine, adding "-lstdc++" to -Bstatic  generates linker errors. 
+
+Hence, libstdc++ needs to be bundled as DLL (and consequently, libgcc.)
+
+If you try to build yourself, make sure -lstdc++ does not follow -Bdynamic in the linker flags, because otherwise it will link to the DLL.
diff --git a/libs/SDLMixerX/SRB2Note_cmakeflags.png b/libs/SDLMixerX/SRB2Note_cmakeflags.png
new file mode 100644
index 0000000000000000000000000000000000000000..ffcf269a401c5a545630adbcb4e9ed69f4bd6c56
Binary files /dev/null and b/libs/SDLMixerX/SRB2Note_cmakeflags.png differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/bin/SDL2_mixer_ext.dll b/libs/SDLMixerX/i686-w64-mingw32/bin/SDL2_mixer_ext.dll
new file mode 100644
index 0000000000000000000000000000000000000000..158aad8c33bba348c6acff67aee5de4623868f2a
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/bin/SDL2_mixer_ext.dll differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/bin/libfluidsynth-2.dll b/libs/SDLMixerX/i686-w64-mingw32/bin/libfluidsynth-2.dll
new file mode 100644
index 0000000000000000000000000000000000000000..1b768a6c15440b4959fd0684d2be9d244a46260f
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/bin/libfluidsynth-2.dll differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/bin/libgcc_s_sjlj-1.dll b/libs/SDLMixerX/i686-w64-mingw32/bin/libgcc_s_sjlj-1.dll
new file mode 100644
index 0000000000000000000000000000000000000000..0a62946cb1353164f10a402341b4c7d220dfda7d
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/bin/libgcc_s_sjlj-1.dll differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/bin/libstdc++-6.dll b/libs/SDLMixerX/i686-w64-mingw32/bin/libstdc++-6.dll
new file mode 100644
index 0000000000000000000000000000000000000000..e9feebaaa540371f9f772c66fe8dbd11b60138c9
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/bin/libstdc++-6.dll differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/SDL_mixer_ext.h b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/SDL_mixer_ext.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a6b91b5048840b583db2b05fee5d7bfdef2fb39
--- /dev/null
+++ b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/SDL_mixer_ext.h
@@ -0,0 +1,867 @@
+/*
+  SDL Mixer X:  An extended audio mixer library, forked from SDL_mixer
+  Copyright (C) 2014-2018 Vitaly Novichkov <admin@wohlnet.ru>
+
+  SDL_mixer:  An audio mixer library based on the SDL library
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_MIXER_H_
+#define SDL_MIXER_H_
+
+#include "SDL_stdinc.h"
+#include "SDL_rwops.h"
+#include "SDL_audio.h"
+#include "SDL_endian.h"
+#include "SDL_version.h"
+#include "begin_code.h"
+
+/* Let applications recogonize which SDL Mixer edition is in use: Official or Extended fork by Wohlstand */
+#define SDL_MIXER_X 1
+
+#define MIXSDLCALL
+
+#if defined(FORCE_STDCALLS) && defined(_WIN32)
+#ifdef SDLCALL
+#undef SDLCALL
+#endif
+#define SDLCALL   __stdcall
+#define SDLCALLCC __stdcall
+#else
+#define SDLCALLCC
+#endif
+
+#ifndef DEPRECATED
+#ifdef __GNUC__
+#define DEPRECATED(func) func __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define DEPRECATED(func) __declspec(deprecated) func
+#else
+#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
+#define DEPRECATED(func) func
+#endif
+#endif
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL
+*/
+#define SDL_MIXER_MAJOR_VERSION 2
+#define SDL_MIXER_MINOR_VERSION 2
+#define SDL_MIXER_PATCHLEVEL    0
+
+/* This macro can be used to fill a version structure with the compile-time
+ * version of the SDL_mixer library.
+ */
+#define SDL_MIXER_VERSION(X)                        \
+{                                                   \
+    (X)->major = SDL_MIXER_MAJOR_VERSION;           \
+    (X)->minor = SDL_MIXER_MINOR_VERSION;           \
+    (X)->patch = SDL_MIXER_PATCHLEVEL;              \
+}
+
+/* Backwards compatibility */
+#define MIX_MAJOR_VERSION   SDL_MIXER_MAJOR_VERSION
+#define MIX_MINOR_VERSION   SDL_MIXER_MINOR_VERSION
+#define MIX_PATCHLEVEL      SDL_MIXER_PATCHLEVEL
+#define MIX_VERSION(X)      SDL_MIXER_VERSION(X)
+
+/**
+ *  This is the version number macro for the current SDL_mixer version.
+ */
+#define SDL_MIXER_COMPILEDVERSION \
+    SDL_VERSIONNUM(SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL)
+
+/**
+ *  This macro will evaluate to true if compiled with SDL_mixer at least X.Y.Z.
+ */
+#define SDL_MIXER_VERSION_ATLEAST(X, Y, Z) \
+    (SDL_MIXER_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z))
+
+/* This function gets the version of the dynamically linked SDL_mixer library.
+   it should NOT be used to fill a version structure, instead you should
+   use the SDL_MIXER_VERSION() macro.
+ */
+extern DECLSPEC const SDL_version * SDLCALL Mix_Linked_Version(void);
+
+typedef enum
+{
+    MIX_INIT_FLAC   = 0x00000001,
+    MIX_INIT_MOD    = 0x00000002,
+    MIX_INIT_MP3    = 0x00000008,
+    MIX_INIT_OGG    = 0x00000010,
+    MIX_INIT_MID    = 0x00000020,
+    MIX_INIT_OPUS   = 0x00000040
+} MIX_InitFlags;
+
+/* Loads dynamic libraries and prepares them for use.  Flags should be
+   one or more flags from MIX_InitFlags OR'd together.
+   It returns the flags successfully initialized, or 0 on failure.
+ */
+extern DECLSPEC int SDLCALL Mix_Init(int flags);
+
+/* Unloads libraries loaded with Mix_Init */
+extern DECLSPEC void SDLCALL Mix_Quit(void);
+
+
+/* The default mixer has 8 simultaneous mixing channels */
+#ifndef MIX_CHANNELS
+#define MIX_CHANNELS    8
+#endif
+
+/* Good default values for a PC soundcard */
+#define MIX_DEFAULT_FREQUENCY   44100
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+#define MIX_DEFAULT_FORMAT  AUDIO_S16LSB
+#else
+#define MIX_DEFAULT_FORMAT  AUDIO_S16MSB
+#endif
+#define MIX_DEFAULT_CHANNELS    2
+#define MIX_MAX_VOLUME          SDL_MIX_MAXVOLUME /* Volume of a chunk */
+
+/* The internal format for an audio chunk */
+typedef struct Mix_Chunk {
+    int allocated;
+    Uint8 *abuf;
+    Uint32 alen;
+    Uint8 volume;       /* Per-sample volume, 0-128 */
+} Mix_Chunk;
+
+/* The different fading types supported */
+typedef enum {
+    MIX_NO_FADING,
+    MIX_FADING_OUT,
+    MIX_FADING_IN
+} Mix_Fading;
+
+/* These are types of music files (not libraries used to load them) */
+typedef enum {
+    MUS_NONE,
+    MUS_CMD,
+    MUS_WAV,
+    MUS_MOD,
+    MUS_MID,
+    MUS_OGG,
+    MUS_MP3,
+    MUS_MP3_MAD_UNUSED,
+    MUS_FLAC,
+    MUS_MODPLUG_UNUSED,
+    MUS_OPUS,
+    MUS_GME,
+    MUS_ADLMIDI/*Use ADLMIDI coded for super-special formats like IMF, MUS or XMI are can't be played without ADLMIDI*/
+} Mix_MusicType;
+
+typedef enum {
+    MIDI_ADLMIDI,
+    MIDI_Native,
+    MIDI_Timidity,
+    MIDI_OPNMIDI,
+    MIDI_Fluidsynth,
+    MIDI_ANY,
+    MIDI_KnuwnDevices   /* Count of MIDI device types */
+} Mix_MIDI_Device;
+
+/* Volume model type in the ADLMIDI */
+typedef enum {
+    ADLMIDI_VM_AUTO,
+    ADLMIDI_VM_GENERIC,
+    ADLMIDI_VM_CMF,
+    ADLMIDI_VM_DMX,
+    ADLMIDI_VM_APOGEE,
+    ADLMIDI_VM_9X
+} Mix_ADLMIDI_VolumeModel;
+
+/* OPL3 chip emulators for ADLMIDI */
+typedef enum {
+    ADLMIDI_OPL3_EMU_DEFAULT = -1,
+    ADLMIDI_OPL3_EMU_NUKED = 0,
+    ADLMIDI_OPL3_EMU_NUKED_1_7_4,
+    ADLMIDI_OPL3_EMU_DOSBOX,
+} Mix_ADLMIDI_Emulator;
+
+/* OPN2 chip emulators for OPNMIDI */
+typedef enum {
+    OPNMIDI_OPN2_EMU_DEFAULT = -1,
+    OPNMIDI_OPN2_EMU_MIME = 0,
+    OPNMIDI_OPN2_EMU_NUKED,
+    OPNMIDI_OPN2_EMU_GENS,
+} Mix_OPNMIDI_Emulator;
+
+/* The internal format for a music chunk interpreted via mikmod */
+typedef struct _Mix_Music Mix_Music;
+
+/* Open the mixer with a certain audio format */
+extern DECLSPEC int SDLCALL Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);
+
+/* Open the mixer with specific device and certain audio format */
+extern DECLSPEC int SDLCALL Mix_OpenAudioDevice(int frequency, Uint16 format, int channels, int chunksize, const char* device, int allowed_changes);
+
+/* Dynamically change the number of channels managed by the mixer.
+   If decreasing the number of channels, the upper channels are
+   stopped.
+   This function returns the new number of allocated channels.
+ */
+extern DECLSPEC int SDLCALL Mix_AllocateChannels(int numchans);
+
+/* Find out what the actual audio device parameters are.
+   This function returns 1 if the audio has been opened, 0 otherwise.
+ */
+extern DECLSPEC int SDLCALL Mix_QuerySpec(int *frequency,Uint16 *format,int *channels);
+
+/* Load a wave file or a music (.mod .s3m .it .xm) file
+    IMPORTANT: To choice a track number of NSF, GBM, HES, etc file,
+               you must append "|xxx" to end of file path for
+               Mix_LoadMUS function.
+               Where xxx - actual number of chip track, (from 0 to N-1)
+               Examples: "file.nsf|12", "file.hes|2"
+*/
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV_RW(SDL_RWops *src, int freesrc);
+#define Mix_LoadWAV(file)   Mix_LoadWAV_RW(SDL_RWFromFile(file, "rb"), 1)
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS(const char *file);
+
+/* Load a music file from an SDL_RWop object
+ * Matt Campbell (matt@campbellhome.dhs.org) April 2000 */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS_RW(SDL_RWops *src, int freesrc);
+
+/* Load a music file from an SDL_RWop object with custom arguments (trackID for GME or settings for a MIDI playing)
+ * Arguments are taking no effect for file formats which are not supports extra arguments.
+  */
+extern DECLSPEC Mix_Music *SDLCALL Mix_LoadMUS_RW_ARG(SDL_RWops *src, int freesrc, const char *args);
+
+/* Load a music file from an SDL_RWop object with custom trackID for GME.
+ * trackID argument takes no effect for non-NSF,HES,GBM,etc. file formats.
+ * Default value should be 0
+ */
+extern DECLSPEC Mix_Music *SDLCALL Mix_LoadMUS_RW_GME(SDL_RWops *src, int freesrc, int trackID);
+
+/* Load a music file from an SDL_RWop object assuming a specific format */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUSType_RW(SDL_RWops *src, Mix_MusicType type, int freesrc);
+
+/* Load a music file from an SDL_RWop object assuming a specific format
+   with custom arguments (trackID for GME or settings for a MIDI playing) */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUSType_RW_ARG(SDL_RWops *src, Mix_MusicType type, int freesrc, const char *args);
+
+/* Load a wave file of the mixer format from a memory buffer */
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_WAV(Uint8 *mem);
+
+/* Load raw audio data of the mixer format from a memory buffer */
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len);
+
+/* Free an audio chunk previously loaded */
+extern DECLSPEC void SDLCALL Mix_FreeChunk(Mix_Chunk *chunk);
+extern DECLSPEC void SDLCALL Mix_FreeMusic(Mix_Music *music);
+
+/* Get a list of chunk/music decoders that this build of SDL_mixer provides.
+   This list can change between builds AND runs of the program, if external
+   libraries that add functionality become available.
+   You must successfully call Mix_OpenAudio() before calling these functions.
+   This API is only available in SDL_mixer 1.2.9 and later.
+
+   // usage...
+   int i;
+   const int total = Mix_GetNumChunkDecoders();
+   for (i = 0; i < total; i++)
+       printf("Supported chunk decoder: [%s]\n", Mix_GetChunkDecoder(i));
+
+   Appearing in this list doesn't promise your specific audio file will
+   decode...but it's handy to know if you have, say, a functioning Timidity
+   install.
+
+   These return values are static, read-only data; do not modify or free it.
+   The pointers remain valid until you call Mix_CloseAudio().
+*/
+extern DECLSPEC int SDLCALL Mix_GetNumChunkDecoders(void);
+extern DECLSPEC const char * SDLCALL Mix_GetChunkDecoder(int index);
+extern DECLSPEC SDL_bool SDLCALL Mix_HasChunkDecoder(const char *name);
+extern DECLSPEC int SDLCALL Mix_GetNumMusicDecoders(void);
+extern DECLSPEC const char * SDLCALL Mix_GetMusicDecoder(int index);
+extern DECLSPEC SDL_bool SDLCALL Mix_HasMusicDecoder(const char *name);
+
+/* Find out the music format of a mixer music, or the currently playing
+   music, if 'music' is NULL.
+*/
+extern DECLSPEC Mix_MusicType SDLCALL Mix_GetMusicType(const Mix_Music *music);
+
+/* Get music title from meta-tag if possible. If title tag is empty, filename will be returned */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicTitle(const Mix_Music *music);
+/* Get music title from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicTitleTag(const Mix_Music *music);
+/* Get music artist from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicArtistTag(const Mix_Music *music);
+/* Get music album from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicAlbumTag(const Mix_Music *music);
+/* Get music copyright from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicCopyrightTag(const Mix_Music *music);
+
+/* Set a function that is called after all mixing is performed.
+   This can be used to provide real-time visual display of the audio stream
+   or add a custom mixer filter for the stream data.
+*/
+extern DECLSPEC void SDLCALL Mix_SetPostMix(void (SDLCALL *mix_func)(void *udata, Uint8 *stream, int len), void *arg);
+
+/* Add your own music player or additional mixer function.
+   If 'mix_func' is NULL, the default music player is re-enabled.
+ */
+extern DECLSPEC void SDLCALL Mix_HookMusic(void (SDLCALL *mix_func)(void *udata, Uint8 *stream, int len), void *arg);
+
+/* Add your own callback for when the music has finished playing or when it is
+ * stopped from a call to Mix_HaltMusic.
+ */
+extern DECLSPEC void SDLCALL Mix_HookMusicFinished(void (SDLCALL *music_finished)(void));
+
+/* Get a pointer to the user data for the current music hook */
+extern DECLSPEC void * SDLCALL Mix_GetMusicHookData(void);
+
+/*
+ * Add your own callback when a channel has finished playing. NULL
+ *  to disable callback. The callback may be called from the mixer's audio
+ *  callback or it could be called as a result of Mix_HaltChannel(), etc.
+ *  do not call SDL_LockAudio() from this callback; you will either be
+ *  inside the audio callback, or SDL_mixer will explicitly lock the audio
+ *  before calling your callback.
+ */
+extern DECLSPEC void SDLCALL Mix_ChannelFinished(void (SDLCALL *channel_finished)(int channel));
+
+
+/* Special Effects API by ryan c. gordon. (icculus@icculus.org) */
+
+#define MIX_CHANNEL_POST  -2
+
+/* This is the format of a special effect callback:
+ *
+ *   myeffect(int chan, void *stream, int len, void *udata);
+ *
+ * (chan) is the channel number that your effect is affecting. (stream) is
+ *  the buffer of data to work upon. (len) is the size of (stream), and
+ *  (udata) is a user-defined bit of data, which you pass as the last arg of
+ *  Mix_RegisterEffect(), and is passed back unmolested to your callback.
+ *  Your effect changes the contents of (stream) based on whatever parameters
+ *  are significant, or just leaves it be, if you prefer. You can do whatever
+ *  you like to the buffer, though, and it will continue in its changed state
+ *  down the mixing pipeline, through any other effect functions, then finally
+ *  to be mixed with the rest of the channels and music for the final output
+ *  stream.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ */
+typedef void (SDLCALL *Mix_EffectFunc_t)(int chan, void *stream, int len, void *udata);
+
+/*
+ * This is a callback that signifies that a channel has finished all its
+ *  loops and has completed playback. This gets called if the buffer
+ *  plays out normally, or if you call Mix_HaltChannel(), implicitly stop
+ *  a channel via Mix_AllocateChannels(), or unregister a callback while
+ *  it's still playing.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ */
+typedef void (SDLCALL *Mix_EffectDone_t)(int chan, void *udata);
+
+
+/* Register a special effect function. At mixing time, the channel data is
+ *  copied into a buffer and passed through each registered effect function.
+ *  After it passes through all the functions, it is mixed into the final
+ *  output stream. The copy to buffer is performed once, then each effect
+ *  function performs on the output of the previous effect. Understand that
+ *  this extra copy to a buffer is not performed if there are no effects
+ *  registered for a given chunk, which saves CPU cycles, and any given
+ *  effect will be extra cycles, too, so it is crucial that your code run
+ *  fast. Also note that the data that your function is given is in the
+ *  format of the sound device, and not the format you gave to Mix_OpenAudio(),
+ *  although they may in reality be the same. This is an unfortunate but
+ *  necessary speed concern. Use Mix_QuerySpec() to determine if you can
+ *  handle the data before you register your effect, and take appropriate
+ *  actions.
+ * You may also specify a callback (Mix_EffectDone_t) that is called when
+ *  the channel finishes playing. This gives you a more fine-grained control
+ *  than Mix_ChannelFinished(), in case you need to free effect-specific
+ *  resources, etc. If you don't need this, you can specify NULL.
+ * You may set the callbacks before or after calling Mix_PlayChannel().
+ * Things like Mix_SetPanning() are just internal special effect functions,
+ *  so if you are using that, you've already incurred the overhead of a copy
+ *  to a separate buffer, and that these effects will be in the queue with
+ *  any functions you've registered. The list of registered effects for a
+ *  channel is reset when a chunk finishes playing, so you need to explicitly
+ *  set them with each call to Mix_PlayChannel*().
+ * You may also register a special effect function that is to be run after
+ *  final mixing occurs. The rules for these callbacks are identical to those
+ *  in Mix_RegisterEffect, but they are run after all the channels and the
+ *  music have been mixed into a single stream, whereas channel-specific
+ *  effects run on a given channel before any other mixing occurs. These
+ *  global effect callbacks are call "posteffects". Posteffects only have
+ *  their Mix_EffectDone_t function called when they are unregistered (since
+ *  the main output stream is never "done" in the same sense as a channel).
+ *  You must unregister them manually when you've had enough. Your callback
+ *  will be told that the channel being mixed is (MIX_CHANNEL_POST) if the
+ *  processing is considered a posteffect.
+ *
+ * After all these effects have finished processing, the callback registered
+ *  through Mix_SetPostMix() runs, and then the stream goes to the audio
+ *  device.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ *
+ * returns zero if error (no such channel), nonzero if added.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
+
+
+/* You may not need to call this explicitly, unless you need to stop an
+ *  effect from processing in the middle of a chunk's playback.
+ * Posteffects are never implicitly unregistered as they are for channels,
+ *  but they may be explicitly unregistered through this function by
+ *  specifying MIX_CHANNEL_POST for a channel.
+ * returns zero if error (no such channel or effect), nonzero if removed.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f);
+
+
+/* You may not need to call this explicitly, unless you need to stop all
+ *  effects from processing in the middle of a chunk's playback. Note that
+ *  this will also shut off some internal effect processing, since
+ *  Mix_SetPanning() and others may use this API under the hood. This is
+ *  called internally when a channel completes playback.
+ * Posteffects are never implicitly unregistered as they are for channels,
+ *  but they may be explicitly unregistered through this function by
+ *  specifying MIX_CHANNEL_POST for a channel.
+ * returns zero if error (no such channel), nonzero if all effects removed.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_UnregisterAllEffects(int channel);
+
+
+#define MIX_EFFECTSMAXSPEED  "MIX_EFFECTSMAXSPEED"
+
+/*
+ * These are the internally-defined mixing effects. They use the same API that
+ *  effects defined in the application use, but are provided here as a
+ *  convenience. Some effects can reduce their quality or use more memory in
+ *  the name of speed; to enable this, make sure the environment variable
+ *  MIX_EFFECTSMAXSPEED (see above) is defined before you call
+ *  Mix_OpenAudio().
+ */
+
+
+/* Set the panning of a channel. The left and right channels are specified
+ *  as integers between 0 and 255, quietest to loudest, respectively.
+ *
+ * Technically, this is just individual volume control for a sample with
+ *  two (stereo) channels, so it can be used for more than just panning.
+ *  If you want real panning, call it like this:
+ *
+ *   Mix_SetPanning(channel, left, 255 - left);
+ *
+ * ...which isn't so hard.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the panning will be done to the final mixed stream before passing it on
+ *  to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally, and returns without
+ *  registering the effect function if the audio device is not configured
+ *  for stereo output. Setting both (left) and (right) to 255 causes this
+ *  effect to be unregistered, since that is the data's normal state.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if panning effect enabled. Note that an audio device in mono
+ *  mode is a no-op, but this call will return successful in that case.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetPanning(int channel, Uint8 left, Uint8 right);
+
+
+/* Set the position of a channel. (angle) is an integer from 0 to 360, that
+ *  specifies the location of the sound in relation to the listener. (angle)
+ *  will be reduced as neccesary (540 becomes 180 degrees, -100 becomes 260).
+ *  Angle 0 is due north, and rotates clockwise as the value increases.
+ *  For efficiency, the precision of this effect may be limited (angles 1
+ *  through 7 might all produce the same effect, 8 through 15 are equal, etc).
+ *  (distance) is an integer between 0 and 255 that specifies the space
+ *  between the sound and the listener. The larger the number, the further
+ *  away the sound is. Using 255 does not guarantee that the channel will be
+ *  culled from the mixing process or be completely silent. For efficiency,
+ *  the precision of this effect may be limited (distance 0 through 5 might
+ *  all produce the same effect, 6 through 10 are equal, etc). Setting (angle)
+ *  and (distance) to 0 unregisters this effect, since the data would be
+ *  unchanged.
+ *
+ * If you need more precise positional audio, consider using OpenAL for
+ *  spatialized effects instead of SDL_mixer. This is only meant to be a
+ *  basic effect for simple "3D" games.
+ *
+ * If the audio device is configured for mono output, then you won't get
+ *  any effectiveness from the angle; however, distance attenuation on the
+ *  channel will still occur. While this effect will function with stereo
+ *  voices, it makes more sense to use voices with only one channel of sound,
+ *  so when they are mixed through this effect, the positioning will sound
+ *  correct. You can convert them to mono through SDL before giving them to
+ *  the mixer in the first place if you like.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the positioning will be done to the final mixed stream before passing it
+ *  on to the audio device.
+ *
+ * This is a convenience wrapper over Mix_SetDistance() and Mix_SetPanning().
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if position effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetPosition(int channel, Sint16 angle, Uint8 distance);
+
+
+/* Set the "distance" of a channel. (distance) is an integer from 0 to 255
+ *  that specifies the location of the sound in relation to the listener.
+ *  Distance 0 is overlapping the listener, and 255 is as far away as possible
+ *  A distance of 255 does not guarantee silence; in such a case, you might
+ *  want to try changing the chunk's volume, or just cull the sample from the
+ *  mixing process with Mix_HaltChannel().
+ * For efficiency, the precision of this effect may be limited (distances 1
+ *  through 7 might all produce the same effect, 8 through 15 are equal, etc).
+ *  (distance) is an integer between 0 and 255 that specifies the space
+ *  between the sound and the listener. The larger the number, the further
+ *  away the sound is.
+ * Setting (distance) to 0 unregisters this effect, since the data would be
+ *  unchanged.
+ * If you need more precise positional audio, consider using OpenAL for
+ *  spatialized effects instead of SDL_mixer. This is only meant to be a
+ *  basic effect for simple "3D" games.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the distance attenuation will be done to the final mixed stream before
+ *  passing it on to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if position effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetDistance(int channel, Uint8 distance);
+
+
+/*
+ * !!! FIXME : Haven't implemented, since the effect goes past the
+ *              end of the sound buffer. Will have to think about this.
+ *               --ryan.
+ */
+#if 0
+/* Causes an echo effect to be mixed into a sound. (echo) is the amount
+ *  of echo to mix. 0 is no echo, 255 is infinite (and probably not
+ *  what you want).
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the reverbing will be done to the final mixed stream before passing it on
+ *  to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally. If you specify an echo
+ *  of zero, the effect is unregistered, as the data is already in that state.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if reversing effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern no_parse_DECLSPEC int SDLCALL Mix_SetReverb(int channel, Uint8 echo);
+#endif
+
+/* Causes a channel to reverse its stereo. This is handy if the user has his
+ *  speakers hooked up backwards, or you would like to have a minor bit of
+ *  psychedelia in your sound code.  :)  Calling this function with (flip)
+ *  set to non-zero reverses the chunks's usual channels. If (flip) is zero,
+ *  the effect is unregistered.
+ *
+ * This uses the Mix_RegisterEffect() API internally, and thus is probably
+ *  more CPU intensive than having the user just plug in his speakers
+ *  correctly. Mix_SetReverseStereo() returns without registering the effect
+ *  function if the audio device is not configured for stereo output.
+ *
+ * If you specify MIX_CHANNEL_POST for (channel), then this the effect is used
+ *  on the final mixed stream before sending it on to the audio device (a
+ *  posteffect).
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if reversing effect is enabled. Note that an audio device in mono
+ *  mode is a no-op, but this call will return successful in that case.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetReverseStereo(int channel, int flip);
+
+/* end of effects API. --ryan. */
+
+
+/* Reserve the first channels (0 -> n-1) for the application, i.e. don't allocate
+   them dynamically to the next sample if requested with a -1 value below.
+   Returns the number of reserved channels.
+ */
+extern DECLSPEC int SDLCALL Mix_ReserveChannels(int num);
+
+/* Channel grouping functions */
+
+/* Attach a tag to a channel. A tag can be assigned to several mixer
+   channels, to form groups of channels.
+   If 'tag' is -1, the tag is removed (actually -1 is the tag used to
+   represent the group of all the channels).
+   Returns true if everything was OK.
+ */
+extern DECLSPEC int SDLCALL Mix_GroupChannel(int which, int tag);
+/* Assign several consecutive channels to a group */
+extern DECLSPEC int SDLCALL Mix_GroupChannels(int from, int to, int tag);
+/* Finds the first available channel in a group of channels,
+   returning -1 if none are available.
+ */
+extern DECLSPEC int SDLCALL Mix_GroupAvailable(int tag);
+/* Returns the number of channels in a group. This is also a subtle
+   way to get the total number of channels when 'tag' is -1
+ */
+extern DECLSPEC int SDLCALL Mix_GroupCount(int tag);
+/* Finds the "oldest" sample playing in a group of channels */
+extern DECLSPEC int SDLCALL Mix_GroupOldest(int tag);
+/* Finds the "most recent" (i.e. last) sample playing in a group of channels */
+extern DECLSPEC int SDLCALL Mix_GroupNewer(int tag);
+
+/* Play an audio chunk on a specific channel.
+   If the specified channel is -1, play on the first free channel.
+   If 'loops' is greater than zero, loop the sound that many times.
+   If 'loops' is -1, loop inifinitely (~65000 times).
+   Returns which channel was used to play the sound.
+*/
+#define Mix_PlayChannel(channel,chunk,loops) Mix_PlayChannelTimed(channel,chunk,loops,-1)
+/* The same as above, but the sound is played at most 'ticks' milliseconds */
+extern DECLSPEC int SDLCALL Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ticks);
+extern DECLSPEC int SDLCALL Mix_PlayMusic(Mix_Music *music, int loops);
+#define Mix_PlayChannelVol(channel,chunk,loops,vol) Mix_PlayChannelTimedVolume(channel,chunk,loops,-1,vol)/*MIXER-X*/
+extern DECLSPEC int SDLCALL Mix_PlayChannelTimedVolume(int which, Mix_Chunk *chunk, int loops, int ticks, int volume);/*MIXER-X*/
+
+/* Fade in music or a channel over "ms" milliseconds, same semantics as the "Play" functions */
+extern DECLSPEC int SDLCALL Mix_FadeInMusic(Mix_Music *music, int loops, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeInMusicPos(Mix_Music *music, int loops, int ms, double position);
+#define Mix_FadeInChannel(channel,chunk,loops,ms) Mix_FadeInChannelTimed(channel,chunk,loops,ms,-1)
+extern DECLSPEC int SDLCALL Mix_FadeInChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ms, int ticks);
+#define Mix_FadeInChannelVolume(channel,chunk,loops,ms,vol) Mix_FadeInChannelTimed(channel,chunk,loops,ms,-1,vol)/*MIXER-X*/
+extern DECLSPEC int SDLCALL Mix_FadeInChannelTimedVolume(int which, Mix_Chunk *chunk, int loops, int ms, int ticks, int volume);/*MIXER-X*/
+
+
+/* Set the volume in the range of 0-128 of a specific channel or chunk.
+   If the specified channel is -1, set volume for all channels.
+   Returns the original volume.
+   If the specified volume is -1, just return the current volume.
+*/
+extern DECLSPEC int SDLCALL Mix_Volume(int channel, int volume);
+extern DECLSPEC int SDLCALL Mix_VolumeChunk(Mix_Chunk *chunk, int volume);
+extern DECLSPEC int SDLCALL Mix_VolumeMusic(int volume);
+
+/* Halt playing of a particular channel */
+extern DECLSPEC int SDLCALL Mix_HaltChannel(int channel);
+extern DECLSPEC int SDLCALL Mix_HaltGroup(int tag);
+extern DECLSPEC int SDLCALL Mix_HaltMusic(void);
+
+/* Change the expiration delay for a particular channel.
+   The sample will stop playing after the 'ticks' milliseconds have elapsed,
+   or remove the expiration if 'ticks' is -1
+*/
+extern DECLSPEC int SDLCALL Mix_ExpireChannel(int channel, int ticks);
+
+/* Halt a channel, fading it out progressively till it's silent
+   The ms parameter indicates the number of milliseconds the fading
+   will take.
+ */
+extern DECLSPEC int SDLCALL Mix_FadeOutChannel(int which, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeOutGroup(int tag, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeOutMusic(int ms);
+
+/* Query the fading status of a channel */
+extern DECLSPEC Mix_Fading SDLCALL Mix_FadingMusic(void);
+extern DECLSPEC Mix_Fading SDLCALL Mix_FadingChannel(int which);
+
+/* Pause/Resume a particular channel */
+extern DECLSPEC void SDLCALL Mix_Pause(int channel);
+extern DECLSPEC void SDLCALL Mix_Resume(int channel);
+extern DECLSPEC int SDLCALL Mix_Paused(int channel);
+
+/* Pause/Resume the music stream */
+extern DECLSPEC void SDLCALL Mix_PauseMusic(void);
+extern DECLSPEC void SDLCALL Mix_ResumeMusic(void);
+extern DECLSPEC void SDLCALL Mix_RewindMusic(void);
+extern DECLSPEC int SDLCALL Mix_PausedMusic(void);
+
+/* Set the current position in the music stream.
+   This returns 0 if successful, or -1 if it failed or isn't implemented.
+   This function is only implemented for MOD music formats (set pattern
+   order number) and for WAV, OGG, FLAC, MP3_MAD, MP3_MPG, and MODPLUG music
+   (set position in seconds), at the moment.
+*/
+extern DECLSPEC int SDLCALL Mix_SetMusicPosition(double position);
+/*
+    Get the time current position of music stream
+    returns -1.0 if this feature is not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicPosition(Mix_Music *music);
+/*
+    Get the total time length of music stream
+    returns -1.0 if this feature is not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicTotalTime(Mix_Music *music);
+
+/*
+    Get the loop start time position of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopStartTime(Mix_Music *music);
+/*
+    Get the loop end time position of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopEndTime(Mix_Music *music);
+/*
+    Get the loop time length of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopLengthTime(Mix_Music *music);
+
+
+/* Check the status of a specific channel.
+   If the specified channel is -1, check all channels.
+*/
+extern DECLSPEC int SDLCALL Mix_Playing(int channel);
+extern DECLSPEC int SDLCALL Mix_PlayingMusic(void);
+
+/* Stop music and set external music playback command */
+extern DECLSPEC int SDLCALL Mix_SetMusicCMD(const char *command);
+
+/* Synchro value is set by MikMod from modules while playing */
+extern DECLSPEC int SDLCALL Mix_SetSynchroValue(int value);
+extern DECLSPEC int SDLCALL Mix_GetSynchroValue(void);
+
+/* Set/Get/Iterate SoundFonts paths to use by supported MIDI backends */
+extern DECLSPEC int SDLCALL Mix_SetSoundFonts(const char *paths);
+extern DECLSPEC const char* SDLCALL Mix_GetSoundFonts(void);
+extern DECLSPEC int SDLCALL Mix_EachSoundFont(int (SDLCALL *function)(const char*, void*), void *data);
+
+/* Get the Mix_Chunk currently associated with a mixer channel
+    Returns NULL if it's an invalid channel, or there's no chunk associated.
+*/
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_GetChunk(int channel);
+
+/* Close the mixer, halting all playing audio */
+extern DECLSPEC void SDLCALL Mix_CloseAudio(void);
+
+/* Add additional Timidity bank path */
+extern DECLSPEC void SDLCALL Mix_Timidity_addToPathList(const char *path);
+
+/* ADLMIDI Setup functions */
+/* Get count of available hardcoded banks */
+extern DECLSPEC int    SDLCALL Mix_ADLMIDI_getTotalBanks(void);
+/* Get array of the bank names */
+extern DECLSPEC const char *const *SDLCALL Mix_ADLMIDI_getBankNames(void);
+/* Get bank ID */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getBankID(void);
+/* Set bank ID (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setBankID(int bnk);
+/* Get state of deep vibrato */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getTremolo(void);
+/* Set deep tremolo mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setTremolo(int tr);
+/* Get state of deep vibrato */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getVibrato(void);
+/* Set deep vibrato mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setVibrato(int vib);
+/* Get state of scalable modulation mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getScaleMod(void);
+/* Set scalable modulation mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setScaleMod(int sc);
+/* Get state of adlib drums mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getAdLibMode(void);
+/* Set adlib drums mode mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setAdLibMode(int tr);
+/* Get state of logarithmic mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getLogarithmicVolumes(void);
+/* Set logarithmic volumes mode in the generic/CMF volume models (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setLogarithmicVolumes(int lv);
+/* Get current volume model ID */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getVolumeModel(void);
+/* Change current volumes model (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setVolumeModel(int vm);
+/* Get full range mode for CC74-Brightness controller */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getFullRangeBrightness(void);
+/* Set full range mode for CC74-Brightness controller */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setFullRangeBrightness(int frb);
+/* Get the current OPL3 Emulator for ADLMIDI */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getEmulator(void);
+/* Select the OPL3 Emulator for ADLMIDI */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setEmulator(int emu);
+/* Reset all ADLMIDI properties to default state */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setSetDefaults(void);
+
+/* Sets WOPL bank file for ADLMIDI playing device, affects on MIDI file reopen */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setCustomBankFile(const char *bank_wonl_path);
+
+/* Reset all OPNMIDI properties to default state */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setSetDefaults(void);
+/* Get full range mode for CC74-Brightness controller */
+extern DECLSPEC int  SDLCALL Mix_OPNMIDI_getFullRangeBrightness(void);
+/* Set full range mode for CC74-Brightness controller */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setFullRangeBrightness(int frb);
+/* Get the OPN2 Emulator for OPNMIDI */
+extern DECLSPEC int  SDLCALL Mix_OPNMIDI_getEmulator(void);
+/* Select the OPN2 Emulator for OPNMIDI */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setEmulator(int emu);
+/* Sets WOPN bank file for OPNMIDI playing device, affects on MIDI file reopen */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setCustomBankFile(const char *bank_wonp_path);
+
+/* Get type of MIDI player library currently in use */
+extern DECLSPEC int  SDLCALL Mix_GetMidiPlayer(void);
+
+/* Get type of MIDI player library prepared for next opening of MIDI file */
+extern DECLSPEC int  SDLCALL Mix_GetNextMidiPlayer(void);
+
+/* Set the MIDI playing library (ADLMIDI, Timidity, Native MIDI (if available) and FluidSynth) */
+extern DECLSPEC int  SDLCALL Mix_SetMidiPlayer(int player);
+
+/* Disables support of MIDI file arguments */
+extern DECLSPEC void SDLCALL Mix_SetLockMIDIArgs(int lock_midiargs);
+
+/*  DEPRECATED NAMES for new-added SDL Mixer X functions
+    Those names are made with mistake - beginning with "MIX_" than "Mix_"
+    which makes confusion when you looking for Mix_ function in your IDE
+    because some applications are still use them, to don't break ABI we will keep those
+    aliases until we will remove all usages of them from applications and libraries are used them
+*/
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_GetMidiDevice(void));
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_GetNextMidiDevice(void));
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_SetMidiDevice(int player));
+
+/* We'll use SDL for reporting errors */
+#define Mix_SetError    SDL_SetError
+#define Mix_GetError    SDL_GetError
+#define Mix_ClearError  SDL_ClearError
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include "close_code.h"
+
+#endif /* SDL_MIXER_H_ */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/begin_code.h b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/begin_code.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c2106246f2583ba711451bc7e39fc2af7e9eec7
--- /dev/null
+++ b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/begin_code.h
@@ -0,0 +1,167 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file begin_code.h
+ *
+ *  This file sets things up for C dynamic library function definitions,
+ *  static inlined functions, and structures aligned at 4-byte alignment.
+ *  If you don't like ugly C preprocessor code, don't look at this file. :)
+ */
+
+/* This shouldn't be nested -- included it around code only. */
+#ifdef _begin_code_h
+#error Nested inclusion of begin_code.h
+#endif
+#define _begin_code_h
+
+#ifndef SDL_DEPRECATED
+#  if (__GNUC__ >= 4)  /* technically, this arrived in gcc 3.1, but oh well. */
+#    define SDL_DEPRECATED __attribute__((deprecated))
+#  else
+#    define SDL_DEPRECATED
+#  endif
+#endif
+
+#ifndef SDL_UNUSED
+#  ifdef __GNUC__
+#    define SDL_UNUSED __attribute__((unused))
+#  else
+#    define SDL_UNUSED
+#  endif
+#endif
+
+/* Some compilers use a special export keyword */
+#ifndef DECLSPEC
+# if defined(__WIN32__) || defined(__WINRT__)
+#  ifdef __BORLANDC__
+#   ifdef BUILD_SDL
+#    define DECLSPEC
+#   else
+#    define DECLSPEC    __declspec(dllimport)
+#   endif
+#  else
+#   define DECLSPEC __declspec(dllexport)
+#  endif
+# elif defined(__OS2__)
+#   ifdef BUILD_SDL
+#    define DECLSPEC    __declspec(dllexport)
+#   else
+#    define DECLSPEC
+#   endif
+# else
+#  if defined(__GNUC__) && __GNUC__ >= 4
+#   define DECLSPEC __attribute__ ((visibility("default")))
+#  else
+#   define DECLSPEC
+#  endif
+# endif
+#endif
+
+/* By default SDL uses the C calling convention */
+#ifndef SDLCALL
+#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__)
+#define SDLCALL __cdecl
+#elif defined(__OS2__) || defined(__EMX__)
+#define SDLCALL _System
+# if defined (__GNUC__) && !defined(_System)
+#  define _System /* for old EMX/GCC compat.  */
+# endif
+#else
+#define SDLCALL
+#endif
+#endif /* SDLCALL */
+
+/* Removed DECLSPEC on Symbian OS because SDL cannot be a DLL in EPOC */
+#ifdef __SYMBIAN32__
+#undef DECLSPEC
+#define DECLSPEC
+#endif /* __SYMBIAN32__ */
+
+/* Force structure packing at 4 byte alignment.
+   This is necessary if the header is included in code which has structure
+   packing set to an alternate value, say for loading structures from disk.
+   The packing is reset to the previous value in close_code.h
+ */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef _MSC_VER
+#pragma warning(disable: 4103)
+#endif
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#ifdef _M_X64
+/* Use 8-byte alignment on 64-bit architectures, so pointers are aligned */
+#pragma pack(push,8)
+#else
+#pragma pack(push,4)
+#endif
+#endif /* Compiler needs structure packing set */
+
+#ifndef SDL_INLINE
+#if defined(__GNUC__)
+#define SDL_INLINE __inline__
+#elif defined(_MSC_VER) || defined(__BORLANDC__) || \
+      defined(__DMC__) || defined(__SC__) || \
+      defined(__WATCOMC__) || defined(__LCC__) || \
+      defined(__DECC) || defined(__CC_ARM)
+#define SDL_INLINE __inline
+#ifndef __inline__
+#define __inline__ __inline
+#endif
+#else
+#define SDL_INLINE inline
+#ifndef __inline__
+#define __inline__ inline
+#endif
+#endif
+#endif /* SDL_INLINE not defined */
+
+#ifndef SDL_FORCE_INLINE
+#if defined(_MSC_VER)
+#define SDL_FORCE_INLINE __forceinline
+#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) )
+#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__
+#else
+#define SDL_FORCE_INLINE static SDL_INLINE
+#endif
+#endif /* SDL_FORCE_INLINE not defined */
+
+#ifndef SDL_NORETURN
+#if defined(__GNUC__)
+#define SDL_NORETURN __attribute__((noreturn))
+#elif defined(_MSC_VER)
+#define SDL_NORETURN __declspec(noreturn)
+#else
+#define SDL_NORETURN
+#endif
+#endif /* SDL_NORETURN not defined */
+
+/* Apparently this is needed by several Windows compilers */
+#if !defined(__MACH__)
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif /* NULL */
+#endif /* ! Mac OS X - breaks precompiled headers */
diff --git a/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/close_code.h b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/close_code.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3b70a4c833d80305bbffd306c8ac099babdf419
--- /dev/null
+++ b/libs/SDLMixerX/i686-w64-mingw32/include/SDL2/close_code.h
@@ -0,0 +1,37 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file close_code.h
+ *
+ *  This file reverses the effects of begin_code.h and should be included
+ *  after you finish any function and structure declarations in your headers
+ */
+
+#undef _begin_code_h
+
+/* Reset structure packing at previous byte alignment */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#pragma pack(pop)
+#endif /* Compiler needs structure packing set */
diff --git a/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext-static.a b/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext-static.a
new file mode 100644
index 0000000000000000000000000000000000000000..8a59b5a612a0da4ef1763cdc33eb71205fc0ea62
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext-static.a differ
diff --git a/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext.dll.a b/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..b5e43ef620bf3c756b2a5d580f41d2cd5b41b95a
Binary files /dev/null and b/libs/SDLMixerX/i686-w64-mingw32/lib/libSDL2_mixer_ext.dll.a differ
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/bin/SDL2_mixer_ext.dll b/libs/SDLMixerX/x86_64-w64-mingw32/bin/SDL2_mixer_ext.dll
new file mode 100644
index 0000000000000000000000000000000000000000..7762d5e8d624c672ae2ad0488dae105a96d6cc38
Binary files /dev/null and b/libs/SDLMixerX/x86_64-w64-mingw32/bin/SDL2_mixer_ext.dll differ
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/bin/libfluidsynth-2.dll b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libfluidsynth-2.dll
new file mode 100644
index 0000000000000000000000000000000000000000..5dbcac34a5f2eaa188c771f17397cf8137aaa2ba
Binary files /dev/null and b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libfluidsynth-2.dll differ
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/bin/libgcc_s_sjlj-1.dll b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libgcc_s_sjlj-1.dll
new file mode 100644
index 0000000000000000000000000000000000000000..9684a21557b4dc418b42054a35e0396a4d3fff54
Binary files /dev/null and b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libgcc_s_sjlj-1.dll differ
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/bin/libstdc++-6.dll b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libstdc++-6.dll
new file mode 100644
index 0000000000000000000000000000000000000000..220099c40a1fb54cb5b17a56731d0c55d694ae28
Binary files /dev/null and b/libs/SDLMixerX/x86_64-w64-mingw32/bin/libstdc++-6.dll differ
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/SDL_mixer_ext.h b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/SDL_mixer_ext.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a6b91b5048840b583db2b05fee5d7bfdef2fb39
--- /dev/null
+++ b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/SDL_mixer_ext.h
@@ -0,0 +1,867 @@
+/*
+  SDL Mixer X:  An extended audio mixer library, forked from SDL_mixer
+  Copyright (C) 2014-2018 Vitaly Novichkov <admin@wohlnet.ru>
+
+  SDL_mixer:  An audio mixer library based on the SDL library
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_MIXER_H_
+#define SDL_MIXER_H_
+
+#include "SDL_stdinc.h"
+#include "SDL_rwops.h"
+#include "SDL_audio.h"
+#include "SDL_endian.h"
+#include "SDL_version.h"
+#include "begin_code.h"
+
+/* Let applications recogonize which SDL Mixer edition is in use: Official or Extended fork by Wohlstand */
+#define SDL_MIXER_X 1
+
+#define MIXSDLCALL
+
+#if defined(FORCE_STDCALLS) && defined(_WIN32)
+#ifdef SDLCALL
+#undef SDLCALL
+#endif
+#define SDLCALL   __stdcall
+#define SDLCALLCC __stdcall
+#else
+#define SDLCALLCC
+#endif
+
+#ifndef DEPRECATED
+#ifdef __GNUC__
+#define DEPRECATED(func) func __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define DEPRECATED(func) __declspec(deprecated) func
+#else
+#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
+#define DEPRECATED(func) func
+#endif
+#endif
+
+/* Set up for C function definitions, even when using C++ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL
+*/
+#define SDL_MIXER_MAJOR_VERSION 2
+#define SDL_MIXER_MINOR_VERSION 2
+#define SDL_MIXER_PATCHLEVEL    0
+
+/* This macro can be used to fill a version structure with the compile-time
+ * version of the SDL_mixer library.
+ */
+#define SDL_MIXER_VERSION(X)                        \
+{                                                   \
+    (X)->major = SDL_MIXER_MAJOR_VERSION;           \
+    (X)->minor = SDL_MIXER_MINOR_VERSION;           \
+    (X)->patch = SDL_MIXER_PATCHLEVEL;              \
+}
+
+/* Backwards compatibility */
+#define MIX_MAJOR_VERSION   SDL_MIXER_MAJOR_VERSION
+#define MIX_MINOR_VERSION   SDL_MIXER_MINOR_VERSION
+#define MIX_PATCHLEVEL      SDL_MIXER_PATCHLEVEL
+#define MIX_VERSION(X)      SDL_MIXER_VERSION(X)
+
+/**
+ *  This is the version number macro for the current SDL_mixer version.
+ */
+#define SDL_MIXER_COMPILEDVERSION \
+    SDL_VERSIONNUM(SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL)
+
+/**
+ *  This macro will evaluate to true if compiled with SDL_mixer at least X.Y.Z.
+ */
+#define SDL_MIXER_VERSION_ATLEAST(X, Y, Z) \
+    (SDL_MIXER_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z))
+
+/* This function gets the version of the dynamically linked SDL_mixer library.
+   it should NOT be used to fill a version structure, instead you should
+   use the SDL_MIXER_VERSION() macro.
+ */
+extern DECLSPEC const SDL_version * SDLCALL Mix_Linked_Version(void);
+
+typedef enum
+{
+    MIX_INIT_FLAC   = 0x00000001,
+    MIX_INIT_MOD    = 0x00000002,
+    MIX_INIT_MP3    = 0x00000008,
+    MIX_INIT_OGG    = 0x00000010,
+    MIX_INIT_MID    = 0x00000020,
+    MIX_INIT_OPUS   = 0x00000040
+} MIX_InitFlags;
+
+/* Loads dynamic libraries and prepares them for use.  Flags should be
+   one or more flags from MIX_InitFlags OR'd together.
+   It returns the flags successfully initialized, or 0 on failure.
+ */
+extern DECLSPEC int SDLCALL Mix_Init(int flags);
+
+/* Unloads libraries loaded with Mix_Init */
+extern DECLSPEC void SDLCALL Mix_Quit(void);
+
+
+/* The default mixer has 8 simultaneous mixing channels */
+#ifndef MIX_CHANNELS
+#define MIX_CHANNELS    8
+#endif
+
+/* Good default values for a PC soundcard */
+#define MIX_DEFAULT_FREQUENCY   44100
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+#define MIX_DEFAULT_FORMAT  AUDIO_S16LSB
+#else
+#define MIX_DEFAULT_FORMAT  AUDIO_S16MSB
+#endif
+#define MIX_DEFAULT_CHANNELS    2
+#define MIX_MAX_VOLUME          SDL_MIX_MAXVOLUME /* Volume of a chunk */
+
+/* The internal format for an audio chunk */
+typedef struct Mix_Chunk {
+    int allocated;
+    Uint8 *abuf;
+    Uint32 alen;
+    Uint8 volume;       /* Per-sample volume, 0-128 */
+} Mix_Chunk;
+
+/* The different fading types supported */
+typedef enum {
+    MIX_NO_FADING,
+    MIX_FADING_OUT,
+    MIX_FADING_IN
+} Mix_Fading;
+
+/* These are types of music files (not libraries used to load them) */
+typedef enum {
+    MUS_NONE,
+    MUS_CMD,
+    MUS_WAV,
+    MUS_MOD,
+    MUS_MID,
+    MUS_OGG,
+    MUS_MP3,
+    MUS_MP3_MAD_UNUSED,
+    MUS_FLAC,
+    MUS_MODPLUG_UNUSED,
+    MUS_OPUS,
+    MUS_GME,
+    MUS_ADLMIDI/*Use ADLMIDI coded for super-special formats like IMF, MUS or XMI are can't be played without ADLMIDI*/
+} Mix_MusicType;
+
+typedef enum {
+    MIDI_ADLMIDI,
+    MIDI_Native,
+    MIDI_Timidity,
+    MIDI_OPNMIDI,
+    MIDI_Fluidsynth,
+    MIDI_ANY,
+    MIDI_KnuwnDevices   /* Count of MIDI device types */
+} Mix_MIDI_Device;
+
+/* Volume model type in the ADLMIDI */
+typedef enum {
+    ADLMIDI_VM_AUTO,
+    ADLMIDI_VM_GENERIC,
+    ADLMIDI_VM_CMF,
+    ADLMIDI_VM_DMX,
+    ADLMIDI_VM_APOGEE,
+    ADLMIDI_VM_9X
+} Mix_ADLMIDI_VolumeModel;
+
+/* OPL3 chip emulators for ADLMIDI */
+typedef enum {
+    ADLMIDI_OPL3_EMU_DEFAULT = -1,
+    ADLMIDI_OPL3_EMU_NUKED = 0,
+    ADLMIDI_OPL3_EMU_NUKED_1_7_4,
+    ADLMIDI_OPL3_EMU_DOSBOX,
+} Mix_ADLMIDI_Emulator;
+
+/* OPN2 chip emulators for OPNMIDI */
+typedef enum {
+    OPNMIDI_OPN2_EMU_DEFAULT = -1,
+    OPNMIDI_OPN2_EMU_MIME = 0,
+    OPNMIDI_OPN2_EMU_NUKED,
+    OPNMIDI_OPN2_EMU_GENS,
+} Mix_OPNMIDI_Emulator;
+
+/* The internal format for a music chunk interpreted via mikmod */
+typedef struct _Mix_Music Mix_Music;
+
+/* Open the mixer with a certain audio format */
+extern DECLSPEC int SDLCALL Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);
+
+/* Open the mixer with specific device and certain audio format */
+extern DECLSPEC int SDLCALL Mix_OpenAudioDevice(int frequency, Uint16 format, int channels, int chunksize, const char* device, int allowed_changes);
+
+/* Dynamically change the number of channels managed by the mixer.
+   If decreasing the number of channels, the upper channels are
+   stopped.
+   This function returns the new number of allocated channels.
+ */
+extern DECLSPEC int SDLCALL Mix_AllocateChannels(int numchans);
+
+/* Find out what the actual audio device parameters are.
+   This function returns 1 if the audio has been opened, 0 otherwise.
+ */
+extern DECLSPEC int SDLCALL Mix_QuerySpec(int *frequency,Uint16 *format,int *channels);
+
+/* Load a wave file or a music (.mod .s3m .it .xm) file
+    IMPORTANT: To choice a track number of NSF, GBM, HES, etc file,
+               you must append "|xxx" to end of file path for
+               Mix_LoadMUS function.
+               Where xxx - actual number of chip track, (from 0 to N-1)
+               Examples: "file.nsf|12", "file.hes|2"
+*/
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_LoadWAV_RW(SDL_RWops *src, int freesrc);
+#define Mix_LoadWAV(file)   Mix_LoadWAV_RW(SDL_RWFromFile(file, "rb"), 1)
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS(const char *file);
+
+/* Load a music file from an SDL_RWop object
+ * Matt Campbell (matt@campbellhome.dhs.org) April 2000 */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUS_RW(SDL_RWops *src, int freesrc);
+
+/* Load a music file from an SDL_RWop object with custom arguments (trackID for GME or settings for a MIDI playing)
+ * Arguments are taking no effect for file formats which are not supports extra arguments.
+  */
+extern DECLSPEC Mix_Music *SDLCALL Mix_LoadMUS_RW_ARG(SDL_RWops *src, int freesrc, const char *args);
+
+/* Load a music file from an SDL_RWop object with custom trackID for GME.
+ * trackID argument takes no effect for non-NSF,HES,GBM,etc. file formats.
+ * Default value should be 0
+ */
+extern DECLSPEC Mix_Music *SDLCALL Mix_LoadMUS_RW_GME(SDL_RWops *src, int freesrc, int trackID);
+
+/* Load a music file from an SDL_RWop object assuming a specific format */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUSType_RW(SDL_RWops *src, Mix_MusicType type, int freesrc);
+
+/* Load a music file from an SDL_RWop object assuming a specific format
+   with custom arguments (trackID for GME or settings for a MIDI playing) */
+extern DECLSPEC Mix_Music * SDLCALL Mix_LoadMUSType_RW_ARG(SDL_RWops *src, Mix_MusicType type, int freesrc, const char *args);
+
+/* Load a wave file of the mixer format from a memory buffer */
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_WAV(Uint8 *mem);
+
+/* Load raw audio data of the mixer format from a memory buffer */
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len);
+
+/* Free an audio chunk previously loaded */
+extern DECLSPEC void SDLCALL Mix_FreeChunk(Mix_Chunk *chunk);
+extern DECLSPEC void SDLCALL Mix_FreeMusic(Mix_Music *music);
+
+/* Get a list of chunk/music decoders that this build of SDL_mixer provides.
+   This list can change between builds AND runs of the program, if external
+   libraries that add functionality become available.
+   You must successfully call Mix_OpenAudio() before calling these functions.
+   This API is only available in SDL_mixer 1.2.9 and later.
+
+   // usage...
+   int i;
+   const int total = Mix_GetNumChunkDecoders();
+   for (i = 0; i < total; i++)
+       printf("Supported chunk decoder: [%s]\n", Mix_GetChunkDecoder(i));
+
+   Appearing in this list doesn't promise your specific audio file will
+   decode...but it's handy to know if you have, say, a functioning Timidity
+   install.
+
+   These return values are static, read-only data; do not modify or free it.
+   The pointers remain valid until you call Mix_CloseAudio().
+*/
+extern DECLSPEC int SDLCALL Mix_GetNumChunkDecoders(void);
+extern DECLSPEC const char * SDLCALL Mix_GetChunkDecoder(int index);
+extern DECLSPEC SDL_bool SDLCALL Mix_HasChunkDecoder(const char *name);
+extern DECLSPEC int SDLCALL Mix_GetNumMusicDecoders(void);
+extern DECLSPEC const char * SDLCALL Mix_GetMusicDecoder(int index);
+extern DECLSPEC SDL_bool SDLCALL Mix_HasMusicDecoder(const char *name);
+
+/* Find out the music format of a mixer music, or the currently playing
+   music, if 'music' is NULL.
+*/
+extern DECLSPEC Mix_MusicType SDLCALL Mix_GetMusicType(const Mix_Music *music);
+
+/* Get music title from meta-tag if possible. If title tag is empty, filename will be returned */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicTitle(const Mix_Music *music);
+/* Get music title from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicTitleTag(const Mix_Music *music);
+/* Get music artist from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicArtistTag(const Mix_Music *music);
+/* Get music album from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicAlbumTag(const Mix_Music *music);
+/* Get music copyright from meta-tag if possible */
+extern DECLSPEC const char *SDLCALL Mix_GetMusicCopyrightTag(const Mix_Music *music);
+
+/* Set a function that is called after all mixing is performed.
+   This can be used to provide real-time visual display of the audio stream
+   or add a custom mixer filter for the stream data.
+*/
+extern DECLSPEC void SDLCALL Mix_SetPostMix(void (SDLCALL *mix_func)(void *udata, Uint8 *stream, int len), void *arg);
+
+/* Add your own music player or additional mixer function.
+   If 'mix_func' is NULL, the default music player is re-enabled.
+ */
+extern DECLSPEC void SDLCALL Mix_HookMusic(void (SDLCALL *mix_func)(void *udata, Uint8 *stream, int len), void *arg);
+
+/* Add your own callback for when the music has finished playing or when it is
+ * stopped from a call to Mix_HaltMusic.
+ */
+extern DECLSPEC void SDLCALL Mix_HookMusicFinished(void (SDLCALL *music_finished)(void));
+
+/* Get a pointer to the user data for the current music hook */
+extern DECLSPEC void * SDLCALL Mix_GetMusicHookData(void);
+
+/*
+ * Add your own callback when a channel has finished playing. NULL
+ *  to disable callback. The callback may be called from the mixer's audio
+ *  callback or it could be called as a result of Mix_HaltChannel(), etc.
+ *  do not call SDL_LockAudio() from this callback; you will either be
+ *  inside the audio callback, or SDL_mixer will explicitly lock the audio
+ *  before calling your callback.
+ */
+extern DECLSPEC void SDLCALL Mix_ChannelFinished(void (SDLCALL *channel_finished)(int channel));
+
+
+/* Special Effects API by ryan c. gordon. (icculus@icculus.org) */
+
+#define MIX_CHANNEL_POST  -2
+
+/* This is the format of a special effect callback:
+ *
+ *   myeffect(int chan, void *stream, int len, void *udata);
+ *
+ * (chan) is the channel number that your effect is affecting. (stream) is
+ *  the buffer of data to work upon. (len) is the size of (stream), and
+ *  (udata) is a user-defined bit of data, which you pass as the last arg of
+ *  Mix_RegisterEffect(), and is passed back unmolested to your callback.
+ *  Your effect changes the contents of (stream) based on whatever parameters
+ *  are significant, or just leaves it be, if you prefer. You can do whatever
+ *  you like to the buffer, though, and it will continue in its changed state
+ *  down the mixing pipeline, through any other effect functions, then finally
+ *  to be mixed with the rest of the channels and music for the final output
+ *  stream.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ */
+typedef void (SDLCALL *Mix_EffectFunc_t)(int chan, void *stream, int len, void *udata);
+
+/*
+ * This is a callback that signifies that a channel has finished all its
+ *  loops and has completed playback. This gets called if the buffer
+ *  plays out normally, or if you call Mix_HaltChannel(), implicitly stop
+ *  a channel via Mix_AllocateChannels(), or unregister a callback while
+ *  it's still playing.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ */
+typedef void (SDLCALL *Mix_EffectDone_t)(int chan, void *udata);
+
+
+/* Register a special effect function. At mixing time, the channel data is
+ *  copied into a buffer and passed through each registered effect function.
+ *  After it passes through all the functions, it is mixed into the final
+ *  output stream. The copy to buffer is performed once, then each effect
+ *  function performs on the output of the previous effect. Understand that
+ *  this extra copy to a buffer is not performed if there are no effects
+ *  registered for a given chunk, which saves CPU cycles, and any given
+ *  effect will be extra cycles, too, so it is crucial that your code run
+ *  fast. Also note that the data that your function is given is in the
+ *  format of the sound device, and not the format you gave to Mix_OpenAudio(),
+ *  although they may in reality be the same. This is an unfortunate but
+ *  necessary speed concern. Use Mix_QuerySpec() to determine if you can
+ *  handle the data before you register your effect, and take appropriate
+ *  actions.
+ * You may also specify a callback (Mix_EffectDone_t) that is called when
+ *  the channel finishes playing. This gives you a more fine-grained control
+ *  than Mix_ChannelFinished(), in case you need to free effect-specific
+ *  resources, etc. If you don't need this, you can specify NULL.
+ * You may set the callbacks before or after calling Mix_PlayChannel().
+ * Things like Mix_SetPanning() are just internal special effect functions,
+ *  so if you are using that, you've already incurred the overhead of a copy
+ *  to a separate buffer, and that these effects will be in the queue with
+ *  any functions you've registered. The list of registered effects for a
+ *  channel is reset when a chunk finishes playing, so you need to explicitly
+ *  set them with each call to Mix_PlayChannel*().
+ * You may also register a special effect function that is to be run after
+ *  final mixing occurs. The rules for these callbacks are identical to those
+ *  in Mix_RegisterEffect, but they are run after all the channels and the
+ *  music have been mixed into a single stream, whereas channel-specific
+ *  effects run on a given channel before any other mixing occurs. These
+ *  global effect callbacks are call "posteffects". Posteffects only have
+ *  their Mix_EffectDone_t function called when they are unregistered (since
+ *  the main output stream is never "done" in the same sense as a channel).
+ *  You must unregister them manually when you've had enough. Your callback
+ *  will be told that the channel being mixed is (MIX_CHANNEL_POST) if the
+ *  processing is considered a posteffect.
+ *
+ * After all these effects have finished processing, the callback registered
+ *  through Mix_SetPostMix() runs, and then the stream goes to the audio
+ *  device.
+ *
+ * DO NOT EVER call SDL_LockAudio() from your callback function!
+ *
+ * returns zero if error (no such channel), nonzero if added.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
+
+
+/* You may not need to call this explicitly, unless you need to stop an
+ *  effect from processing in the middle of a chunk's playback.
+ * Posteffects are never implicitly unregistered as they are for channels,
+ *  but they may be explicitly unregistered through this function by
+ *  specifying MIX_CHANNEL_POST for a channel.
+ * returns zero if error (no such channel or effect), nonzero if removed.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_UnregisterEffect(int channel, Mix_EffectFunc_t f);
+
+
+/* You may not need to call this explicitly, unless you need to stop all
+ *  effects from processing in the middle of a chunk's playback. Note that
+ *  this will also shut off some internal effect processing, since
+ *  Mix_SetPanning() and others may use this API under the hood. This is
+ *  called internally when a channel completes playback.
+ * Posteffects are never implicitly unregistered as they are for channels,
+ *  but they may be explicitly unregistered through this function by
+ *  specifying MIX_CHANNEL_POST for a channel.
+ * returns zero if error (no such channel), nonzero if all effects removed.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_UnregisterAllEffects(int channel);
+
+
+#define MIX_EFFECTSMAXSPEED  "MIX_EFFECTSMAXSPEED"
+
+/*
+ * These are the internally-defined mixing effects. They use the same API that
+ *  effects defined in the application use, but are provided here as a
+ *  convenience. Some effects can reduce their quality or use more memory in
+ *  the name of speed; to enable this, make sure the environment variable
+ *  MIX_EFFECTSMAXSPEED (see above) is defined before you call
+ *  Mix_OpenAudio().
+ */
+
+
+/* Set the panning of a channel. The left and right channels are specified
+ *  as integers between 0 and 255, quietest to loudest, respectively.
+ *
+ * Technically, this is just individual volume control for a sample with
+ *  two (stereo) channels, so it can be used for more than just panning.
+ *  If you want real panning, call it like this:
+ *
+ *   Mix_SetPanning(channel, left, 255 - left);
+ *
+ * ...which isn't so hard.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the panning will be done to the final mixed stream before passing it on
+ *  to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally, and returns without
+ *  registering the effect function if the audio device is not configured
+ *  for stereo output. Setting both (left) and (right) to 255 causes this
+ *  effect to be unregistered, since that is the data's normal state.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if panning effect enabled. Note that an audio device in mono
+ *  mode is a no-op, but this call will return successful in that case.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetPanning(int channel, Uint8 left, Uint8 right);
+
+
+/* Set the position of a channel. (angle) is an integer from 0 to 360, that
+ *  specifies the location of the sound in relation to the listener. (angle)
+ *  will be reduced as neccesary (540 becomes 180 degrees, -100 becomes 260).
+ *  Angle 0 is due north, and rotates clockwise as the value increases.
+ *  For efficiency, the precision of this effect may be limited (angles 1
+ *  through 7 might all produce the same effect, 8 through 15 are equal, etc).
+ *  (distance) is an integer between 0 and 255 that specifies the space
+ *  between the sound and the listener. The larger the number, the further
+ *  away the sound is. Using 255 does not guarantee that the channel will be
+ *  culled from the mixing process or be completely silent. For efficiency,
+ *  the precision of this effect may be limited (distance 0 through 5 might
+ *  all produce the same effect, 6 through 10 are equal, etc). Setting (angle)
+ *  and (distance) to 0 unregisters this effect, since the data would be
+ *  unchanged.
+ *
+ * If you need more precise positional audio, consider using OpenAL for
+ *  spatialized effects instead of SDL_mixer. This is only meant to be a
+ *  basic effect for simple "3D" games.
+ *
+ * If the audio device is configured for mono output, then you won't get
+ *  any effectiveness from the angle; however, distance attenuation on the
+ *  channel will still occur. While this effect will function with stereo
+ *  voices, it makes more sense to use voices with only one channel of sound,
+ *  so when they are mixed through this effect, the positioning will sound
+ *  correct. You can convert them to mono through SDL before giving them to
+ *  the mixer in the first place if you like.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the positioning will be done to the final mixed stream before passing it
+ *  on to the audio device.
+ *
+ * This is a convenience wrapper over Mix_SetDistance() and Mix_SetPanning().
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if position effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetPosition(int channel, Sint16 angle, Uint8 distance);
+
+
+/* Set the "distance" of a channel. (distance) is an integer from 0 to 255
+ *  that specifies the location of the sound in relation to the listener.
+ *  Distance 0 is overlapping the listener, and 255 is as far away as possible
+ *  A distance of 255 does not guarantee silence; in such a case, you might
+ *  want to try changing the chunk's volume, or just cull the sample from the
+ *  mixing process with Mix_HaltChannel().
+ * For efficiency, the precision of this effect may be limited (distances 1
+ *  through 7 might all produce the same effect, 8 through 15 are equal, etc).
+ *  (distance) is an integer between 0 and 255 that specifies the space
+ *  between the sound and the listener. The larger the number, the further
+ *  away the sound is.
+ * Setting (distance) to 0 unregisters this effect, since the data would be
+ *  unchanged.
+ * If you need more precise positional audio, consider using OpenAL for
+ *  spatialized effects instead of SDL_mixer. This is only meant to be a
+ *  basic effect for simple "3D" games.
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the distance attenuation will be done to the final mixed stream before
+ *  passing it on to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if position effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetDistance(int channel, Uint8 distance);
+
+
+/*
+ * !!! FIXME : Haven't implemented, since the effect goes past the
+ *              end of the sound buffer. Will have to think about this.
+ *               --ryan.
+ */
+#if 0
+/* Causes an echo effect to be mixed into a sound. (echo) is the amount
+ *  of echo to mix. 0 is no echo, 255 is infinite (and probably not
+ *  what you want).
+ *
+ * Setting (channel) to MIX_CHANNEL_POST registers this as a posteffect, and
+ *  the reverbing will be done to the final mixed stream before passing it on
+ *  to the audio device.
+ *
+ * This uses the Mix_RegisterEffect() API internally. If you specify an echo
+ *  of zero, the effect is unregistered, as the data is already in that state.
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if reversing effect is enabled.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern no_parse_DECLSPEC int SDLCALL Mix_SetReverb(int channel, Uint8 echo);
+#endif
+
+/* Causes a channel to reverse its stereo. This is handy if the user has his
+ *  speakers hooked up backwards, or you would like to have a minor bit of
+ *  psychedelia in your sound code.  :)  Calling this function with (flip)
+ *  set to non-zero reverses the chunks's usual channels. If (flip) is zero,
+ *  the effect is unregistered.
+ *
+ * This uses the Mix_RegisterEffect() API internally, and thus is probably
+ *  more CPU intensive than having the user just plug in his speakers
+ *  correctly. Mix_SetReverseStereo() returns without registering the effect
+ *  function if the audio device is not configured for stereo output.
+ *
+ * If you specify MIX_CHANNEL_POST for (channel), then this the effect is used
+ *  on the final mixed stream before sending it on to the audio device (a
+ *  posteffect).
+ *
+ * returns zero if error (no such channel or Mix_RegisterEffect() fails),
+ *  nonzero if reversing effect is enabled. Note that an audio device in mono
+ *  mode is a no-op, but this call will return successful in that case.
+ *  Error messages can be retrieved from Mix_GetError().
+ */
+extern DECLSPEC int SDLCALL Mix_SetReverseStereo(int channel, int flip);
+
+/* end of effects API. --ryan. */
+
+
+/* Reserve the first channels (0 -> n-1) for the application, i.e. don't allocate
+   them dynamically to the next sample if requested with a -1 value below.
+   Returns the number of reserved channels.
+ */
+extern DECLSPEC int SDLCALL Mix_ReserveChannels(int num);
+
+/* Channel grouping functions */
+
+/* Attach a tag to a channel. A tag can be assigned to several mixer
+   channels, to form groups of channels.
+   If 'tag' is -1, the tag is removed (actually -1 is the tag used to
+   represent the group of all the channels).
+   Returns true if everything was OK.
+ */
+extern DECLSPEC int SDLCALL Mix_GroupChannel(int which, int tag);
+/* Assign several consecutive channels to a group */
+extern DECLSPEC int SDLCALL Mix_GroupChannels(int from, int to, int tag);
+/* Finds the first available channel in a group of channels,
+   returning -1 if none are available.
+ */
+extern DECLSPEC int SDLCALL Mix_GroupAvailable(int tag);
+/* Returns the number of channels in a group. This is also a subtle
+   way to get the total number of channels when 'tag' is -1
+ */
+extern DECLSPEC int SDLCALL Mix_GroupCount(int tag);
+/* Finds the "oldest" sample playing in a group of channels */
+extern DECLSPEC int SDLCALL Mix_GroupOldest(int tag);
+/* Finds the "most recent" (i.e. last) sample playing in a group of channels */
+extern DECLSPEC int SDLCALL Mix_GroupNewer(int tag);
+
+/* Play an audio chunk on a specific channel.
+   If the specified channel is -1, play on the first free channel.
+   If 'loops' is greater than zero, loop the sound that many times.
+   If 'loops' is -1, loop inifinitely (~65000 times).
+   Returns which channel was used to play the sound.
+*/
+#define Mix_PlayChannel(channel,chunk,loops) Mix_PlayChannelTimed(channel,chunk,loops,-1)
+/* The same as above, but the sound is played at most 'ticks' milliseconds */
+extern DECLSPEC int SDLCALL Mix_PlayChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ticks);
+extern DECLSPEC int SDLCALL Mix_PlayMusic(Mix_Music *music, int loops);
+#define Mix_PlayChannelVol(channel,chunk,loops,vol) Mix_PlayChannelTimedVolume(channel,chunk,loops,-1,vol)/*MIXER-X*/
+extern DECLSPEC int SDLCALL Mix_PlayChannelTimedVolume(int which, Mix_Chunk *chunk, int loops, int ticks, int volume);/*MIXER-X*/
+
+/* Fade in music or a channel over "ms" milliseconds, same semantics as the "Play" functions */
+extern DECLSPEC int SDLCALL Mix_FadeInMusic(Mix_Music *music, int loops, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeInMusicPos(Mix_Music *music, int loops, int ms, double position);
+#define Mix_FadeInChannel(channel,chunk,loops,ms) Mix_FadeInChannelTimed(channel,chunk,loops,ms,-1)
+extern DECLSPEC int SDLCALL Mix_FadeInChannelTimed(int channel, Mix_Chunk *chunk, int loops, int ms, int ticks);
+#define Mix_FadeInChannelVolume(channel,chunk,loops,ms,vol) Mix_FadeInChannelTimed(channel,chunk,loops,ms,-1,vol)/*MIXER-X*/
+extern DECLSPEC int SDLCALL Mix_FadeInChannelTimedVolume(int which, Mix_Chunk *chunk, int loops, int ms, int ticks, int volume);/*MIXER-X*/
+
+
+/* Set the volume in the range of 0-128 of a specific channel or chunk.
+   If the specified channel is -1, set volume for all channels.
+   Returns the original volume.
+   If the specified volume is -1, just return the current volume.
+*/
+extern DECLSPEC int SDLCALL Mix_Volume(int channel, int volume);
+extern DECLSPEC int SDLCALL Mix_VolumeChunk(Mix_Chunk *chunk, int volume);
+extern DECLSPEC int SDLCALL Mix_VolumeMusic(int volume);
+
+/* Halt playing of a particular channel */
+extern DECLSPEC int SDLCALL Mix_HaltChannel(int channel);
+extern DECLSPEC int SDLCALL Mix_HaltGroup(int tag);
+extern DECLSPEC int SDLCALL Mix_HaltMusic(void);
+
+/* Change the expiration delay for a particular channel.
+   The sample will stop playing after the 'ticks' milliseconds have elapsed,
+   or remove the expiration if 'ticks' is -1
+*/
+extern DECLSPEC int SDLCALL Mix_ExpireChannel(int channel, int ticks);
+
+/* Halt a channel, fading it out progressively till it's silent
+   The ms parameter indicates the number of milliseconds the fading
+   will take.
+ */
+extern DECLSPEC int SDLCALL Mix_FadeOutChannel(int which, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeOutGroup(int tag, int ms);
+extern DECLSPEC int SDLCALL Mix_FadeOutMusic(int ms);
+
+/* Query the fading status of a channel */
+extern DECLSPEC Mix_Fading SDLCALL Mix_FadingMusic(void);
+extern DECLSPEC Mix_Fading SDLCALL Mix_FadingChannel(int which);
+
+/* Pause/Resume a particular channel */
+extern DECLSPEC void SDLCALL Mix_Pause(int channel);
+extern DECLSPEC void SDLCALL Mix_Resume(int channel);
+extern DECLSPEC int SDLCALL Mix_Paused(int channel);
+
+/* Pause/Resume the music stream */
+extern DECLSPEC void SDLCALL Mix_PauseMusic(void);
+extern DECLSPEC void SDLCALL Mix_ResumeMusic(void);
+extern DECLSPEC void SDLCALL Mix_RewindMusic(void);
+extern DECLSPEC int SDLCALL Mix_PausedMusic(void);
+
+/* Set the current position in the music stream.
+   This returns 0 if successful, or -1 if it failed or isn't implemented.
+   This function is only implemented for MOD music formats (set pattern
+   order number) and for WAV, OGG, FLAC, MP3_MAD, MP3_MPG, and MODPLUG music
+   (set position in seconds), at the moment.
+*/
+extern DECLSPEC int SDLCALL Mix_SetMusicPosition(double position);
+/*
+    Get the time current position of music stream
+    returns -1.0 if this feature is not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicPosition(Mix_Music *music);
+/*
+    Get the total time length of music stream
+    returns -1.0 if this feature is not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicTotalTime(Mix_Music *music);
+
+/*
+    Get the loop start time position of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopStartTime(Mix_Music *music);
+/*
+    Get the loop end time position of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopEndTime(Mix_Music *music);
+/*
+    Get the loop time length of music stream
+    returns -1.0 if this feature is not used for this music or not supported for some codec
+ */
+extern DECLSPEC double SDLCALL Mix_GetMusicLoopLengthTime(Mix_Music *music);
+
+
+/* Check the status of a specific channel.
+   If the specified channel is -1, check all channels.
+*/
+extern DECLSPEC int SDLCALL Mix_Playing(int channel);
+extern DECLSPEC int SDLCALL Mix_PlayingMusic(void);
+
+/* Stop music and set external music playback command */
+extern DECLSPEC int SDLCALL Mix_SetMusicCMD(const char *command);
+
+/* Synchro value is set by MikMod from modules while playing */
+extern DECLSPEC int SDLCALL Mix_SetSynchroValue(int value);
+extern DECLSPEC int SDLCALL Mix_GetSynchroValue(void);
+
+/* Set/Get/Iterate SoundFonts paths to use by supported MIDI backends */
+extern DECLSPEC int SDLCALL Mix_SetSoundFonts(const char *paths);
+extern DECLSPEC const char* SDLCALL Mix_GetSoundFonts(void);
+extern DECLSPEC int SDLCALL Mix_EachSoundFont(int (SDLCALL *function)(const char*, void*), void *data);
+
+/* Get the Mix_Chunk currently associated with a mixer channel
+    Returns NULL if it's an invalid channel, or there's no chunk associated.
+*/
+extern DECLSPEC Mix_Chunk * SDLCALL Mix_GetChunk(int channel);
+
+/* Close the mixer, halting all playing audio */
+extern DECLSPEC void SDLCALL Mix_CloseAudio(void);
+
+/* Add additional Timidity bank path */
+extern DECLSPEC void SDLCALL Mix_Timidity_addToPathList(const char *path);
+
+/* ADLMIDI Setup functions */
+/* Get count of available hardcoded banks */
+extern DECLSPEC int    SDLCALL Mix_ADLMIDI_getTotalBanks(void);
+/* Get array of the bank names */
+extern DECLSPEC const char *const *SDLCALL Mix_ADLMIDI_getBankNames(void);
+/* Get bank ID */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getBankID(void);
+/* Set bank ID (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setBankID(int bnk);
+/* Get state of deep vibrato */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getTremolo(void);
+/* Set deep tremolo mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setTremolo(int tr);
+/* Get state of deep vibrato */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getVibrato(void);
+/* Set deep vibrato mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setVibrato(int vib);
+/* Get state of scalable modulation mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getScaleMod(void);
+/* Set scalable modulation mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setScaleMod(int sc);
+/* Get state of adlib drums mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getAdLibMode(void);
+/* Set adlib drums mode mode (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setAdLibMode(int tr);
+/* Get state of logarithmic mode */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getLogarithmicVolumes(void);
+/* Set logarithmic volumes mode in the generic/CMF volume models (0 off, 1 on) (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setLogarithmicVolumes(int lv);
+/* Get current volume model ID */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getVolumeModel(void);
+/* Change current volumes model (Applying on stop/play) */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setVolumeModel(int vm);
+/* Get full range mode for CC74-Brightness controller */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getFullRangeBrightness(void);
+/* Set full range mode for CC74-Brightness controller */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setFullRangeBrightness(int frb);
+/* Get the current OPL3 Emulator for ADLMIDI */
+extern DECLSPEC int  SDLCALL Mix_ADLMIDI_getEmulator(void);
+/* Select the OPL3 Emulator for ADLMIDI */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setEmulator(int emu);
+/* Reset all ADLMIDI properties to default state */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setSetDefaults(void);
+
+/* Sets WOPL bank file for ADLMIDI playing device, affects on MIDI file reopen */
+extern DECLSPEC void SDLCALL Mix_ADLMIDI_setCustomBankFile(const char *bank_wonl_path);
+
+/* Reset all OPNMIDI properties to default state */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setSetDefaults(void);
+/* Get full range mode for CC74-Brightness controller */
+extern DECLSPEC int  SDLCALL Mix_OPNMIDI_getFullRangeBrightness(void);
+/* Set full range mode for CC74-Brightness controller */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setFullRangeBrightness(int frb);
+/* Get the OPN2 Emulator for OPNMIDI */
+extern DECLSPEC int  SDLCALL Mix_OPNMIDI_getEmulator(void);
+/* Select the OPN2 Emulator for OPNMIDI */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setEmulator(int emu);
+/* Sets WOPN bank file for OPNMIDI playing device, affects on MIDI file reopen */
+extern DECLSPEC void SDLCALL Mix_OPNMIDI_setCustomBankFile(const char *bank_wonp_path);
+
+/* Get type of MIDI player library currently in use */
+extern DECLSPEC int  SDLCALL Mix_GetMidiPlayer(void);
+
+/* Get type of MIDI player library prepared for next opening of MIDI file */
+extern DECLSPEC int  SDLCALL Mix_GetNextMidiPlayer(void);
+
+/* Set the MIDI playing library (ADLMIDI, Timidity, Native MIDI (if available) and FluidSynth) */
+extern DECLSPEC int  SDLCALL Mix_SetMidiPlayer(int player);
+
+/* Disables support of MIDI file arguments */
+extern DECLSPEC void SDLCALL Mix_SetLockMIDIArgs(int lock_midiargs);
+
+/*  DEPRECATED NAMES for new-added SDL Mixer X functions
+    Those names are made with mistake - beginning with "MIX_" than "Mix_"
+    which makes confusion when you looking for Mix_ function in your IDE
+    because some applications are still use them, to don't break ABI we will keep those
+    aliases until we will remove all usages of them from applications and libraries are used them
+*/
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_GetMidiDevice(void));
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_GetNextMidiDevice(void));
+DEPRECATED(extern DECLSPEC int  SDLCALL Mix_SetMidiDevice(int player));
+
+/* We'll use SDL for reporting errors */
+#define Mix_SetError    SDL_SetError
+#define Mix_GetError    SDL_GetError
+#define Mix_ClearError  SDL_ClearError
+
+/* Ends C function definitions when using C++ */
+#ifdef __cplusplus
+}
+#endif
+#include "close_code.h"
+
+#endif /* SDL_MIXER_H_ */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/begin_code.h b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/begin_code.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c2106246f2583ba711451bc7e39fc2af7e9eec7
--- /dev/null
+++ b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/begin_code.h
@@ -0,0 +1,167 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file begin_code.h
+ *
+ *  This file sets things up for C dynamic library function definitions,
+ *  static inlined functions, and structures aligned at 4-byte alignment.
+ *  If you don't like ugly C preprocessor code, don't look at this file. :)
+ */
+
+/* This shouldn't be nested -- included it around code only. */
+#ifdef _begin_code_h
+#error Nested inclusion of begin_code.h
+#endif
+#define _begin_code_h
+
+#ifndef SDL_DEPRECATED
+#  if (__GNUC__ >= 4)  /* technically, this arrived in gcc 3.1, but oh well. */
+#    define SDL_DEPRECATED __attribute__((deprecated))
+#  else
+#    define SDL_DEPRECATED
+#  endif
+#endif
+
+#ifndef SDL_UNUSED
+#  ifdef __GNUC__
+#    define SDL_UNUSED __attribute__((unused))
+#  else
+#    define SDL_UNUSED
+#  endif
+#endif
+
+/* Some compilers use a special export keyword */
+#ifndef DECLSPEC
+# if defined(__WIN32__) || defined(__WINRT__)
+#  ifdef __BORLANDC__
+#   ifdef BUILD_SDL
+#    define DECLSPEC
+#   else
+#    define DECLSPEC    __declspec(dllimport)
+#   endif
+#  else
+#   define DECLSPEC __declspec(dllexport)
+#  endif
+# elif defined(__OS2__)
+#   ifdef BUILD_SDL
+#    define DECLSPEC    __declspec(dllexport)
+#   else
+#    define DECLSPEC
+#   endif
+# else
+#  if defined(__GNUC__) && __GNUC__ >= 4
+#   define DECLSPEC __attribute__ ((visibility("default")))
+#  else
+#   define DECLSPEC
+#  endif
+# endif
+#endif
+
+/* By default SDL uses the C calling convention */
+#ifndef SDLCALL
+#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__)
+#define SDLCALL __cdecl
+#elif defined(__OS2__) || defined(__EMX__)
+#define SDLCALL _System
+# if defined (__GNUC__) && !defined(_System)
+#  define _System /* for old EMX/GCC compat.  */
+# endif
+#else
+#define SDLCALL
+#endif
+#endif /* SDLCALL */
+
+/* Removed DECLSPEC on Symbian OS because SDL cannot be a DLL in EPOC */
+#ifdef __SYMBIAN32__
+#undef DECLSPEC
+#define DECLSPEC
+#endif /* __SYMBIAN32__ */
+
+/* Force structure packing at 4 byte alignment.
+   This is necessary if the header is included in code which has structure
+   packing set to an alternate value, say for loading structures from disk.
+   The packing is reset to the previous value in close_code.h
+ */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef _MSC_VER
+#pragma warning(disable: 4103)
+#endif
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#ifdef _M_X64
+/* Use 8-byte alignment on 64-bit architectures, so pointers are aligned */
+#pragma pack(push,8)
+#else
+#pragma pack(push,4)
+#endif
+#endif /* Compiler needs structure packing set */
+
+#ifndef SDL_INLINE
+#if defined(__GNUC__)
+#define SDL_INLINE __inline__
+#elif defined(_MSC_VER) || defined(__BORLANDC__) || \
+      defined(__DMC__) || defined(__SC__) || \
+      defined(__WATCOMC__) || defined(__LCC__) || \
+      defined(__DECC) || defined(__CC_ARM)
+#define SDL_INLINE __inline
+#ifndef __inline__
+#define __inline__ __inline
+#endif
+#else
+#define SDL_INLINE inline
+#ifndef __inline__
+#define __inline__ inline
+#endif
+#endif
+#endif /* SDL_INLINE not defined */
+
+#ifndef SDL_FORCE_INLINE
+#if defined(_MSC_VER)
+#define SDL_FORCE_INLINE __forceinline
+#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) )
+#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__
+#else
+#define SDL_FORCE_INLINE static SDL_INLINE
+#endif
+#endif /* SDL_FORCE_INLINE not defined */
+
+#ifndef SDL_NORETURN
+#if defined(__GNUC__)
+#define SDL_NORETURN __attribute__((noreturn))
+#elif defined(_MSC_VER)
+#define SDL_NORETURN __declspec(noreturn)
+#else
+#define SDL_NORETURN
+#endif
+#endif /* SDL_NORETURN not defined */
+
+/* Apparently this is needed by several Windows compilers */
+#if !defined(__MACH__)
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif /* NULL */
+#endif /* ! Mac OS X - breaks precompiled headers */
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/close_code.h b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/close_code.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3b70a4c833d80305bbffd306c8ac099babdf419
--- /dev/null
+++ b/libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2/close_code.h
@@ -0,0 +1,37 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+/**
+ *  \file close_code.h
+ *
+ *  This file reverses the effects of begin_code.h and should be included
+ *  after you finish any function and structure declarations in your headers
+ */
+
+#undef _begin_code_h
+
+/* Reset structure packing at previous byte alignment */
+#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__)
+#ifdef __BORLANDC__
+#pragma nopackwarning
+#endif
+#pragma pack(pop)
+#endif /* Compiler needs structure packing set */
diff --git a/libs/SDLMixerX/x86_64-w64-mingw32/lib/libSDL2_mixer_ext.dll.a b/libs/SDLMixerX/x86_64-w64-mingw32/lib/libSDL2_mixer_ext.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..c1cede98d61ceeedfbd113c79c9d0da92e40b3d2
Binary files /dev/null and b/libs/SDLMixerX/x86_64-w64-mingw32/lib/libSDL2_mixer_ext.dll.a differ
diff --git a/bin/Resources/fmod.dll b/libs/dll-binaries/i686/Old/fmod.dll
similarity index 100%
rename from bin/Resources/fmod.dll
rename to libs/dll-binaries/i686/Old/fmod.dll
diff --git a/bin/Resources/fmodexL.dll b/libs/dll-binaries/i686/Old/fmodexL.dll
similarity index 100%
rename from bin/Resources/fmodexL.dll
rename to libs/dll-binaries/i686/Old/fmodexL.dll
diff --git a/bin/Resources/libgcc_s_dw2-1.dll b/libs/dll-binaries/i686/Old/libgcc_s_dw2-1.dll
similarity index 100%
rename from bin/Resources/libgcc_s_dw2-1.dll
rename to libs/dll-binaries/i686/Old/libgcc_s_dw2-1.dll
diff --git a/bin/Resources/libintl-8.dll b/libs/dll-binaries/i686/Old/libintl-8.dll
similarity index 100%
rename from bin/Resources/libintl-8.dll
rename to libs/dll-binaries/i686/Old/libintl-8.dll
diff --git a/libs/dll-binaries/i686/exchndl.dll b/libs/dll-binaries/i686/exchndl.dll
new file mode 100644
index 0000000000000000000000000000000000000000..d6beb764a77629be1f8bcc3677fb85927eec8672
Binary files /dev/null and b/libs/dll-binaries/i686/exchndl.dll differ
diff --git a/bin/Resources/fmodex.dll b/libs/dll-binaries/i686/fmodex.dll
similarity index 100%
rename from bin/Resources/fmodex.dll
rename to libs/dll-binaries/i686/fmodex.dll
diff --git a/libs/dll-binaries/i686/libgme.dll b/libs/dll-binaries/i686/libgme.dll
new file mode 100644
index 0000000000000000000000000000000000000000..9a31bc4d2e0c1d0c0be5c8215c704a92320b9e62
Binary files /dev/null and b/libs/dll-binaries/i686/libgme.dll differ
diff --git a/libs/dll-binaries/i686/mgwhelp.dll b/libs/dll-binaries/i686/mgwhelp.dll
new file mode 100644
index 0000000000000000000000000000000000000000..3cf97424d2c3e762d768b36c4a6855056a6e20c4
Binary files /dev/null and b/libs/dll-binaries/i686/mgwhelp.dll differ
diff --git a/bin/Resources/fmod64.dll b/libs/dll-binaries/x86_64/Old/fmod64.dll
similarity index 100%
rename from bin/Resources/fmod64.dll
rename to libs/dll-binaries/x86_64/Old/fmod64.dll
diff --git a/bin/Resources/fmodexL64.dll b/libs/dll-binaries/x86_64/Old/fmodexL64.dll
similarity index 100%
rename from bin/Resources/fmodexL64.dll
rename to libs/dll-binaries/x86_64/Old/fmodexL64.dll
diff --git a/libs/dll-binaries/x86_64/exchndl.dll b/libs/dll-binaries/x86_64/exchndl.dll
new file mode 100644
index 0000000000000000000000000000000000000000..747d7a3d560364af4d1ab588e251afd5d7b12871
Binary files /dev/null and b/libs/dll-binaries/x86_64/exchndl.dll differ
diff --git a/bin/Resources/fmodex64.dll b/libs/dll-binaries/x86_64/fmodex64.dll
similarity index 100%
rename from bin/Resources/fmodex64.dll
rename to libs/dll-binaries/x86_64/fmodex64.dll
diff --git a/libs/dll-binaries/x86_64/libgme.dll b/libs/dll-binaries/x86_64/libgme.dll
new file mode 100644
index 0000000000000000000000000000000000000000..598c2d71cf80e4d2556691e1eb71edd7020a2f26
Binary files /dev/null and b/libs/dll-binaries/x86_64/libgme.dll differ
diff --git a/libs/dll-binaries/x86_64/mgwhelp.dll b/libs/dll-binaries/x86_64/mgwhelp.dll
new file mode 100644
index 0000000000000000000000000000000000000000..4e30e140e1b4fdfbb1b6ca3b72092e9b85b9bb9a
Binary files /dev/null and b/libs/dll-binaries/x86_64/mgwhelp.dll differ
diff --git a/libs/gme/CMakeLists.txt b/libs/gme/CMakeLists.txt
index 8beee872f9e44da54c16a2f4e8f489570915eedf..392b01856da89e07ac5430d4db0df223194152f3 100644
--- a/libs/gme/CMakeLists.txt
+++ b/libs/gme/CMakeLists.txt
@@ -4,7 +4,7 @@ project(libgme)
 include (CheckCXXCompilerFlag)
 
 # When version is changed, also change the one in gme/gme.h to match
-set(GME_VERSION 0.6.0 CACHE INTERNAL "libgme Version")
+set(GME_VERSION 0.6.2 CACHE INTERNAL "libgme Version")
 
 # 2.6+ always assumes FATAL_ERROR, but 2.4 and below don't.
 # Of course, 2.4 might work, in which case you're welcome to drop
@@ -57,6 +57,8 @@ if (USE_GME_NSFE AND NOT USE_GME_NSF)
     SET(USE_GME_NSF 1 CACHE BOOL "Enable NES NSF music emulation" FORCE)
 endif()
 
+option(BUILD_SHARED_LIBS "Build shared library (set to OFF for static library)" ON)
+
 # Check for GCC "visibility" support.
 if (CMAKE_COMPILER_IS_GNUCXX)
    check_cxx_compiler_flag (-fvisibility=hidden __LIBGME_TEST_VISIBILITY)
@@ -79,10 +81,10 @@ if (CMAKE_COMPILER_IS_GNUCXX)
          endif()
       endif()
    endif() # test visibility
-endif (CMAKE_COMPILER_IS_GNUCXX)
 
-# Cache this result
-set( LIBGME_HAVE_GCC_VISIBILITY ${ENABLE_VISIBILITY} CACHE BOOL "GCC support for hidden visibility")
+    # Cache this result
+    set( LIBGME_HAVE_GCC_VISIBILITY ${ENABLE_VISIBILITY} CACHE BOOL "GCC support for hidden visibility")
+endif (CMAKE_COMPILER_IS_GNUCXX)
 
 # Shared library defined here
 add_subdirectory(gme)
diff --git a/libs/gme/changes.txt b/libs/gme/changes.txt
index 62391ebb5e286fa5a6d0242033a001f655bd4f25..034ba48217586d0bea7c2a72ffd7565c94e4c970 100644
--- a/libs/gme/changes.txt
+++ b/libs/gme/changes.txt
@@ -1,262 +1,5 @@
 Game_Music_Emu Change Log
 -------------------------
 
-Game_Music_Emu 0.6.0
---------------------
-
-- Note: A 0.5.6 release was referenced but never tagged or packaged.
-
-- SPC improvements:
-    - Switched to newer snes_spc 0.9.0 for SPC emulation. Uses fast DSP.
-    - Fixed Spc_Emu::gain().
-    - Fixed support for files <0x10200 bytes.
-
-- Other bugfixes:
-    - Fixed a couple of GBS bugs, one involving access of memory after
-      realloc.
-    - Blip_Buffer works on systems where 'double' is a single-precision
-      floating-point type.
-    - Fix uninitialized buffer size in dual_resampler.
-    - Compilation warnings squashed out as of clang 3.3-pre and gcc 4.7.2.
-
-- API changes/additions:
-    - Removed documentation of C++ interface, as the C interface in gme.h is
-      the only supported one.
-    - Added gme_enable_accuracy() for enabling more accurate sound emulation
-      options (currently affects SPC only).
-
-- Build system improvements:
-    - Add pkg_config support.
-    - Fix build on case-insensitive systems.
-    - Allow for install on Cygwin.
-    - Fix install on multilib systems, such as many 64-bit distros (CMake must
-      be able to figure out your system's libsuffix, if any).
-    - C++ implementation symbols are not leaked into the resultant library
-      file (requires symbol visibility support).
-
-- Sample player improvements:
-    - Can toggle fast/accurate emulation (with the 'A' key).
-
-Game_Music_Emu 0.5.5
---------------------
-- CMake build support has been added.  You can build Game_Music_Emu as
-a shared library and install it so that you do not have to include your
-own copy if you know libgme will be present on your target system.
-Requires CMake 2.6 or higher.
-
-
-Game_Music_Emu 0.5.2
---------------------
-- *TONS* of changes and improvements. You should re-read the new header
-files and documentation as the changes will allow you to simplify your
-code a lot (it might even be simpler to just rewrite it). Existing code
-should continue to work without changes in most cases (see Deprecated
-features in gme.txt).
-
-- New file formats: AY, HES, KSS, SAP, NSFE
-
-- All-new comprehensive C interface (also usable from C++). Simplifies
-many things, especially file loading, and brings everything together in
-one header file (gme.h).
-
-- Information tags and track names and times can be accessed for all
-game music formats
-
-- New features supported by all emulators: end of track fading,
-automatic silence detection, adjustable song tempo, seek to new time in
-track
-
-- Updated mini player example to support track names and times, echo,
-tempo, and channel muting, and added visual waveform display
-
-- Improved configuration to use blargg_config.h, which you can modify
-and keep when you update to a newer libary version. Includes flag for
-library to automatically handle gzipped files using zlib (so you don't
-need to use Gzip_File_Reader anymore).
-
-- GBS: Fixed wave channel to not reset waveform when APU is powered off
-(affected Garfield). Also improved invalid bank selection (affected Game
-& Watch and others).
-
-- VGM: Added support for alternate noise shifter register
-configurations, used by other systems like the BBC Micro.
-
-- SPC: Removed IPL ROM dump from emulator, as none of the SPC files I
-scanned needed it, and an SPC file can include a copy if necessary. Also
-re-enabled supposed clamping in gaussian interpolation between the third
-and fourth lookups, though I don't know whether it matters
-
-- Added Music_Emu::load_mem() to use music data already in memory
-(without copying it)
-
-- Added Music_Emu::warning(), which reports minor problems when loading
-and playing a music file
-
-- Added Music_Emu::set_gain() for uniform adjustment of gain. Can only
-be set during initialization, so not useful as a general volume control.
-
-- Added custom operator new to ensure that no exceptions are thrown in
-the library (I'd use std::nothrow if it were part of pre-ISO (ARM) C++)
-
-- Added BLIP_BUFFER_FAST flag to blargg_config.h to use a lower quality
-bandlimited synthesis in "classic" emulators, which might help
-performance on ancient processors (measure first!). Don't use this
-unless absolutely necessary, as quality suffers.
-
-- Improved performance a bit for x86 platforms
-
-- Text files now in DOS newline format so they will open in Notepad
-properly
-
-- Removed requirement that file header structures not have any padding
-added to the end
-
-- Fixed common bug in all CPU emulators where negative program counter
-could crash emulator (occurred during a negative branch from the
-beginning of memory). Also fixed related bug in Z80 emulator for
-IX/IY+displacement mode.
-
-- Eliminated all warnings when compiling on gcc 4.0. The following
-generates no diagnostics:
-
-	gcc -S gme/*.cpp -o /dev/null -ansi -fno-gnu-keywords
-	-fno-nonansi-builtins -pedantic -W -Wabi -Wall -Wcast-align
-	-Wcast-qual -Wchar-subscripts -Wdisabled-optimization -Werror
-	-Winline -Wlong-long -Wmultichar -Winvalid-offsetof
-	-Wnon-virtual-dtor -Woverloaded-virtual -Wparentheses
-	-Wpointer-arith -Wredundant-decls -Wreorder -Wsign-compare
-	-Wsign-promo -Wunknown-pragmas -Wwrite-strings
-
-
-Game_Music_Emu 0.3.0
---------------------
-- Added more demos, including music player using the SDL multimedia
-library for sound, and improved documentation
-
-- All: Improved interface to emulators to allow simpler setup and
-loading. Instead of various init() functions, all now support
-set_sample_rate( long rate ) and load( const char* file_path ).
-
-- All: Removed error return from start_track() and play(), and added
-error_count() to get the total number of emulation errors since the
-track was last started. See demos for examples of new usage.
-
-- All: Fixed mute_voices() muting to be preserved after loading files
-and starting tracks, instead of being cleared as it was whenever a track
-was started
-
-- VGM: Rewrote Vgm_Emu to support Sega Genesis/Mega Drive FM sound at
-any sample rate with optional FM oversampling, support for alternate
-YM2612 sound cores, and support for optional YM2413
-
-- VGM: Added tempo control, useful for slowing 60Hz NTSC Sega Genesis
-music to 50Hz PAL
-
-- VGM: Removed Vgm_Emu::track_data(), since I realized that this
-information is already present in the VGM header (oops!)
-
-- GYM: Changed Gym_Emu::track_length() operation (see Gym_Emu.h)
-
-- NSF: Added support for Sunsoft FME-7 sound chip used by Gimmick
-soundtrack
-
-- NSF: Fixed Namco 106 problems with Final Lap and others
-
-- Moved library sources to gme/ directory to reduce clutter, and merged
-boost/ functionality into blargg_common.h
-
-- Added Gzip_File_Reader for transparently using gzipped files
-
-
-Game_Music_Emu 0.2.4
---------------------
-- Created a discussion forum for problems and feedback:
-http://groups-beta.google.com/group/blargg-sound-libs
-
-- Changed error return value of Blip_Buffer::sample_rate() (also for
-Stereo_Buffer, Effects_Buffer, etc.) to blargg_err_t (defined in
-blargg_common.h), to make error reporting consistent with other
-functions. This means the "no error" return value is the opposite of
-what it was before, which will break current code which checks the error
-return value:
-
-	// current code (broken)
-	if ( !buf.sample_rate( samples_per_sec ) )
-		out_of_memory();
-	
-	// quick-and-dirty fix (just remove the ! operation)
-	if ( buf.sample_rate( samples_per_sec ) )
-		out_of_memory();
-	
-	// proper fix
-	blargg_err_t error = buf.sample_rate( samples_per_sec );
-	if ( error )
-		report_error( error );
-
-- Implemented workaround for MSVC++ 6 compiler limitations, allowing it
-to work on that compiler again
-
-- Added sample clamping to avoid wrap-around at high volumes, allowing
-higher volume with little distortion
-
-- Added to-do list and design notes
-
-- Added Music_Emu::skip( long sample_count ) to skip ahead in current
-track
-
-- Added Gym_Emu::track_length() and Vgm_Emu::track_length() for
-determining the length of non-looped GYM and VGM files
-
-- Partially implemented DMC non-linearity when its value is directly set
-using $4011, which reduces previously over-emphasized "popping" of
-percussion on some games (TMNT II in particular)
-
-- Fixed Fir_Resampler, used for SPC and GYM playback (was incorrectly
-using abs() instead of fabs()...argh)
-
-- Fixed SPC emulation bugs: eliminated clicks in Plok! soundtrack and
-now stops sample slightly earlier than the end, as the SNES does. Fixed
-a totally broken CPU addressing mode.
-
-- Fixed Konami VRC6 saw wave (was very broken before). Now VRC6 music
-sounds decent
-
-- Fixed a minor GBS emulation bug
-
-- Fixed GYM loop point bug when track was restarted before loop point
-had been reached
-
-- Made default GBS frequency equalization less muffled
-
-- Added pseudo-surround effect removal for SPC files
-
-- Added Music_Emu::voice_names() which returns names for each voice.
-
-- Added BLARGG_SOURCE_BEGIN which allows custom compiler options to be
-easily set for library sources
-
-- Changed assignment of expansion sound chips in Nsf_Emu to be spread
-more evenly when using Effects_Buffer
-
-- Changed 'size_t' values in Blip_Buffer interface to 'long'
-
-- Changed demo to generate a WAVE sound file rather than an AIFF file
-
-
-Game_Music_Emu 0.2.0
---------------------
-- Redid framework and rewrote/cleaned up emulators
-
-- Changed licensing to GNU Lesser General Public License (LGPL)
-
-- Added Sega Genesis GYM and Super Nintendo SPC emulators
-
-- Added Namco-106 and Konami VRC6 sound chip support to NSF emulator
-
-- Eliminated use of static mutable data in emulators, allowing
-multi-instance safety
-
-
-Game_Music_Emu 0.1.0
---------------------
-- First release
+Please see the git version history (e.g. git shortlog tags/0.6.0..tags/0.6.1)
+for the accurate change log.
diff --git a/libs/gme/demo/basics.c b/libs/gme/demo/basics.c
index 551782518aad88cd0fdb7bbe4fae4e2cab168654..741574afe051e8455b816fd3c6275224b87c10e3 100644
--- a/libs/gme/demo/basics.c
+++ b/libs/gme/demo/basics.c
@@ -1,7 +1,5 @@
 /* C example that opens a game music file and records 10 seconds to "out.wav" */
 
-static char filename [] = "test.nsf"; /* opens this file (can be any music type) */
-
 #include "gme/gme.h"
 
 #include "Wave_Writer.h" /* wave_ functions for writing sound file */
@@ -10,10 +8,15 @@ static char filename [] = "test.nsf"; /* opens this file (can be any music type)
 
 void handle_error( const char* str );
 
-int main()
+int main(int argc, char *argv[])
 {
+	const char *filename = "test.nsf"; /* Default file to open */
+	if ( argc >= 2 )
+		filename = argv[1];
+
 	long sample_rate = 44100; /* number of samples per second */
-	int track = 0; /* index of track to play (0 = first) */
+	/* index of track to play (0 = first) */
+	int track = argc >= 3 ? atoi(argv[2]) : 0;
 	
 	/* Open music file in new emulator */
 	Music_Emu* emu;
diff --git a/libs/gme/demo/cpp_basics.cpp b/libs/gme/demo/cpp_basics.cpp
index 53fab4186aa2209fd93b253f4569b93b1f304ad0..5222fe271449fc4ec4b98913553f440b0a028032 100644
--- a/libs/gme/demo/cpp_basics.cpp
+++ b/libs/gme/demo/cpp_basics.cpp
@@ -1,7 +1,5 @@
 // C++ example that opens a game music file and records 10 seconds to "out.wav"
 
-static char filename [] = "test.nsf"; /* opens this file (can be any music type) */
-
 #include "gme/Music_Emu.h"
 
 #include "Wave_Writer.h"
@@ -10,10 +8,15 @@ static char filename [] = "test.nsf"; /* opens this file (can be any music type)
 
 void handle_error( const char* str );
 
-int main()
+int main(int argc, char *argv[])
 {
+	const char *filename = "test.nsf"; /* Default file to open */
+	if ( argc >= 2 )
+		filename = argv[1];
+
 	long sample_rate = 44100; // number of samples per second
-	int track = 0; // index of track to play (0 = first)
+	// index of track to play (0 = first)
+	int track = argc >= 3 ? atoi(argv[2]) : 0;
 	
 	// Determine file type
 	gme_type_t file_type;
diff --git a/libs/gme/gme.txt b/libs/gme/gme.txt
index d9a2452c70e91388c1ed9416b1e7f439a4ac60a9..5a7d2f560f0ce2c6efd52bc0a8e7bf07e3e9a25b 100644
--- a/libs/gme/gme.txt
+++ b/libs/gme/gme.txt
@@ -1,10 +1,10 @@
-Game_Music_Emu 0.6.0
+Game_Music_Emu 0.6.2
 --------------------
-Author : Shay Green <gblargg@gmail.com>
-Website: http://www.slack.net/~ant/libs/
-Forum  : http://groups.google.com/group/blargg-sound-libs
-Source : https://code.google.com/p/game-music-emu/
-License: GNU Lesser General Public License (LGPL)
+Author     : Shay Green <gblargg@gmail.com>
+Maintainer : Michael Pyne <mpyne@purinchu.net>
+Website    : https://bitbucket.org/mpyne/game-music-emu/
+Source     : https://bitbucket.org/mpyne/game-music-emu/
+License    : GNU Lesser General Public License (LGPL), see LICENSE.txt
 
 Contents
 --------
diff --git a/libs/gme/gme/CMakeLists.txt b/libs/gme/gme/CMakeLists.txt
index 3c6464fc775903829082d5c9e61bd7441f2b15e8..534be8a858b4c2d0e546d8096318aff4346e434a 100644
--- a/libs/gme/gme/CMakeLists.txt
+++ b/libs/gme/gme/CMakeLists.txt
@@ -143,7 +143,7 @@ add_definitions(-DBLARGG_BUILD_DLL)
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
 # Add library to be compiled.
-add_library(gme SHARED ${libgme_SRCS})
+add_library(gme ${libgme_SRCS})
 
 # The version is the release.  The "soversion" is the API version.  As long
 # as only build fixes are performed (i.e. no backwards-incompatible changes
@@ -159,4 +159,4 @@ install(TARGETS gme LIBRARY DESTINATION lib${LIB_SUFFIX}
                     ARCHIVE DESTINATION lib) # DLL platforms
 
 install(FILES ${EXPORTED_HEADERS} DESTINATION include/gme)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc DESTINATION lib/pkgconfig)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig)
diff --git a/libs/gme/gme/Data_Reader.cpp b/libs/gme/gme/Data_Reader.cpp
index 5bbfbf551d8d54304d9bc1ee81a4b860ab850e15..f18928f4bf0d9b5660d2e1ead32e458c57368cf6 100644
--- a/libs/gme/gme/Data_Reader.cpp
+++ b/libs/gme/gme/Data_Reader.cpp
@@ -22,8 +22,13 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 
 const char Data_Reader::eof_error [] = "Unexpected end of file";
 
+#define RETURN_VALIDITY_CHECK( cond ) \
+	do { if ( unlikely( !(cond) ) ) return "Corrupt file"; } while(0)
+
 blargg_err_t Data_Reader::read( void* p, long s )
 {
+	RETURN_VALIDITY_CHECK( s > 0 );
+
 	long result = read_avail( p, s );
 	if ( result != s )
 	{
@@ -38,6 +43,8 @@ blargg_err_t Data_Reader::read( void* p, long s )
 
 blargg_err_t Data_Reader::skip( long count )
 {
+	RETURN_VALIDITY_CHECK( count >= 0 );
+
 	char buf [512];
 	while ( count )
 	{
@@ -54,7 +61,8 @@ long File_Reader::remain() const { return size() - tell(); }
 
 blargg_err_t File_Reader::skip( long n )
 {
-	assert( n >= 0 );
+	RETURN_VALIDITY_CHECK( n >= 0 );
+
 	if ( !n )
 		return 0;
 	return seek( tell() + n );
@@ -67,13 +75,14 @@ Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
 	in = dr;
 	remain_ = dr->remain();
 	if ( remain_ > size )
-		remain_ = size;
+		remain_ = max( 0l, size );
 }
 
 long Subset_Reader::remain() const { return remain_; }
 
 long Subset_Reader::read_avail( void* p, long s )
 {
+	s = max( 0l, s );
 	if ( s > remain_ )
 		s = remain_;
 	remain_ -= s;
@@ -85,7 +94,7 @@ long Subset_Reader::read_avail( void* p, long s )
 Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
 {
 	header = (char const*) h;
-	header_end = header + size;
+	header_end = header + max( 0l, size );
 	in = r;
 }
 
@@ -93,6 +102,7 @@ long Remaining_Reader::remain() const { return header_end - header + in->remain(
 
 long Remaining_Reader::read_first( void* out, long count )
 {
+	count = max( 0l, count );
 	long first = header_end - header;
 	if ( first )
 	{
@@ -107,8 +117,9 @@ long Remaining_Reader::read_first( void* out, long count )
 
 long Remaining_Reader::read_avail( void* out, long count )
 {
+	count = max( 0l, count );
 	long first = read_first( out, count );
-	long second = count - first;
+	long second = max( 0l, count - first );
 	if ( second )
 	{
 		second = in->read_avail( (char*) out + first, second );
@@ -120,8 +131,9 @@ long Remaining_Reader::read_avail( void* out, long count )
 
 blargg_err_t Remaining_Reader::read( void* out, long count )
 {
+	count = max( 0l, count );
 	long first = read_first( out, count );
-	long second = count - first;
+	long second = max( 0l, count - first );
 	if ( !second )
 		return 0;
 	return in->read( (char*) out + first, second );
@@ -131,7 +143,7 @@ blargg_err_t Remaining_Reader::read( void* out, long count )
 
 Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
 	begin( (const char*) p ),
-	size_( s )
+	size_( max( 0l, s ) )
 {
 	pos = 0;
 }
@@ -141,6 +153,7 @@ long Mem_File_Reader::size() const { return size_; }
 long Mem_File_Reader::read_avail( void* p, long s )
 {
 	long r = remain();
+	s = max( 0l, s );
 	if ( s > r )
 		s = r;
 	memcpy( p, begin + pos, s );
@@ -152,6 +165,7 @@ long Mem_File_Reader::tell() const { return pos; }
 
 blargg_err_t Mem_File_Reader::seek( long n )
 {
+	RETURN_VALIDITY_CHECK( n >= 0 );
 	if ( n > size_ )
 		return eof_error;
 	pos = n;
@@ -164,7 +178,7 @@ Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
 	callback( c ),
 	data( d )
 {
-	remain_ = size;
+	remain_ = max( 0l, size );
 }
 
 long Callback_Reader::remain() const { return remain_; }
@@ -173,13 +187,14 @@ long Callback_Reader::read_avail( void* out, long count )
 {
 	if ( count > remain_ )
 		count = remain_;
-	if ( Callback_Reader::read( out, count ) )
+	if ( count < 0 || Callback_Reader::read( out, count ) )
 		count = -1;
 	return count;
 }
 
 blargg_err_t Callback_Reader::read( void* out, long count )
 {
+	RETURN_VALIDITY_CHECK( count >= 0 );
 	if ( count > remain_ )
 		return eof_error;
 	return callback( data, out, count );
@@ -210,11 +225,12 @@ long Std_File_Reader::size() const
 
 long Std_File_Reader::read_avail( void* p, long s )
 {
-	return fread( p, 1, s, (FILE*) file_ );
+	return fread( p, 1, max( 0l, s ), (FILE*) file_ );
 }
 
 blargg_err_t Std_File_Reader::read( void* p, long s )
 {
+	RETURN_VALIDITY_CHECK( s > 0 );
 	if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
 		return 0;
 	if ( feof( (FILE*) file_ ) )
diff --git a/libs/gme/gme/Data_Reader.h b/libs/gme/gme/Data_Reader.h
index acf571f6711fe47679c97239a4c1adae7e9160ff..6c22b678e5de3c81d1286adf3a7f3d95f79024df 100644
--- a/libs/gme/gme/Data_Reader.h
+++ b/libs/gme/gme/Data_Reader.h
@@ -129,6 +129,8 @@ private:
 };
 
 #ifdef HAVE_ZLIB_H
+#include <zlib.h>
+
 // Gzip compressed file reader
 class Gzip_File_Reader : public File_Reader {
 public:
@@ -143,7 +145,7 @@ public:
 	long tell() const;
 	blargg_err_t seek( long );
 private:
-	void* file_;
+	gzFile file_;
 	long size_;
 };
 #endif
diff --git a/libs/gme/gme/Music_Emu.cpp b/libs/gme/gme/Music_Emu.cpp
index 30b25dcfc64b08fef7ecee36a76514f2ef62d79e..942e86e27db8bf23a9495d734714818ca04ed110 100644
--- a/libs/gme/gme/Music_Emu.cpp
+++ b/libs/gme/gme/Music_Emu.cpp
@@ -178,6 +178,11 @@ blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const
 	return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo;
 }
 
+long Music_Emu::tell_samples() const
+{
+	return out_time;
+}
+
 long Music_Emu::tell() const
 {
 	blargg_long rate = sample_rate() * stereo;
@@ -185,14 +190,18 @@ long Music_Emu::tell() const
 	return sec * 1000 + (out_time - sec * rate) * 1000 / rate;
 }
 
-blargg_err_t Music_Emu::seek( long msec )
+blargg_err_t Music_Emu::seek_samples( long time )
 {
-	blargg_long time = msec_to_samples( msec );
 	if ( time < out_time )
 		RETURN_ERR( start_track( current_track_ ) );
 	return skip( time - out_time );
 }
 
+blargg_err_t Music_Emu::seek( long msec )
+{
+	return seek_samples( msec_to_samples( msec ) );
+}
+
 blargg_err_t Music_Emu::skip( long count )
 {
 	require( current_track() >= 0 ); // start_track() must have been called already
diff --git a/libs/gme/gme/Music_Emu.h b/libs/gme/gme/Music_Emu.h
index b96f4b61177a8a507cd2c29149d2705d34565a2b..d98f7ce7ea06dbcb2e7b595e7c0e2dc638502a1d 100644
--- a/libs/gme/gme/Music_Emu.h
+++ b/libs/gme/gme/Music_Emu.h
@@ -41,9 +41,15 @@ public:
 	// Number of milliseconds (1000 msec = 1 second) played since beginning of track
 	long tell() const;
 	
+	// Number of samples generated since beginning of track
+	long tell_samples() const;
+
 	// Seek to new time in track. Seeking backwards or far forward can take a while.
 	blargg_err_t seek( long msec );
 	
+	// Equivalent to restarting track then skipping n samples
+	blargg_err_t seek_samples( long n );
+	
 	// Skip n samples
 	blargg_err_t skip( long n );
 	
diff --git a/libs/gme/gme/Nsfe_Emu.cpp b/libs/gme/gme/Nsfe_Emu.cpp
index 824a1a24006bb0b05cb11197345021541c7ea9c9..55ac4688fff1773f424eed8db56177677f560125 100644
--- a/libs/gme/gme/Nsfe_Emu.cpp
+++ b/libs/gme/gme/Nsfe_Emu.cpp
@@ -134,6 +134,9 @@ blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu )
 		RETURN_ERR( in.read( block_header, sizeof block_header ) );
 		blargg_long size = get_le32( block_header [0] );
 		blargg_long tag  = get_le32( block_header [1] );
+
+		if ( size <= 0 )
+			return "Corrupt file";
 		
 		//debug_printf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) );
 		
diff --git a/libs/gme/gme/Spc_Cpu.cpp b/libs/gme/gme/Spc_Cpu.cpp
index 90f60ed2970e772c49a18dee81152889935d75fc..19aae1135de0abaf1c5c1ae1f669d7e6f26ca22b 100644
--- a/libs/gme/gme/Spc_Cpu.cpp
+++ b/libs/gme/gme/Spc_Cpu.cpp
@@ -433,9 +433,7 @@ void Snes_Spc::cpu_write( int data, int addr, rel_time_t time )
 			#endif
 			
 			// Registers other than $F2 and $F4-$F7
-			//if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 )
-			// TODO: this is a bit on the fragile side
-			if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36%
+			if ( reg != 2 && (reg < 4 || reg > 7) ) // 36%
 				cpu_write_smp_reg( data, time, reg );
 		}
 		// High mem/address wrap-around
diff --git a/libs/gme/gme/Spc_Cpu.h b/libs/gme/gme/Spc_Cpu.h
index 4742e09908038f6e4bb77023bb6308e71df5a4e5..10c2450909d5b7054f95a218476a082020ccd4ed 100644
--- a/libs/gme/gme/Spc_Cpu.h
+++ b/libs/gme/gme/Spc_Cpu.h
@@ -76,8 +76,8 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 // TODO: remove non-wrapping versions?
 #define SPC_NO_SP_WRAPAROUND 0
 
-#define SET_SP( v )     (sp = ram + 0x101 + (v))
-#define GET_SP()        (sp - 0x101 - ram)
+#define SET_SP( v )     (sp = ram + 0x101 + ((uint8_t) v))
+#define GET_SP()        (uint8_t (sp - 0x101 - ram))
 
 #if SPC_NO_SP_WRAPAROUND
 #define PUSH16( v )     (sp -= 2, SET_LE16( sp, v ))
@@ -485,7 +485,7 @@ loop:
 	
 	case 0xAF: // MOV (X)+,A
 		WRITE_DP( 0, x, a + no_read_before_write  );
-		x++;
+		x = (uint8_t) (x + 1);
 		goto loop;
 	
 // 5. 8-BIT LOGIC OPERATION COMMANDS
@@ -808,7 +808,7 @@ loop:
 		unsigned temp = y * a;
 		a = (uint8_t) temp;
 		nz = ((temp >> 1) | temp) & 0x7F;
-		y = temp >> 8;
+		y = (uint8_t) (temp >> 8);
 		nz |= y;
 		goto loop;
 	}
@@ -838,6 +838,7 @@ loop:
 		
 		nz = (uint8_t) a;
 		a = (uint8_t) a;
+		y = (uint8_t) y;
 		
 		goto loop;
 	}
@@ -1004,7 +1005,7 @@ loop:
 	case 0x7F: // RET1
 		temp = *sp;
 		SET_PC( GET_LE16( sp + 1 ) );
-		sp += 3;
+		SET_SP( GET_SP() + 3 );
 		goto set_psw;
 	case 0x8E: // POP PSW
 		POP( temp );
diff --git a/libs/gme/gme/blargg_source.h b/libs/gme/gme/blargg_source.h
index b011777ad8f786fdc73d0d418d5f4acf3dbd2625..b65afd30b62e174260ea26c230e22306d3c55a03 100644
--- a/libs/gme/gme/blargg_source.h
+++ b/libs/gme/gme/blargg_source.h
@@ -18,6 +18,19 @@ all other #include lines. */
 #undef require
 #define require( expr ) assert( expr )
 
+// Use to provide hints to compiler for optimized code layout in situations where we
+// can almost always expect a conditional to go one way or the other.  Should only be
+// used in situations where an unexpected branch is truly exceptional though!
+#undef likely
+#undef unlikely
+#ifdef __GNUC__
+    #define likely( x ) __builtin_expect(x, 1)
+    #define unlikely( x ) __builtin_expect(x, 0)
+#else
+    #define likely( x ) (x)
+    #define unlikely( x ) (x)
+#endif
+
 // Like printf() except output goes to debug log file. Might be defined to do
 // nothing (not even evaluate its arguments).
 // void debug_printf( const char* format, ... );
diff --git a/libs/gme/gme/gme.cpp b/libs/gme/gme/gme.cpp
index c05f25eb444961d3d8db18a7f58e9a0311b5c969..47709840a031d5ef8606cee8b05add7c9879353b 100644
--- a/libs/gme/gme/gme.cpp
+++ b/libs/gme/gme/gme.cpp
@@ -337,7 +337,9 @@ BLARGG_EXPORT gme_err_t gme_play           ( Music_Emu* me, int n, short* p )
 BLARGG_EXPORT void      gme_set_fade       ( Music_Emu* me, int start_msec )      { me->set_fade( start_msec ); }
 BLARGG_EXPORT int       gme_track_ended    ( Music_Emu const* me )                { return me->track_ended(); }
 BLARGG_EXPORT int       gme_tell           ( Music_Emu const* me )                { return me->tell(); }
+BLARGG_EXPORT int       gme_tell_samples   ( Music_Emu const* me )                { return me->tell_samples(); }
 BLARGG_EXPORT gme_err_t gme_seek           ( Music_Emu* me, int msec )            { return me->seek( msec ); }
+BLARGG_EXPORT gme_err_t gme_seek_samples   ( Music_Emu* me, int n )               { return me->seek_samples( n ); }
 BLARGG_EXPORT int       gme_voice_count    ( Music_Emu const* me )                { return me->voice_count(); }
 BLARGG_EXPORT void      gme_ignore_silence ( Music_Emu* me, int disable )         { me->ignore_silence( disable != 0 ); }
 BLARGG_EXPORT void      gme_set_tempo      ( Music_Emu* me, double t )            { me->set_tempo( t ); }
diff --git a/libs/gme/gme/gme.h b/libs/gme/gme/gme.h
index 1f2a2d15007755788884f879444f4fb05a675324..cb07061b4b9ab0c4d20de0a5148de5be8098ba78 100644
--- a/libs/gme/gme/gme.h
+++ b/libs/gme/gme/gme.h
@@ -1,6 +1,6 @@
 /* Game music emulator library C interface (also usable from C++) */
 
-/* Game_Music_Emu 0.6.0 */
+/* Game_Music_Emu 0.6.1 */
 #ifndef GME_H
 #define GME_H
 
@@ -8,7 +8,7 @@
 	extern "C" {
 #endif
 
-#define GME_VERSION 0x000600 /* 1 byte major, 1 byte minor, 1 byte patch-level */
+#define GME_VERSION 0x000601 /* 1 byte major, 1 byte minor, 1 byte patch-level */
 
 /* Error string returned by library functions, or NULL if no error (success) */
 typedef const char* gme_err_t;
@@ -47,9 +47,15 @@ int gme_track_ended( Music_Emu const* );
 /* Number of milliseconds (1000 = one second) played since beginning of track */
 int gme_tell( Music_Emu const* );
 
+/* Number of samples generated since beginning of track */
+int gme_tell_samples( Music_Emu const* );
+
 /* Seek to new time in track. Seeking backwards or far forward can take a while. */
 gme_err_t gme_seek( Music_Emu*, int msec );
 
+/* Equivalent to restarting track then skipping n samples */
+gme_err_t gme_seek_samples( Music_Emu*, int n );
+
 
 /******** Informational ********/
 
diff --git a/libs/gme/gme/libgme.pc.in b/libs/gme/gme/libgme.pc.in
index 4f420d9edab58d30b6620d3464722d80046aa85a..49fd5b1df44294e3cd76821c7e88cca1e4922b04 100644
--- a/libs/gme/gme/libgme.pc.in
+++ b/libs/gme/gme/libgme.pc.in
@@ -3,7 +3,7 @@
 # later are used by pkg-config.
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=${prefix}
-lib_suffix=
+lib_suffix=@LIB_SUFFIX@
 libdir=${exec_prefix}/lib${lib_suffix}
 includedir=${prefix}/include
 
@@ -13,3 +13,4 @@ URL: http://code.google.com/p/game-music-emu/
 Version: @GME_VERSION@
 Cflags: -I${includedir}
 Libs: -L${libdir} -lgme
+Libs.private: -lstdc++
diff --git a/libs/gme/include/gme/gme.h b/libs/gme/include/gme/gme.h
index 1f2a2d15007755788884f879444f4fb05a675324..cb07061b4b9ab0c4d20de0a5148de5be8098ba78 100644
--- a/libs/gme/include/gme/gme.h
+++ b/libs/gme/include/gme/gme.h
@@ -1,6 +1,6 @@
 /* Game music emulator library C interface (also usable from C++) */
 
-/* Game_Music_Emu 0.6.0 */
+/* Game_Music_Emu 0.6.1 */
 #ifndef GME_H
 #define GME_H
 
@@ -8,7 +8,7 @@
 	extern "C" {
 #endif
 
-#define GME_VERSION 0x000600 /* 1 byte major, 1 byte minor, 1 byte patch-level */
+#define GME_VERSION 0x000601 /* 1 byte major, 1 byte minor, 1 byte patch-level */
 
 /* Error string returned by library functions, or NULL if no error (success) */
 typedef const char* gme_err_t;
@@ -47,9 +47,15 @@ int gme_track_ended( Music_Emu const* );
 /* Number of milliseconds (1000 = one second) played since beginning of track */
 int gme_tell( Music_Emu const* );
 
+/* Number of samples generated since beginning of track */
+int gme_tell_samples( Music_Emu const* );
+
 /* Seek to new time in track. Seeking backwards or far forward can take a while. */
 gme_err_t gme_seek( Music_Emu*, int msec );
 
+/* Equivalent to restarting track then skipping n samples */
+gme_err_t gme_seek_samples( Music_Emu*, int n );
+
 
 /******** Informational ********/
 
diff --git a/libs/gme/readme.txt b/libs/gme/readme.txt
index 82a501dbdad2e5bb4c4ec80e7f4eb210fab9f754..4cfe5e7a8e71b121556df97736d6ddc68d4d1840 100644
--- a/libs/gme/readme.txt
+++ b/libs/gme/readme.txt
@@ -1,4 +1,4 @@
-Game_Music_Emu 0.6.0: Game Music Emulators
+Game_Music_Emu 0.6.2: Game Music Emulators
 ------------------------------------------
 Game_Music_Emu is a collection of video game music file emulators that
 support the following formats and systems:
@@ -34,30 +34,45 @@ several architectures, Mac OS, MorphOS, Xbox, PlayStation Portable,
 GP2X, and Nintendo DS.
 
 Author : Shay Green <gblargg@gmail.com>
-Website: http://www.slack.net/~ant/
-Forum  : http://groups.google.com/group/blargg-sound-libs
+Website: https://bitbucket.org/mpyne/game-music-emu/wiki/Home
 License: GNU Lesser General Public License (LGPL)
 
+Current Maintainer: Michael Pyne <mpyne@purinchu.net>
 
 Getting Started
 ---------------
 Build a program consisting of demo/basics.c, demo/Wave_Writer.cpp, and
-all source files in gme/. If you have CMake 2.6 or later, execute
+all source files in gme/.
 
-	run cmake
-	cd demo
-	run make
+Or, if you have CMake 2.6 or later, execute at a command prompt (from the
+extracted source directory):
 
-Be sure "test.nsf" is in the same directory as the program. Running it
+    mkdir build
+    cd build
+    cmake ../         # <-- Pass any needed CMake flags here
+    make              # To build the library
+    cd demo
+    make              # To build the demo itself
+
+Be sure "test.nsf" is in the same directory as the demo program. Running it
 should generate the recording "out.wav".
 
+You can use "make install" to install the library. To choose where to install
+the library to, use the CMake argument "-DCMAKE_INSTALL_PREFIX=/usr/local"
+(and replace /usr/local with the base path you wish to use). Alternately, you
+can specify the base path to install to when you run "make install" by passing
+'DESTDIR=/usr/local' on the make install command line (again, replace
+/usr/local as appropriate).
+
+To build a static library instead of shared (the default), pass
+-DBUILD_SHARED_LIBS=OFF to the cmake command when running cmake.
+
 A slightly more extensive demo application is available in the player/
 directory.  It requires SDL to build.
 
 Read gme.txt for more information. Post to the discussion forum for
 assistance.
 
-
 Files
 -----
 gme.txt               General notes about the library
diff --git a/libs/gme/win32/libgme.dll.a b/libs/gme/win32/libgme.dll.a
index d56d87396d511830d163ef4d081584eeac2d4e21..2c5e95853e7d8897e23debb4d0d2fa6ddbd8e651 100644
Binary files a/libs/gme/win32/libgme.dll.a and b/libs/gme/win32/libgme.dll.a differ
diff --git a/libs/gme/win64/libgme.dll.a b/libs/gme/win64/libgme.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..8348f12de7973c43e23d17be8f9b52d35c1c2cc2
Binary files /dev/null and b/libs/gme/win64/libgme.dll.a differ
diff --git a/libs/libgme.props b/libs/libgme.props
new file mode 100644
index 0000000000000000000000000000000000000000..209f6b9a88219d178e1d4ab066c94dd18539df25
--- /dev/null
+++ b/libs/libgme.props
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Platform)' == 'Win32' OR '$(Platform)' == 'x64'">
+    <IncludePath>$(SolutionDir)libs\gme\include;$(IncludePath)</IncludePath>
+    <LibraryPath Condition="'$(Platform)' == 'Win32'">$(SolutionDir)libs\gme\win32;$(LibraryPath)</LibraryPath>
+    <LibraryPath Condition="'$(Platform)' == 'x64'">$(SolutionDir)libs\gme\win64;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Platform)' == 'Win32' OR '$(Platform)' == 'x64'">
+    <Link>
+      <AdditionalDependencies>libgme.dll.a;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/libs/libopenmpt.props b/libs/libopenmpt.props
new file mode 100644
index 0000000000000000000000000000000000000000..8825907b63e33d145b1bf5f605e1b8f80c9bb10d
--- /dev/null
+++ b/libs/libopenmpt.props
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <IncludePath>$(SolutionDir)libs\libopenmpt\inc;$(IncludePath)</IncludePath>
+	<LibraryPath Condition="'$(Platform)' == 'Win32'">$(SolutionDir)libs\libopenmpt\lib\x86;$(LibraryPath)</LibraryPath>
+    <LibraryPath Condition="'$(Platform)' == 'x64'">$(SolutionDir)libs\libopenmpt\lib\x86_64;$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <Link>
+      <AdditionalDependencies>libopenmpt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/libs/libopenmpt/LICENSE.txt b/libs/libopenmpt/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e0f012166dcc5c0ec131866434bc7497eb318a26
--- /dev/null
+++ b/libs/libopenmpt/LICENSE.txt
@@ -0,0 +1,26 @@
+Copyright (c) 2004-2019, OpenMPT contributors
+Copyright (c) 1997-2003, Olivier Lapicque
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the OpenMPT project nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/libs/libopenmpt/Licenses/License.Vorbis.txt b/libs/libopenmpt/Licenses/License.Vorbis.txt
new file mode 100644
index 0000000000000000000000000000000000000000..153b926a152a6bdd3230f64c0583a146d797f52e
--- /dev/null
+++ b/libs/libopenmpt/Licenses/License.Vorbis.txt
@@ -0,0 +1,28 @@
+Copyright (c) 2002-2018 Xiph.org Foundation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/libopenmpt/Licenses/License.mpg123.Authors.txt b/libs/libopenmpt/Licenses/License.mpg123.Authors.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c1eac976db60f180058fddedc83992f0b88ab14d
--- /dev/null
+++ b/libs/libopenmpt/Licenses/License.mpg123.Authors.txt
@@ -0,0 +1,175 @@
+This is an attempt to give credit to the people who contributed in some way to the mpg123 project.
+There are names and email addresses listed. Please use these addresses only to contact contributors with some question about their mpg123 contribution.
+You are explicitly not allowed to send them unwanted business offers or to question the quality of their sex life.
+--------------------
+
+Being on the list of contributing authors not necessarily means that there
+is significant copyright in parts of the source code. There are obviously
+contributions of differing complexity. I try to mention people who motivated
+changes at least by suggesting some definite code changes, even if their
+code did not enter the mpg123 source verbatim. Trivial changes like pointing
+out ovbious syntax errors that make compilers cry do not land here.
+
+--------------------
+
+Current maintainers with various sorts of contributions:
+	Thomas Orgis <thomas@orgis.org>
+	Patrick Dehne <patrick@steidle.net>
+	Jonathan Yong <10walls@gmail.com>
+	Taihei Momma <tmkk@mac.com>
+
+Co-initiator of the revived mpg123 project, but not that involved anymore:
+	Nicholas J Humfrey <njh@ecs.soton.ac.uk>
+
+Special thanks go to Taihei, the person who keeps the assembly optimisations alive
+and takes care of portability to OSX/iOS platforms.
+
+Generic address pointing to the current maintainer (hopefully still works in future in case maintainership will change again): <maintainer@mpg123.org>
+
+The creator: Michael Hipp (email: hippm@informatik.uni-tuebingen.de - please bother maintainers first)
+
+Contributions/ideas Thomas Orgis era (includes backports from mhipp trunk):
+
+Won-Kyu Park <wkpark@gmail.com>: patch to get rid of asm textrels (x86 PIC)
+Michael Weiser <michaelweiser@users.sf.net>: update of coreaudio output to AudioComponents API
+Bent Bisballe Nyeng <bbn@dynastrom.com>: patch for MPG123_NO_PEEK_END and MPG123_FORCE_SEEKABLE
+Eric S. Raymond <esr@thyrsus.com>: man page fixes
+Tobias Weber <tobiw@suprafluid.com>: patch for --disable-equalizer
+Hans de Goede <j.w.r.degoede@gmail.com>: patch to skip APE tags
+Stephan Vedder <stephan.vedder@gmail.com>: MSVC++ 2013 port update
+Rajeev V. Pillai <rajeevvp@users.sf.net>: pointing out/patching issues in HTTP code and behaviour of mpg123 remote/terminal control
+Jarno Lehtinen <lehtinen@sci.fi>: tinyalsa output
+Anthony Wells <borgboyone@yahoo.com>: initial version of ID3v2 APIC patch
+David Wohlferd <limegreensocks@yahoo.com>: Win32 WaveOut buffer destructor fix.
+Mike Gorchak <mike.gorchak.qnx@gmail.com>: QNX native audio output (QSA)
+Dan McGee <dpmcgee@gmail.com>: various patches (also for test suite)
+Jonathan Yong (jon_y) <10walls@gmail.com>: win32 hacking, win32 wasapi audio.
+Malcolm Boczek <MBoczek@terraindustries.com>: Common language runtime wrapper
+Elbert Pol (TeLLie) <elbert.pol@gmail.com>: OS/2 port fixup
+Jeroen Valkonet <jvalkon@xs4all.nl>: motivate pitch control, suggestive patch for pitch command in generic control interface
+Andy Hefner <ahefner@gmail.com>: patch for that second UTF16 issue
+Taihei Monma <tmkk@mac.com>: A whole lot of new/improved assembler code, including Altivec!
+Christian Weisgerber <naddy@openbsd.org>, Brad Smith: sndio output
+Patrick Dehne (P4tr3ck) <patrick@steidle.net>: more MSVC++ porting, patch to handle missing bit reservoirs
+Thorsten Glaser <tg@mirbsd.de>: icy2utf8, suggest utf8 locale stuff
+Dan Smith <dan@algenta.com>: ABI fixes for ensuring stack alignment (esp. for MinGW-built DLL with MSVC)
+Michael Ryzhykh <mclroy@gmail.com>: mpg123.spec.in
+Stefan Lenselink <Stefan@lenselink.org>: initial aRts output
+Sergej Kvachonok <ravenexp@gmail.com>: win32 audio rewrite
+Winston: SunOS-4.1.4/gcc-2.7.0 testing and suggestions for fixes (legacy Makefile, integer type headers)
+Mika Tiainen: pointing out the fix for the UTF to ASCII filtering of tags to actually work
+Nick Kurshev <nickols_k@mail.ru>: extended 3dnow (from mplayer)
+Zuxy Meng <zuxy.meng@gmail.com>: SSE (from mplayer)
+Honza <cundrak@quick.cz>: idea and prototype patch for ICY meta data support
+Petr Baudis <pasky@ucw.cz>: patches: term sigcont, id3 display unicode fallback and condensed output
+Petr Salinger <Petr.Salinger@seznam.cz>: i486 enhancement
+mpdavig@users.sourceforge.net: linux-ppc-nas Makefile.legacy entry
+Adrian Bacon <adrian.bacon@xs4all.nl>: patched decode_i586_dither (noise shaped float/int rounding)
+Cool Feet Audio project <nutcase@dtfm.org>: realtime equalizer control
+Steve Grundell <www@grundell.u-net.com>: clean stdout in control mode with stdout decoding
+Romain Dolbeau <romain@dolbeau.org>: Altivec support (taken from mplayer)
+higway <higway@mednet.md>: MMX Patch
+Clemens Ladisch <clemens@ladisch.de>: ALSA 0.9/1.0 support
+
+Debian Daniel Kobras <kobras@debian.org> era:
+
+Steve Kemp <skx@debian.org>
+Dan Olson <theoddone33@icculus.org>
+Syuuhei Kashiyama <squash@mb.kcom.ne.jp>
+Rupert Levene <rupert.debian@hotpop.com>
+Andreas Dilger <adilger@turbolinux.com>
+Erik B. Andersen <andersee@debian.org>
+Chris Butler <chrisb@debian.org>
+Martin Sjogren <md9ms@mdstud.chalmers.se>
+Chet Hosey <chosey@budapress.com>
+Roland Rosenfeld <roland@spinnaker.de>
+
+
+Debian Colin Watson <cjwatson@debian.org> era:
+
+Helge Deller <deller@gmx.de>
+Chet Hosey <chosey@budapress.com>
+Christopher C. Chimelis <chris@debian.org>
+Roland Rosenfeld <roland@spinnaker.de>
+Marcelo E. Magallon <mmagallo@debian.org>
+
+
+Initial Debianers:
+
+Tommi Virtanen <tv@debian.org>
+Paul Haggart <phaggart@debian.org>
+
+
+Contributions/ideas Michael Hipp era:
+
+Mikko Tommila: DCT9
+Oliver Fromme <oliver.fromme@heim3.tu-clausthal.de>
+MPEG Software Simulation Group: reference decoder package
+Tobias Bading: idea for DCT64 in subband synthesis from maplay package
+Jeff Tsay and Mikko Tommila: MDCT36 from maplay package
+Philipp Knirsch <phil@mpik-tueb.mpg.de>: DCT36/manual unroll idea
+Thomas Woerner: SGI Audio
+Damien Clermonte: HP-UX audio fixes
+Niclas Lindstrom <nil@wineasy.se>: OS2 port
+Stefan Bieschewski <stb@acm.org>: Pentium optimizations, decode_i586.s
+Martin Denn <mdenn@unix-ag.uni-kl.de>: NAS port
+Niklas Beisert <nbeisert@physik.tu-muenchen.de>: MPEG 2.5 tables
+<mycroft@NetBSD.ORG> and <augustss@cs.chalmers.se>: NetBSD Patch(es)
+Kevin Brintnall <kbrint@visi.com>: BSD patch
+Tony Million: win32 port
+Steven Tiger Lang: advanced shuffle
+Eric B. Mitchell: esd port
+Ryan R. Prosser <prosser@geocities.com>: esd port for Solaris
+Andreas Neuhaus: initial generic control interface
+
+(additionally fetched from changelog:)
+
+Ralf Hildebrandt <R.Hildebrandt@TU-BS.DE>: audio_alib changes
+<sms@moe.2bsd.com>: BSDOS 4.0 with gcc added to Makefile
+Bertrand Petit <elrond@phoe.netdev.net>: frontend changes
+Erik Mouw <J.A.K.Mouw@its.tudelft.nl>: SGI audio fix for non RAD machines
+Daniel O'Connor <darius@guppy.dons.net.au>: freebsd-esd make-entry
+D. Skarda <0rfelyus@atrey.karlin.mff.cuni.cz>: enhanced head_check
+Wilson, Jeff D <jeff.wilson@wilcom.com>: xterm-title
+Robert Bihlmeyer <robbe@orcus.priv.at>: esd changes
+Hannu Napari's <Hannu.Napari@hut.fi>: SGI audio patches
+<Juergen.Schoew@unix-ag.uni-siegen.de>: native AIX support
+<psst@euskalnet.net>: playlist patch
+Gilles Zunino <Gilles.Zunino@hei.fupl.asso.fr>: SGI audio patches
+Matthew Parslow <roy@alpha.alphalink.com.au>: esdserver patch
+<dlux@dlux.sch.bme.hu>: equalizer patch (equalfile setting)
+Ducroquet Erwan <ducroque@ufr-info-p7.ibp.fr>: HPUX/ALib support
+Shane Wegner <shane@CM.NU>: genrepatch
+Samuel Audet <guardia@step.polymtl.ca>: wav-File patch
+"J. Dean Brederson" <jdb@cs.utah.edu>: SGI-RAD support
+Chou Ye-chi <is84002@cis.nctu.edu.tw>: sajberplay/FreeBSD patch
+Fabrice Bellard <bellard@email.enst.fr>: 486 optimizations
+A. Hermansen <ahermans@vf.telia.no> and J. Kysela <perex@jcu.cz>: ALSA output
+KIMURA Takuhiro <kim@hannah.ipc.miyakyo-u.ac.jp>: K6-3DNow
+Petr Stehlik <stehlik@cas3.zlin.vutbr.cz>: MINT
+Andy <andy@snoogie.demon.co.uk>: float2int speed up proposal
+Brian Foutz <foutz@anise.ee.cornell.edu>: TK3Play
+Thomas Niederreiter <tn@tarantel.rz.fh-muenchen.de>: RIFF header fix
+Stefan Gybas <cab@studbox.uni-stuttgart.de>: m68k patch 
+Grant Erickson <eric0139@tc.umn.edu>: Linux PPC patch
+Peter Berger <peterb@hoopoe.psc.edu>: BSDi patch 
+Henrik P Johnson <king@one.se>: HTTP auth
+Steven Tiger Lang <tiger@tyger.org>: advanced shuffle
+"Brian J. Swetland" <swetland@uiuc.edu>: front-end (remote) patch
+<leo@zycad.com>
+Tillmann Steinbrecher <tst@gmx.de>: shuffle patch
+M.Stekelenburg <m.stekelenburg@student.utwente.nl>: i386-getbits
+Antti Andreimann <anttix@cyberix.edu.ee>: outburst patch
+Hur TaeSung <saturn@arari.snu.ac.kr>: 'http accept' patch 
+
+(from post-0.59 changes that yet have to go into new trunk:)
+
+Hans Schwengeler <schweng@astro.unibas.ch>: audio_dec additions
+Wojciech Barañski's Mp3Play (check the tools folder): Mp3Play frontend
+Daniel Koukola: audio_oss.c patch
+Munechika SUMIKAWA <sumikawa@ebina.hitachi.co.jp>: IPv6
+TEMNOTA <temnota@kmv.ru>: HTTP,FTP patch/playlist fix
+Peter Surda <shurdeek@panorama.sth.ac.at>: VBR patch
+Ben <ben@blaukopf.com>: ARM startup time improvements 
+Dave MacKenzie <djm@pix.net>: init_output() patch
+pasky's <pasky@ju.cz>: close-on-stop patch
diff --git a/libs/libopenmpt/Licenses/License.mpg123.txt b/libs/libopenmpt/Licenses/License.mpg123.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d7bb85fc3be6d8a00bc8e875569d30deb9f0dceb
--- /dev/null
+++ b/libs/libopenmpt/Licenses/License.mpg123.txt
@@ -0,0 +1,772 @@
+This is the file that contains the terms of use, copying, etc. for the mpg123 distribution package.
+
+Main message, to include in "About ..." boxes, etc:
+
+	Copyright (c) 1995-2013 by Michael Hipp and others,
+	free software under the terms of the LGPL v2.1
+
+There is an attempt to cover the actual list of authors in the AUTHORS file.
+Project maintainer since 2006 is Thomas Orgis and many people have contributed
+since the Michael Hipp era, but he stays the initial source and it would
+be impractical to count them all individually, so it's "and others".
+Source files contain the phrase "the mpg123 project" to the same effect
+in their license boilerplate; especially those that were added after
+maintainership changed. The person mainly responsible for the first version
+is usually named in the phrase "initially written by ...".
+
+All files in the distribution that don't carry a license note on their own are
+licensed under the terms of the LGPL 2.1; exceptions may apply, especially to
+files not in the official distribution but in the revision control repository.
+
+The formal license text follows.
+
+=======================
+1. The LGPL version 2.1
+=======================
+
+
+  GNU LESSER GENERAL PUBLIC LICENSE
+       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+     END OF TERMS AND CONDITIONS
+
+
+====================
+2. The GPL version 2
+====================
+
+
+   GNU GENERAL PUBLIC LICENSE
+       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+     END OF TERMS AND CONDITIONS
diff --git a/libs/libopenmpt/Licenses/License.ogg.txt b/libs/libopenmpt/Licenses/License.ogg.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6111c6c5a6b95057e43d36b7c217b073bf5f9b22
--- /dev/null
+++ b/libs/libopenmpt/Licenses/License.ogg.txt
@@ -0,0 +1,28 @@
+Copyright (c) 2002, Xiph.org Foundation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libs/libopenmpt/Licenses/License.zlib.txt b/libs/libopenmpt/Licenses/License.zlib.txt
new file mode 100644
index 0000000000000000000000000000000000000000..51106de4753292ad59de03de9e634e6814eeb7a2
--- /dev/null
+++ b/libs/libopenmpt/Licenses/License.zlib.txt
@@ -0,0 +1,115 @@
+ZLIB DATA COMPRESSION LIBRARY
+
+zlib 1.2.11 is a general purpose data compression library.  All the code is
+thread safe.  The data format used by the zlib library is described by RFCs
+(Request for Comments) 1950 to 1952 in the files
+http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and
+rfc1952 (gzip format).
+
+All functions of the compression library are documented in the file zlib.h
+(volunteer to write man pages welcome, contact zlib@gzip.org).  A usage example
+of the library is given in the file test/example.c which also tests that
+the library is working correctly.  Another example is given in the file
+test/minigzip.c.  The compression library itself is composed of all source
+files in the root directory.
+
+To compile all files and run the test program, follow the instructions given at
+the top of Makefile.in.  In short "./configure; make test", and if that goes
+well, "make install" should work for most flavors of Unix.  For Windows, use
+one of the special makefiles in win32/ or contrib/vstudio/ .  For VMS, use
+make_vms.com.
+
+Questions about zlib should be sent to <zlib@gzip.org>, or to Gilles Vollant
+<info@winimage.com> for the Windows DLL version.  The zlib home page is
+http://zlib.net/ .  Before reporting a problem, please check this site to
+verify that you have the latest version of zlib; otherwise get the latest
+version and check whether the problem still exists or not.
+
+PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help.
+
+Mark Nelson <markn@ieee.org> wrote an article about zlib for the Jan.  1997
+issue of Dr.  Dobb's Journal; a copy of the article is available at
+http://marknelson.us/1997/01/01/zlib-engine/ .
+
+The changes made in version 1.2.11 are documented in the file ChangeLog.
+
+Unsupported third party contributions are provided in directory contrib/ .
+
+zlib is available in Java using the java.util.zip package, documented at
+http://java.sun.com/developer/technicalArticles/Programming/compression/ .
+
+A Perl interface to zlib written by Paul Marquess <pmqs@cpan.org> is available
+at CPAN (Comprehensive Perl Archive Network) sites, including
+http://search.cpan.org/~pmqs/IO-Compress-Zlib/ .
+
+A Python interface to zlib written by A.M. Kuchling <amk@amk.ca> is
+available in Python 1.5 and later versions, see
+http://docs.python.org/library/zlib.html .
+
+zlib is built into tcl: http://wiki.tcl.tk/4610 .
+
+An experimental package to read and write files in .zip format, written on top
+of zlib by Gilles Vollant <info@winimage.com>, is available in the
+contrib/minizip directory of zlib.
+
+
+Notes for some targets:
+
+- For Windows DLL versions, please see win32/DLL_FAQ.txt
+
+- For 64-bit Irix, deflate.c must be compiled without any optimization. With
+  -O, one libpng test fails. The test works in 32 bit mode (with the -n32
+  compiler flag). The compiler bug has been reported to SGI.
+
+- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works
+  when compiled with cc.
+
+- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is
+  necessary to get gzprintf working correctly. This is done by configure.
+
+- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with
+  other compilers. Use "make test" to check your compiler.
+
+- gzdopen is not supported on RISCOS or BEOS.
+
+- For PalmOs, see http://palmzlib.sourceforge.net/
+
+
+Acknowledgments:
+
+  The deflate format used by zlib was defined by Phil Katz.  The deflate and
+  zlib specifications were written by L.  Peter Deutsch.  Thanks to all the
+  people who reported problems and suggested various improvements in zlib; they
+  are too numerous to cite here.
+
+Copyright notice:
+
+ (C) 1995-2017 Jean-loup Gailly and Mark Adler
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Jean-loup Gailly        Mark Adler
+  jloup@gzip.org          madler@alumni.caltech.edu
+
+If you use the zlib library in a product, we would appreciate *not* receiving
+lengthy legal documents to sign.  The sources are provided for free but without
+warranty of any kind.  The library has been entirely written by Jean-loup
+Gailly and Mark Adler; it does not include third-party code.
+
+If you redistribute modified sources, we would appreciate that you include in
+the file ChangeLog history information documenting your changes.  Please read
+the FAQ for more information on the distribution of modified source versions.
diff --git a/libs/libopenmpt/SRB2NOTE-DEBIAN.md b/libs/libopenmpt/SRB2NOTE-DEBIAN.md
new file mode 100644
index 0000000000000000000000000000000000000000..7e050d29a648772e6c7bab503db2a10a37320a00
--- /dev/null
+++ b/libs/libopenmpt/SRB2NOTE-DEBIAN.md
@@ -0,0 +1,90 @@
+# libopenmpt Debian backport info
+
+Backported libopenmpt 0.4.0 packages are available at ppa:stjr/srb2
+for Ubuntu Disco, Cosmic, Bionic, Xenial, and Trusty as of 2019/01/04.
+
+Debian Jessie users should use the Trusty package. Later Debian versions
+may use Disco or another working version.
+
+* libopenmpt 0.4.0 source package: http://archive.ubuntu.com/ubuntu/pool/universe/libo/libopenmpt/libopenmpt_0.4.0-1.dsc
+
+# Backporting from Disco to Cosmic and Bionic
+
+Cosmic and Bionic require no changes to the source package. They have
+the prerequisite `debhelper` and `dpkg-dev` versions, a matching
+`automake` version (1.15), as well as G++ > 5.
+
+Use the `backportpackage` script to automatically tag the package to
+Cosmic and Bionic, then upload to PPA:
+
+```
+sudo apt install ubuntu-dev-tools
+export UBUMAIL="John Doe <email@com.com>" # Name and email associated with your PGP sign key
+backportpackage -d [cosmic/bionic] -u [path-to-ppa] --key=[key-fingerprint] http://archive.ubuntu.com/ubuntu/pool/universe/libo/libopenmpt/libopenmpt_0.4.0-1.dsc
+```
+
+# Backporting from Disco to Xenial
+
+Download the package:
+
+```
+dget http://archive.ubuntu.com/ubuntu/pool/universe/libo/libopenmpt/libopenmpt_0.4.0-1.dsc
+```
+
+Xenial has an earlier `debhelper` version, but the rest of the prerequisites
+are available.
+
+The required changes (see patch `debian/libopenmpt-0.4.0-xenial-backport.diff` in this directory):
+
+* `debian/control`
+    * Add `automake` to Build-Depends
+    * Change `debhelper` version to `(>= 9.0~)`
+* `debian/compat`
+    * Change to `10`
+
+Then run these commands:
+
+```
+dch -i # Edit the changelog; make sure the Name and email match the PGP sign key and that the changelog targets xenial
+debuild -s -d -k0x[key-fingerprint] # Build the updated package
+dput [path-to-ppa] [path-to-changes-file] # Upload to PPA
+```
+
+# Backporting from Disco to Trusty
+
+Download the package:
+
+```
+dget http://archive.ubuntu.com/ubuntu/pool/universe/libo/libopenmpt/libopenmpt_0.4.0-1.dsc
+```
+
+Trusty requires more changes because it uses G++ 4.8 whereas the source
+package expects G++ >= 5. Automake is an earlier version as well --
+1.14 vs 1.15 -- so `autoreconf` needs to be run at build time.
+
+The required changes (see patch `debian/libopenmpt-0.4.0-trusty-backport.diff` in this directory):
+
+* `debian/control`
+    * Add `automake` and `libtool` to Build-Depends
+    * Change `debhelper` version to `(>= 9.0~)`
+    * Change `dpkg-dev` version to `(>= 1.17.0)`
+* `debian/compat`
+    * Change to `10`
+* `debian/rules`
+    * Under `override_dh_auto_configure`, input this line as the first command:
+      `autoreconf --force --install`
+        * This re-configures the package for Automake 1.14
+* `debian/libopenmpt-modplug1.symbols` and `debian/libopenmpt0.symbols`
+    * Delete these files
+        * The C++ ABI for G++ 4.8 is incompatible with G++ >= 5, so the
+          generated symbols will be different than the expected symbols
+          in the `*.symbols` files. Deleting these files will skip the
+          symbol check.
+
+Then run these commands:
+
+```
+dch -i # Edit the changelog; make sure the Name and email match the PGP sign key and that the changelog targets trusty
+debuild -s -d -k0x[key-fingerprint] # Build the updated package
+dput [path-to-ppa] [path-to-changes-file] # Upload to PPA
+```
diff --git a/libs/libopenmpt/SRB2NOTE.md b/libs/libopenmpt/SRB2NOTE.md
new file mode 100644
index 0000000000000000000000000000000000000000..d664ddd7e49930e69cdf6c81d7981568fb2e3d78
--- /dev/null
+++ b/libs/libopenmpt/SRB2NOTE.md
@@ -0,0 +1,54 @@
+# libopenmpt mingw-w64 binary info
+
+Current built version as of 2019/05/23 is 0.4.4+r11531.pkg
+
+* mingw binaries (.dll): `bin/[x86 or x86_64]/mingw`
+* mingw import libraries (.dll.a): `lib/[x86 or x86_64]/mingw`
+
+# Building libopenmpt with mingw-w64
+
+libopenmpt must be built from the Makefile / Android dev package in the
+[Downloads page](https://lib.openmpt.org/libopenmpt/download/#all-downloads)
+
+Use the mingw-w64 distributions from
+[SourceForge](https://sourceforge.net/projects/mingw-w64/files/#readme).
+
+You can download the appropriate 7-zip archive, extract to a folder of
+your choice, remove any existing mingw directories from your PATH,
+then call `mingw32-make.exe` from its direct location.
+
+FOR LIBOPENMPT, YOU MUST USE A MINGW PACKAGE THAT SUPPORTS THE POSIX
+THREADING MODEL! DO NOT COMPILE WITH A WIN32 THREADING MODEL!
+
+I use GCC 7.3.0:
+
+* [x86_64-posix-seh](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-posix/seh/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z)
+* [i686-posix-dwarf](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.3.0/threads-posix/dwarf/i686-7.3.0-release-posix-dwarf-rt_v5-rev0.7z)
+
+## x86 build instructions
+
+```
+set CFLAGS=-march=pentium -static-libgcc
+set CXXFLAGS=-march=pentium -static-libgcc -static-libstdc++
+set LDFLAGS=-Wl,--out-implib,bin/libopenmpt.dll.a -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,-Bdynamic,--no-whole-archive
+
+cd [libopenmpt-make-src]
+[x86-mingw-w64-bin]/mingw32-make.exe CONFIG=mingw64-win32
+```
+
+`libopenmpt.dll` and `libopenmpt.dll.a` will be built in the
+`bin/` folder.
+
+## x86_64 build instructions
+
+```
+set CFLAGS=-march=nocona -static-libgcc
+set CXXFLAGS=-march=nocona -static-libgcc -static-libstdc++
+set LDFLAGS=-Wl,--out-implib,bin/libopenmpt.dll.a -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,-Bdynamic,--no-whole-archive
+
+cd [libopenmpt-make-src]
+[x86_64-mingw-w64-bin]/mingw32-make.exe CONFIG=mingw64-win64
+```
+
+`libopenmpt.dll` and `libopenmpt.dll.a` will be built in the
+`bin/` folder.
\ No newline at end of file
diff --git a/libs/libopenmpt/bin/x86/libopenmpt.dll b/libs/libopenmpt/bin/x86/libopenmpt.dll
new file mode 100644
index 0000000000000000000000000000000000000000..0fc9e7656d5681b59df4a605b5f39ffe59fd55a9
Binary files /dev/null and b/libs/libopenmpt/bin/x86/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll b/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll
new file mode 100644
index 0000000000000000000000000000000000000000..5fa3642566039530a3ddf633d45617ef9d93f9bd
Binary files /dev/null and b/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-mpg123.dll b/libs/libopenmpt/bin/x86/openmpt-mpg123.dll
new file mode 100644
index 0000000000000000000000000000000000000000..79148a40ef3cef8e0f745ff14d1ccc1ac4aab025
Binary files /dev/null and b/libs/libopenmpt/bin/x86/openmpt-mpg123.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-ogg.dll b/libs/libopenmpt/bin/x86/openmpt-ogg.dll
new file mode 100644
index 0000000000000000000000000000000000000000..6b5a42e8f1fd11353588880cd36c649993eebe9e
Binary files /dev/null and b/libs/libopenmpt/bin/x86/openmpt-ogg.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-vorbis.dll b/libs/libopenmpt/bin/x86/openmpt-vorbis.dll
new file mode 100644
index 0000000000000000000000000000000000000000..486b30731c234696ad1feb3ca9740eebbd390668
Binary files /dev/null and b/libs/libopenmpt/bin/x86/openmpt-vorbis.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-zlib.dll b/libs/libopenmpt/bin/x86/openmpt-zlib.dll
new file mode 100644
index 0000000000000000000000000000000000000000..b6e028ab4f31b40c620983e73b76b747f9f3249e
Binary files /dev/null and b/libs/libopenmpt/bin/x86/openmpt-zlib.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/libopenmpt.dll b/libs/libopenmpt/bin/x86_64/libopenmpt.dll
new file mode 100644
index 0000000000000000000000000000000000000000..536492798e9167241fc526d010f6ed25e9d0686e
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll b/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll
new file mode 100644
index 0000000000000000000000000000000000000000..3f6bea95f72b0a8ee68a41128f8bb700918d568d
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll b/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll
new file mode 100644
index 0000000000000000000000000000000000000000..f96d446f25c61df1368c4219324e3a2bf6138723
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll b/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll
new file mode 100644
index 0000000000000000000000000000000000000000..3fd9514147973966faf77c7c2e08fb61c3fa4878
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll b/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll
new file mode 100644
index 0000000000000000000000000000000000000000..56047c82fbf4ff4eecc148718ed2b9fc1882a9da
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll b/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll
new file mode 100644
index 0000000000000000000000000000000000000000..562d8e6a9e89b933d2ea4ef30ae2d7e500e35c97
Binary files /dev/null and b/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll differ
diff --git a/libs/libopenmpt/changelog.md b/libs/libopenmpt/changelog.md
new file mode 100644
index 0000000000000000000000000000000000000000..9847be1197908bc6962277f54d7a68f0b7d95a79
--- /dev/null
+++ b/libs/libopenmpt/changelog.md
@@ -0,0 +1,791 @@
+
+Changelog {#changelog}
+=========
+
+For fully detailed change log, please see the source repository directly. This
+is just a high-level summary.
+
+### libopenmpt 0.4.4 (2019-04-07)
+
+ *  [**Bug**] Channel VU meters were swapped.
+
+ *  Startrekker: Clamp speed to 31 ticks per row.
+ *  MTM: Ignore unused Exy commands on import. Command E5x (Set Finetune) is now
+    applied correctly.
+ *  MOD: Sample swapping was always enabled since it has been separated from the
+    ProTracker 1/2 compatibility flag. Now it is always enabled for Amiga-style
+    modules and otherwise the old heuristic is used again.
+
+ *  stb_vorbis: Update to v1.16 (2019-03-05).
+
+### libopenmpt 0.4.3 (2019-02-11)
+
+ *  [**Sec**] Possible crash due to null-pointer access when doing a portamento
+    from an OPL instrument to an empty instrument note map slot (r11348).
+
+ *  [**Bug**] libopenmpt did not compile on Apple platforms in C++17 mode.
+
+ *  IT: Various fixes for note-off + instrument number in Old Effects mode.
+ *  MO3: Import IT row highlights as written by MO3 2.4.1.2 or newer. Required
+    for modules using modern tempo mode.
+
+ *  miniz: Update to v2.0.8 (2018-09-19).
+ *  stb_vorbis: Update to v1.15 (2019-02-07).
+
+### libopenmpt 0.4.2 (2019-01-22)
+
+ *  [**Sec**] DSM: Assertion failure during file parsing with debug STLs
+    (r11209).
+ *  [**Sec**] J2B: Assertion failure during file parsing with debug STLs
+    (r11216).
+
+ *  S3M: Allow volume change of OPL instruments after Note Cut.
+
+### libopenmpt 0.4.1 (2019-01-06)
+
+ *  [**Bug**] Binaries compiled for winold (Windows XP, Vista, 7, for CPUs
+    without SSE2 support) did not actually work on CPUs without SSE2 support.
+ *  [**Bug**] libmodplug: Public symbols of the C++ API had `visibility=hidden`
+    set on non-MSVC systems, which made them not publicly accessible.
+ *  [**Bug**] Project files for Windows 10 desktop builds on ARM and ARM64
+    (`build/vs2017win10`) were missing from Windows source package.
+ *  [**Bug**] MSVC project files in Windows source package lacked additional
+    files required to build DLLs.
+
+ *  MO3: Apply playback changes based on "ModPlug-made" header flag.
+
+ *  minimp3: Update to commit e9df0760e94044caded36a55d70ab4152134adc5
+    (2018-12-23).
+
+### libopenmpt 0.4.0 (2018-12-23)
+
+ *  [**New**] libopenmpt now includes emulation of the OPL chip and thus plays
+    OPL instruments in S3M, C67 and MPTM files. OPL chip emulation volume can be
+    changed with the new ctl `render.opl.volume_factor`.
+ *  [**New**] libopenmpt now supports CDFM / Composer 670 module files.
+ *  [**New**] Autotools `configure` and plain `Makefile` now honor the variable
+    `CXXSTDLIB_PCLIBSPRIVATE` which serves the sole purpose of listing the
+    standard library (or libraries) required for static linking. The contents of
+    this variable will be put in `libopenmpt.pc` `Libs.private` and used for
+    nothing else. See \ref libopenmpt_c_staticlinking .
+ *  [**New**] foo_openmpt: foo_openmpt now also works on Windows XP.
+ *  [**New**] libopenmpt Emscripten builds now ship with MP3 support by
+    default, based on minimp3 by Lion (github.com/lieff).
+ *  [**New**] libopenmpt: New ctl `play.at_end` can be used to change what
+    happens when the song end is reached:
+     *  "fadeout": Fades the module out for a short while. Subsequent reads
+        after the fadeout will return 0 rendered frames. This is the default and
+        identical to the behaviour in previous libopenmpt versions. 
+     *  "continue": Returns 0 rendered frames when the song end is reached.
+        Subsequent reads will continue playing from the song start or loop
+        start. This can be used for custom loop logic, such as loop
+        auto-detection and longer fadeouts.
+     *  "stop": Returns 0 rendered frames when the song end is reached.
+        Subsequent reads will return 0 rendered frames.
+ *  [**New**] Add new metadata fields `"originaltype"` and `"originaltype_long"`
+    which allow more clearly reflecting what is going on with converted formats
+    like MO3 and GDM.
+ *  [**New**] `Makefile` `CONFIG=emscripten` now can generate WebAssembly via
+    the additional option `EMSCRIPTEN_TARGET=wasm`.
+ *  [**New**] Compiling for DOS is now experimentally supported via DJGPP GCC
+    7.2 or later.
+
+ *  [**Change**] minimp3: Instead of the LGPL-2.1-licensed minimp3 by KeyJ,
+    libopenmpt now uses the CC0-1.0-licensed minimp3 by Lion (github.com/lieff)
+    as a fallback if libmpg123 is unavailable. The `USE_MINIMP3` `Makefile`
+    option is gone and minimp3 will be used automatically in the `Makefile`
+    build system if libmpg123 is not available.
+ *  [**Change**] openmpt123: openmpt123 now rejects `--output-type` in `--ui`
+    and `--batch` modes and also rejects `--output` in `--render` mode. These
+    combinations of options really made no sense and were rather confusing.
+ *  [**Change**] Android NDK build system now uses libc++ (`c++_shared`) instead
+    of GNU libstdc++ (`gnustl_shared`), as recommended by Android NDK r16b.
+ *  [**Change**] xmp-openmpt: `openmpt-mpg123.dll` is no longer optional and
+    must be placed into the same directory as `xmp-openmpt.dll`.
+ *  [**Change**] in_openmpt: `openmpt-mpg123.dll` is no longer optional and must
+    be placed either into the directory of the player itself or into the same
+    directory as `in_openmpt.dll`. This is dependent on how the player loads its
+    plugins. For WinAMP 5, `openmpt-mpg123.dll` needs to be in the directory
+    which contains `winamp.exe`. `in_openmpt.dll` needs to be in the `Plugins`
+    directory.
+ *  [**Change**] foo_openmpt: foo_openmpt is now packaged as a fb2k-component
+    package for easier installation.
+ *  [**Change**] When building libopenmpt with MinGW-w64, it is now recommended
+    to use the posix thread model (as opposed to the win32 threading model),
+    because the former does support std::mutex while the latter does not. When
+    building with win32 threading model with the Autotools build system, it is
+    recommended to provide the `mingw-std-threads` package. Building libopenmpt
+    with MinGW-w64 without any `std::thread`/`std::mutex` support is deprecated
+    and support for such configurations will be removed in libopenmpt 0.5.
+ *  [**Change**] `Makefile` `CONFIG=emscripten` now has 4 `EMSCRIPTEN_TARGET=`
+    settings: `wasm` generates WebAssembly, `asmjs128m` generates asm.js with a
+    fixed size 128MB heap, `asmjs` generates asm.js with a fixed default size
+    heap (as of Emscripten 1.38.11, this amounts to 16MB), `js` generates
+    JavaScript with dynamic heap growth and with compatibility for older VMs.
+ *  [**Change**] libmodplug: Update public headers to libmodplug 0.8.8.5. This
+    adds support for kind-of automatic MODPLUG_EXPORT decoration on Windows.
+
+ *  [**Regression**] Support for Clang 3.4, 3.5 has been removed.
+ *  [**Regression**] Building with Android NDK older than NDK r16b is not
+    supported any more.
+ *  [**Regression**] Support for Emscripten versions older than 1.38.5 has been
+    removed.
+ *  [**Regression**] Support for libmpg123 older than 1.14.0 has been removed.
+ *  [**Regression**] Using MediaFoundation to decode MP3 samples is no longer
+    supported. Use libmpg123 or minimp3 instead.
+ *  [**Regression**] libmodplug: Support for emulating libmodplug 0.8.7 API/ABI
+    has been removed.
+
+ *  [**Bug**] xmp-openmpt: Sample rate and number of output channels were not
+    applied correctly when using per-file settings.
+ *  [**Bug**] Internal mixer state was not initialized properly when initially
+    rendering in 44100kHz stereo format.
+ *  [**Bug**] openmpt123: Prevent libsdl2 and libsdl from being enabled at the
+    same time because they conflict with each other.
+ *  [**Bug**] libmodplug: Setting `SNDMIX_NORESAMPLING` in the C++ API always
+    resulted in linear interpolation instead of nearest neighbour
+
+ *  IT: In Compatible Gxx mode, allow sample changes next to a tone portamento
+    effect if a previous sample has already stopped playing.
+ *  IT: Fix broken volume envelopes with negative values as found in breakdwn.it
+    by Elysis.
+ *  MOD: Slides and delayed notes are executed on every repetition of a row with
+    row delay (fixes "ode to protracker").
+ *  XM: If the sustain point of the panning envelope is reached before key-off,
+    it is never released.
+ *  XM: Do not default recall volume / panning for delayed instrument-less notes
+ *  XM :E60 loop bug was not considered in song length calucation.
+ *  S3M: Notes without instrument number use previous note's sample offset.
+ *  Tighten M15 and MOD file rejection heuristics.
+ *  J2B: Ignore frequency limits from file header. Fixes Medivo.j2b, broken
+    since libopenmpt-0.2.6401-beta17.
+ *  STM: More accurate tempo calculation.
+ *  STM: Better support for early format revisions (no such files have been
+    found in the wild, though).
+ *  STM: Last character of sample name was missing.
+ *  SFX: Work around bad conversions of the "Operation Stealth" soundtrack by
+    turning pattern breaks into note stops.
+ *  IMF: Filter cutoff was upside down and the cutoff range was too small.
+ *  ParamEq plugin center frequency was not limited correctly.
+ *  Keep track of active SFx macro during seeking.
+ *  The "note cut" duplicate note action did not volume-ramp the previously
+    playing sample.
+ *  A song starting with non-existing patterns could not be played.
+ *  DSM: Support restart position and 16-bit samples.
+ *  DTM: Import global volume.
+ *  MOD: Support notes in octave 2, like in FastTracker 2 (fixes DOPE.MOD).
+ *  Do not apply Amiga playback heuristics to MOD files that have clearly been
+    written with a PC tracker.
+ *  MPTM: More logical release node behaviour.
+ *  Subsong search is now less thorough. It could previously find many subsongs
+    that are technically correct (unplayed rows at the beginning of patterns
+    that have been jumped over due to pattern breaks), but so far no real-world
+    module that would require such a thorough subsong detection was found. The
+    old mechanism caused way more false positives than intended with real-world
+    modules, though.
+ *  Restrict the unpacked size of compressed DMF, IT, MDL and MO3 samples to
+    avoid huge allocations with malformed small files.
+
+### libopenmpt 0.3 (2017-09-27)
+
+ *  [**New**] New error handling functionality in the C API, which in particular
+    allows distinguishing potentially transient out-of-memory errors from parse
+    errors during module loading. 
+ *  [**New**] New API `openmpt::module::get_selected_subsong()` (C++) and
+    `openmpt_module_get_selected_subsong()` (C).
+ *  [**New**] Faster file header probing API `openmpt::probe_file_header()` and
+    `openmpt::probe_file_header_get_recommended_size` (C++), and
+    `openmpt_probe_file_header()`,
+    `openmpt_probe_file_header_without_filesize()`,
+    `openmpt_probe_file_header_from_stream()` and
+    `openmpt_probe_file_header_get_recommended_size()` (C).
+ *  [**New**] New API `openmpt::could_open_probability()` (C++) and
+    `openmpt_could_open_probability()` (C). This fixes a spelling error in the
+    old 0.2 API.
+ *  [**New**] openmpt123: openmpt123 can now open M3U, M3U8, M3UEXT, M3U8EXT and
+    PLSv2 playlists via the `--playlist` option.
+ *  [**New**] openmpt123: openmpt123 now supports very fast file header probing
+    via the `--probe` option.
+ *  [**New**] Libopenmpt now supports building for Windows 10 Universal (Windows
+    Store 8.2) APIs with MSVC, and also for the older Windows Runtime APIs with
+    MinGW-w64.
+ *  [**New**] New API header `libopenmpt_ext.h` which implements the libopenmpt
+    extension APIs also for the C interface.
+ *  [**New**] The Reverb effect (S99 in S3M/IT/MPTM, and X99 in XM) is now
+    implemented in libopenmpt.
+ *  [**New**] For Amiga modules, a new resampler based on the Amiga's sound
+    characteristics has been added. It can be activated by passing the
+    `render.resampler.emulate_amiga` ctl with a value of `1`. Non-Amiga modules
+    are not affected by this, and setting the ctl overrides the resampler choice
+    specified by `OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH` or
+    `openmpt::module::RENDER_INTERPOLATIONFILTER_LENGTH`. Support for the MOD
+    command E0x (Set LED Filter) is also available when the Amiga resampler is
+    enabled. 
+
+ *  [**Change**] libopenmpt versioning changed and follows the more conventional
+    major.minor.patch as well as the recommendations of the
+    [SemVer](http://semver.org/) scheme now. In addition to the SemVer
+    requirements, pre-1.0.0 versions will also honor API and ABI stability in
+    libopenmpt (i.e. libopenmpt ignores SemVer Clause 4). 
+ *  [**Change**] The output directories of the MSVC build system were changed to
+    `bin/vs2015-shared/x86-64-win7/` (and similar) layout which allows building
+    in the same tree with different compiler versions without overwriting other
+    outputs.
+ *  [**Change**] The emscripten build now exports libopenmpt as 'libopenmpt'
+    instead of the default 'Module'.
+ *  [**Change**] Android: The build system changed. The various Android.mk files
+    have been merged into a single one which can be controlled using command
+    line options.
+ *  [**Change**] The `Makefile` build system now passes `std=c++11` to the
+    compiler by default. Older compilers may still work if you pass
+    `STDCXX=c++0x` to the `make` invocation.
+ *  [**Change**] The `Makefile` option `ANCIENT=1` is gone.
+ *  [**Change**] The optional dependencies on `libltdl` or `libdl` are gone.
+    They are no longer needed for any functionality.
+
+ *  [**Regression**] Compiling client code using the C++ API now requires a
+    compiler running in C++11 mode.
+ *  [**Regression**] Support for GCC 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7 has been
+    removed.
+ *  [**Regression**] Support for Clang 3.0, 3.1, 3.2, 3.3 has been removed.
+ *  [**Regression**] Support for Emscripten versions older than 1.31.0 has been
+    removed.
+ *  [**Regression**] Support for Android NDK versions older than 11 has been
+    removed.
+ *  [**Regression**] Visual Studio 2008, 2010, 2012, 2013 support has been
+    removed.
+ *  [**Regression**] Dynamic run-time loading of libmpg123 is no longer
+    supported. Libmpg123 must be linked at link-time now.
+ *  [**Regression**] xmp-openmpt: xmp-openmpt now requires XMPlay 3.8 or later
+    and compiling xmp-openmpt requires an appropriate XMPlay SDK with
+    `XMPIN_FACE` >= `4`.
+ *  [**Regression**] Support for libmpg123 older than 1.13.0 has been removed.
+ *  [**Regression**] Un4seen unmo3 support has been removed.
+
+ *  [**Bug**] C++ API: `openmpt::exception` did not define copy and move
+    constructors or copy and move assignment operators in libopenmpt 0.2. The
+    compiler-generated ones were not adequate though. libopenmpt 0.3 adds the
+    appropriate special member functions. This adds the respective symbol names
+    to the exported ABI, which, depending on the compiler, might or might not
+    have been there in libopenmpt 0.2. The possibly resulting possible ODR
+    violation only affects cases that did crash in the libopenmpt 0.2 API anyway
+    due to memory double-free, and does not cause any further problems in
+    practice for all known platforms and compilers.
+ *  [**Bug**] The C API could crash instead of failing gracefully in
+    out-of-memory situations.
+ *  [**Bug**] The test suite could fail on MacOSX or FreeBSD in non-fatal ways
+    when no locale was active.
+ *  [**Bug**] `libopenmpt_stream_callbacks_fd.h` and
+    `libopenmpt_stream_callbacks_file.h` were missing in Windows development
+    packages.
+ *  [**Bug**] libopenmpt on Windows did not properly guard against current
+    working directory DLL injection attacks.
+ *  [**Bug**] localtime() was used to determine the version of Schism Tracker
+    used to save IT and S3M files. This function is not guaranteed to be
+    thread-safe by the standard and is now no longer used.
+ *  [**Bug**] Possible crashes with malformed IT, ITP, AMS, MDL, MED, MPTM, PSM
+    and Startrekker files.
+ *  [**Bug**] Possible hangs with malformed DBM, MPTM and PSM files.
+ *  [**Bug**] Possible hangs with malformed files containing cyclic plugin
+    routings.
+ *  [**Bug**] Excessive loading times with malformed ITP / truncated AMS files.
+ *  [**Bug**] Plugins did not work correctly when changing the sample rate
+    between two render calls.
+ *  [**Bug**] Possible NULL-pointer dereference read during obscure
+    out-of-memory situations while handling exceptions in the C API.
+ *  [**Bug**] libmodplug: `libmodplug.pc` was wrong.
+ *  [**Bug**] Cross-compiling libopenmpt with autotools for Windows now properly
+    sets `-municode` and `-mconsole` as well as all required Windows system
+    libraries.
+ *  [**Bug**] foo_openmpt: Interpolation filter and volume ramping settings were
+    confused in previous versions. This version resets both to the defaults.
+ *  [**Bug**] libmodplug: The CSoundFile::Read function in the emulated
+    libmodplug C++ API returned the wrong value, causing qmmp (and possibly
+    other software) to crash.
+
+ *  Support for SoundTracker Pro II (STP) and Digital Tracker (DTM) modules.
+ *  Increased accuracy of the sample position and sample rate to drift less when
+    playing very long samples.
+ *  Various playback improvements for IT and XM files.
+ *  Channel frequency could wrap around after some excessive portamento / down
+    in some formats since libopenmpt 0.2-beta17.
+ *  Playback improvements for S3M files made with Impulse Tracker and
+    Schism Tracker.
+ *  ParamEq plugin emulation didn't do anything at full gain (+15dB).
+ *  All standard DMO effects are now also emulated on non-Windows and non-MSVC
+    systems.
+ *  Added `libopenmpt_stream_callbacks_buffer.h` which adds
+    `openmpt_stream_callbacks` support for in-memory buffers, possibly even only
+    using a truncated prefix view into a bigger file which is useful for
+    probing.
+ *  Avoid enabling some ProTracker-specific quirks for MOD files most likely
+    created with ScreamTracker 3.
+ *  Tremolo effect only had half the intended strength in MOD files.
+ *  Pattern loops ending on the last row a pattern were not executed correctly
+    in S3M files.
+ *  Work-around for reading MIDI macros and plugin settings in some malformed IT
+    files written by old UNMO3 versions.
+ *  Improve tracker detection in IT format.
+ *  Playback fixes for 8-channel MED files
+ *  Do not set note volume to 0 on out-of-range offset in XM files.
+ *  Better import of some slide commands in SFX files.
+ *  Sample 15 in "Crew Generation" by Necros requires short loops at the
+    beginning of the sample to not be ignored. Since we need to ignore them in
+    some (non-ProTracker) modules, we heuristically disable the old loop
+    sanitization behaviour based on the module channel count.
+ *  Both normal and percentage offset in PLM files were handled as percentage
+    offset.
+ *  MT2 files with instruments that had both sample and plugin assignments were
+    not read correctly.
+ *  Some valid FAR files were rejected erroneously.
+ *  Support for VBlank timing flag and comment field in PT36 files.
+ *  Improved accuracy of vibrato command in DIGI / DBM files.
+ *  STM: Add support for "WUZAMOD!" magic bytes and allow some slightly
+    malformed STM files to load which were previously rejected.
+ *  Detect whether "hidden" patterns in the order list of SoundTracker modules
+    should be taken into account or not.
+ *  Tighten heuristics for rejecting invalid 669, M15, MOD and ICE files and
+    loosen them in other places to allow some valid MOD files to load.
+ *  Improvements to seeking: Channel panning was not always updated from
+    instruments / samples when seeking, and out-of-range global volume was not
+    applied correctly in some formats.
+ *  seek.sync_samples=1 did not apply PTM reverse offset effect and the volume
+    slide part of combined volume slide + vibrato commands.
+ *  If the order list was longer than 256 items and there was a pattern break
+    effect without a position jump on the last pattern of the sequence, it did
+    not jump to the correct restart order.
+ *  `Makefile` has now explicit support for FreeBSD with no special option or
+    configuration required.
+ *  openmpt123: Improved section layout in man page.
+ *  libmodplug: Added all missing C++ API symbols that are accessible via the
+    public libmodplug header file.
+ *  Autotools build system now has options `--disable-openmpt123`,
+    `--disable-tests` and `--disable-examples` which may be desireable when
+    cross-compiling.
+ *  Windows binary packages now ship with libmpg123 included.
+
+### libopenmpt 0.2-beta20 (2016-08-07)
+
+ *  [**Bug**] PSM loader was broken on big-endian platforms since forever.
+ *  [**Bug**] `load.skip_samples` ctl did not work for PSM16 modules.
+
+ *  There is a new `subsong` ctl, which can return the currently selected
+    subsong.
+ *  More accurate ProTracker arpeggio wrap-around emulation.
+ *  More accurate sample tuning in PSM16 files.
+ *  Samples in DSM files were sometimes detuned and some pattern commands were
+    not imported correctly.
+ *  More accurate import of MDL 7-bit panning command.
+ *  Only import pattern commands supported by the UltraTracker version that was
+    used to save ULT files. Add support for command 5-C (end loop).
+ *  DMF sample loop lengths were off by one.
+ *  Unis 669 pan slide effect was too deep.
+ *  Several valid (but slightly corrupted possibly due to disk failures or data
+    transfer errors) SoundTracker files were no longer loading since libopenmpt
+    0.2-beta18.
+
+### libopenmpt 0.2-beta19 (2016-07-23)
+
+ *  [**Change**] libopenmpt now uses C++14 `[[deprecated]]` attribute instead
+    of compiler-specific solutions when appropriate.
+ *  [**Change**] libopenmpt C++ header now uses C++11 `noexcept` instead of
+    C++98 `throw()` exception specification when supported. `throw()` is
+    deprecated since C++11. This does not change API or ABI as they are
+    equivalent. Use `LIBOPENMPT_ASSUME_CPLUSPLUS_NOEXCEPT` to override the
+    default.
+ *  [**Change**] The preprocessor macro `LIBOPENMPT_ANCIENT_COMPILER_STDINT` is
+    gone. Please use `LIBOPENMPT_ASSUME_CPLUSPLUS_CSTDINT instead`.
+    Additionally, the typedefs moved from illegal namespace ::std into somewhat
+    less dangerous namespace ::openmpt::std. You can test
+    `#ifdef LIBOPENMPT_QUIRK_NO_CSTDINT` client-side to check whether
+    `libopenmpt.hpp` used the non-standard types. (Note: Of all supported
+    compilers, this change only affects the 3 compilers with only limited
+    support: MSVC 2008, GCC 4.1, GCC 4.2.)
+
+ *  [**Bug**] xmp-openmpt: Crash when viewing sample texts.
+
+ *  The public libopenmpt C++ header has auto-detection logic for the used C++
+    standard now. In case your client code compiler misreports the standard
+    version or you want to override it for other reasons,
+    `#define LIBOPENMPT_ASSUME_CPLUSPLUS` to the value of the standard version
+    you desire to be used. There is also a macro for each individual aspect,
+    like `LIBOPENMPT_ASSUME_CPLUSPLUS_CSTDINT`,
+    `LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED`,
+    `LIBOPENMPT_ASSUME_CPLUSPLUS_NOEXCEPT` which take precedence over the
+    general macro.
+ *  Portamento with sample swap behaviour was wrong for ProTracker MODs.
+ *  Rewritten loader and various playback fixes for MDL files.
+ *  libopenmpt 0.2-beta18 broke import of many pattern commands in DBM, DMF and
+    ULT files.
+ *  ADPCM samples in MOD files were broken since libopenmpt 0.2-beta17.
+
+### libopenmpt 0.2-beta18 (2016-07-11)
+
+ *  [**Change**] openmpt123: Add PulseAudio output support. Autotools and
+    `Makefile` build systems now depend on `libpulse` and `libpulse-simple` by
+    default. Disable with `--without-pulseaudio` or `NO_PULSEAUDIO=1`
+    respectively. When enabled, PulseAudio will be the default output driver,
+ *  [**Change**] xmp-openmpt: Settings are now stored in xmplay.ini like with
+    every other plugin.
+
+ *  [**Regression**] openmpt123: Support for FLAC < 1.3.0 has been removed. FLAC
+    before 1.3.0 is broken beyond repair as it provides `assert.h` in the
+    include path.
+
+ *  [**Bug**] Generated pkg-config file libopenmpt.pc by both `Makefile` and
+    Autotools build systems was totally broken.
+ *  [**Bug**] libopenmpt no longer uses the non-thread-safe global std::rand()
+    function.
+ *  [**Bug**] Sample loops in GDM modules did not work when using Emscripten.
+ *  [**Bug**] XM and MO3 loaders could crash due to unaligned memory accesses.
+ *  [**Bug**] Fixed incorrect handling of custom MPTM tunings on big endian
+    platforms.
+ *  [**Bug**] Fixed various problems found with clang 3.8 static analyzer,
+    address sanitizer and undefined behaviour sanitizer.
+ *  [**Bug**] File header probing functionality was broken for most formats.
+ *  [**Bug**] With non-seekable streams, the entire file was almost always
+    cached even if it was not of any supported module type.
+
+ *  Seeking in allsubsongs-mode now works correctly.
+ *  openmpt123: Added subsong support.
+ *  Various playback fixes for 669, IT, MT2 and MTM files.
+ *  Some MOD files with more than 128 patterns (e.g. NIETNU.MOD) were not loaded
+    correctly.
+ *  A new example `libopenmpt_example_c_probe` has been added which demonstrates
+    the usage and flexibility of openmpt_could_open_propability() in the C API
+    under various constraints.
+
+### libopenmpt 0.2-beta17 (2016-05-21)
+
+ *  [**Change**] The Makefile and Autotools build systems now require to
+    explicitly specify `NO_LTDL=1` or `--without-ltdl` respectively if no
+    support for dynamic loading of third party libraries via libtool libltdl is
+    desired.
+ *  [**Change**] In the Makefile build system option `USE_MO3` and the Autotools
+    build system option `--enable-mo3` are gone. Dynamic loading of un4seen
+    unmo3 is now always enabled when dynamic loading is possible and built-in
+    MO3 support is not possible because either a MP3 or a Vorbis decoder is
+    missing.
+ *  [**Change**] The MSVC build system changed. The `libopenmptDLL` project is
+    gone. Use the new `ReleaseShared` configuration of the `libopenmpt` project
+    instead. libopenmpt now links against zlib by default. A separate project
+    with smaller footprint linking against miniz is still available as
+    `libopenmpt-small`.
+ *  [**Change**] The constants used to query library information from
+    `openmpt_get_string()` and `openmpt::string::get()` (i.e. OPENMPT_STRING_FOO
+    and openmpt::string::FOO) have been deprecated because having syntactic
+    constants for theses keys makes extending the API in a backwards and
+    forwards compatible way harder than it should be. Please just use the string
+    literals directly.
+ *  [**Change**] Deprecated API identifiers will now cause deprecation warnings
+    with MSVC, GCC and clang. `#define LIBOPENMPT_NO_DEPRECATE` to disable the
+    warnings.
+ *  [**Change**] openmpt123: `--[no-]shuffle` option has been renamed to
+    `--[no-]randomize`. A new `--[no-]shuffle` option has been added which
+    shuffles randomly through the playlist as opposed to randomizing the
+    playlist upfront.
+ *  [**Change**] Support for Un4seen unmo3 has generally been deprecated in
+    favour of the new internal mo3 decoder. Un4seen unmo3 support will be
+    removed on 2018-01-01.
+
+ *  [**Bug**] Memory consumption during loading has been reduced by about 1/3 in
+    case a seekable input stream is provided (either via C API callback open
+    functions or via C++ API iostream constructors).
+ *  [**Bug**] Some samples in AMS modules were detuned when using Emscripten.
+ *  [**Bug**] Possible crash with excessive portamento down in some formats.
+ *  [**Bug**] Possible crashes with malformed AMF, AMS, DBM, IT, MDL, MED, MPTM,
+    MT2, PSM and MMCMP-, XPK- and PP20-compressed files.
+ *  [**Bug**] `openmpt::module::format_pattern_row_channel` with `width == 0`
+    was returning an empty string instead of an string with unconstrained
+    length.
+
+ *  Support for ProTracker 3.6 IFF-style modules and SoundFX / MultiMedia Sound
+    (SFX / MMS) modules.
+ *  libopenmpt now has support for DMO plugins on Windows when built with MSVC.
+    Additionally, the DMO Compression, Distortion, Echo, Gargle, ParamEQ and
+    WavesReverb DSPs are emulated on on all other platforms.
+ *  libopenmpt now supports the DigiBooster Echo DSP.
+ *  To avoid any of the aforementioned plugins to be used, the load.skip_plugins
+    ctl can be passed when loading a module.
+ *  libopenmpt got native MO3 support with MP3 decoding either via libmpg123 or
+    MediaFoundation (on Windows 7 and up) and Vorbis decoding via libogg,
+    libvorbis, libvorbisfile or stb_vorbis.
+ *  libopenmpt MSVC builds with Visual Studio 2010 or later on Windows 7 or
+    later now use an internal MO3 decoder with libogg, libvorbis, libvorbisfile,
+    and libmpg123 or minimp3 or MediaFoundation suppport by default. Visual
+    Studio 2008 builds still use unmo3.dll by default but also support the
+    built-in decoder in which case libmpg123 is required.
+ *  libopenmpt with Makefile or Autotools build system can now also use
+    glibc/libdl instead of libtool/libltdl for dynamic loading of third-party
+    libraries. Options `NO_DL=1` and `--without-dl` have been added
+    respectively.
+ *  The `Makefile` build system got 4 new options NO_MPG123, NO_OGG, NO_VORBIS,
+    NO_VORBISFILE. The default is to use the new dependencies automatically.
+ *  The `Autotools` build system got 4 new options --without-mpg123,
+    --without-ogg, --without-vorbis, --without-vorbisfile. The default is to use
+    the new dependencies automatically.
+ *  Makefile and Android builds got support for using minimp3 instead of
+    libmpg123. For Android, use `Android-minimp3-stbvorbis.mk`, for Makefile use
+    `USE_MINIMP3=1`. You have to download
+    [minimp3](http://keyj.emphy.de/minimp3/) yourself and put its contents into
+    `include/minimp3/`.
+ *  `"source_url"`, `"source_date"` and `"build_compiler"` keys have been added
+    to `openmpt_string_get()` and `openmpt::string::get()`.
+ *  openmpt123: Add new `--[no-]restart]` option which restarts the playlist
+    when finished.
+ *  Improved Ultimate SoundTracker version detection heuristics.
+ *  Playing a sample at a sample rate close to the mix rate could lead to small
+    clicks when using vibrato.
+ *  More fine-grained internal legacy module compatibility settings to correctly
+    play back modules made with older versions of OpenMPT and a few other
+    trackers.
+ *  The tail of compressed MDL samples was slightly off.
+ *  Some probably hex-edited XM files (e.g. cybernostra weekend.xm) were not
+    loaded correctly.
+ *  Countless other playback fixes for MOD, XM, S3M, IT and MT2 files.
+
+### libopenmpt 0.2-beta16 (2015-11-22)
+
+ *  [**Change**] The Autotools build system does strict checking of all
+    dependencies now. Instead of best effort auto-magic detection of all
+    potentially optional dependencies, the default set of dependencies is now
+    enforced unless each individual dependency gets explicitely disabled via
+    `--without-foo` or `--disable-foo` `./configure` switches. Run
+    `./configure --help` for the full list of options.
+
+ *  [**Bug**] Some MOD files were erroneously detected as 669 files.
+ *  [**Bug**] Some malformed AMF files could result in very long loading times.
+ *  [**Bug**] Fixed crashes in IMF and MT2 loaders.
+ *  [**Bug**] MTM files generated by UNMO3 were not loaded properly.
+ 
+ *  Improved MTM playback.
+ *  `make CONFIG=haiku` for Haiku has been added.
+ *  Language bindings for FreeBASIC have been added (see
+    `libopenmpt/bindings/`).
+
+### libopenmpt 0.2-beta15 (2015-10-31)
+
+ *  [**Change**] openmpt123: SDL2 is now supported and preferred to SDL1 if
+    available with the `Makefile` build system.
+
+ *  [**Bug**] Emscripten support for older emscripten versions broke in -beta14.
+    These are now supported again when using `make CONFIG=emscripten-old`.
+ *  [**Bug**] Fixed crashes in MED loader.
+
+ *  Playback improvements and loader fixes for MOD, MT2 and MED.
+
+### libopenmpt 0.2-beta14 (2015-09-13)
+
+ *  [**Change**] The C++ API example now uses the PortAudio C++ bindings
+    instead of the C API.
+ *  [**Change**] Default compiler options for Emscripten have been changed to
+    more closely match the Emscripten recommendations.
+
+ *  [**Bug**] Client code compilation with C89 compilers was broken in beta13.
+ *  [**Bug**] Test suite failed on certain Emscripten/node.js combinations.
+ *  [**Bug**] Fixed various crashes or hangs in DMF, OKT, PLM, IT and MPTM
+    loaders.
+
+ *  Implemented error handling in the libopenmpt API examples.
+ *  Various playback improvements and fixes for OKT, IT and MOD.
+
+### libopenmpt 0.2-beta13 (2015-08-16)
+
+ *  [**Change**] The MSVC build system has been redone. Solutions are now
+    located in `build/vsVERSION/`.
+
+ *  [**Bug**] get_current_channel_vu_left and get_current_channel_vu_right only
+    return the volume of the front left and right channels now.
+    get_current_channel_vu_rear_left and get_current_channel_vu_rear_right
+    do now actually work and return non-zero values.
+ *  [**Bug**] Fix crashes and hangs in MED and MDL loaders and with some
+    truncated compressed IT samples.
+ *  [**Bug**] Fix crash when playing extremely high-pitched samples.
+
+ *  Completed C and C++ documentation
+ *  Added new key for openmpt::module::get_metadata, "message_raw", which
+    returns an empty string if there is no song message rather than a list of
+    instrument names.
+ *  in_openmpt: Support for compiling with VS2008.
+ *  xmp-openmpt: Support for compiling with VS2008.
+ *  in_openmpt: Add a more readable file information window.
+
+### libopenmpt 0.2-beta12 (2015-04-19)
+
+ *  Playback fix when row delay effect is used together with offset command.
+ *  A couple of fixes for the seek.sync_samples=1 case.
+ *  IT compatibility fix for IT note delay.
+ *  ProTracker MOD playback compatibility improvement.
+
+### libopenmpt 0.2-beta11 (2015-04-18)
+
+ *  [**Change**] openmpt_stream_seek_func() now gets called with
+    OPENMPT_STREAM_SEEK_SET, OPENMPT_STREAM_SEEK_CUR and
+    OPENMPT_STREAM_SEEK_END whence parameter instead of SEEK_SET, SEEK_CUR and
+    SEEK_END. These are defined to 0, 1 and 2 respectively which corresponds to
+    the definition in all common C libraries. If your C library uses different
+    constants, this theoretically breaks binary compatibility. The old
+    libopenmpt code, however, never actually called the seek function, thus,
+    there will be no problem in practice.
+ *  [**Change**] openmpt123: When both SDL1.2 and PortAudio are available,
+    SDL is now the preferred backend because SDL is more widespread and better
+    tested on all kinds of different platforms, and in general, SDL is just
+    more reliable.
+
+ *  [**Bug**] libopenmpt now also compiles with GCC 4.3.
+
+ *  libopenmpt now supports PLM (Disorder Tracker 2) files.
+ *  Various playback improvements and fixes for IT, S3M, XM, MOD, PTM and 669
+    files.
+
+### libopenmpt 0.2-beta10 (2015-02-17)
+
+ *  [**Change**] Makefile configuration filenames changed from
+    `build/make/Makefile.config.*` to `build/make/config-*.mk`.
+ *  [**Change**] libopenmpt for Android now supports unmo3 from un4seen. See
+    `build/android_ndk/README.AndroidNDK.txt` for details.
+
+ *  [**Bug**] Fix out-of-bounds read in mixer code for ProTracker-compatible
+    MOD files which was introduced back in r4223 / beta6.
+
+ *  Vibrato effect was too weak in beta8 and beta9 in IT linear slide mode.
+ *  Very small fine portamento was wrong in beta8 and beta9 in IT linear slide
+    mode.
+ *  Tiny IT playback compatibility improvements.
+ *  STM playback improvements.
+
+### libopenmpt 0.2-beta9 (2014-12-21)
+
+ *  [**Bug**] libopenmpt_ext.hpp was missing from the Windows binary zip files.
+
+### libopenmpt 0.2-beta8 (2014-12-21)
+
+ *  [**Change**] foo_openmpt: Settings are now accessible via foobar2000
+    advanced settings.
+ *  [**Change**] Autotools based build now supports libunmo3. Specify
+    --enable-unmo3.
+ *  [**Change**] Support for dynamic loading of libunmo3 on MacOS X.
+ *  [**Change**] libopenmpt now uses libltld (from libtool) for dynamic loading
+    of libunmo3 on all non-Windows platforms.
+ *  [**Change**] Support for older compilers:
+     *  GCC 4.1.x to 4.3.x (use `make ANCIENT=1`)
+     *  Microsoft Visual Studio 2008 (with latest Service Pack)
+        (see `build/vs2008`)
+ *  [**Change**] libopenmpt_ext.hpp is now distributed by default. The API is
+    still considered experimental and not guaranteed to stay API or ABI
+    compatible.
+ *  [**Change**] xmp-openmpt / in_openmpt: No more libopenmpt_settings.dll.
+    The settings dialog now uses a statically linked copy of MFC.
+
+ *  [**Bug**] The -autotools tarballs were not working at all.
+
+ *  Vastly improved MT2 loader.
+ *  Improved S3M playback compatibility.
+ *  Added openmpt::ext::interactive, an extension which adds a whole bunch of
+    new functionality to change playback in some way or another.
+ *  Added possibility to sync sample playback when using
+    openmpt::module::set_position_* by setting the ctl value
+    seek.sync_samples=1  
+ *  Support for "hidden" subsongs has been added.
+    They are accessible through the same interface as ordinary subsongs, i.e.
+    use openmpt::module::select_subsong to switch between any kind of subsongs.
+ *  All subsongs can now be played consecutively by passing -1 as the subsong
+    index in openmpt::module::select_subsong.
+ *  Added documentation for a couple of more functions.
+
+### libopenmpt 0.2-beta7 (2014-09-07)
+
+ *  [**Change**] libopenmpt now has an GNU Autotools based build system (in
+    addition to all previously supported ways of building libopenmpt).
+    Autotools support is packaged separately as tarballs ending in
+    `-autotools.tar.gz`.
+
+ *  [**Bug**] The distributed windows .zip file did not include pugixml.
+
+ *  [**Regression**] openmpt123: Support for writing WavPack (.wv) files has
+    been removed.
+    
+    Reasoning:
+     1. WavPack support was incomplete and did not include support for writing
+        WavPack metadata at all.
+     2. openmpt123 already supports libSndFile which can be used to write
+        uncompressed lossless WAV files which can then be encoded to whatever
+        format the user desires with other tools.
+
+### libopenmpt 0.2-beta6 (2014-09-06)
+
+ *  [**Change**] openmpt123: SDL is now also used by default if availble, in
+    addition to PortAudio.
+ *  [**Change**] Support for emscripten is no longer experimental.
+ *  [**Change**] libopenmpt itself can now also be compiled with VS2008.
+
+ *  [**Bug**] Fix all known crashes on platforms that do not support unaligned
+    memory access.
+ *  [**Bug**] openmpt123: Effect column was always missing in pattern display.
+
+### libopenmpt 0.2-beta5 (2014-06-15)
+
+ *  [**Change**] Add unmo3 support for non-Windows builds.
+ *  [**Change**] Namespace all internal functions in order to allow statically
+    linking against libopenmpt without risking duplicate symbols.
+ *  [**Change**] Iconv is now completely optional and only used on Linux
+    systems by default.
+ *  [**Change**] Added libopenmpt_example_c_stdout.c, an example without
+    requiring PortAudio.
+ *  [**Change**] Add experimental support for building libopenmpt with
+    emscripten.
+
+ *  [**Bug**] Fix ping-pong loop behaviour which broke in 0.2-beta3.
+ *  [**Bug**] Fix crashes when accessing invalid patterns through libopenmpt
+    API.
+ *  [**Bug**] Makefile: Support building with missing optional dependencies
+    without them being stated explicitely.
+ *  [**Bug**] openmpt123: Crash when quitting while playback is stopped.
+ *  [**Bug**] openmpt123: Crash when writing output to a file in interactive UI
+    mode.
+ *  [**Bug**] openmpt123: Wrong FLAC output filename in --render mode.
+
+ *  Various smaller playback accuracy improvements.
+
+### libopenmpt 0.2-beta4 (2014-02-25)
+
+ *  [**Bug**] Makefile: Dependency tracking for the test suite did not work.
+
+### libopenmpt 0.2-beta3 (2014-02-21)
+
+ *  [**Change**] The test suite is now built by default with Makefile based
+    builds. Use `TEST=0` to skip building the tests. `make check` runs the test
+    suite.
+
+ *  [**Bug**] Crash in MOD and XM loaders on architectures not supporting
+    unaligned memory access.
+ *  [**Bug**] MMCMP, PP20 and XPK unpackers should now work on non-x86 hardware
+    and implement proper bounds checking.
+ *  [**Bug**] openmpt_module_get_num_samples() returned the wrong value.
+ *  [**Bug**] in_openmpt: DSP plugins did not work properly.
+ *  [**Bug**] in_openmpt/xmp-openmpt: Setting name for stereo separation was
+    misspelled. This version will revert your stereo separation settings to
+    default.
+ *  [**Bug**] Crash when loading some corrupted modules with stereo samples.
+
+ *  Support building on Android NDK.
+ *  Avoid clicks in sample loops when using interpolation.
+ *  IT filters are now done in integer instead of floating point. This improves
+    performance, especially on architectures with no or a slow FPU.
+ *  MOD pattern break handling fixes.
+ *  Various XM playback improvements.
+ *  Improved and switchable dithering when using 16bit integer API.
+
+### libopenmpt 0.2-beta2 (2014-01-12)
+
+ *  [**Bug**] MT2 loader crash.
+ *  [**Bug**] Saving settings in in_openmpt and xmp-openmpt did not work.
+ *  [**Bug**] Load libopenmpt_settings.dll also from below Plugins directory in
+    Winamp.
+
+ *  DBM playback improvements.
+
+### libopenmpt 0.2-beta1 (2013-12-31)
+
+ *  First release.
+
diff --git a/libs/libopenmpt/debian/libopenmpt-0.4.0-trusty-backport.diff b/libs/libopenmpt/debian/libopenmpt-0.4.0-trusty-backport.diff
new file mode 100644
index 0000000000000000000000000000000000000000..1c9b56ba53d2e0ef17ee1f8c9d224294c82504e1
--- /dev/null
+++ b/libs/libopenmpt/debian/libopenmpt-0.4.0-trusty-backport.diff
@@ -0,0 +1,550 @@
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/changelog ./debian/changelog
+--- ../../orig/libopenmpt-0.4.0/debian/changelog	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/changelog	2019-01-04 17:56:35.024725537 -0500
+@@ -1,3 +1,38 @@
++libopenmpt (0.4.0-ubuntu14.04.1~ppa14) trusty; urgency=medium
++
++  * Remove debian symbols files due to gcc 4.8 - 5 C++ abi incompatibility
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 17:56:01 -0500
++
++libopenmpt (0.4.0-ubuntu14.04.1~ppa13) trusty; urgency=medium
++
++  * autoreconf libtool
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 17:10:45 -0500
++
++libopenmpt (0.4.0-ubuntu14.04.1~ppa12) trusty; urgency=medium
++
++  * debian/rules autoreconf
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 17:03:19 -0500
++
++libopenmpt (0.4.0-ubuntu14.04.1~ppa11) trusty; urgency=medium
++
++  * debian/rules autoreconf
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 16:59:08 -0500
++
++libopenmpt (0.4.0-ubuntu14.04.1~ppa10) trusty; urgency=medium
++
++  * Backport to trusty
++  * Added automake build depend
++  * Adjusted debhelper depend to >= 9.0~
++  * Adjusted dpkg-dev depend to >= 1.17.0
++  * Adjusted debian/compat to 10
++  * autoreconf --force --install
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 16:54:04 -0500
++
+ libopenmpt (0.4.0-1) unstable; urgency=medium
+ 
+   * New upstream release.
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/compat ./debian/compat
+--- ../../orig/libopenmpt-0.4.0/debian/compat	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/compat	2019-01-04 16:39:17.613976357 -0500
+@@ -1 +1 @@
+-11
++10
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/control ./debian/control
+--- ../../orig/libopenmpt-0.4.0/debian/control	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/control	2019-01-04 17:10:42.008523333 -0500
+@@ -4,10 +4,12 @@
+ Maintainer: Debian Multimedia Maintainers <debian-multimedia@lists.debian.org>
+ Uploaders: James Cowgill <jcowgill@debian.org>
+ Build-Depends:
+- debhelper (>= 11.1~),
++ automake,
++ libtool,
++ debhelper (>= 9.0~),
+  dh-exec,
+  doxygen,
+- dpkg-dev (>= 1.18.0),
++ dpkg-dev (>= 1.17.0),
+  libflac-dev,
+  libmpg123-dev,
+  libogg-dev,
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/files ./debian/files
+--- ../../orig/libopenmpt-0.4.0/debian/files	1969-12-31 19:00:00.000000000 -0500
++++ ./debian/files	2019-01-04 17:56:47.813250880 -0500
+@@ -0,0 +1 @@
++libopenmpt_0.4.0-ubuntu14.04.1~ppa14_source.buildinfo libs optional
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/libopenmpt0.symbols ./debian/libopenmpt0.symbols
+--- ../../orig/libopenmpt-0.4.0/debian/libopenmpt0.symbols	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/libopenmpt0.symbols	1969-12-31 19:00:00.000000000 -0500
+@@ -1,256 +0,0 @@
+-libopenmpt.so.0 libopenmpt0 #MINVER#
+-* Build-Depends-Package: libopenmpt-dev
+-# Ignore std:: template instantiations
+- (regex|optional)"^_ZN?K?S" 0.2.7025~beta20.1
+- _ZN7openmpt10module_ext13get_interfaceERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt10module_extC1EPKcjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt10module_extC1EPKcmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt10module_extC1EPKvjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt10module_extC1EPKvmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC1ERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC1ERKSt6vectorIcSaIcEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcES2_EESC_St4lessISC_ESaISt4pairIKSC_SC_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC1ERSiRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES9_St4lessIS9_ESaISt4pairIKS9_S9_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt10module_extC2EPKcjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt10module_extC2EPKcmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt10module_extC2EPKvjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt10module_extC2EPKvmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC2ERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC2ERKSt6vectorIcSaIcEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcES2_EESC_St4lessISC_ESaISt4pairIKSC_SC_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extC2ERSiRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES9_St4lessIS9_ESaISt4pairIKS9_S9_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extD0Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extD1Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extD2Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt10module_extaSERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt16get_core_versionEv@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt17probe_file_headerEyPKhj@Base 0.3.0
+- (arch-bits=64)_ZN7openmpt17probe_file_headerEmPKhm@Base 0.3.0
+- (arch-bits=32)_ZN7openmpt17probe_file_headerEyPKhjy@Base 0.3.0
+- (arch-bits=64)_ZN7openmpt17probe_file_headerEmPKhmm@Base 0.3.0
+- (arch-bits=32)_ZN7openmpt17probe_file_headerEyRSi@Base 0.3.0
+- (arch-bits=64)_ZN7openmpt17probe_file_headerEmRSi@Base 0.3.0
+- _ZN7openmpt19get_library_versionEv@Base 0.2.7025~beta20.1
+- _ZN7openmpt22could_open_probabilityERSidRSo@Base 0.3.0
+- _ZN7openmpt22could_open_propabilityERSidRSo@Base 0.2.7025~beta20.1
+- _ZN7openmpt22is_extension_supportedERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt24get_supported_extensionsB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt38probe_file_header_get_recommended_sizeEv@Base 0.3.0
+- _ZN7openmpt6module14select_subsongEi@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module16set_render_paramEii@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module16set_repeat_countEi@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module20set_position_secondsEd@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module21read_interleaved_quadEijPf@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module21read_interleaved_quadEimPf@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module21read_interleaved_quadEijPs@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module21read_interleaved_quadEimPs@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module22set_position_order_rowEii@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module23read_interleaved_stereoEijPf@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module23read_interleaved_stereoEimPf@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module23read_interleaved_stereoEijPs@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module23read_interleaved_stereoEimPs@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPf@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPf@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPfS1_@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPfS1_@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPfS1_S1_S1_@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPfS1_S1_S1_@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPs@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPs@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPsS1_@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPsS1_@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6module4readEijPsS1_S1_S1_@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6module4readEimPsS1_S1_S1_@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module7ctl_setERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_@Base 0.2.7025~beta20.1
+- _ZN7openmpt6module8set_implEPNS_11module_implE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1EPKcS2_RSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC1EPKcjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC1EPKcmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1EPKhS2_RSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC1EPKhjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC1EPKhmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC1EPKvjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC1EPKvmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1ERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1ERKSt6vectorIcSaIcEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcES2_EESC_St4lessISC_ESaISt4pairIKSC_SC_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1ERKSt6vectorIhSaIhEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESD_St4lessISD_ESaISt4pairIKSD_SD_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1ERSiRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES9_St4lessIS9_ESaISt4pairIKS9_S9_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC1Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2EPKcS2_RSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC2EPKcjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC2EPKcmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2EPKhS2_RSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC2EPKhjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC2EPKhmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZN7openmpt6moduleC2EPKvjRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZN7openmpt6moduleC2EPKvmRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESA_St4lessISA_ESaISt4pairIKSA_SA_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2ERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2ERKSt6vectorIcSaIcEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcES2_EESC_St4lessISC_ESaISt4pairIKSC_SC_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2ERKSt6vectorIhSaIhEERSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESD_St4lessISD_ESaISt4pairIKSD_SD_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2ERSiRSoRKSt3mapINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES9_St4lessIS9_ESaISt4pairIKS9_S9_EEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleC2Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleD0Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleD1Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleD2Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt6moduleaSERKS0_@Base 0.2.7025~beta20.1
+- _ZN7openmpt6string3getERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionC1EOS0_@Base 0.3.0
+- _ZN7openmpt9exceptionC1ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionC1ERKS0_@Base 0.3.0
+- _ZN7openmpt9exceptionC2EOS0_@Base 0.3.0
+- _ZN7openmpt9exceptionC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionC2ERKS0_@Base 0.3.0
+- _ZN7openmpt9exceptionD0Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionD1Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionD2Ev@Base 0.2.7025~beta20.1
+- _ZN7openmpt9exceptionaSEOS0_@Base 0.3.0
+- _ZN7openmpt9exceptionaSERKS0_@Base 0.3.0
+- _ZNK7openmpt6module12get_metadataERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module14get_num_ordersEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module15get_current_rowEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module15get_num_samplesEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module15get_order_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_num_channelsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_num_patternsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_num_subsongsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_render_paramEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_repeat_countEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module16get_sample_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_channel_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_current_orderEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_current_speedEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_current_tempoEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_metadata_keysB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_order_patternEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_pattern_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module17get_subsong_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module19get_current_patternEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module19get_num_instrumentsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module20get_duration_secondsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module20get_instrument_namesB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module20get_pattern_num_rowsEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module20get_position_secondsEv@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module20get_selected_subsongEv@Base 0.3.0
+- (arch-bits=32)_ZNK7openmpt6module26format_pattern_row_channelB5cxx11Eiiijb@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZNK7openmpt6module26format_pattern_row_channelB5cxx11Eiiimb@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module27get_current_channel_vu_leftEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module27get_current_channel_vu_monoEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module28get_current_channel_vu_rightEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module28get_current_playing_channelsEv@Base 0.2.7025~beta20.1
+- (arch-bits=32)_ZNK7openmpt6module29highlight_pattern_row_channelB5cxx11Eiiijb@Base 0.2.7025~beta20.1
+- (arch-bits=64)_ZNK7openmpt6module29highlight_pattern_row_channelB5cxx11Eiiimb@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module31get_pattern_row_channel_commandEiiii@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module32get_current_channel_vu_rear_leftEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module33get_current_channel_vu_rear_rightEi@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module34format_pattern_row_channel_commandB5cxx11Eiiii@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module37highlight_pattern_row_channel_commandB5cxx11Eiiii@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module7ctl_getERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE@Base 0.2.7025~beta20.1
+- _ZNK7openmpt6module8get_ctlsB5cxx11Ev@Base 0.2.7025~beta20.1
+- _ZNK7openmpt9exception4whatEv@Base 0.2.7025~beta20.1
+- _ZTIN7openmpt10module_extE@Base 0.2.7025~beta20.1
+- _ZTIN7openmpt6moduleE@Base 0.2.7025~beta20.1
+- _ZTIN7openmpt9exceptionE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZTISt11_Mutex_baseILN9__gnu_cxx12_Lock_policyE2EE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZTISt16_Sp_counted_baseILN9__gnu_cxx12_Lock_policyE2EE@Base 0.2.7025~beta20.1
+- _ZTSN7openmpt10module_extE@Base 0.2.7025~beta20.1
+- _ZTSN7openmpt6moduleE@Base 0.2.7025~beta20.1
+- _ZTSN7openmpt9exceptionE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZTSSt11_Mutex_baseILN9__gnu_cxx12_Lock_policyE2EE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZTSSt16_Sp_counted_baseILN9__gnu_cxx12_Lock_policyE2EE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZTSSt19_Sp_make_shared_tag@Base 0.2.7025~beta20.1
+- _ZTVN7openmpt10module_extE@Base 0.2.7025~beta20.1
+- _ZTVN7openmpt6moduleE@Base 0.2.7025~beta20.1
+- _ZTVN7openmpt9exceptionE@Base 0.2.7025~beta20.1
+- (optional=weak)_ZZNSt19_Sp_make_shared_tag5_S_tiEvE5__tag@Base 0.4.0
+- openmpt_could_open_probability2@Base 0.3.0
+- openmpt_could_open_probability@Base 0.3.0
+- openmpt_could_open_propability@Base 0.2.7025~beta20.1
+- openmpt_error_func_default@Base 0.3.0
+- openmpt_error_func_errno@Base 0.3.0
+- openmpt_error_func_errno_userdata@Base 0.3.0
+- openmpt_error_func_ignore@Base 0.3.0
+- openmpt_error_func_log@Base 0.3.0
+- openmpt_error_func_store@Base 0.3.0
+- openmpt_error_is_transient@Base 0.3.0
+- openmpt_error_string@Base 0.3.0
+- openmpt_free_string@Base 0.2.7025~beta20.1
+- openmpt_get_core_version@Base 0.2.7025~beta20.1
+- openmpt_get_library_version@Base 0.2.7025~beta20.1
+- openmpt_get_string@Base 0.2.7025~beta20.1
+- openmpt_get_supported_extensions@Base 0.2.7025~beta20.1
+- openmpt_is_extension_supported@Base 0.2.7025~beta20.1
+- openmpt_log_func_default@Base 0.2.7025~beta20.1
+- openmpt_log_func_silent@Base 0.2.7025~beta20.1
+- openmpt_module_create2@Base 0.3.0
+- openmpt_module_create@Base 0.2.7025~beta20.1
+- openmpt_module_create_from_memory2@Base 0.3.0
+- openmpt_module_create_from_memory@Base 0.2.7025~beta20.1
+- openmpt_module_ctl_get@Base 0.2.7025~beta20.1
+- openmpt_module_ctl_set@Base 0.2.7025~beta20.1
+- openmpt_module_destroy@Base 0.2.7025~beta20.1
+- openmpt_module_error_clear@Base 0.3.0
+- openmpt_module_error_get_last@Base 0.3.0
+- openmpt_module_error_get_last_message@Base 0.3.0
+- openmpt_module_error_set_last@Base 0.3.0
+- openmpt_module_ext_create@Base 0.3.0
+- openmpt_module_ext_create_from_memory@Base 0.3.0
+- openmpt_module_ext_destroy@Base 0.3.0
+- openmpt_module_ext_get_interface@Base 0.3.0
+- openmpt_module_ext_get_module@Base 0.3.0
+- openmpt_module_format_pattern_row_channel@Base 0.2.7025~beta20.1
+- openmpt_module_format_pattern_row_channel_command@Base 0.2.7025~beta20.1
+- openmpt_module_get_channel_name@Base 0.2.7025~beta20.1
+- openmpt_module_get_ctls@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_channel_vu_left@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_channel_vu_mono@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_channel_vu_rear_left@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_channel_vu_rear_right@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_channel_vu_right@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_order@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_pattern@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_playing_channels@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_row@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_speed@Base 0.2.7025~beta20.1
+- openmpt_module_get_current_tempo@Base 0.2.7025~beta20.1
+- openmpt_module_get_duration_seconds@Base 0.2.7025~beta20.1
+- openmpt_module_get_instrument_name@Base 0.2.7025~beta20.1
+- openmpt_module_get_metadata@Base 0.2.7025~beta20.1
+- openmpt_module_get_metadata_keys@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_channels@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_instruments@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_orders@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_patterns@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_samples@Base 0.2.7025~beta20.1
+- openmpt_module_get_num_subsongs@Base 0.2.7025~beta20.1
+- openmpt_module_get_order_name@Base 0.2.7025~beta20.1
+- openmpt_module_get_order_pattern@Base 0.2.7025~beta20.1
+- openmpt_module_get_pattern_name@Base 0.2.7025~beta20.1
+- openmpt_module_get_pattern_num_rows@Base 0.2.7025~beta20.1
+- openmpt_module_get_pattern_row_channel_command@Base 0.2.7025~beta20.1
+- openmpt_module_get_position_seconds@Base 0.2.7025~beta20.1
+- openmpt_module_get_render_param@Base 0.2.7025~beta20.1
+- openmpt_module_get_repeat_count@Base 0.2.7025~beta20.1
+- openmpt_module_get_sample_name@Base 0.2.7025~beta20.1
+- openmpt_module_get_selected_subsong@Base 0.3.0
+- openmpt_module_get_subsong_name@Base 0.2.7025~beta20.1
+- openmpt_module_highlight_pattern_row_channel@Base 0.2.7025~beta20.1
+- openmpt_module_highlight_pattern_row_channel_command@Base 0.2.7025~beta20.1
+- openmpt_module_read_float_mono@Base 0.2.7025~beta20.1
+- openmpt_module_read_float_quad@Base 0.2.7025~beta20.1
+- openmpt_module_read_float_stereo@Base 0.2.7025~beta20.1
+- openmpt_module_read_interleaved_float_quad@Base 0.2.7025~beta20.1
+- openmpt_module_read_interleaved_float_stereo@Base 0.2.7025~beta20.1
+- openmpt_module_read_interleaved_quad@Base 0.2.7025~beta20.1
+- openmpt_module_read_interleaved_stereo@Base 0.2.7025~beta20.1
+- openmpt_module_read_mono@Base 0.2.7025~beta20.1
+- openmpt_module_read_quad@Base 0.2.7025~beta20.1
+- openmpt_module_read_stereo@Base 0.2.7025~beta20.1
+- openmpt_module_select_subsong@Base 0.2.7025~beta20.1
+- openmpt_module_set_error_func@Base 0.3.0
+- openmpt_module_set_log_func@Base 0.3.0
+- openmpt_module_set_position_order_row@Base 0.2.7025~beta20.1
+- openmpt_module_set_position_seconds@Base 0.2.7025~beta20.1
+- openmpt_module_set_render_param@Base 0.2.7025~beta20.1
+- openmpt_module_set_repeat_count@Base 0.2.7025~beta20.1
+- openmpt_probe_file_header@Base 0.3.0
+- openmpt_probe_file_header_from_stream@Base 0.3.0
+- openmpt_probe_file_header_get_recommended_size@Base 0.3.0
+- openmpt_probe_file_header_without_filesize@Base 0.3.0
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/libopenmpt-modplug1.symbols ./debian/libopenmpt-modplug1.symbols
+--- ../../orig/libopenmpt-0.4.0/debian/libopenmpt-modplug1.symbols	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/libopenmpt-modplug1.symbols	1969-12-31 19:00:00.000000000 -0500
+@@ -1,200 +0,0 @@
+-libopenmpt_modplug.so.1 libopenmpt-modplug1 #MINVER#
+-* Build-Depends-Package: libopenmpt-modplug-dev
+- LIBOPENMPT_MODPLUG1@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_ExportIT@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_ExportMOD@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_ExportS3M@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_ExportXM@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetCurrentOrder@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetCurrentPattern@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetCurrentRow@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetCurrentSpeed@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetCurrentTempo@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetLength@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetMasterVolume@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetMessage@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetModuleType@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetName@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetPattern@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetPlayingChannels@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_GetSettings@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_InitMixerCallback@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_InstrumentName@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_Load@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_NumChannels@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_NumInstruments@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_NumPatterns@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_NumSamples@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_Read@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_SampleName@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_Seek@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_SeekOrder@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_SetMasterVolume@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_SetSettings@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_Unload@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- ModPlug_UnloadMixerCallback@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile10FreeSampleEPv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10InitPlayerEb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10NoteChangeEjibb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10PackSampleERii@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10ProcessAGCEi@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10ProcessRowEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10ReadSampleEP14_MODINSTRUMENTjPKcj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10RetrigNoteEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile10gdwSysInfoE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile10gnCPUUsageE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile10gnChannelsE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile11DoFreqSlideEP11_MODCHANNELi@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11FineVibratoEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11FreePatternEPv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11InitSysInfoEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11LoopPatternEii@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11PatternLoopEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile11VolumeSlideEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12FineVolumeUpEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12ITInstrToMPTEPKvP17_INSTRUMENTHEADERj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12IsSampleUsedEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12PanningSlideEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12PortamentoUpEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12ResetMidiCfgEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12SetMixConfigEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile12gnReverbTypeE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile12gpSndMixHookE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13CanPackSampleEPcjjPh@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile13CheckCPUUsageEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile13DestroySampleEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile13ResetChannelsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile13SetCurrentPosEj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13SetWaveConfigEjjjb@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13gdwMixingFreqE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13gdwSoundSetupE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13m_nXBassDepthE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile13m_nXBassRangeE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile14AllocateSampleEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14FineVolumeDownEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14GlobalFadeSongEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14GlobalVolSlideEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14LoadMixPluginsEPKvj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14PortamentoDownEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14ProcessEffectsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14SaveMixPluginsEP8_IO_FILEb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14SetPatternNameEjPKc@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14TonePortamentoEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile14m_nReverbDelayE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile14m_nReverbDepthE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile15AllocatePatternEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile15ChannelVolSlideEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile15CreateStereoMixEi@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile15GetSongCommentsEPcjj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile15SetCurrentOrderEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile15SetMasterVolumeEjb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile15SetWaveConfigExEbbbbbbb@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile15gnBitsPerSampleE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile15m_nStreamVolumeE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile16AdjustSampleLoopEP14_MODINSTRUMENT@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile16FinePortamentoUpEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile16InstrumentChangeEP11_MODCHANNELjbbb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile16IsInstrumentUsedEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile16ProcessMidiMacroEjPKcj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile16m_nProLogicDelayE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile16m_nProLogicDepthE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile17DestroyInstrumentEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile17MapMidiInstrumentEjjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile17SetResamplingModeEj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile17m_nMaxMixChannelsE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile18FinePortamentoDownEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile18GetRawSongCommentsEPcjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile18ReadSampleFromSongEjPS_j@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile18SetXBassParametersEjj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile19DetectUnusedSamplesEPb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile19ExtendedMODCommandsEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile19ExtendedS3MCommandsEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile19SetReverbParametersEjj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile19gnVolumeRampSamplesE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile19m_nStereoSeparationE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile20FrequencyToTransposeEP14_MODINSTRUMENT@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile20FrequencyToTransposeEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile20Normalize24BitBufferEPhjjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile20TransposeToFrequencyEii@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile21ExtendedChannelEffectEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile21ExtraFinePortamentoUpEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile21RemoveSelectedSamplesEPb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile21SetSurroundParametersEjj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile21gpMixPluginCreateProcE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile22ReadInstrumentFromSongEjPS_j@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile23ExtraFinePortamentoDownEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile23RemoveInstrumentSamplesEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile4ReadEPvj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile5gnAGCE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile6CreateEPKhj@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile6KeyOffEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile6ReadITEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile6ReadXMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile6SetAGCEb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7DestroyEv@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile7NoteCutEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7Read669EPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadABCEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadAMFEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadAMSEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadDBMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadDMFEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadDSMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadFAREPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadJ2BEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadMDLEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadMIDEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadMT2EPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadMTMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadMedEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadModEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadOKTEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadPATEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadPSMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadPTMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadS3MEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadSTMEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadUMXEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadUltEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7ReadWavEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7TestABCEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7TestMIDEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7TestPATEPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7TremoloEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile7VibratoEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8CheckNNAEjjib@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8FadeSongEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8ReadAMS2EPKhj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8ReadNoteEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8ResetAGCEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8SetSpeedEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile8SetTempoEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile9GetLengthEbb@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFile9PanbrelloEP11_MODCHANNELj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZN10CSoundFile9gnVUMeterE@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFileC1Ev@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFileC2Ev@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFileD1Ev@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZN10CSoundFileD2Ev@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZNK10CSoundFile10S3MConvertEP11_MODCOMMANDb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile13GetCurrentPosEv@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZNK10CSoundFile13GetNNAChannelEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile13GetSampleNameEjPc@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14GetMaxPositionEv@LIBOPENMPT_MODPLUG1 0.2.7386~beta20.3
+- _ZNK10CSoundFile14GetNumChannelsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14GetNumPatternsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14GetPatternNameEjPcj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14GetSaveFormatsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14IsSongFinishedEjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14ModSaveCommandEPK11_MODCOMMANDb@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile14S3MSaveConvertEPjS0_b@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17ConvertModCommandEP11_MODCOMMAND@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17CutOffToFrequencyEji@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetBestSaveFormatEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetFreqFromPeriodEjji@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetInstrumentNameEjPc@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetNoteFromPeriodEj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetNumInstrumentsEv@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile17GetPeriodFromNoteEjij@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile18SetupChannelFilterEP11_MODCHANNELbi@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+- _ZNK10CSoundFile19IsValidBackwardJumpEjjjj@LIBOPENMPT_MODPLUG1 0.2.7561~beta20.5
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/rules ./debian/rules
+--- ../../orig/libopenmpt-0.4.0/debian/rules	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/rules	2019-01-04 17:03:16.206691071 -0500
+@@ -11,9 +11,10 @@
+ 	dh $@
+ 
+ override_dh_autoreconf:
+-	dh_autoreconf --as-needed
++	autoreconf --force --install
+ 
+ override_dh_auto_configure:
++	debian/rules override_dh_autoreconf
+ 	dh_auto_configure -- --disable-static --enable-libopenmpt_modplug
+ 
+ override_dh_auto_build:
diff --git a/libs/libopenmpt/debian/libopenmpt-0.4.0-xenial-backport.diff b/libs/libopenmpt/debian/libopenmpt-0.4.0-xenial-backport.diff
new file mode 100644
index 0000000000000000000000000000000000000000..9cb88b748600c1d97fe276bce8a77a7da5759145
--- /dev/null
+++ b/libs/libopenmpt/debian/libopenmpt-0.4.0-xenial-backport.diff
@@ -0,0 +1,40 @@
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/changelog ./debian/changelog
+--- ../../orig/libopenmpt-0.4.0/debian/changelog	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/changelog	2019-01-04 16:37:08.788775423 -0500
+@@ -1,3 +1,12 @@
++libopenmpt (0.4.0-ubuntu16.04.1~ppa10) xenial; urgency=medium
++
++  * Backport to Xenial
++  * automake build depend added
++  * debhelper depend adjusted to >= 9.0~
++  * deb compat level adjusted to 10
++
++ -- Sonic Team Junior <stjr@srb2.org>  Fri, 04 Jan 2019 16:35:08 -0500
++
+ libopenmpt (0.4.0-1) unstable; urgency=medium
+ 
+   * New upstream release.
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/compat ./debian/compat
+--- ../../orig/libopenmpt-0.4.0/debian/compat	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/compat	2019-01-04 16:34:31.830370437 -0500
+@@ -1 +1 @@
+-11
++10
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/control ./debian/control
+--- ../../orig/libopenmpt-0.4.0/debian/control	2018-12-24 16:43:58.000000000 -0500
++++ ./debian/control	2019-01-04 16:34:59.339499384 -0500
+@@ -4,7 +4,8 @@
+ Maintainer: Debian Multimedia Maintainers <debian-multimedia@lists.debian.org>
+ Uploaders: James Cowgill <jcowgill@debian.org>
+ Build-Depends:
+- debhelper (>= 11.1~),
++ automake,
++ debhelper (>= 9.0~),
+  dh-exec,
+  doxygen,
+  dpkg-dev (>= 1.18.0),
+diff -uraN ../../orig/libopenmpt-0.4.0/debian/files ./debian/files
+--- ../../orig/libopenmpt-0.4.0/debian/files	1969-12-31 19:00:00.000000000 -0500
++++ ./debian/files	2019-01-04 16:37:20.001229883 -0500
+@@ -0,0 +1 @@
++libopenmpt_0.4.0-ubuntu16.04.1~ppa10_source.buildinfo libs optional
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt.h
new file mode 100644
index 0000000000000000000000000000000000000000..d41324885e5255d0573597f610172b934c7e38e6
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt.h
@@ -0,0 +1,1449 @@
+/*
+ * libopenmpt.h
+ * ------------
+ * Purpose: libopenmpt public c interface
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_H
+#define LIBOPENMPT_H
+
+#include "libopenmpt_config.h"
+#include <stddef.h>
+#include <stdint.h>
+
+/*!
+ * \page libopenmpt_c_overview C API
+ *
+ * \section libopenmpt_c_error Error Handling
+ *
+ * - Functions with no return value in the corresponding C++ API return 0 on
+ * failure and 1 on success.
+ * - Functions that return a string in the corresponding C++ API return a
+ * dynamically allocated const char *. In case of failure or memory allocation
+ * failure, a NULL pointer is returned.
+ * - Functions that return integer values signal error condition by returning
+ * an invalid value (-1 in most cases, 0 in some cases).
+ * - All functions that work on an \ref openmpt_module object will call an
+ * \ref openmpt_error_func and depending on the value returned by this function
+ * log the error code and/xor/or store it inside the openmpt_module object.
+ * Stored error codes can be accessed with the openmpt_module_error_get_last()
+ * and openmpt_module_error_get_last_message(). Stored errors will not get
+ * cleared automatically and should be reset with openmpt_module_error_clear().
+ * - Some functions not directly related to an \ref openmpt_module object take
+ * an explicit \ref openmpt_error_func error function callback and a pointer to
+ * an int and behave analog to the functions working on an \ref openmpt_module
+ * object.
+ *
+ * \section libopenmpt_c_strings Strings
+ *
+ * - All strings returned from libopenmpt are encoded in UTF-8.
+ * - All strings passed to libopenmpt should also be encoded in UTF-8.
+ * Behaviour in case of invalid UTF-8 is unspecified.
+ * - libopenmpt does not enforce or expect any particular Unicode
+ * normalization form.
+ * - All strings returned from libopenmpt are dynamically allocated and must
+ * be freed with openmpt_free_string(). Do NOT use the C standard library
+ * free() for libopenmpt strings as that would make your code invalid on
+ * windows when dynamically linking against libopenmpt which itself statically
+ * links to the C runtime.
+ * - All strings passed to libopenmpt are copied. No ownership is assumed or
+ * transferred.
+ *
+ * \section libopenmpt_c_fileio File I/O
+ *
+ * libopenmpt can use 3 different strategies for file I/O.
+ *
+ * - openmpt_module_create_from_memory2() will load the module from the provided
+ * memory buffer, which will require loading all data upfront by the library
+ * caller.
+ * - openmpt_module_create2() with a seekable stream will load the module via
+ * callbacks to the stream interface. libopenmpt will not implement an
+ * additional buffering layer in this case which means the callbacks are assumed
+ * to be performant even with small i/o sizes.
+ * - openmpt_module_create2() with an unseekable stream will load the module via
+ * callbacks to the stream interface. libopempt will make an internal copy as
+ * it goes along, and sometimes have to pre-cache the whole file in case it
+ * needs to know the complete file size. This strategy is intended to be used
+ * if the file is located on a high latency network.
+ *
+ * | create function                                 | speed  | memory consumption |
+ * | ----------------------------------------------: | :----: | :----------------: |
+ * | openmpt_module_create_from_memory2()            | <p style="background-color:green" >fast  </p> | <p style="background-color:yellow">medium</p> | 
+ * | openmpt_module_create2() with seekable stream   | <p style="background-color:red"   >slow  </p> | <p style="background-color:green" >low   </p> |
+ * | openmpt_module_create2() with unseekable stream | <p style="background-color:yellow">medium</p> | <p style="background-color:red"   >high  </p> |
+ *
+ * In all cases, the data or stream passed to the create function is no longer
+ * needed after the openmpt_module has been created and can be freed by the
+ * caller.
+ *
+ * \section libopenmpt_c_outputformat Output Format
+ *
+ * libopenmpt supports a wide range of PCM output formats:
+ * [8000..192000]/[mono|stereo|quad]/[f32|i16].
+ *
+ * Unless you have some very specific requirements demanding a particular aspect
+ * of the output format, you should always prefer 48000/stereo/f32 as the
+ * libopenmpt PCM format.
+ *
+ * - Please prefer 48000Hz unless the user explicitly demands something else.
+ * Practically all audio equipment and file formats use 48000Hz nowadays.
+ * - Practically all module formats are made for stereo output. Mono will not
+ * give you any measurable speed improvements and can trivially be obtained from
+ * the stereo output anyway. Quad is not expected by almost all modules and even
+ * if they do use surround effects, they expect the effects to be mixed to 
+ * stereo. 
+ * - Floating point output provides headroom instead of hard clipping if the
+ * module is louder than 0dBFs, will give you a better signal-to-noise ratio
+ * than int16 output, and avoid the need to apply an additional dithering to the
+ * output by libopenmpt. Unless your platform has no floating point unit at all,
+ * floating point will thus also be slightly faster.
+ *
+ * \section libopenmpt_c_threads libopenmpt in multi-threaded environments
+ *
+ * - libopenmpt is thread-aware.
+ * - Individual libopenmpt objects are not thread-safe.
+ * - libopenmpt itself does not spawn any user-visible threads but may spawn
+ * threads for internal use.
+ * - You must ensure to only ever access a particular libopenmpt object from a
+ * single thread at a time.
+ * - Consecutive accesses can happen from different threads.
+ * - Different objects can be accessed concurrently from different threads.
+ *
+ * \section libopenmpt_c_staticlinking Statically linking to libopenmpt
+ *
+ * libopenmpt is implemented in C++. This implies that linking to libopenmpt
+ * statically requires linking to the C++ runtime and standard library. The
+ * **highly preferred and recommended** way to do this is by using the C++
+ * compiler instead of the platform linker to do the linking. This will do all
+ * necessary things that are C++ specific (in particular, it will pull in the
+ * appropriate runtime and/or library). If for whatever reason it is not
+ * possible to use the C++ compiler for statically linking against libopenmpt,
+ * the libopenmpt build system can list the required libraries in the pkg-config
+ * file `libopenmpt.pc`. However, there is no reliable way to determine the name
+ * of the required library or libraries from within the build system. The
+ * libopenmpt autotools `configure` and plain `Makefile` honor the custom
+ * variable `CXXSTDLIB_PCLIBSPRIVATE` which serves the sole purpose of listing
+ * the standard library (or libraries) required for static linking. The contents
+ * of this variable will be put in `libopenmpt.pc` `Libs.private` and used for
+ * nothing else.
+ *
+ * This problem is inherent to libraries implemented in C++ that can also be used
+ * without a C++ compiler. Other libraries try to solve that by listing
+ * `-lstdc++` unconditionally in `Libs.private`. However, that will break
+ * platforms that use a different C++ standard library (in particular FreeBSD).
+ *
+ * See https://lists.freedesktop.org/archives/pkg-config/2016-August/001055.html .
+ *
+ * Dymically linking to libopenmpt does not require anything special and will
+ * work as usual (and exactly as done for libraries implemented in C).
+ *
+ * Note: This section does not apply when using Microsoft Visual Studio or
+ * Andriod NDK ndk-build build systems.
+ *
+ * \section libopenmpt_c_detailed Detailed documentation
+ *
+ * \ref libopenmpt_c
+ * 
+ * In case a function is not documented here, you might want to look at the
+ * \ref libopenmpt_cpp documentation. The C and C++ APIs are kept semantically
+ * as close as possible.
+ *
+ * \section libopenmpt_c_examples Examples
+ *
+ * \subsection libopenmpt_c_example_unsafe Unsafe, simplified example without any error checking to get a first idea of the API
+ * \include libopenmpt_example_c_unsafe.c
+ * \subsection libopenmpt_c_example_file FILE*
+ * \include libopenmpt_example_c.c
+ * \subsection libopenmpt_c_example_inmemory in memory
+ * \include libopenmpt_example_c_mem.c
+ * \subsection libopenmpt_c_example_stdout reading FILE* and writing PCM data to STDOUT (usable without PortAudio)
+ * \include libopenmpt_example_c_stdout.c
+ *
+ */
+
+/*! \defgroup libopenmpt_c libopenmpt C */
+
+/*! \addtogroup libopenmpt_c
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*! \brief Get the libopenmpt version number
+ *
+ * Returns the libopenmpt version number.
+ * \return The value represents (major << 24 + minor << 16 + patch << 0).
+ * \remarks libopenmpt < 0.3.0-pre used the following scheme: (major << 24 + minor << 16 + revision).
+ */
+LIBOPENMPT_API uint32_t openmpt_get_library_version(void);
+
+/*! \brief Get the core version number
+ *
+ * Return the OpenMPT core version number.
+ * \return The value represents (majormajor << 24 + major << 16 + minor << 8 + minorminor).
+ */
+LIBOPENMPT_API uint32_t openmpt_get_core_version(void);
+
+/*! Return a verbose library version string from openmpt_get_string(). \deprecated Please use `"library_version"` directly. */
+#define OPENMPT_STRING_LIBRARY_VERSION  LIBOPENMPT_DEPRECATED_STRING( "library_version" )
+/*! Return a verbose library features string from openmpt_get_string(). \deprecated Please use `"library_features"` directly. */
+#define OPENMPT_STRING_LIBRARY_FEATURES LIBOPENMPT_DEPRECATED_STRING( "library_features" )
+/*! Return a verbose OpenMPT core version string from openmpt_get_string(). \deprecated Please use `"core_version"` directly. */
+#define OPENMPT_STRING_CORE_VERSION     LIBOPENMPT_DEPRECATED_STRING( "core_version" )
+/*! Return information about the current build (e.g. the build date or compiler used) from openmpt_get_string(). \deprecated Please use `"build"` directly. */
+#define OPENMPT_STRING_BUILD            LIBOPENMPT_DEPRECATED_STRING( "build" )
+/*! Return all contributors from openmpt_get_string(). \deprecated Please use `"credits"` directly. */
+#define OPENMPT_STRING_CREDITS          LIBOPENMPT_DEPRECATED_STRING( "credits" )
+/*! Return contact information about libopenmpt from openmpt_get_string(). \deprecated Please use `"contact"` directly. */
+#define OPENMPT_STRING_CONTACT          LIBOPENMPT_DEPRECATED_STRING( "contact" )
+/*! Return the libopenmpt license from openmpt_get_string(). \deprecated Please use `"license"` directly. */
+#define OPENMPT_STRING_LICENSE          LIBOPENMPT_DEPRECATED_STRING( "license" )
+
+/*! \brief Free a string returned by libopenmpt
+ *
+ * Frees any string that got returned by libopenmpt.
+ */
+LIBOPENMPT_API void openmpt_free_string( const char * str );
+
+/*! \brief Get library related metadata.
+ *
+ * \param key Key to query.
+ *       Possible keys are:
+ *        -  "library_version": verbose library version string
+ *        -  "library_version_is_release": "1" if the version is an officially released version
+ *        -  "library_features": verbose library features string
+ *        -  "core_version": verbose OpenMPT core version string
+ *        -  "source_url": original source code URL
+ *        -  "source_date": original source code date
+ *        -  "source_revision": original source code revision
+ *        -  "source_is_modified": "1" if the original source has been modified
+ *        -  "source_has_mixed_revisions": "1" if the original source has been compiled from different various revision
+ *        -  "source_is_package": "1" if the original source has been obtained from a source pacakge instead of source code version control
+ *        -  "build": information about the current build (e.g. the build date or compiler used)
+ *        -  "build_compiler": information about the compiler used to build libopenmpt
+ *        -  "credits": all contributors
+ *        -  "contact": contact information about libopenmpt
+ *        -  "license": the libopenmpt license
+ *        -  "url": libopenmpt website URL
+ *        -  "support_forum_url": libopenmpt support and discussions forum URL
+ *        -  "bugtracker_url": libopenmpt bug and issue tracker URL
+ * \return A (possibly multi-line) string containing the queried information. If no information is available, the string is empty.
+ */
+LIBOPENMPT_API const char * openmpt_get_string( const char * key );
+
+/*! \brief Get a list of supported file extensions
+ *
+ * \return The semicolon-separated list of extensions supported by this libopenmpt build. The extensions are returned lower-case without a leading dot.
+ */
+LIBOPENMPT_API const char * openmpt_get_supported_extensions(void);
+
+/*! \brief Query whether a file extension is supported
+ *
+ * \param extension file extension to query without a leading dot. The case is ignored.
+ * \return 1 if the extension is supported by libopenmpt, 0 otherwise.
+ */
+LIBOPENMPT_API int openmpt_is_extension_supported( const char * extension );
+
+/*! Seek to the given offset relative to the beginning of the file. */
+#define OPENMPT_STREAM_SEEK_SET 0
+/*! Seek to the given offset relative to the current position in the file. */
+#define OPENMPT_STREAM_SEEK_CUR 1
+/*! Seek to the given offset relative to the end of the file. */
+#define OPENMPT_STREAM_SEEK_END 2
+
+/*! \brief Read bytes from stream
+ *
+ * Read bytes data from stream to dst.
+ * \param stream Stream to read data from
+ * \param dst Target where to copy data.
+ * \param bytes Number of bytes to read.
+ * \return Number of bytes actually read and written to dst.
+ * \retval 0 End of stream or error.
+ * \remarks Short reads are allowed as long as they return at least 1 byte if EOF is not reached.
+ */
+typedef size_t (*openmpt_stream_read_func)( void * stream, void * dst, size_t bytes );
+
+/*! \brief Seek stream position
+ *
+ * Seek to stream position offset at whence.
+ * \param stream Stream to operate on.
+ * \param offset Offset to seek to.
+ * \param whence OPENMPT_STREAM_SEEK_SET, OPENMPT_STREAM_SEEK_CUR, OPENMPT_STREAM_SEEK_END. See C89 documentation.
+ * \return Returns 0 on success.
+ * \retval 0 Success.
+ * \retval -1 Failure. Position does not get updated.
+ * \remarks libopenmpt will not try to seek beyond the file size, thus it is not important whether you allow for virtual positioning after the file end, or return an error in that case. The position equal to the file size needs to be seekable to.
+ */
+typedef int (*openmpt_stream_seek_func)( void * stream, int64_t offset, int whence );
+
+/*! \brief Tell stream position
+ *
+ * Tell position of stream.
+ * \param stream Stream to operate on.
+ * \return Current position in stream.
+ * \retval -1 Failure.
+ */
+typedef int64_t (*openmpt_stream_tell_func)( void * stream );
+
+/*! \brief Stream callbacks
+ *
+ * Stream callbacks used by libopenmpt for stream operations.
+ * \sa openmpt_stream_get_file_callbacks
+ * \sa openmpt_stream_get_fd_callbacks
+ * \sa openmpt_stream_get_buffer_callbacks
+ */
+typedef struct openmpt_stream_callbacks {
+
+	/*! \brief Read callback.
+	 *
+	 * \sa openmpt_stream_read_func
+	 */
+	openmpt_stream_read_func read;
+
+	/*! \brief Seek callback.
+	 *
+	 * Seek callback can be NULL if seeking is not supported.
+	 * \sa openmpt_stream_seek_func
+	 */
+	openmpt_stream_seek_func seek;
+
+	/*! \brief Tell callback.
+	 *
+	 * Tell callback can be NULL if seeking is not supported.
+	 * \sa openmpt_stream_tell_func
+	 */
+	openmpt_stream_tell_func tell;
+
+} openmpt_stream_callbacks;
+
+/*! \brief Logging function
+ *
+ * \param message UTF-8 encoded log message.
+ * \param user User context that was passed to openmpt_module_create2(), openmpt_module_create_from_memory2() or openmpt_could_open_probability2().
+ */
+typedef void (*openmpt_log_func)( const char * message, void * user );
+
+/*! \brief Default logging function
+ *
+ * Default logging function that logs anything to stderr.
+ */
+LIBOPENMPT_API void openmpt_log_func_default( const char * message, void * user );
+
+/*! \brief Silent logging function
+ *
+ * Silent logging function that throws any log message away.
+ */
+LIBOPENMPT_API void openmpt_log_func_silent( const char * message, void * user );
+
+/*! No error. \since 0.3.0 */
+#define OPENMPT_ERROR_OK                     0
+
+/*! Lowest value libopenmpt will use for any of its own error codes. \since 0.3.0 */
+#define OPENMPT_ERROR_BASE                   256
+
+/*! Unknown internal error. \since 0.3.0 */
+#define OPENMPT_ERROR_UNKNOWN                ( OPENMPT_ERROR_BASE +   1 )
+
+/*! Unknown internal C++ exception. \since 0.3.0 */
+#define OPENMPT_ERROR_EXCEPTION              ( OPENMPT_ERROR_BASE +  11 )
+
+/*! Out of memory. \since 0.3.0 */
+#define OPENMPT_ERROR_OUT_OF_MEMORY          ( OPENMPT_ERROR_BASE +  21 )
+
+/*! Runtime error. \since 0.3.0 */
+#define OPENMPT_ERROR_RUNTIME                ( OPENMPT_ERROR_BASE +  30 )
+/*! Range error. \since 0.3.0 */
+#define OPENMPT_ERROR_RANGE                  ( OPENMPT_ERROR_BASE +  31 )
+/*! Arithmetic overflow. \since 0.3.0 */
+#define OPENMPT_ERROR_OVERFLOW               ( OPENMPT_ERROR_BASE +  32 )
+/*! Arithmetic underflow. \since 0.3.0 */
+#define OPENMPT_ERROR_UNDERFLOW              ( OPENMPT_ERROR_BASE +  33 )
+
+/*! Logic error. \since 0.3.0 */
+#define OPENMPT_ERROR_LOGIC                  ( OPENMPT_ERROR_BASE +  40 )
+/*! Value domain error. \since 0.3.0 */
+#define OPENMPT_ERROR_DOMAIN                 ( OPENMPT_ERROR_BASE +  41 )
+/*! Maximum supported size exceeded. \since 0.3.0 */
+#define OPENMPT_ERROR_LENGTH                 ( OPENMPT_ERROR_BASE +  42 )
+/*! Argument out of range. \since 0.3.0 */
+#define OPENMPT_ERROR_OUT_OF_RANGE           ( OPENMPT_ERROR_BASE +  43 )
+/*! Invalid argument. \since 0.3.0 */
+#define OPENMPT_ERROR_INVALID_ARGUMENT       ( OPENMPT_ERROR_BASE +  44 )
+
+/*! General libopenmpt error. \since 0.3.0 */
+#define OPENMPT_ERROR_GENERAL                ( OPENMPT_ERROR_BASE + 101 )
+/*! openmpt_module * is invalid. \since 0.3.0 */
+#define OPENMPT_ERROR_INVALID_MODULE_POINTER ( OPENMPT_ERROR_BASE + 102 )
+/*! NULL pointer argument. \since 0.3.0 */
+#define OPENMPT_ERROR_ARGUMENT_NULL_POINTER  ( OPENMPT_ERROR_BASE + 103 )
+
+/*! \brief Check whether the error is transient
+ *
+ * Checks whether an error code represents a transient error which may not occur again in a later try if for example memory has been freed up after an out-of-memory error.
+ * \param error Error code.
+ * \retval 0 Error is not transient.
+ * \retval 1 Error is transient.
+ * \sa OPENMPT_ERROR_OUT_OF_MEMORY
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_is_transient( int error );
+
+/*! \brief Convert error code to text
+ *
+ * Converts an error code into a text string describing the error.
+ * \param error Error code.
+ * \return Allocated string describing the error.
+ * \retval NULL Not enough memory to allocate the string.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API const char * openmpt_error_string( int error );
+
+/*! Do not log or store the error. \since 0.3.0 */
+#define OPENMPT_ERROR_FUNC_RESULT_NONE    0
+/*! Log the error. \since 0.3.0 */
+#define OPENMPT_ERROR_FUNC_RESULT_LOG     ( 1 << 0 )
+/*! Store the error. \since 0.3.0 */
+#define OPENMPT_ERROR_FUNC_RESULT_STORE   ( 1 << 1 )
+/*! Log and store the error. \since 0.3.0 */
+#define OPENMPT_ERROR_FUNC_RESULT_DEFAULT ( OPENMPT_ERROR_FUNC_RESULT_LOG | OPENMPT_ERROR_FUNC_RESULT_STORE )
+
+/*! \brief Error function
+ *
+ * \param error Error code.
+ * \param user User context that was passed to openmpt_module_create2(), openmpt_module_create_from_memory2() or openmpt_could_open_probability2().
+ * \return Mask of OPENMPT_ERROR_FUNC_RESULT_LOG and OPENMPT_ERROR_FUNC_RESULT_STORE.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_NONE Do not log or store the error.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_LOG Log the error.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_STORE Store the error.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_DEFAULT Log and store the error.
+ * \sa OPENMPT_ERROR_FUNC_RESULT_NONE
+ * \sa OPENMPT_ERROR_FUNC_RESULT_LOG
+ * \sa OPENMPT_ERROR_FUNC_RESULT_STORE
+ * \sa OPENMPT_ERROR_FUNC_RESULT_DEFAULT
+ * \sa openmpt_error_func_default
+ * \sa openmpt_error_func_log
+ * \sa openmpt_error_func_store
+ * \sa openmpt_error_func_ignore
+ * \sa openmpt_error_func_errno
+ * \since 0.3.0
+ */
+typedef int (*openmpt_error_func)( int error, void * user );
+
+/*! \brief Default error function
+ *
+ * Causes all errors to be logged and stored.
+ * \param error Error code.
+ * \param user Ignored.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_DEFAULT Always.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_func_default( int error, void * user );
+
+/*! \brief Log error function
+ *
+ * Causes all errors to be logged.
+ * \param error Error code.
+ * \param user Ignored.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_LOG Always.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_func_log( int error, void * user );
+
+/*! \brief Store error function
+ *
+ * Causes all errors to be stored.
+ * \param error Error code.
+ * \param user Ignored.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_STORE Always.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_func_store( int error, void * user );
+
+/*! \brief Ignore error function
+ *
+ * Causes all errors to be neither logged nor stored.
+ * \param error Error code.
+ * \param user Ignored.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_NONE Always.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_func_ignore( int error, void * user );
+
+/*! \brief Errno error function
+ *
+ * Causes all errors to be stored in the pointer passed in as user.
+ * \param error Error code.
+ * \param user Pointer to an int as generated by openmpt_error_func_errno_userdata.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_NONE user is not NULL.
+ * \retval OPENMPT_ERROR_FUNC_RESULT_DEFAULT user is NULL.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_error_func_errno( int error, void * user );
+
+/*! \brief User pointer for openmpt_error_func_errno
+ *
+ * Provides a suitable user pointer argument for openmpt_error_func_errno.
+ * \param error Pointer to an integer value to be used as output by openmpt_error_func_errno.
+ * \retval (void*)error.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API void * openmpt_error_func_errno_userdata( int * error );
+
+/*! \brief Roughly scan the input stream to find out whether libopenmpt might be able to open it
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to scan.
+ * \param effort Effort to make when validating stream. Effort 0.0 does not even look at stream at all and effort 1.0 completely loads the file from stream. A lower effort requires less data to be loaded but only gives a rough estimate answer. Use an effort of 0.25 to only verify the header data of the module file.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param user Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \return Probability between 0.0 and 1.0.
+ * \remarks openmpt_could_open_probability() can return any value between 0.0 and 1.0. Only 0.0 and 1.0 are definitive answers, all values in between are just estimates. In general, any return value >0.0 means that you should try loading the file, and any value below 1.0 means that loading may fail. If you want a threshold above which you can be reasonably sure that libopenmpt will be able to load the file, use >=0.5. If you see the need for a threshold below which you could reasonably outright reject a file, use <0.25 (Note: Such a threshold for rejecting on the lower end is not recommended, but may be required for better integration into some other framework's probe scoring.).
+ * \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \deprecated Please use openmpt_could_open_probability2().
+ * \since 0.3.0
+ */
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_probability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
+
+/*! \brief Roughly scan the input stream to find out whether libopenmpt might be able to open it
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to scan.
+ * \param effort Effort to make when validating stream. Effort 0.0 does not even look at stream at all and effort 1.0 completely loads the file from stream. A lower effort requires less data to be loaded but only gives a rough estimate answer. Use an effort of 0.25 to only verify the header data of the module file.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param user Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \return Probability between 0.0 and 1.0.
+ * \remarks openmpt_could_open_probability() can return any value between 0.0 and 1.0. Only 0.0 and 1.0 are definitive answers, all values in between are just estimates. In general, any return value >0.0 means that you should try loading the file, and any value below 1.0 means that loading may fail. If you want a threshold above which you can be reasonably sure that libopenmpt will be able to load the file, use >=0.5. If you see the need for a threshold below which you could reasonably outright reject a file, use <0.25 (Note: Such a threshold for rejecting on the lower end is not recommended, but may be required for better integration into some other framework's probe scoring.).
+ * \remarks openmpt_could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability() returned 0.5.
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \deprecated Please use openmpt_could_open_probability2().
+ */
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED double openmpt_could_open_propability( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * user );
+
+/*! \brief Roughly scan the input stream to find out whether libopenmpt might be able to open it
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to scan.
+ * \param effort Effort to make when validating stream. Effort 0.0 does not even look at stream at all and effort 1.0 completely loads the file from stream. A lower effort requires less data to be loaded but only gives a rough estimate answer. Use an effort of 0.25 to only verify the header data of the module file.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param loguser Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \return Probability between 0.0 and 1.0.
+ * \remarks openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize() provide a simpler and faster interface that fits almost all use cases better. It is recommended to use openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize() instead of openmpt_could_open_probability().
+ * \remarks openmpt_could_open_probability2() can return any value between 0.0 and 1.0. Only 0.0 and 1.0 are definitive answers, all values in between are just estimates. In general, any return value >0.0 means that you should try loading the file, and any value below 1.0 means that loading may fail. If you want a threshold above which you can be reasonably sure that libopenmpt will be able to load the file, use >=0.5. If you see the need for a threshold below which you could reasonably outright reject a file, use <0.25 (Note: Such a threshold for rejecting on the lower end is not recommended, but may be required for better integration into some other framework's probe scoring.).
+ * \remarks openmpt_could_open_probability2() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your callback functions whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt_could_open_probability2() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt_could_open_probability2() returned 0.5. \include libopenmpt_example_c_probe.c
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \sa openmpt_probe_file_header
+ * \sa openmpt_probe_file_header_without_filesize
+ * \since 0.3.0
+ */
+LIBOPENMPT_API double openmpt_could_open_probability2( openmpt_stream_callbacks stream_callbacks, void * stream, double effort, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message );
+
+/*! \brief Get recommended header size for successfull format probing
+ *
+ * \sa openmpt_probe_file_header()
+ * \sa openmpt_probe_file_header_without_filesize()
+ * \since 0.3.0
+ */
+LIBOPENMPT_API size_t openmpt_probe_file_header_get_recommended_size(void);
+
+/*! Probe for module formats in openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES    0x1ul
+/*! Probe for module-specific container formats in openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS 0x2ul
+
+/*! Probe for the default set of formats in openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT    ( OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES | OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS )
+/*! Probe for no formats in openmpt_probe_file_header() or openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_FLAGS_NONE       0x0ul
+
+/*! Possible return values fo openmpt_probe_file_header() and openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS      1
+/*! Possible return values fo openmpt_probe_file_header() and openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE      0
+/*! Possible return values fo openmpt_probe_file_header() and openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA (-1)
+/*! Possible return values fo openmpt_probe_file_header() and openmpt_probe_file_header_without_filesize(). \since 0.3.0 */
+#define OPENMPT_PROBE_FILE_HEADER_RESULT_ERROR        (-255)
+
+/*! \brief Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+ *
+ * \param flags Ored mask of OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES and OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS, or OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT.
+ * \param data Beginning of the file data.
+ * \param size Size of the beginning of the file data.
+ * \param filesize Full size of the file data on disk.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param loguser Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \remarks It is recommended to provide openmpt_probe_file_header_get_recommended_size() bytes of data for data and size. If the file is smaller, only provide the filesize amount and set size and filesize to the file's size. 
+ * \remarks openmpt_could_open_probability2() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt_probe_file_header() though, if possible.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS The file will most likely be supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE The file is not supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA An answer could not be determined with the amount of data provided.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_ERROR An internal error occurred.
+ * \sa openmpt_probe_file_header_get_recommended_size()
+ * \sa openmpt_probe_file_header_without_filesize()
+ * \sa openmpt_probe_file_header_from_stream()
+ * \sa openmpt_could_open_probability2()
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_probe_file_header( uint64_t flags, const void * data, size_t size, uint64_t filesize, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message );
+/*! \brief Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+ *
+ * \param flags Ored mask of OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES and OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS, or OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT.
+ * \param data Beginning of the file data.
+ * \param size Size of the beginning of the file data.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param loguser Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \remarks It is recommended to use openmpt_probe_file_header() and provide the acutal file's size as a parameter if at all possible. libopenmpt can provide more accurate answers if the filesize is known.
+ * \remarks It is recommended to provide openmpt_probe_file_header_get_recommended_size() bytes of data for data and size. If the file is smaller, only provide the filesize amount and set size to the file's size. 
+ * \remarks openmpt_could_open_probability2() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt_probe_file_header() though, if possible.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS The file will most likely be supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE The file is not supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA An answer could not be determined with the amount of data provided.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_ERROR An internal error occurred.
+ * \sa openmpt_probe_file_header_get_recommended_size()
+ * \sa openmpt_probe_file_header()
+ * \sa openmpt_probe_file_header_from_stream()
+ * \sa openmpt_could_open_probability2()
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_probe_file_header_without_filesize( uint64_t flags, const void * data, size_t size, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message );
+
+/*! \brief Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+ *
+ * \param flags Ored mask of OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES and OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS, or OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT.
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to scan.
+ * \param logfunc Logging function where warning and errors are written. May be NULL.
+ * \param loguser Logging function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \remarks The stream is left in an unspecified state when this function returns.
+ * \remarks It is recommended to provide openmpt_probe_file_header_get_recommended_size() bytes of data for data and size. If the file is smaller, only provide the filesize amount and set size and filesize to the file's size. 
+ * \remarks openmpt_could_open_probability2() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt_probe_file_header() though, if possible.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS The file will most likely be supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE The file is not supported by libopenmpt.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA An answer could not be determined with the amount of data provided.
+ * \retval OPENMPT_PROBE_FILE_HEADER_RESULT_ERROR An internal error occurred.
+ * \sa openmpt_probe_file_header_get_recommended_size()
+ * \sa openmpt_probe_file_header()
+ * \sa openmpt_probe_file_header_without_filesize()
+ * \sa openmpt_could_open_probability2()
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_probe_file_header_from_stream( uint64_t flags, openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message );
+
+
+/*! \brief Opaque type representing a libopenmpt module
+ */
+typedef struct openmpt_module openmpt_module;
+
+typedef struct openmpt_module_initial_ctl {
+	const char * ctl;
+	const char * value;
+} openmpt_module_initial_ctl;
+
+/*! \brief Construct an openmpt_module
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to load the module from.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module. May be NULL.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param ctls A map of initial ctl values. See openmpt_module_get_ctls()
+ * \return A pointer to the constructed openmpt_module, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module has been constructed successfully.
+ * \sa openmpt_stream_callbacks
+ * \sa \ref libopenmpt_c_fileio
+ * \deprecated Please use openmpt_module_create2().
+ */
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED openmpt_module * openmpt_module_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Construct an openmpt_module
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to load the module from.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module. May be NULL.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \param ctls A map of initial ctl values. See openmpt_module_get_ctls()
+ * \return A pointer to the constructed openmpt_module, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module has been constructed successfully.
+ * \sa openmpt_stream_callbacks
+ * \sa \ref libopenmpt_c_fileio
+ * \since 0.3.0
+ */
+LIBOPENMPT_API openmpt_module * openmpt_module_create2( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Construct an openmpt_module
+ *
+ * \param filedata Data to load the module from.
+ * \param filesize Amount of data available.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param ctls A map of initial ctl values. See openmpt_module_get_ctls()
+ * \return A pointer to the constructed openmpt_module, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module has been constructed successfully.
+ * \sa \ref libopenmpt_c_fileio
+ * \deprecated Please use openmpt_module_create_from_memory2().
+ */
+LIBOPENMPT_API LIBOPENMPT_DEPRECATED openmpt_module * openmpt_module_create_from_memory( const void * filedata, size_t filesize, openmpt_log_func logfunc, void * loguser, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Construct an openmpt_module
+ *
+ * \param filedata Data to load the module from.
+ * \param filesize Amount of data available.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \param ctls A map of initial ctl values. See openmpt_module_get_ctls()
+ * \return A pointer to the constructed openmpt_module, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module has been constructed successfully.
+ * \sa \ref libopenmpt_c_fileio
+ * \since 0.3.0
+ */
+LIBOPENMPT_API openmpt_module * openmpt_module_create_from_memory2( const void * filedata, size_t filesize, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Unload a previously created openmpt_module from memory.
+ *
+ * \param mod The module to unload.
+ */
+LIBOPENMPT_API void openmpt_module_destroy( openmpt_module * mod );
+
+/*! \brief Set logging function.
+ *
+ * Set the logging function of an already constructed openmpt_module.
+ * \param mod The module handle to work on.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \since 0.3.0
+ */
+LIBOPENMPT_API void openmpt_module_set_log_func( openmpt_module * mod, openmpt_log_func logfunc, void * loguser );
+
+/*! \brief Set error function.
+ *
+ * Set the error function of an already constructed openmpt_module.
+ * \param mod The module handle to work on.
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API void openmpt_module_set_error_func( openmpt_module * mod, openmpt_error_func errfunc, void * erruser );
+
+/*! \brief Get last error.
+ *
+ * Return the error currently stored in an openmpt_module. The stored error is not cleared.
+ * \param mod The module handle to work on.
+ * \return The error currently stored.
+ * \sa openmpt_module_error_get_last_message
+ * \sa openmpt_module_error_set_last
+ * \sa openmpt_module_error_clear
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_module_error_get_last( openmpt_module * mod );
+
+/*! \brief Get last error message.
+ *
+ * Return the error message currently stored in an openmpt_module. The stored error is not cleared.
+ * \param mod The module handle to work on.
+ * \return The error message currently stored.
+ * \sa openmpt_module_error_set_last
+ * \sa openmpt_module_error_clear
+ * \since 0.3.0
+ */
+LIBOPENMPT_API const char * openmpt_module_error_get_last_message( openmpt_module * mod );
+
+/*! \brief Set last error.
+ *
+ * Set the error currently stored in an openmpt_module.
+ * \param mod The module handle to work on.
+ * \param error Error to be stored.
+ * \sa openmpt_module_error_get_last
+ * \sa openmpt_module_error_clear
+ * \since 0.3.0
+ */
+LIBOPENMPT_API void openmpt_module_error_set_last( openmpt_module * mod, int error );
+
+/*! \brief Clear last error.
+ *
+ * Set the error currently stored in an openmpt_module to OPPENMPT_ERROR_OK.
+ * \param mod The module handle to work on.
+ * \sa openmpt_module_error_get_last
+ * \sa openmpt_module_error_set_last
+ * \since 0.3.0
+ */
+LIBOPENMPT_API void openmpt_module_error_clear( openmpt_module * mod );
+
+/**
+ * \defgroup openmpt_module_render_param Render param indices
+ *
+ * \brief Parameter index to use with openmpt_module_get_render_param() and openmpt_module_set_render_param()
+ * @{
+ */
+/*! \brief Master Gain
+ *
+ * The related value represents a relative gain in milliBel.\n
+ * The default value is 0.\n
+ * The supported value range is unlimited.\n
+ */
+#define OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL        1
+/*! \brief Stereo Separation
+ *
+ * The related value represents the stereo separation generated by the libopenmpt mixer in percent.\n
+ * The default value is 100.\n
+ * The supported value range is [0,200].\n
+ */
+#define OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT   2
+/*! \brief Interpolation Filter
+ *
+ * The related value represents the interpolation filter length used by the libopenmpt mixer.\n
+ * The default value is 0, which indicates a recommended default value.\n
+ * The supported value range is [0,inf). Values greater than the implementation limit are clamped to the maximum supported value.\n
+ * Currently supported values:
+ *  - 0: internal default
+ *  - 1: no interpolation (zero order hold)
+ *  - 2: linear interpolation
+ *  - 4: cubic interpolation
+ *  - 8: windowed sinc with 8 taps
+ */
+#define OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH 3
+/*! \brief Volume Ramping Strength
+ *
+ * The related value represents the amount of volume ramping done by the libopenmpt mixer.\n
+ * The default value is -1, which indicates a recommended default value.\n
+ * The meaningful value range is [-1..10].\n
+ * A value of 0 completely disables volume ramping. This might cause clicks in sound output.\n
+ * Higher values imply slower/softer volume ramps.
+ */
+#define OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH     4
+/** @}*/
+
+/**
+ * \defgroup openmpt_module_command_index Pattern cell indices
+ *
+ * \brief Parameter index to use with openmpt_module_get_pattern_row_channel_command(), openmpt_module_format_pattern_row_channel_command() and openmpt_module_highlight_pattern_row_channel_command()
+ * @{
+ */
+#define OPENMPT_MODULE_COMMAND_NOTE         0
+#define OPENMPT_MODULE_COMMAND_INSTRUMENT   1
+#define OPENMPT_MODULE_COMMAND_VOLUMEEFFECT 2
+#define OPENMPT_MODULE_COMMAND_EFFECT       3
+#define OPENMPT_MODULE_COMMAND_VOLUME       4
+#define OPENMPT_MODULE_COMMAND_PARAMETER    5
+/** @}*/
+
+/*! \brief Select a sub-song from a multi-song module
+ *
+ * \param mod The module handle to work on.
+ * \param subsong Index of the sub-song. -1 plays all sub-songs consecutively.
+ * \return 1 on success, 0 on failure.
+ * \sa openmpt_module_get_num_subsongs, openmpt_module_get_selected_subsong, openmpt_module_get_subsong_name
+ * \remarks Whether subsong -1 (all subsongs consecutively), subsong 0 or some other subsong is selected by default, is an implementation detail and subject to change. If you do not want to care about subsongs, it is recommended to just not call openmpt_module_select_subsong() at all.
+ */
+LIBOPENMPT_API int openmpt_module_select_subsong( openmpt_module * mod, int32_t subsong );
+/*! \brief Get currently selected sub-song from a multi-song module
+ *
+ * \param mod The module handle to work on.
+ * \return Currently selected sub-song. -1 for all subsongs consecutively, 0 or greater for the current sub-song index.
+ * \sa openmpt_module_get_num_subsongs, openmpt_module_select_subsong, openmpt_module_get_subsong_name
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_selected_subsong( openmpt_module * mod );
+/*! \brief Set Repeat Count
+ *
+ * \param mod The module handle to work on.
+ * \param repeat_count Repeat Count
+ *   - -1: repeat forever
+ *   - 0: play once, repeat zero times (the default)
+ *   - n>0: play once and repeat n times after that
+ * \return 1 on success, 0 on failure.
+ * \sa openmpt_module_get_repeat_count
+ */
+LIBOPENMPT_API int openmpt_module_set_repeat_count( openmpt_module * mod, int32_t repeat_count );
+/*! \brief Get Repeat Count
+ *
+ * \param mod The module handle to work on.
+ * \return Repeat Count
+ *   - -1: repeat forever
+ *   - 0: play once, repeat zero times (the default)
+ *   - n>0: play once and repeat n times after that
+ * \sa openmpt_module_set_repeat_count
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_repeat_count( openmpt_module * mod );
+
+/*! \brief approximate song duration
+ *
+ * \param mod The module handle to work on.
+ * \return Approximate duration of current sub-song in seconds.
+ */
+LIBOPENMPT_API double openmpt_module_get_duration_seconds( openmpt_module * mod );
+
+/*! \brief Set approximate current song position
+ *
+ * \param mod The module handle to work on.
+ * \param seconds Seconds to seek to. If seconds is out of range, the position gets set to song start or end respectively.
+ * \return Approximate new song position in seconds.
+ * \sa openmpt_module_get_position_seconds
+ */
+LIBOPENMPT_API double openmpt_module_set_position_seconds( openmpt_module * mod, double seconds );
+/*! \brief Get current song position
+ *
+ * \param mod The module handle to work on.
+ * \return Current song position in seconds.
+ * \sa openmpt_module_set_position_seconds
+ */
+LIBOPENMPT_API double openmpt_module_get_position_seconds( openmpt_module * mod );
+
+/*! \brief Set approximate current song position
+ *
+ * If order or row are out of range, to position is not modified and the current position is returned.
+ * \param mod The module handle to work on.
+ * \param order Pattern order number to seek to.
+ * \param row Pattern row number to seek to.
+ * \return Approximate new song position in seconds.
+ * \sa openmpt_module_set_position_seconds
+ * \sa openmpt_module_get_position_seconds
+ */
+LIBOPENMPT_API double openmpt_module_set_position_order_row( openmpt_module * mod, int32_t order, int32_t row );
+
+/*! \brief Get render parameter
+ *
+ * \param mod The module handle to work on.
+ * \param param Parameter to query. See \ref openmpt_module_render_param
+ * \param value Pointer to the variable that receives the current value of the parameter.
+ * \return 1 on success, 0 on failure (invalid param or value is NULL).
+ * \sa OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL
+ * \sa OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT
+ * \sa OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH
+ * \sa OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH
+ * \sa openmpt_module_set_render_param
+ */
+LIBOPENMPT_API int openmpt_module_get_render_param( openmpt_module * mod, int param, int32_t * value );
+/*! \brief Set render parameter
+ *
+ * \param mod The module handle to work on.
+ * \param param Parameter to set. See \ref openmpt_module_render_param
+ * \param value The value to set param to.
+ * \return 1 on success, 0 on failure (invalid param).
+ * \sa OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL
+ * \sa OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT
+ * \sa OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH
+ * \sa OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH
+ * \sa openmpt_module_get_render_param
+ */
+LIBOPENMPT_API int openmpt_module_set_render_param( openmpt_module * mod, int param, int32_t value );
+
+/*@{*/
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param mono Pointer to a buffer of at least count elements that receives the mono/center output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_mono(   openmpt_module * mod, int32_t samplerate, size_t count, int16_t * mono );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param left Pointer to a buffer of at least count elements that receives the left output.
+ * \param right Pointer to a buffer of at least count elements that receives the right output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_stereo( openmpt_module * mod, int32_t samplerate, size_t count, int16_t * left, int16_t * right );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param left Pointer to a buffer of at least count elements that receives the left output.
+ * \param right Pointer to a buffer of at least count elements that receives the right output.
+ * \param rear_left Pointer to a buffer of at least count elements that receives the rear left output.
+ * \param rear_right Pointer to a buffer of at least count elements that receives the rear right output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_quad(   openmpt_module * mod, int32_t samplerate, size_t count, int16_t * left, int16_t * right, int16_t * rear_left, int16_t * rear_right );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param mono Pointer to a buffer of at least count elements that receives the mono/center output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_float_mono(   openmpt_module * mod, int32_t samplerate, size_t count, float * mono );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param left Pointer to a buffer of at least count elements that receives the left output.
+ * \param right Pointer to a buffer of at least count elements that receives the right output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_float_stereo( openmpt_module * mod, int32_t samplerate, size_t count, float * left, float * right );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param left Pointer to a buffer of at least count elements that receives the left output.
+ * \param right Pointer to a buffer of at least count elements that receives the right output.
+ * \param rear_left Pointer to a buffer of at least count elements that receives the rear left output.
+ * \param rear_right Pointer to a buffer of at least count elements that receives the rear right output.
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_float_quad(   openmpt_module * mod, int32_t samplerate, size_t count, float * left, float * right, float * rear_left, float * rear_right );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param interleaved_stereo Pointer to a buffer of at least count*2 elements that receives the interleaved stereo output in the order (L,R).
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_interleaved_stereo( openmpt_module * mod, int32_t samplerate, size_t count, int16_t * interleaved_stereo );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param interleaved_quad Pointer to a buffer of at least count*4 elements that receives the interleaved suad surround output in the order (L,R,RL,RR).
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_interleaved_quad(   openmpt_module * mod, int32_t samplerate, size_t count, int16_t * interleaved_quad   );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param interleaved_stereo Pointer to a buffer of at least count*2 elements that receives the interleaved stereo output in the order (L,R).
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+ * \sa \ref libopenmpt_c_outputformat
+ */
+LIBOPENMPT_API size_t openmpt_module_read_interleaved_float_stereo( openmpt_module * mod, int32_t samplerate, size_t count, float * interleaved_stereo );
+/*! \brief Render audio data
+ *
+ * \param mod The module handle to work on.
+ * \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+ * \param count Number of audio frames to render per channel.
+ * \param interleaved_quad Pointer to a buffer of at least count*4 elements that receives the interleaved suad surround output in the order (L,R,RL,RR).
+ * \return The number of frames actually rendered.
+ * \retval 0 The end of song has been reached.
+ * \remarks The output buffers are only written to up to the returned number of elements.
+ * \remarks You can freely switch between any of the "openmpt_module_read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+ * \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+ * \sa \ref libopenmpt_c_outputformat
+*/
+LIBOPENMPT_API size_t openmpt_module_read_interleaved_float_quad(   openmpt_module * mod, int32_t samplerate, size_t count, float * interleaved_quad   );
+/*@}*/
+
+/*! \brief Get the list of supported metadata item keys
+ *
+ * \param mod The module handle to work on.
+ * \return Metadata item keys supported by openmpt_module_get_metadata, as a semicolon-separated list.
+ * \sa openmpt_module_get_metadata
+ */
+LIBOPENMPT_API const char * openmpt_module_get_metadata_keys( openmpt_module * mod );
+/*! \brief Get a metadata item value
+ *
+ * \param mod The module handle to work on.
+ * \param key Metadata item key to query. Use openmpt_module_get_metadata_keys to check for available keys.
+ *          Possible keys are:
+ *          - type: Module format extension (e.g. it)
+ *          - type_long: Format name associated with the module format (e.g. Impulse Tracker)
+ *          - originaltype: Module format extension (e.g. it) of the original module in case the actual type is a converted format (e.g. mo3 or gdm)
+ *          - originaltype_long: Format name associated with the module format (e.g. Impulse Tracker) of the original module in case the actual type is a converted format (e.g. mo3 or gdm)
+ *          - container: Container format the module file is embedded in, if any (e.g. umx)
+ *          - container_long: Full container name if the module is embedded in a container (e.g. Unreal Music)
+ *          - tracker: Tracker that was (most likely) used to save the module file, if known
+ *          - artist: Author of the module
+ *          - title: Module title
+ *          - date: Date the module was last saved, in ISO-8601 format.
+ *          - message: Song message. If the song message is empty or the module format does not support song messages, a list of instrument and sample names is returned instead.
+ *          - message_raw: Song message. If the song message is empty or the module format does not support song messages, an empty string is returned.
+ *          - warnings: A list of warnings that were generated while loading the module.
+ * \return The associated value for key.
+ * \sa openmpt_module_get_metadata_keys
+ */
+LIBOPENMPT_API const char * openmpt_module_get_metadata( openmpt_module * mod, const char * key );
+
+/*! \brief Get the current speed
+ *
+ * \param mod The module handle to work on.
+ * \return The current speed in ticks per row.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_speed( openmpt_module * mod );
+/*! \brief Get the current tempo
+ *
+ * \param mod The module handle to work on.
+ * \return The current tempo in tracker units. The exact meaning of this value depends on the tempo mode being used.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_tempo( openmpt_module * mod );
+/*! \brief Get the current order
+ *
+ * \param mod The module handle to work on.
+ * \return The current order at which the module is being played back.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_order( openmpt_module * mod );
+/*! \brief Get the current pattern
+ *
+ * \param mod The module handle to work on.
+ * \return The current pattern that is being played.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_pattern( openmpt_module * mod );
+/*! \brief Get the current row
+ *
+ * \param mod The module handle to work on.
+ * \return The current row at which the current pattern is being played.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_row( openmpt_module * mod );
+/*! \brief Get the current amount of playing channels.
+ *
+ * \param mod The module handle to work on.
+ * \return The amount of sample channels that are currently being rendered.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_current_playing_channels( openmpt_module * mod );
+
+/*! \brief Get an approximate indication of the channel volume.
+ *
+ * \param mod The module handle to work on.
+ * \param channel The channel whose volume should be retrieved.
+ * \return The approximate channel volume.
+ * \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+ */
+LIBOPENMPT_API float openmpt_module_get_current_channel_vu_mono( openmpt_module * mod, int32_t channel );
+/*! \brief Get an approximate indication of the channel volume on the front-left speaker.
+ *
+ * \param mod The module handle to work on.
+ * \param channel The channel whose volume should be retrieved.
+ * \return The approximate channel volume.
+ * \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+ */
+LIBOPENMPT_API float openmpt_module_get_current_channel_vu_left( openmpt_module * mod, int32_t channel );
+/*! \brief Get an approximate indication of the channel volume on the front-right speaker.
+ *
+ * \param mod The module handle to work on.
+ * \param channel The channel whose volume should be retrieved.
+ * \return The approximate channel volume.
+ * \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+ */
+LIBOPENMPT_API float openmpt_module_get_current_channel_vu_right( openmpt_module * mod, int32_t channel );
+/*! \brief Get an approximate indication of the channel volume on the rear-left speaker.
+ *
+ * \param mod The module handle to work on.
+ * \param channel The channel whose volume should be retrieved.
+ * \return The approximate channel volume.
+ * \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+ */
+LIBOPENMPT_API float openmpt_module_get_current_channel_vu_rear_left( openmpt_module * mod, int32_t channel );
+/*! \brief Get an approximate indication of the channel volume on the rear-right speaker.
+ *
+ * \param mod The module handle to work on.
+ * \param channel The channel whose volume should be retrieved.
+ * \return The approximate channel volume.
+ * \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+ */
+LIBOPENMPT_API float openmpt_module_get_current_channel_vu_rear_right( openmpt_module * mod, int32_t channel );
+
+/*! \brief Get the number of sub-songs
+ *
+ * \param mod The module handle to work on.
+ * \return The number of sub-songs in the module. This includes any "hidden" songs (songs that share the same sequence, but start at different order indices) and "normal" sub-songs or "sequences" (if the format supports them).
+ * \sa openmpt_module_get_subsong_name, openmpt_module_select_subsong, openmpt_module_get_selected_subsong
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_subsongs( openmpt_module * mod );
+/*! \brief Get the number of pattern channels
+ *
+ * \param mod The module handle to work on.
+ * \return The number of pattern channels in the module. Not all channels do necessarily contain data.
+ * \remarks The number of pattern channels is completely independent of the number of output channels. libopenmpt can render modules in mono, stereo or quad surround, but the choice of which of the three modes to use must not be made based on the return value of this function, which may be any positive integer amount. Only use this function for informational purposes.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_channels( openmpt_module * mod );
+/*! \brief Get the number of orders
+ *
+ * \param mod The module handle to work on.
+ * \return The number of orders in the current sequence of the module.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_orders( openmpt_module * mod );
+/*! \brief Get the number of patterns
+ *
+ * \param mod The module handle to work on.
+ * \return The number of distinct patterns in the module.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_patterns( openmpt_module * mod );
+/*! \brief Get the number of instruments
+ *
+ * \param mod The module handle to work on.
+ * \return The number of instrument slots in the module. Instruments are a layer on top of samples, and are not supported by all module formats.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_instruments( openmpt_module * mod );
+/*! \brief Get the number of samples
+ *
+ * \param mod The module handle to work on.
+ * \return The number of sample slots in the module.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_num_samples( openmpt_module * mod );
+
+/*! \brief Get a sub-song name
+ *
+ * \param mod The module handle to work on.
+ * \param index The sub-song whose name should be retrieved
+ * \return The sub-song name.
+ * \sa openmpt_module_get_num_subsongs, openmpt_module_select_subsong, openmpt_module_get_selected_subsong
+ */
+LIBOPENMPT_API const char * openmpt_module_get_subsong_name( openmpt_module * mod, int32_t index );
+/*! \brief Get a channel name
+ *
+ * \param mod The module handle to work on.
+ * \param index The channel whose name should be retrieved
+ * \return The channel name.
+ * \sa openmpt_module_get_num_channels
+ */
+LIBOPENMPT_API const char * openmpt_module_get_channel_name( openmpt_module * mod, int32_t index );
+/*! \brief Get an order name
+ *
+ * \param mod The module handle to work on.
+ * \param index The order whose name should be retrieved
+ * \return The order name.
+ * \sa openmpt_module_get_num_orders
+ */
+LIBOPENMPT_API const char * openmpt_module_get_order_name( openmpt_module * mod, int32_t index );
+/*! \brief Get a pattern name
+ *
+ * \param mod The module handle to work on.
+ * \param index The pattern whose name should be retrieved
+ * \return The pattern name.
+ * \sa openmpt_module_get_num_patterns
+ */
+LIBOPENMPT_API const char * openmpt_module_get_pattern_name( openmpt_module * mod, int32_t index );
+/*! \brief Get an instrument name
+ *
+ * \param mod The module handle to work on.
+ * \param index The instrument whose name should be retrieved
+ * \return The instrument name.
+ * \sa openmpt_module_get_num_instruments
+ */
+LIBOPENMPT_API const char * openmpt_module_get_instrument_name( openmpt_module * mod, int32_t index );
+/*! \brief Get a sample name
+ *
+ * \param mod The module handle to work on.
+ * \param index The sample whose name should be retrieved
+ * \return The sample name.
+ * \sa openmpt_module_get_num_samples
+ */
+LIBOPENMPT_API const char * openmpt_module_get_sample_name( openmpt_module * mod, int32_t index );
+
+/*! \brief Get pattern at order position
+ *
+ * \param mod The module handle to work on.
+ * \param order The order item whose pattern index should be retrieved.
+ * \return The pattern index found at the given order position of the current sequence.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_order_pattern( openmpt_module * mod, int32_t order );
+/*! \brief Get the number of rows in a pattern
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose row count should be retrieved.
+ * \return The number of rows in the given pattern. If the pattern does not exist, 0 is returned.
+ */
+LIBOPENMPT_API int32_t openmpt_module_get_pattern_num_rows( openmpt_module * mod, int32_t pattern );
+
+/*! \brief Get raw pattern content
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose data should be retrieved.
+ * \param row The row from which the data should be retrieved.
+ * \param channel The channel from which the data should be retrieved.
+ * \param command The cell index at which the data should be retrieved. See \ref openmpt_module_command_index
+ * \return The internal, raw pattern data at the given pattern position.
+ */
+LIBOPENMPT_API uint8_t openmpt_module_get_pattern_row_channel_command( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, int command );
+
+/*! \brief Get formatted (human-readable) pattern content
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose data should be retrieved.
+ * \param row The row from which the data should be retrieved.
+ * \param channel The channel from which the data should be retrieved.
+ * \param command The cell index at which the data should be retrieved.
+ * \return The formatted pattern data at the given pattern position. See \ref openmpt_module_command_index
+ * \sa openmpt_module_highlight_pattern_row_channel_command
+ */
+LIBOPENMPT_API const char * openmpt_module_format_pattern_row_channel_command( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, int command );
+/*! \brief Get highlighting information for formatted pattern content
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose data should be retrieved.
+ * \param row The row from which the data should be retrieved.
+ * \param channel The channel from which the data should be retrieved.
+ * \param command The cell index at which the data should be retrieved. See \ref openmpt_module_command_index
+ * \return The highlighting string for the formatted pattern data as retrieved by openmpt_module_get_pattern_row_channel_command at the given pattern position.
+ * \remarks The returned string will map each character position of the string returned by openmpt_module_get_pattern_row_channel_command to a highlighting instruction.
+ *          Possible highlighting characters are:
+ *          - " " : empty/space
+ *          - "." : empty/dot
+ *          - "n" : generic note
+ *          - "m" : special note
+ *          - "i" : generic instrument
+ *          - "u" : generic volume column effect
+ *          - "v" : generic volume column parameter
+ *          - "e" : generic effect column effect
+ *          - "f" : generic effect column parameter
+ * \sa openmpt_module_get_pattern_row_channel_command
+ */
+LIBOPENMPT_API const char * openmpt_module_highlight_pattern_row_channel_command( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, int command );
+
+/*! \brief Get formatted (human-readable) pattern content
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose data should be retrieved.
+ * \param row The row from which the data should be retrieved.
+ * \param channel The channel from which the data should be retrieved.
+ * \param width The maximum number of characters the string should contain. 0 means no limit.
+ * \param pad If true, the string will be resized to the exact length provided in the width parameter.
+ * \return The formatted pattern data at the given pattern position.
+ * \sa openmpt_module_highlight_pattern_row_channel
+ */
+LIBOPENMPT_API const char * openmpt_module_format_pattern_row_channel( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, size_t width, int pad );
+/*! \brief Get highlighting information for formatted pattern content
+ *
+ * \param mod The module handle to work on.
+ * \param pattern The pattern whose data should be retrieved.
+ * \param row The row from which the data should be retrieved.
+ * \param channel The channel from which the data should be retrieved.
+ * \param width The maximum number of characters the string should contain. 0 means no limit.
+ * \param pad If true, the string will be resized to the exact length provided in the width parameter.
+ * \return The highlighting string for the formatted pattern data as retrieved by openmpt_module_format_pattern_row_channel at the given pattern position.
+ * \sa openmpt_module_format_pattern_row_channel
+ */
+LIBOPENMPT_API const char * openmpt_module_highlight_pattern_row_channel( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, size_t width, int pad );
+
+/*! \brief Retrieve supported ctl keys
+ *
+ * \param mod The module handle to work on.
+ * \return A semicolon-separated list containing all supported ctl keys.
+ * \remarks Currently supported ctl values are:
+ *          - load.skip_samples: Set to "1" to avoid loading samples into memory
+ *          - load.skip_patterns: Set to "1" to avoid loading patterns into memory
+ *          - load.skip_plugins: Set to "1" to avoid loading plugins
+ *          - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+ *          - seek.sync_samples: Set to "1" to sync sample playback when using openmpt_module_set_position_seconds or openmpt_module_set_position_order_row.
+ *          - subsong: The current subsong. Setting it has identical semantics as openmpt_module_select_subsong(), getting it returns the currently selected subsong.
+ *          - play.at_end: Chooses the behaviour when the end of song is reached:
+ *                         - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
+ *                         - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
+ *                         - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
+ *          - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
+ *          - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
+ *          - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting.
+ *          - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+ *          - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt_module_read. Supported values are:
+ *                    - 0: No dithering.
+ *                    - 1: Default mode. Chosen by OpenMPT code, might change.
+ *                    - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
+ *                    - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
+ */
+LIBOPENMPT_API const char * openmpt_module_get_ctls( openmpt_module * mod );
+/*! \brief Get current ctl value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be retrieved.
+ * \return The associated ctl value, or NULL on failure.
+ * \sa openmpt_module_get_ctls
+ */
+LIBOPENMPT_API const char * openmpt_module_ctl_get( openmpt_module * mod, const char * ctl );
+/*! \brief Set ctl value
+ *
+ * \param mod The module handle to work on.
+ * \param ctl The ctl key whose value should be set.
+ * \param value The value that should be set.
+ * \return 1 if successful, 0 in case the value is not sensible (e.g. negative tempo factor) or the ctl is not recognized.
+ * \sa openmpt_module_get_ctls
+ */
+LIBOPENMPT_API int openmpt_module_ctl_set( openmpt_module * mod, const char * ctl, const char * value );
+
+/* remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR */
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * @}
+ */
+
+#endif /* LIBOPENMPT_H */
+
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt.hpp b/libs/libopenmpt/inc/libopenmpt/libopenmpt.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..55097f8303a6ccc06ec9db58b7da1c454bcab453
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt.hpp
@@ -0,0 +1,1015 @@
+/*
+ * libopenmpt.hpp
+ * --------------
+ * Purpose: libopenmpt public c++ interface
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_HPP
+#define LIBOPENMPT_HPP
+
+#include "libopenmpt_config.h"
+
+#include <exception>
+#include <iosfwd>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <cstdint>
+
+/*!
+ * \page libopenmpt_cpp_overview C++ API
+ *
+ * \section libopenmpt_cpp_error Error Handling
+ *
+ * libopenmpt C++ uses C++ exception handling for errror reporting.
+ *
+ * Unless otherwise noted, any libopenmpt function may throw exceptions and
+ * all exceptions thrown by libopenmpt itself are derived from
+ * openmpt::exception.
+ * In addition, any libopenmpt function may also throw any exception specified
+ * by the C++ language and C++ standard library. These are all derived from
+ * std::exception.
+ *
+ * \section libopenmpt_cpp_strings Strings
+ *
+ * - All strings returned from libopenmpt are encoded in UTF-8.
+ * - All strings passed to libopenmpt should also be encoded in UTF-8.
+ * Behaviour in case of invalid UTF-8 is unspecified.
+ * - libopenmpt does not enforce or expect any particular Unicode
+ * normalization form.
+ *
+ * \section libopenmpt_cpp_fileio File I/O
+ *
+ * libopenmpt can use 3 different strategies for file I/O.
+ *
+ * - openmpt::module::module() with any kind of memory buffer as parameter will
+ * load the module from the provided memory buffer, which will require loading
+ * all data upfront by the library
+ * caller.
+ * - openmpt::module::module() with a seekable std::istream as parameter will
+ * load the module via the stream interface. libopenmpt will not implement an
+ * additional buffering layer in this case whih means the callbacks are assumed
+ * to be performant even with small i/o sizes.
+ * - openmpt::module::module() with an unseekable std::istream as parameter
+ * will load the module via the stream interface. libopempt will make an
+ * internal copy as it goes along, and sometimes have to pre-cache the whole
+ * file in case it needs to know the complete file size. This strategy is
+ * intended to be used if the file is located on a high latency network.
+ *
+ * | constructor       | speed  | memory consumption |
+ * | ----------------: | :----: | :----------------: |
+ * | memory buffer     | <p style="background-color:green" >fast  </p> | <p style="background-color:yellow">medium</p> | 
+ * | seekable stream   | <p style="background-color:red"   >slow  </p> | <p style="background-color:green" >low   </p> |
+ * | unseekable stream | <p style="background-color:yellow">medium</p> | <p style="background-color:red"   >high  </p> |
+ *
+ * In all cases, the data or stream passed to the constructor is no longer
+ * needed after the openmpt::module has been constructed and can be destroyed
+ * by the caller.
+ *
+ * \section libopenmpt_cpp_outputformat Output Format
+ *
+ * libopenmpt supports a wide range of PCM output formats:
+ * [8000..192000]/[mono|stereo|quad]/[f32|i16].
+ *
+ * Unless you have some very specific requirements demanding a particular aspect
+ * of the output format, you should always prefer 48000/stereo/f32 as the
+ * libopenmpt PCM format.
+ *
+ * - Please prefer 48000Hz unless the user explicitly demands something else.
+ * Practically all audio equipment and file formats use 48000Hz nowadays.
+ * - Practically all module formats are made for stereo output. Mono will not
+ * give you any measurable speed improvements and can trivially be obtained from
+ * the stereo output anyway. Quad is not expected by almost all modules and even
+ * if they do use surround effects, they expect the effects to be mixed to 
+ * stereo. 
+ * - Floating point output provides headroom instead of hard clipping if the
+ * module is louder than 0dBFs, will give you a better signal-to-noise ratio
+ * than int16 output, and avoid the need to apply an additional dithering to the
+ * output by libopenmpt. Unless your platform has no floating point unit at all,
+ * floating point will thus also be slightly faster.
+ *
+ * \section libopenmpt_cpp_threads libopenmpt in multi-threaded environments
+ *
+ * - libopenmpt is thread-aware.
+ * - Individual libopenmpt objects are not thread-safe.
+ * - libopenmpt itself does not spawn any user-visible threads but may spawn
+ * threads for internal use.
+ * - You must ensure to only ever access a particular libopenmpt object via
+ * non-const member functions from a single thread at a time.
+ * - You may access a particular libopenmpt objects concurrently from different
+ * threads when using only const member functions from all threads.
+ * - Consecutive accesses can happen from different threads.
+ * - Different objects can be accessed concurrently from different threads.
+ *
+ * \section libopenmpt-cpp-windows Windows support
+ *
+ * Using the libopenmpt C++ API when libopenmpt is compiled as a DLL on Windows
+ * requires `#define LIBOPENMPT_USE_DLL` (or some equivalent build system
+ * configuration) before `#include <libopenmpt/libopenmpt.hpp>` in order to
+ * correctly import the symbols from the DLL.
+ *
+ * \section libopenmpt-cpp-detailed Detailed documentation
+ *
+ * \ref libopenmpt_cpp
+ *
+ * \section libopenmpt_cpp_examples Example
+ *
+ * \include libopenmpt_example_cxx.cpp
+ *
+ */
+
+/*! \defgroup libopenmpt_cpp libopenmpt C++ */
+
+/*! \addtogroup libopenmpt_cpp
+  @{
+*/
+
+namespace openmpt {
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4275)
+#endif
+//! libopenmpt exception base class
+/*!
+  Base class used for all exceptions that are thrown by libopenmpt itself. Libopenmpt may additionally throw any exception thrown by the standard library which are all derived from std::exception.
+  \sa \ref libopenmpt_cpp_error
+*/
+class LIBOPENMPT_CXX_API exception : public std::exception {
+private:
+	char * text;
+public:
+	exception( const std::string & text ) noexcept;
+	exception( const exception & other ) noexcept;
+	exception( exception && other ) noexcept;
+	exception & operator = ( const exception & other ) noexcept;
+	exception & operator = ( exception && other ) noexcept;
+	virtual ~exception() noexcept;
+	const char * what() const noexcept override;
+}; // class exception
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+//! Get the libopenmpt version number
+/*!
+  Returns the libopenmpt version number.
+  \return The value represents (major << 24 + minor << 16 + patch << 0).
+  \remarks libopenmpt < 0.3.0-pre used the following scheme: (major << 24 + minor << 16 + revision).
+*/
+LIBOPENMPT_CXX_API std::uint32_t get_library_version();
+
+//! Get the core version number
+/*!
+  Return the OpenMPT core version number.
+  \return The value represents (majormajor << 24 + major << 16 + minor << 8 + minorminor).
+*/
+LIBOPENMPT_CXX_API std::uint32_t get_core_version();
+
+namespace string {
+
+//! Return a verbose library version string from openmpt::string::get(). \deprecated Please use `"library_version"` directly.
+LIBOPENMPT_DEPRECATED static const char library_version  LIBOPENMPT_ATTR_DEPRECATED [] = "library_version";
+//! Return a verbose library features string from openmpt::string::get(). \deprecated Please use `"library_features"` directly.
+LIBOPENMPT_DEPRECATED static const char library_features LIBOPENMPT_ATTR_DEPRECATED [] = "library_features";
+//! Return a verbose OpenMPT core version string from openmpt::string::get(). \deprecated Please use `"core_version"` directly.
+LIBOPENMPT_DEPRECATED static const char core_version     LIBOPENMPT_ATTR_DEPRECATED [] = "core_version";
+//! Return information about the current build (e.g. the build date or compiler used) from openmpt::string::get(). \deprecated Please use `"build"` directly.
+LIBOPENMPT_DEPRECATED static const char build            LIBOPENMPT_ATTR_DEPRECATED [] = "build";
+//! Return all contributors from openmpt::string::get(). \deprecated Please use `"credits"` directly.
+LIBOPENMPT_DEPRECATED static const char credits          LIBOPENMPT_ATTR_DEPRECATED [] = "credits";
+//! Return contact information about libopenmpt from openmpt::string::get(). \deprecated Please use `"contact"` directly.
+LIBOPENMPT_DEPRECATED static const char contact          LIBOPENMPT_ATTR_DEPRECATED [] = "contact";
+//! Return the libopenmpt license from openmpt::string::get(). \deprecated Please use `"license"` directly.
+LIBOPENMPT_DEPRECATED static const char license          LIBOPENMPT_ATTR_DEPRECATED [] = "license";
+
+//! Get library related metadata.
+/*!
+  \param key Key to query.
+         Possible keys are:
+          -  "library_version": verbose library version string
+          -  "library_version_major": libopenmpt major version number
+          -  "library_version_minor": libopenmpt minor version number
+          -  "library_version_patch": libopenmpt patch version number
+          -  "library_version_prerel": libopenmpt pre-release version string
+          -  "library_version_is_release": "1" if the version is an officially released version
+          -  "library_features": verbose library features string
+          -  "core_version": verbose OpenMPT core version string
+          -  "source_url": original source code URL
+          -  "source_date": original source code date
+          -  "source_revision": original source code revision
+          -  "source_is_modified": "1" if the original source has been modified
+          -  "source_has_mixed_revisions": "1" if the original source has been compiled from different various revision
+          -  "source_is_package": "1" if the original source has been obtained from a source pacakge instead of source code version control
+          -  "build": information about the current build (e.g. the build date or compiler used)
+          -  "build_compiler": information about the compiler used to build libopenmpt
+          -  "credits": all contributors
+          -  "contact": contact information about libopenmpt
+          -  "license": the libopenmpt license
+          -  "url": libopenmpt website URL
+          -  "support_forum_url": libopenmpt support and discussions forum URL
+          -  "bugtracker_url": libopenmpt bug and issue tracker URL
+
+  \return A (possibly multi-line) string containing the queried information. If no information is available, the string is empty.
+*/
+LIBOPENMPT_CXX_API std::string get( const std::string & key );
+
+} // namespace string
+
+//! Get a list of supported file extensions
+/*!
+  \return The list of extensions supported by this libopenmpt build. The extensions are returned lower-case without a leading dot.
+*/
+LIBOPENMPT_CXX_API std::vector<std::string> get_supported_extensions();
+
+//! Query whether a file extension is supported
+/*!
+  \param extension file extension to query without a leading dot. The case is ignored.
+  \return true if the extension is supported by libopenmpt, false otherwise.
+*/
+LIBOPENMPT_CXX_API bool is_extension_supported( const std::string & extension );
+
+//! Roughly scan the input stream to find out whether libopenmpt might be able to open it
+/*!
+  \param stream Input stream to scan.
+  \param effort Effort to make when validating stream. Effort 0.0 does not even look at stream at all and effort 1.0 completely loads the file from stream. A lower effort requires less data to be loaded but only gives a rough estimate answer. Use an effort of 0.25 to only verify the header data of the module file.
+  \param log Log where warning and errors are written.
+  \return Probability between 0.0 and 1.0.
+  \remarks openmpt::probe_file_header() provides a simpler and faster interface that fits almost all use cases better. It is recommended to use openmpt::probe_file_header() instead of openmpt::could_open_probability().
+  \remarks openmpt::could_open_probability() can return any value between 0.0 and 1.0. Only 0.0 and 1.0 are definitive answers, all values in between are just estimates. In general, any return value >0.0 means that you should try loading the file, and any value below 1.0 means that loading may fail. If you want a threshold above which you can be reasonably sure that libopenmpt will be able to load the file, use >=0.5. If you see the need for a threshold below which you could reasonably outright reject a file, use <0.25 (Note: Such a threshold for rejecting on the lower end is not recommended, but may be required for better integration into some other framework's probe scoring.).
+  \remarks openmpt::could_open_probability() expects the complete file data to be eventually available to it, even if it is asked to just parse the header. Verification will be unreliable (both false positives and false negatives), if you pretend that the file is just some few bytes of initial data threshold in size. In order to really just access the first bytes of a file, check in your std::istream implementation whether data or seeking is requested beyond your initial data threshold, and in that case, return an error. openmpt::could_open_probability() will treat this as any other I/O error and return 0.0. You must not expect the correct result in this case. You instead must remember that it asked for more data than you currently want to provide to it and treat this situation as if openmpt::could_open_probability() returned 0.5.
+  \sa \ref libopenmpt_c_fileio
+  \sa openmpt::probe_file_header()
+  \since 0.3.0
+*/
+LIBOPENMPT_CXX_API double could_open_probability( std::istream & stream, double effort = 1.0, std::ostream & log = std::clog );
+
+//! Roughly scan the input stream to find out whether libopenmpt might be able to open it
+/*!
+  \deprecated Please use openmpt::module::could_open_probability().
+*/
+LIBOPENMPT_ATTR_DEPRECATED LIBOPENMPT_CXX_API LIBOPENMPT_DEPRECATED double could_open_propability( std::istream & stream, double effort = 1.0, std::ostream & log = std::clog );
+
+//! Get recommended header size for successfull format probing
+/*!
+  \sa openmpt::probe_file_header()
+  \since 0.3.0
+*/
+LIBOPENMPT_CXX_API std::size_t probe_file_header_get_recommended_size();
+
+//! Probe for module formats in openmpt::probe_file_header(). \since 0.3.0
+static const std::uint64_t probe_file_header_flags_modules    = 0x1ul;
+
+//! Probe for module-specific container formats in openmpt::probe_file_header(). \since 0.3.0
+static const std::uint64_t probe_file_header_flags_containers = 0x2ul;
+
+//! Probe for the default set of formats in openmpt::probe_file_header(). \since 0.3.0
+static const std::uint64_t probe_file_header_flags_default    = probe_file_header_flags_modules | probe_file_header_flags_containers;
+
+//! Probe for no formats in openmpt::probe_file_header(). \since 0.3.0
+static const std::uint64_t probe_file_header_flags_none       = 0x0ul;
+
+//! Possible return values for openmpt::probe_file_header(). \since 0.3.0
+enum probe_file_header_result {
+	probe_file_header_result_success      =  1,
+	probe_file_header_result_failure      =  0,
+	probe_file_header_result_wantmoredata = -1
+};
+
+//! Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+/*!
+  \param flags Ored mask of openmpt::probe_file_header_flags_modules and openmpt::probe_file_header_flags_containers, or openmpt::probe_file_header_flags_default.
+  \param data Beginning of the file data.
+  \param size Size of the beginning of the file data.
+  \param filesize Full size of the file data on disk.
+  \remarks It is recommended to provide openmpt::probe_file_header_get_recommended_size() bytes of data for data and size. If the file is smaller, only provide the filesize amount and set size and filesize to the file's size. 
+  \remarks openmpt::could_open_probability() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt::probe_file_header() though, if possible.
+  \retval probe_file_header_result_success The file will most likely be supported by libopenmpt.
+  \retval probe_file_header_result_failure The file is not supported by libopenmpt.
+  \retval probe_file_header_result_wantmoredata An answer could not be determined with the amount of data provided.
+  \sa openmpt::probe_file_header_get_recommended_size()
+  \sa openmpt::could_open_probability()
+  \since 0.3.0
+*/
+LIBOPENMPT_CXX_API int probe_file_header( std::uint64_t flags, const std::uint8_t * data, std::size_t size, std::uint64_t filesize );
+
+//! Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+/*!
+  \param flags Ored mask of openmpt::probe_file_header_flags_modules and openmpt::probe_file_header_flags_containers, or openmpt::probe_file_header_flags_default.
+  \param data Beginning of the file data.
+  \param size Size of the beginning of the file data.
+  \remarks It is recommended to use the overload of this function that also takes the filesize as parameter if at all possile. libopenmpt can provide more accurate answers if the filesize is known.
+  \remarks It is recommended to provide openmpt::probe_file_header_get_recommended_size() bytes of data for data and size. If the file is smaller, only provide the filesize amount and set size to the file's size. 
+  \remarks openmpt::could_open_probability() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt::probe_file_header() though, if possible.
+  \retval probe_file_header_result_success The file will most likely be supported by libopenmpt.
+  \retval probe_file_header_result_failure The file is not supported by libopenmpt.
+  \retval probe_file_header_result_wantmoredata An answer could not be determined with the amount of data provided.
+  \sa openmpt::probe_file_header_get_recommended_size()
+  \sa openmpt::could_open_probability()
+  \since 0.3.0
+*/
+LIBOPENMPT_CXX_API int probe_file_header( std::uint64_t flags, const std::uint8_t * data, std::size_t size );
+
+//! Probe the provided bytes from the beginning of a file for supported file format headers to find out whether libopenmpt might be able to open it
+/*!
+  \param flags Ored mask of openmpt::probe_file_header_flags_modules and openmpt::probe_file_header_flags_containers, or openmpt::probe_file_header_flags_default.
+  \param stream Input stream to scan.
+  \remarks stream is left in an unspecified state when this function returns.
+  \remarks openmpt::could_open_probability() provides a more elaborate interface that might be required for special use cases. It is recommended to use openmpt::probe_file_header() though, if possible.
+  \retval probe_file_header_result_success The file will most likely be supported by libopenmpt.
+  \retval probe_file_header_result_failure The file is not supported by libopenmpt.
+  \retval probe_file_header_result_wantmoredata An answer could not be determined with the amount of data provided.
+  \sa openmpt::probe_file_header_get_recommended_size()
+  \sa openmpt::could_open_probability()
+  \since 0.3.0
+*/
+LIBOPENMPT_CXX_API int probe_file_header( std::uint64_t flags, std::istream & stream );
+
+class module_impl;
+
+class module_ext;
+
+namespace detail {
+
+typedef std::map< std::string, std::string > initial_ctls_map;
+
+} // namespace detail
+
+class LIBOPENMPT_CXX_API module {
+
+	friend class module_ext;
+
+public:
+
+	//! Parameter index to use with openmpt::module::get_render_param and openmpt::module::set_render_param
+	enum render_param {
+		//! Master Gain
+		/*!
+		  The related value represents a relative gain in milliBel.\n
+		  The default value is 0.\n
+		  The supported value range is unlimited.\n
+		*/
+		RENDER_MASTERGAIN_MILLIBEL        = 1,
+		//! Stereo Separation
+		/*!
+		  The related value represents the stereo separation generated by the libopenmpt mixer in percent.\n
+		  The default value is 100.\n
+		  The supported value range is [0,200].\n
+		*/
+		RENDER_STEREOSEPARATION_PERCENT   = 2,
+		//! Interpolation Filter
+		/*!
+		  The related value represents the interpolation filter length used by the libopenmpt mixer.\n
+		  The default value is 0, which indicates a recommended default value.\n
+		  The supported value range is [0,inf). Values greater than the implementation limit are clamped to the maximum supported value.\n
+		  Currently supported values:
+		   - 0: internal default
+		   - 1: no interpolation (zero order hold)
+		   - 2: linear interpolation
+		   - 4: cubic interpolation
+		   - 8: windowed sinc with 8 taps
+		*/
+		RENDER_INTERPOLATIONFILTER_LENGTH = 3,
+		//! Volume Ramping Strength
+		/*!
+		  The related value represents the amount of volume ramping done by the libopenmpt mixer.\n
+		  The default value is -1, which indicates a recommended default value.\n
+		  The meaningful value range is [-1..10].\n
+		  A value of 0 completely disables volume ramping. This might cause clicks in sound output.\n
+		  Higher values imply slower/softer volume ramps.
+		*/
+		RENDER_VOLUMERAMPING_STRENGTH     = 4
+	};
+
+	//! Parameter index to use with openmpt::module::get_pattern_row_channel_command, openmpt::module::format_pattern_row_channel_command and openmpt::module::highlight_pattern_row_channel_command
+	enum command_index {
+		command_note        = 0,
+		command_instrument  = 1,
+		command_volumeffect = 2,
+		command_effect      = 3,
+		command_volume      = 4,
+		command_parameter   = 5
+	};
+
+private:
+	module_impl * impl;
+private:
+	// non-copyable
+	module( const module & );
+	void operator = ( const module & );
+private:
+	// for module_ext
+	module();
+	void set_impl( module_impl * i );
+public:
+	//! Construct an openmpt::module
+	/*!
+	  \param stream Input stream from which the module is loaded. After the constructor has finished successfully, the input position of stream is set to the byte after the last byte that has been read. If the constructor fails, the state of the input position of stream is undefined.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( std::istream & stream, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param data Data to load the module from.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const std::vector<std::uint8_t> & data, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param beg Begin of data to load the module from.
+	  \param end End of data to load the module from.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const std::uint8_t * beg, const std::uint8_t * end, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param data Data to load the module from.
+	  \param size Amount of data available.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const std::uint8_t * data, std::size_t size, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param data Data to load the module from.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const std::vector<char> & data, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param beg Begin of data to load the module from.
+	  \param end End of data to load the module from.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const char * beg, const char * end, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param data Data to load the module from.
+	  \param size Amount of data available.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const char * data, std::size_t size, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	/*!
+	  \param data Data to load the module from.
+	  \param size Amount of data available.
+	  \param log Log where any warnings or errors are printed to. The lifetime of the reference has to be as long as the lifetime of the module instance.
+	  \param ctls A map of initial ctl values, see openmpt::module::get_ctls.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the provided file cannot be opened.
+	  \remarks The input data can be discarded after an openmpt::module has been constructed successfully.
+	  \sa \ref libopenmpt_cpp_fileio
+	*/
+	module( const void * data, std::size_t size, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	virtual ~module();
+public:
+
+	//! Select a sub-song from a multi-song module
+	/*!
+	  \param subsong Index of the sub-song. -1 plays all sub-songs consecutively.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if sub-song is not in range [-1,openmpt::module::get_num_subsongs()[
+	  \sa openmpt::module::get_num_subsongs, openmpt::module::get_selected_subsong, openmpt::module::get_subsong_names
+	  \remarks Whether subsong -1 (all subsongs consecutively), subsong 0 or some other subsong is selected by default, is an implementation detail and subject to change. If you do not want to care about subsongs, it is recommended to just not call openmpt::module::select_subsong() at all.
+	*/
+	void select_subsong( std::int32_t subsong );
+	//! Get currently selected sub-song from a multi-song module
+	/*!
+	  \return Currently selected sub-song. -1 for all subsongs consecutively, 0 or greater for the current sub-song index.
+	  \sa openmpt::module::get_num_subsongs, openmpt::module::select_subsong, openmpt::module::get_subsong_names
+	  \since 0.3.0
+	*/
+	std::int32_t get_selected_subsong() const;
+	//! Set Repeat Count
+	/*!
+	  \param repeat_count Repeat Count
+	    - -1: repeat forever
+	    - 0: play once, repeat zero times (the default)
+	    - n>0: play once and repeat n times after that
+	  \sa openmpt::module::get_repeat_count
+	*/
+	void set_repeat_count( std::int32_t repeat_count );
+	//! Get Repeat Count
+	/*!
+	  \return Repeat Count
+	    - -1: repeat forever
+	    - 0: play once, repeat zero times (the default)
+	    - n>0: play once and repeat n times after that
+	  \sa openmpt::module::set_repeat_count
+	*/
+	std::int32_t get_repeat_count() const;
+
+	//! Get approximate song duration
+	/*!
+	  \return Approximate duration of current sub-song in seconds.
+	*/
+	double get_duration_seconds() const;
+
+	//! Set approximate current song position
+	/*!
+	  \param seconds Seconds to seek to. If seconds is out of range, the position gets set to song start or end respectively.
+	  \return Approximate new song position in seconds.
+	  \sa openmpt::module::get_position_seconds
+	*/
+	double set_position_seconds( double seconds );
+	//! Get current song position
+	/*!
+	  \return Current song position in seconds.
+	  \sa openmpt::module::set_position_seconds
+	*/
+	double get_position_seconds() const;
+
+	//! Set approximate current song position
+	/*!
+	  If order or row are out of range, to position is not modified and the current position is returned.
+	  \param order Pattern order number to seek to.
+	  \param row Pattern row number to seek to.
+	  \return Approximate new song position in seconds.
+	  \sa openmpt::module::set_position_seconds
+	  \sa openmpt::module::get_position_seconds
+	*/
+	double set_position_order_row( std::int32_t order, std::int32_t row );
+
+	//! Get render parameter
+	/*!
+	  \param param Parameter to query. See openmpt::module::render_param.
+	  \return The current value of the parameter.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if param is invalid.
+	  \sa openmpt::module::render_param
+	  \sa openmpt::module::set_render_param
+	*/
+	std::int32_t get_render_param( int param ) const;
+	//! Set render parameter
+	/*!
+	  \param param Parameter to set. See openmpt::module::render_param.
+	  \param value The value to set param to.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if param is invalid or value is out of range.
+	  \sa openmpt::module::render_param
+	  \sa openmpt::module::get_render_param
+	*/
+	void set_render_param( int param, std::int32_t value );
+
+	/*@{*/
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param mono Pointer to a buffer of at least count elements that receives the mono/center output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, std::int16_t * mono );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param left Pointer to a buffer of at least count elements that receives the left output.
+	  \param right Pointer to a buffer of at least count elements that receives the right output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, std::int16_t * left, std::int16_t * right );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param left Pointer to a buffer of at least count elements that receives the left output.
+	  \param right Pointer to a buffer of at least count elements that receives the right output.
+	  \param rear_left Pointer to a buffer of at least count elements that receives the rear left output.
+	  \param rear_right Pointer to a buffer of at least count elements that receives the rear right output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, std::int16_t * left, std::int16_t * right, std::int16_t * rear_left, std::int16_t * rear_right );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param mono Pointer to a buffer of at least count elements that receives the mono/center output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, float * mono );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param left Pointer to a buffer of at least count elements that receives the left output.
+	  \param right Pointer to a buffer of at least count elements that receives the right output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, float * left, float * right );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param left Pointer to a buffer of at least count elements that receives the left output.
+	  \param right Pointer to a buffer of at least count elements that receives the right output.
+	  \param rear_left Pointer to a buffer of at least count elements that receives the rear left output.
+	  \param rear_right Pointer to a buffer of at least count elements that receives the rear right output.
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read( std::int32_t samplerate, std::size_t count, float * left, float * right, float * rear_left, float * rear_right );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param interleaved_stereo Pointer to a buffer of at least count*2 elements that receives the interleaved stereo output in the order (L,R).
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read_interleaved_stereo( std::int32_t samplerate, std::size_t count, std::int16_t * interleaved_stereo );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param interleaved_quad Pointer to a buffer of at least count*4 elements that receives the interleaved suad surround output in the order (L,R,RL,RR).
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks It is recommended to use the floating point API because of the greater dynamic range and no implied clipping.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read_interleaved_quad( std::int32_t samplerate, std::size_t count, std::int16_t * interleaved_quad );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param interleaved_stereo Pointer to a buffer of at least count*2 elements that receives the interleaved stereo output in the order (L,R).
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read_interleaved_stereo( std::int32_t samplerate, std::size_t count, float * interleaved_stereo );
+	//! Render audio data
+	/*!
+	  \param samplerate Sample rate to render output. Should be in [8000,192000], but this is not enforced.
+	  \param count Number of audio frames to render per channel.
+	  \param interleaved_quad Pointer to a buffer of at least count*4 elements that receives the interleaved suad surround output in the order (L,R,RL,RR).
+	  \return The number of frames actually rendered.
+	  \retval 0 The end of song has been reached.
+	  \remarks The output buffers are only written to up to the returned number of elements.
+	  \remarks You can freely switch between any of the "read*" variants if you see a need to do so. libopenmpt tries to introduce as little switching annoyances as possible. Normally, you would only use a single one of these functions for rendering a particular module.
+	  \remarks Floating point samples are in the [-1.0..1.0] nominal range. They are not clipped to that range though and thus might overshoot.
+	  \sa \ref libopenmpt_cpp_outputformat
+	*/
+	std::size_t read_interleaved_quad( std::int32_t samplerate, std::size_t count, float * interleaved_quad );
+	/*@}*/
+
+	//! Get the list of supported metadata item keys
+	/*!
+	  \return Metadata item keys supported by openmpt::module::get_metadata
+	  \sa openmpt::module::get_metadata
+	*/
+	std::vector<std::string> get_metadata_keys() const;
+	//! Get a metadata item value
+	/*!
+	  \param key Metadata item key to query. Use openmpt::module::get_metadata_keys to check for available keys.
+	           Possible keys are:
+	           - type: Module format extension (e.g. it)
+	           - type_long: Format name associated with the module format (e.g. Impulse Tracker)
+	           - originaltype: Module format extension (e.g. it) of the original module in case the actual type is a converted format (e.g. mo3 or gdm)
+	           - originaltype_long: Format name associated with the module format (e.g. Impulse Tracker) of the original module in case the actual type is a converted format (e.g. mo3 or gdm)
+	           - container: Container format the module file is embedded in, if any (e.g. umx)
+	           - container_long: Full container name if the module is embedded in a container (e.g. Unreal Music)
+	           - tracker: Tracker that was (most likely) used to save the module file, if known
+	           - artist: Author of the module
+	           - title: Module title
+	           - date: Date the module was last saved, in ISO-8601 format.
+	           - message: Song message. If the song message is empty or the module format does not support song messages, a list of instrument and sample names is returned instead.
+	           - message_raw: Song message. If the song message is empty or the module format does not support song messages, an empty string is returned.
+	           - warnings: A list of warnings that were generated while loading the module.
+	  \return The associated value for key.
+	  \sa openmpt::module::get_metadata_keys
+	*/
+	std::string get_metadata( const std::string & key ) const;
+
+	//! Get the current speed
+	/*!
+	  \return The current speed in ticks per row.
+	*/
+	std::int32_t get_current_speed() const;
+	//! Get the current tempo
+	/*!
+	  \return The current tempo in tracker units. The exact meaning of this value depends on the tempo mode being used.
+	*/
+	std::int32_t get_current_tempo() const;
+	//! Get the current order
+	/*!
+	  \return The current order at which the module is being played back.
+	*/
+	std::int32_t get_current_order() const;
+	//! Get the current pattern
+	/*!
+	  \return The current pattern that is being played.
+	*/
+	std::int32_t get_current_pattern() const;
+	//! Get the current row
+	/*!
+	  \return The current row at which the current pattern is being played.
+	*/
+	std::int32_t get_current_row() const;
+	//! Get the current amount of playing channels.
+	/*!
+	  \return The amount of sample channels that are currently being rendered.
+	*/
+	std::int32_t get_current_playing_channels() const;
+
+	//! Get an approximate indication of the channel volume.
+	/*!
+	  \param channel The channel whose volume should be retrieved.
+	  \return The approximate channel volume.
+	  \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+	*/
+	float get_current_channel_vu_mono( std::int32_t channel ) const;
+	//! Get an approximate indication of the channel volume on the front-left speaker.
+	/*!
+	  \param channel The channel whose volume should be retrieved.
+	  \return The approximate channel volume.
+	  \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+	*/
+	float get_current_channel_vu_left( std::int32_t channel ) const;
+	//! Get an approximate indication of the channel volume on the front-right speaker.
+	/*!
+	  \param channel The channel whose volume should be retrieved.
+	  \return The approximate channel volume.
+	  \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+	*/
+	float get_current_channel_vu_right( std::int32_t channel ) const;
+	//! Get an approximate indication of the channel volume on the rear-left speaker.
+	/*!
+	  \param channel The channel whose volume should be retrieved.
+	  \return The approximate channel volume.
+	  \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+	*/
+	float get_current_channel_vu_rear_left( std::int32_t channel ) const;
+	//! Get an approximate indication of the channel volume on the rear-right speaker.
+	/*!
+	  \param channel The channel whose volume should be retrieved.
+	  \return The approximate channel volume.
+	  \remarks The returned value is solely based on the note velocity and does not take the actual waveform of the playing sample into account.
+	*/
+	float get_current_channel_vu_rear_right( std::int32_t channel ) const;
+
+	//! Get the number of sub-songs
+	/*!
+	  \return The number of sub-songs in the module. This includes any "hidden" songs (songs that share the same sequence, but start at different order indices) and "normal" sub-songs or "sequences" (if the format supports them).
+	  \sa openmpt::module::get_subsong_names, openmpt::module::select_subsong, openmpt::module::get_selected_subsong
+	*/
+	std::int32_t get_num_subsongs() const;
+	//! Get the number of pattern channels
+	/*!
+	  \return The number of pattern channels in the module. Not all channels do necessarily contain data.
+	  \remarks The number of pattern channels is completely independent of the number of output channels. libopenmpt can render modules in mono, stereo or quad surround, but the choice of which of the three modes to use must not be made based on the return value of this function, which may be any positive integer amount. Only use this function for informational purposes.
+	*/
+	std::int32_t get_num_channels() const;
+	//! Get the number of orders
+	/*!
+	  \return The number of orders in the current sequence of the module.
+	*/
+	std::int32_t get_num_orders() const;
+	//! Get the number of patterns
+	/*!
+	  \return The number of distinct patterns in the module.
+	*/
+	std::int32_t get_num_patterns() const;
+	//! Get the number of instruments
+	/*!
+	  \return The number of instrument slots in the module. Instruments are a layer on top of samples, and are not supported by all module formats.
+	*/
+	std::int32_t get_num_instruments() const;
+	//! Get the number of samples
+	/*!
+	  \return The number of sample slots in the module.
+	*/
+	std::int32_t get_num_samples() const;
+
+	//! Get a list of sub-song names
+	/*!
+	  \return All sub-song names.
+	  \sa openmpt::module::get_num_subsongs, openmpt::module::select_subsong, openmpt::module::get_selected_subsong
+	*/
+	std::vector<std::string> get_subsong_names() const;
+	//! Get a list of channel names
+	/*!
+	  \return All channel names.
+	  \sa openmpt::module::get_num_channels
+	*/
+	std::vector<std::string> get_channel_names() const;
+	//! Get a list of order names
+	/*!
+	  \return All order names.
+	  \sa openmpt::module::get_num_orders
+	*/
+	std::vector<std::string> get_order_names() const;
+	//! Get a list of pattern names
+	/*!
+	  \return All pattern names.
+	  \sa openmpt::module::get_num_patterns
+	*/
+	std::vector<std::string> get_pattern_names() const;
+	//! Get a list of instrument names
+	/*!
+	  \return All instrument names.
+	  \sa openmpt::module::get_num_instruments
+	*/
+	std::vector<std::string> get_instrument_names() const;
+	//! Get a list of sample names
+	/*!
+	  \return All sample names.
+	  \sa openmpt::module::get_num_samples
+	*/
+	std::vector<std::string> get_sample_names() const;
+
+	//! Get pattern at order position
+	/*!
+	  \param order The order item whose pattern index should be retrieved.
+	  \return The pattern index found at the given order position of the current sequence.
+	*/
+	std::int32_t get_order_pattern( std::int32_t order ) const;
+
+	//! Get the number of rows in a pattern
+	/*!
+	  \param pattern The pattern whose row count should be retrieved.
+	  \return The number of rows in the given pattern. If the pattern does not exist, 0 is returned.
+	*/
+	std::int32_t get_pattern_num_rows( std::int32_t pattern ) const;
+
+	//! Get raw pattern content
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \param command The cell index at which the data should be retrieved. See openmpt::module::command_index
+	  \return The internal, raw pattern data at the given pattern position.
+	*/
+	std::uint8_t get_pattern_row_channel_command( std::int32_t pattern, std::int32_t row, std::int32_t channel, int command ) const;
+
+	//! Get formatted (human-readable) pattern content
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \param command The cell index at which the data should be retrieved.
+	  \return The formatted pattern data at the given pattern position. See openmpt::module::command_index
+	  \sa openmpt::module::highlight_pattern_row_channel_command
+	*/
+	std::string format_pattern_row_channel_command( std::int32_t pattern, std::int32_t row, std::int32_t channel, int command ) const;
+
+	//! Get highlighting information for formatted pattern content
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \param command The cell index at which the data should be retrieved. See openmpt::module::command_index
+	  \return The highlighting string for the formatted pattern data as retrieved by openmpt::module::get_pattern_row_channel_command at the given pattern position.
+	  \remarks The returned string will map each character position of the string returned by openmpt::module::get_pattern_row_channel_command to a highlighting instruction.
+	           Possible highlighting characters are:
+	           - " " : empty/space
+	           - "." : empty/dot
+	           - "n" : generic note
+	           - "m" : special note
+	           - "i" : generic instrument
+	           - "u" : generic volume column effect
+	           - "v" : generic volume column parameter
+	           - "e" : generic effect column effect
+	           - "f" : generic effect column parameter
+	  \sa openmpt::module::get_pattern_row_channel_command
+	*/
+	std::string highlight_pattern_row_channel_command( std::int32_t pattern, std::int32_t row, std::int32_t channel, int command ) const;
+
+	//! Get formatted (human-readable) pattern content
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \param width The maximum number of characters the string should contain. 0 means no limit.
+	  \param pad If true, the string will be resized to the exact length provided in the width parameter.
+	  \return The formatted pattern data at the given pattern position.
+	  \sa openmpt::module::highlight_pattern_row_channel
+	*/
+	std::string format_pattern_row_channel( std::int32_t pattern, std::int32_t row, std::int32_t channel, std::size_t width = 0, bool pad = true ) const;
+	//! Get highlighting information for formatted pattern content
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \param width The maximum number of characters the string should contain. 0 means no limit.
+	  \param pad If true, the string will be resized to the exact length provided in the width parameter.
+	  \return The highlighting string for the formatted pattern data as retrieved by openmpt::module::format_pattern_row_channel at the given pattern position.
+	  \sa openmpt::module::format_pattern_row_channel
+	*/
+	std::string highlight_pattern_row_channel( std::int32_t pattern, std::int32_t row, std::int32_t channel, std::size_t width = 0, bool pad = true ) const;
+
+	//! Retrieve supported ctl keys
+	/*!
+	  \return A vector containing all supported ctl keys.
+	  \remarks Currently supported ctl values are:
+	           - load.skip_samples: Set to "1" to avoid loading samples into memory
+	           - load.skip_patterns: Set to "1" to avoid loading patterns into memory
+	           - load.skip_plugins: Set to "1" to avoid loading plugins
+	           - load.skip_subsongs_init: Set to "1" to avoid pre-initializing sub-songs. Skipping results in faster module loading but slower seeking.
+	           - seek.sync_samples: Set to "1" to sync sample playback when using openmpt::module::set_position_seconds or openmpt::module::set_position_order_row.
+	           - subsong: The current subsong. Setting it has identical semantics as openmpt::module::select_subsong(), getting it returns the currently selected subsong.
+	           - play.at_end: Chooses the behaviour when the end of song is reached:
+	                          - "fadeout": Fades the module out for a short while. Subsequent reads after the fadeout will return 0 rendered frames.
+	                          - "continue": Returns 0 rendered frames when the song end is reached. Subsequent reads will continue playing from the song start or loop start.
+	                          - "stop": Returns 0 rendered frames when the song end is reached. Subsequent reads will return 0 rendered frames.
+	           - play.tempo_factor: Set a floating point tempo factor. "1.0" is the default tempo.
+	           - play.pitch_factor: Set a floating point pitch factor. "1.0" is the default pitch.
+	           - render.resampler.emulate_amiga: Set to "1" to enable the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. 
+	           - render.opl.volume_factor: Set volume factor applied to synthesized OPL sounds, relative to the default OPL volume.
+	           - dither: Set the dither algorithm that is used for the 16 bit versions of openmpt::module::read. Supported values are:
+	                     - 0: No dithering.
+	                     - 1: Default mode. Chosen by OpenMPT code, might change.
+	                     - 2: Rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker).
+	                     - 3: Rectangular, 1 bit depth, simple 1st order noise shaping
+
+	           An exclamation mark ("!") or a question mark ("?") can be appended to any ctl key in order to influence the behaviour in case of an unknown ctl key. "!" causes an exception to be thrown; "?" causes the ctl to be silently ignored. In case neither is appended to the key name, unknown init_ctls are ignored by default and other ctls throw an exception by default.
+	*/
+	std::vector<std::string> get_ctls() const;
+
+	//! Get current ctl value
+	/*!
+	  \param ctl The ctl key whose value should be retrieved.
+	  \return The associated ctl value.
+	  \sa openmpt::module::get_ctls
+	*/
+	std::string ctl_get( const std::string & ctl ) const;
+	//! Set ctl value
+	/*!
+	  \param ctl The ctl key whose value should be set.
+	  \param value The value that should be set.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception in case the value is not sensible (e.g. negative tempo factor) or under the circumstances outlined in openmpt::module::get_ctls.
+	  \sa openmpt::module::get_ctls
+	*/
+	void ctl_set( const std::string & ctl, const std::string & value );
+
+	// remember to add new functions to both C and C++ interfaces and to increase OPENMPT_API_VERSION_MINOR
+
+}; // class module
+
+} // namespace openmpt
+
+/*!
+  @}
+*/
+
+#endif // LIBOPENMPT_HPP
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_config.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..0323ab1e082833f36cde2be9acfffa25db18d2c5
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_config.h
@@ -0,0 +1,204 @@
+/*
+ * libopenmpt_config.h
+ * -------------------
+ * Purpose: libopenmpt public interface configuration
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_CONFIG_H
+#define LIBOPENMPT_CONFIG_H
+
+/*! \defgroup libopenmpt libopenmpt */
+
+/*! \addtogroup libopenmpt
+  @{
+*/
+
+/* provoke warnings if already defined */
+#define LIBOPENMPT_API
+#undef LIBOPENMPT_API
+#define LIBOPENMPT_CXX_API
+#undef LIBOPENMPT_CXX_API
+
+/*! \brief Defined if libopenmpt/libopenmpt_stream_callbacks_buffer.h exists. */
+#define LIBOPENMPT_STREAM_CALLBACKS_BUFFER
+
+/*! \brief Defined if libopenmpt/libopenmpt_stream_callbacks_fd.h exists.
+ * \since 0.3
+ * \remarks
+ *   Use the following to check for availability:
+ *   \code
+ *   #include <libopenmpt/libopenmpt.h>
+ *   #if defined(LIBOPENMPT_STREAM_CALLBACKS_FD) || ((OPENMPT_API_VERSION_MAJOR == 0) && ((OPENMPT_API_VERSION_MINOR == 2) || (OPENMPT_API_VERSION_MINOR == 1)))
+ *   #include <libopenmpt/libopenmpt_stream_callbacks_fd.h>
+ *   #endif
+ *   \endcode
+ */
+#define LIBOPENMPT_STREAM_CALLBACKS_FD
+
+/*! \brief Defined if libopenmpt/libopenmpt_stream_callbacks_file.h exists.
+ * \since 0.3
+ * \remarks
+ *   Use the following to check for availability:
+ *   \code
+ *   #include <libopenmpt/libopenmpt.h>
+ *   #if defined(LIBOPENMPT_STREAM_CALLBACKS_FILE) || ((OPENMPT_API_VERSION_MAJOR == 0) && ((OPENMPT_API_VERSION_MINOR == 2) || (OPENMPT_API_VERSION_MINOR == 1)))
+ *   #include <libopenmpt/libopenmpt_stream_callbacks_file.h>
+ *   #endif
+ *   \endcode
+ */
+#define LIBOPENMPT_STREAM_CALLBACKS_FILE
+
+#if defined(__DOXYGEN__)
+
+#define LIBOPENMPT_API_HELPER_EXPORT
+#define LIBOPENMPT_API_HELPER_IMPORT
+#define LIBOPENMPT_API_HELPER_PUBLIC
+#define LIBOPENMPT_API_HELPER_LOCAL
+
+#elif defined(_MSC_VER)
+
+#define LIBOPENMPT_API_HELPER_EXPORT __declspec(dllexport)
+#define LIBOPENMPT_API_HELPER_IMPORT __declspec(dllimport)
+#define LIBOPENMPT_API_HELPER_PUBLIC 
+#define LIBOPENMPT_API_HELPER_LOCAL  
+
+#elif defined(__EMSCRIPTEN__)
+
+#define LIBOPENMPT_API_HELPER_EXPORT __attribute__((visibility("default"))) __attribute__((used))
+#define LIBOPENMPT_API_HELPER_IMPORT __attribute__((visibility("default"))) __attribute__((used))
+#define LIBOPENMPT_API_HELPER_PUBLIC __attribute__((visibility("default"))) __attribute__((used))
+#define LIBOPENMPT_API_HELPER_LOCAL  __attribute__((visibility("hidden")))
+
+#elif (defined(__GNUC__) || defined(__clang__)) && defined(_WIN32)
+
+#define LIBOPENMPT_API_HELPER_EXPORT __declspec(dllexport)
+#define LIBOPENMPT_API_HELPER_IMPORT __declspec(dllimport)
+#define LIBOPENMPT_API_HELPER_PUBLIC __attribute__((visibility("default")))
+#define LIBOPENMPT_API_HELPER_LOCAL  __attribute__((visibility("hidden")))
+
+#elif defined(__GNUC__) || defined(__clang__)
+
+#define LIBOPENMPT_API_HELPER_EXPORT __attribute__((visibility("default")))
+#define LIBOPENMPT_API_HELPER_IMPORT __attribute__((visibility("default")))
+#define LIBOPENMPT_API_HELPER_PUBLIC __attribute__((visibility("default")))
+#define LIBOPENMPT_API_HELPER_LOCAL  __attribute__((visibility("hidden")))
+
+#elif defined(_WIN32)
+
+#define LIBOPENMPT_API_HELPER_EXPORT __declspec(dllexport)
+#define LIBOPENMPT_API_HELPER_IMPORT __declspec(dllimport)
+#define LIBOPENMPT_API_HELPER_PUBLIC 
+#define LIBOPENMPT_API_HELPER_LOCAL  
+
+#else
+
+#define LIBOPENMPT_API_HELPER_EXPORT 
+#define LIBOPENMPT_API_HELPER_IMPORT 
+#define LIBOPENMPT_API_HELPER_PUBLIC 
+#define LIBOPENMPT_API_HELPER_LOCAL  
+
+#endif
+
+#if defined(LIBOPENMPT_BUILD_DLL)
+#define LIBOPENMPT_API     LIBOPENMPT_API_HELPER_EXPORT
+#elif defined(LIBOPENMPT_USE_DLL)
+#define LIBOPENMPT_API     LIBOPENMPT_API_HELPER_IMPORT
+#else
+#define LIBOPENMPT_API     LIBOPENMPT_API_HELPER_PUBLIC
+#endif
+
+#ifdef __cplusplus
+
+#define LIBOPENMPT_CXX_API LIBOPENMPT_API
+
+#if defined(LIBOPENMPT_USE_DLL)
+#if defined(_MSC_VER) && !defined(_DLL)
+#error "C++ interface is disabled if libopenmpt is built as a DLL and the runtime is statically linked. This is not supported by microsoft and cannot possibly work. Ever."
+#undef LIBOPENMPT_CXX_API
+#define LIBOPENMPT_CXX_API LIBOPENMPT_API_HELPER_LOCAL
+#endif
+#endif
+
+#if defined(__EMSCRIPTEN__)
+
+/* Only the C API is supported for emscripten. Disable the C++ API. */
+#undef LIBOPENMPT_CXX_API
+#define LIBOPENMPT_CXX_API LIBOPENMPT_API_HELPER_LOCAL 
+#endif
+
+#endif
+
+/*!
+  @}
+*/
+
+
+/* C */
+
+#if !defined(LIBOPENMPT_NO_DEPRECATE)
+#if defined(__clang__)
+#define LIBOPENMPT_DEPRECATED __attribute__((deprecated))
+#elif defined(__GNUC__)
+#define LIBOPENMPT_DEPRECATED __attribute__((deprecated))
+#elif defined(_MSC_VER)
+#define LIBOPENMPT_DEPRECATED __declspec(deprecated)
+#else
+#define LIBOPENMPT_DEPRECATED
+#endif
+#endif
+
+#ifndef __cplusplus
+#if !defined(LIBOPENMPT_NO_DEPRECATE)
+LIBOPENMPT_DEPRECATED static const int LIBOPENMPT_DEPRECATED_STRING_CONSTANT = 0;
+#define LIBOPENMPT_DEPRECATED_STRING( str ) ( LIBOPENMPT_DEPRECATED_STRING_CONSTANT ? ( str ) : ( str ) )
+#else
+#define LIBOPENMPT_DEPRECATED_STRING( str ) str
+#endif
+#endif
+
+
+/* C++ */
+
+#ifdef __cplusplus
+
+#ifndef LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED
+/* handle known broken compilers here by defining LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED appropriately */
+#endif
+
+#if defined(LIBOPENMPT_ASSUME_CPLUSPLUS)
+#ifndef LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED
+#define LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED LIBOPENMPT_ASSUME_CPLUSPLUS
+#endif
+#endif
+
+#if !defined(LIBOPENMPT_NO_DEPRECATE)
+#if defined(LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED)
+#if (LIBOPENMPT_ASSUME_CPLUSPLUS_DEPRECATED >= 201402L)
+#define LIBOPENMPT_ATTR_DEPRECATED [[deprecated]]
+#undef LIBOPENMPT_DEPRECATED
+#define LIBOPENMPT_DEPRECATED
+#else
+#define LIBOPENMPT_ATTR_DEPRECATED
+#endif
+#elif (__cplusplus >= 201402L)
+#define LIBOPENMPT_ATTR_DEPRECATED [[deprecated]]
+#undef LIBOPENMPT_DEPRECATED
+#define LIBOPENMPT_DEPRECATED
+#else
+#define LIBOPENMPT_ATTR_DEPRECATED
+#endif
+#else
+#undef LIBOPENMPT_DEPRECATED
+#define LIBOPENMPT_DEPRECATED
+#define LIBOPENMPT_ATTR_DEPRECATED
+#endif
+
+#endif
+
+
+#include "libopenmpt_version.h"
+
+#endif /* LIBOPENMPT_CONFIG_H */
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.h
new file mode 100644
index 0000000000000000000000000000000000000000..e76f758de2f8c62acf1d18b5e8771037070dcd6c
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.h
@@ -0,0 +1,318 @@
+/*
+ * libopenmpt_ext.h
+ * ----------------
+ * Purpose: libopenmpt public c interface for libopenmpt extensions
+ * Notes  :
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_EXT_H
+#define LIBOPENMPT_EXT_H
+
+#include "libopenmpt_config.h"
+#include "libopenmpt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \page libopenmpt_ext_c_overview libopenmpt_ext C API
+ *
+ * libopenmpt_ext is included in all builds by default.
+ *
+ * \section libopenmpt-ext-c-detailed Detailed documentation
+ *
+ * \ref libopenmpt_ext_c
+ *
+ */
+
+/*! \defgroup libopenmpt_ext_c libopenmpt_ext C */
+
+/*! \addtogroup libopenmpt_ext_c
+ * @{
+ */
+
+/*! \brief Opaque type representing a libopenmpt extension module
+ */
+typedef struct openmpt_module_ext openmpt_module_ext;
+
+/*! \brief Construct an openmpt_module_ext
+ *
+ * \param stream_callbacks Input stream callback operations.
+ * \param stream Input stream to load the module from.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module_ext. May be NULL.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \param ctls A map of initial ctl values, see openmpt_module_get_ctls.
+ * \return A pointer to the constructed openmpt_module_ext, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module_ext has been constructed successfully.
+ * \sa openmpt_stream_callbacks
+ * \sa \ref libopenmpt_c_fileio
+ * \since 0.3.0
+ */
+LIBOPENMPT_API openmpt_module_ext * openmpt_module_ext_create( openmpt_stream_callbacks stream_callbacks, void * stream, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Construct an openmpt_module_ext
+ *
+ * \param filedata Data to load the module from.
+ * \param filesize Amount of data available.
+ * \param logfunc Logging function where warning and errors are written. The logging function may be called throughout the lifetime of openmpt_module_ext.
+ * \param loguser User-defined data associated with this module. This value will be passed to the logging callback function (logfunc)
+ * \param errfunc Error function to define error behaviour. May be NULL.
+ * \param erruser Error function user context. Used to pass any user-defined data associated with this module to the logging function.
+ * \param error Pointer to an integer where an error may get stored. May be NULL.
+ * \param error_message Pointer to a string pointer where an error message may get stored. May be NULL.
+ * \param ctls A map of initial ctl values, see openmpt_module_get_ctls.
+ * \return A pointer to the constructed openmpt_module_ext, or NULL on failure.
+ * \remarks The input data can be discarded after an openmpt_module_ext has been constructed successfully.
+ * \sa \ref libopenmpt_c_fileio
+ * \since 0.3.0
+ */
+LIBOPENMPT_API openmpt_module_ext * openmpt_module_ext_create_from_memory( const void * filedata, size_t filesize, openmpt_log_func logfunc, void * loguser, openmpt_error_func errfunc, void * erruser, int * error, const char * * error_message, const openmpt_module_initial_ctl * ctls );
+
+/*! \brief Unload a previously created openmpt_module_ext from memory.
+ *
+ * \param mod_ext The module to unload.
+ */
+LIBOPENMPT_API void openmpt_module_ext_destroy( openmpt_module_ext * mod_ext );
+
+/*! \brief Retrieve the openmpt_module handle from an openmpt_module_ext handle.
+ *
+ * \param mod_ext The extension module handle to convert
+ * \return An equivalent openmpt_module handle to pass to standard libopenmpt functions 
+ * \since 0.3.0
+ */
+LIBOPENMPT_API openmpt_module * openmpt_module_ext_get_module( openmpt_module_ext * mod_ext );
+
+/*! Retrieve a libopenmpt extension.
+ *
+ * \param mod_ext The module handle to work on.
+ * \param interface_id The name of the extension interface to retrieve (e.g. LIBOPENMPT_EXT_C_INTERFACE_PATTERN_VIS).
+ * \param interface Appropriate structure of interface function pointers which is to be filled by this function (e.g. a pointer to a openmpt_module_ext_interface_pattern_vis structure).
+ * \param interface_size Size of the interface's structure of function pointers (e.g. sizeof(openmpt_module_ext_interface_pattern_vis)).
+ * \return 1 on success, 0 if the interface was not found.
+ * \since 0.3.0
+ */
+LIBOPENMPT_API int openmpt_module_ext_get_interface( openmpt_module_ext * mod_ext, const char * interface_id, void * interface, size_t interface_size );
+
+
+
+#ifndef LIBOPENMPT_EXT_C_INTERFACE_PATTERN_VIS
+#define LIBOPENMPT_EXT_C_INTERFACE_PATTERN_VIS "pattern_vis"
+#endif
+
+/*! Pattern command type */
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_UNKNOWN 0
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_GENERAL 1
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_GLOBAL  2
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_VOLUME  3
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_PANNING 4
+#define OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_PITCH   5
+
+typedef struct openmpt_module_ext_interface_pattern_vis {
+	/*! Get pattern command type for pattern highlighting
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param pattern The pattern whose data should be retrieved.
+	 * \param row The row from which the data should be retrieved.
+	 * \param channel The channel from which the data should be retrieved.
+	 * \return The command type in the effect column at the given pattern position (see OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_*)
+	 * \sa openmpt_module_ext_interface_pattern_vis::get_pattern_row_channel_volume_effect_type
+	 */
+	int ( * get_pattern_row_channel_volume_effect_type ) ( openmpt_module_ext * mod_ext, int32_t pattern, int32_t row, int32_t channel );
+
+	/*! Get pattern command type for pattern highlighting
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param pattern The pattern whose data should be retrieved.
+	 * \param row The row from which the data should be retrieved.
+	 * \param channel The channel from which the data should be retrieved.
+	 * \return The command type in the effect column at the given pattern position (see OPENMPT_MODULE_EXT_INTERFACE_PATTERN_VIS_EFFECT_TYPE_*)
+	 * \sa openmpt_module_ext_interface_pattern_vis::get_pattern_row_channel_volume_effect_type
+	 */
+	int ( * get_pattern_row_channel_effect_type ) ( openmpt_module_ext * mod_ext, int32_t pattern, int32_t row, int32_t channel );
+} openmpt_module_ext_interface_pattern_vis;
+
+
+
+#ifndef LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE
+#define LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE "interactive"
+#endif
+
+typedef struct openmpt_module_ext_interface_interactive {
+	/*! Set the current ticks per row (speed)
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param speed The new tick count in range [1, 65535].
+	 * \return 1 on success, 0 on failure.
+	 * \remarks The tick count may be reset by pattern commands at any time.
+	 * \sa openmpt_module_get_current_speed
+	 */
+	int ( * set_current_speed ) ( openmpt_module_ext * mod_ext, int32_t speed );
+
+	/*! Set the current module tempo
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param tempo The new tempo in range [32, 512]. The exact meaning of the value depends on the tempo mode used by the module.
+	 * \return 1 on success, 0 on failure.
+	 * \remarks The tempo may be reset by pattern commands at any time. Use openmpt_module_ext_interface_interactive::set_tempo_factor to apply a tempo factor that is independent of pattern commands.
+	 * \sa openmpt_module_get_current_tempo
+	 */
+	int ( * set_current_tempo ) ( openmpt_module_ext * mod_ext, int32_t tempo );
+
+	/*! Set the current module tempo factor without affecting playback pitch
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param factor The new tempo factor in range ]0.0, 4.0] - 1.0 means unmodified tempo.
+	 * \return 1 on success, 0 on failure.
+	 * \remarks Modifying the tempo without applying the same pitch factor using openmpt_module_ext_interface_interactive::set_pitch_factor may cause rhythmic samples (e.g. drum loops) to go out of sync.
+	 * \sa openmpt_module_ext_interface_interactive::get_tempo_factor
+	 */
+	int ( * set_tempo_factor ) ( openmpt_module_ext * mod_ext, double factor );
+
+	/*! Gets the current module tempo factor
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \return The current tempo factor.
+	 * \sa openmpt_module_ext_interface_interactive::set_tempo_factor
+	 */
+	double ( * get_tempo_factor ) ( openmpt_module_ext * mod_ext );
+
+	/*! Set the current module pitch factor without affecting playback speed
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param factor The new pitch factor in range ]0.0, 4.0] - 1.0 means unmodified pitch.
+	 * \return 1 on success, 0 on failure.
+	 * \remarks Modifying the pitch without applying the the same tempo factor using openmpt_module_ext_interface_interactive::set_tempo_factor may cause rhythmic samples (e.g. drum loops) to go out of sync.
+	 * \remarks To shift the pich by `n` semitones, the parameter can be calculated as follows: `pow( 2.0, n / 12.0 )`
+	 * \sa openmpt_module_ext_interface_interactive::get_pitch_factor
+	 */
+	int ( * set_pitch_factor ) ( openmpt_module_ext * mod_ext, double factor );
+
+	/*! Gets the current module pitch factor
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \return The current pitch factor.
+	 * \sa openmpt_module_ext_interface_interactive::set_pitch_factor
+	*/
+	double ( * get_pitch_factor ) ( openmpt_module_ext * mod_ext );
+
+	/*! Set the current global volume
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param volume The new global volume in range [0.0, 1.0]
+	 * \return 1 on success, 0 on failure.
+	 * \remarks The global volume may be reset by pattern commands at any time. Use openmpt_module_set_render_param to apply a global overall volume factor that is independent of pattern commands.
+	 * \sa openmpt_module_ext_interface_interactive::get_global_volume
+	 */
+	int ( * set_global_volume ) ( openmpt_module_ext * mod_ext, double volume );
+
+	/*! Get the current global volume
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \return The current global volume in range [0.0, 1.0]
+	 * \sa openmpt_module_ext_interface_interactive::set_global_volume
+	 */
+	double ( * get_global_volume ) ( openmpt_module_ext * mod_ext );
+
+	/*! Set the current channel volume for a channel
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param channel The channel whose volume should be set, in range [0, openmpt_module_get_num_channels()[
+	 * \param volume The new channel volume in range [0.0, 1.0]
+	 * \return 1 on success, 0 on failure (channel out of range).
+	 * \remarks The channel volume may be reset by pattern commands at any time.
+	 * \sa openmpt_module_ext_interface_interactive::get_channel_volume
+	 */
+	int ( * set_channel_volume ) ( openmpt_module_ext * mod_ext, int32_t channel, double volume );
+
+	/*! Get the current channel volume for a channel
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param channel The channel whose volume should be retrieved, in range [0, openmpt_module_get_num_channels()[
+	 * \return The current channel volume in range [0.0, 1.0]
+	 * \sa openmpt_module_ext_interface_interactive::set_channel_volume
+	 */
+	double ( * get_channel_volume ) ( openmpt_module_ext * mod_ext, int32_t channel );
+
+	/*! Set the current mute status for a channel
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param channel The channel whose mute status should be set, in range [0, openmpt_module_get_num_channels()[
+	 * \param mute The new mute status. true is muted, false is unmuted.
+	 * \return 1 on success, 0 on failure (channel out of range).
+	 * \sa openmpt_module_ext_interface_interactive::get_channel_mute_status
+	 */
+	int ( * set_channel_mute_status ) ( openmpt_module_ext * mod_ext, int32_t channel, int mute );
+
+	/*! Get the current mute status for a channel
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param channel The channel whose mute status should be retrieved, in range [0, openmpt_module_get_num_channels()[
+	 * \return The current channel mute status. 1 is muted, 0 is unmuted, -1 means the instrument was out of range
+	 * \sa openmpt_module_ext_interface_interactive::set_channel_mute_status
+	 */
+	int ( * get_channel_mute_status ) ( openmpt_module_ext * mod_ext, int32_t channel );
+
+	/*! Set the current mute status for an instrument
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param instrument The instrument whose mute status should be set, in range [0, openmpt_module_get_num_instruments()[ if openmpt_module_get_num_instruments is not 0, otherwise in [0, openmpt_module_get_num_samples()[
+	 * \param mute The new mute status. true is muted, false is unmuted.
+	 * \return 1 on success, 0 on failure (instrument out of range).
+	 * \sa openmpt_module_ext_interface_interactive::get_instrument_mute_status
+	 */
+	int ( * set_instrument_mute_status ) ( openmpt_module_ext * mod_ext, int32_t instrument, int mute );
+
+	/*! Get the current mute status for an instrument
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param instrument The instrument whose mute status should be retrieved, in range [0, openmpt_module_get_num_instruments()[ if openmpt_module_get_num_instruments is not 0, otherwise in [0, openmpt_module_get_num_samples()[
+	 * \return The current instrument mute status. 1 is muted, 0 is unmuted, -1 means the instrument was out of range
+	 * \sa openmpt_module_ext_interface_interactive::set_instrument_mute_status
+	 */
+	int ( * get_instrument_mute_status ) ( openmpt_module_ext * mod_ext, int32_t instrument );
+
+	/*! Play a note using the specified instrument
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param instrument The instrument that should be played, in range [0, openmpt_module_get_num_instruments()[ if openmpt_module_get_num_instruments is not 0, otherwise in [0, openmpt_module_get_num_samples()[
+	 * \param note The note to play, in rage [0, 119]. 60 is the middle C.
+	 * \param volume The volume at which the note should be triggered, in range [0.0, 1.0]
+	 * \param panning The panning position at which the note should be triggered, in range [-1.0, 1.0], 0.0 is center.
+	 * \return The channel on which the note is played. This can pe be passed to openmpt_module_ext_interface_interactive::stop_note to stop the note. -1 means that no channel could be allocated and the note is not played.
+	 * \sa openmpt_module_ext_interface_interactive::stop_note
+	 */
+	int32_t ( * play_note ) ( openmpt_module_ext * mod_ext, int32_t instrument, int32_t note, double volume, double panning );
+
+	/*! Stop the note playing on the specified channel
+	 *
+	 * \param mod_ext The module handle to work on.
+	 * \param channel The channel on which the note should be stopped.
+	 * \return 1 on success, 0 on failure (channel out of range).
+	 * \sa openmpt_module_ext_interface_interactive::play_note
+	 */
+	int ( * stop_note ) ( openmpt_module_ext * mod_ext, int32_t channel );
+} openmpt_module_ext_interface_interactive;
+
+
+
+/* add stuff here */
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * @}
+ */
+
+#endif /* LIBOPENMPT_EXT_H */
+
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.hpp b/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..686eaeeedb93a5705433a07de5fc5f07e5a12764
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_ext.hpp
@@ -0,0 +1,306 @@
+/*
+ * libopenmpt_ext.hpp
+ * ------------------
+ * Purpose: libopenmpt public c++ interface for libopenmpt extensions
+ * Notes  :
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_EXT_HPP
+#define LIBOPENMPT_EXT_HPP
+
+#include "libopenmpt_config.h"
+#include "libopenmpt.hpp"
+
+/*!
+ * \page libopenmpt_ext_cpp_overview libopenmpt_ext C++ API
+ *
+ * libopenmpt_ext is now included in all builds by default.
+ *
+ * \section libopenmpt-ext-cpp-detailed Detailed documentation
+ *
+ * \ref libopenmpt_ext_cpp
+ *
+ */
+
+/*! \defgroup libopenmpt_ext_cpp libopenmpt_ext C++ */
+
+/*! \addtogroup libopenmpt_ext_cpp
+  @{
+*/
+
+namespace openmpt {
+
+class module_ext_impl;
+
+class LIBOPENMPT_CXX_API module_ext : public module {
+	
+private:
+	module_ext_impl * ext_impl;
+private:
+	// non-copyable
+	module_ext( const module_ext & );
+	void operator = ( const module_ext & );
+public:
+	module_ext( std::istream & stream, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	module_ext( const std::vector<char> & data, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	module_ext( const char * data, std::size_t size, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	module_ext( const void * data, std::size_t size, std::ostream & log = std::clog, const std::map< std::string, std::string > & ctls = detail::initial_ctls_map() );
+	virtual ~module_ext();
+
+public:
+
+	//! Retrieve a libopenmpt extension.
+	/*! Example: Retrieving the interactive extension to change the tempo of a module:
+	  \code{.cpp}
+	  openmpt::module_ext *mod = new openmpt::module_ext( stream );
+	  #ifdef LIBOPENMPT_EXT_INTERFACE_INTERACTIVE
+	    openmpt::ext::interactive *interactive = static_cast<openmpt::ext::interactive *>( self->mod->get_interface( openmpt::ext::interactive_id ) );
+	    if ( interactive ) {
+	      interactive->set_tempo_factor( 2.0 ); // play module at double speed
+	    } else {
+	      // interface not available
+	    }
+	  #else
+	    // interfae not available
+	  #endif
+	  \endcode
+	  \param interface_id The name of the extension interface to retrieve.
+	  \return The interface object. This may be a nullptr if the extension was not found.
+	*/
+	void * get_interface( const std::string & interface_id );
+
+}; // class module_ext
+
+namespace ext {
+
+#define LIBOPENMPT_DECLARE_EXT_CXX_INTERFACE(name) \
+	static const char name ## _id [] = # name ; \
+	class name; \
+/**/
+
+#define LIBOPENMPT_EXT_CXX_INTERFACE(name) \
+	protected: \
+		name () {} \
+		virtual ~ name () {} \
+	public: \
+/**/
+
+
+#ifndef LIBOPENMPT_EXT_INTERFACE_PATTERN_VIS
+#define LIBOPENMPT_EXT_INTERFACE_PATTERN_VIS
+#endif
+
+LIBOPENMPT_DECLARE_EXT_CXX_INTERFACE(pattern_vis)
+
+class pattern_vis {
+
+	LIBOPENMPT_EXT_CXX_INTERFACE(pattern_vis)
+
+	//! Pattern command type
+	enum effect_type {
+
+		effect_unknown = 0,
+		effect_general = 1,
+		effect_global = 2,
+		effect_volume = 3,
+		effect_panning = 4,
+		effect_pitch = 5
+
+	}; // enum effect_type
+
+	//! Get pattern command type for pattern highlighting
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \return The command type in the effect column at the given pattern position (see openmpt::ext::pattern_vis::effect_type)
+	  \sa openmpt::ext::pattern_vis::get_pattern_row_channel_effect_type
+	*/
+	virtual effect_type get_pattern_row_channel_volume_effect_type( std::int32_t pattern, std::int32_t row, std::int32_t channel ) const = 0;
+
+	//! Get pattern command type for pattern highlighting
+	/*!
+	  \param pattern The pattern whose data should be retrieved.
+	  \param row The row from which the data should be retrieved.
+	  \param channel The channel from which the data should be retrieved.
+	  \return The command type in the volume column at the given pattern position (see openmpt::ext::pattern_vis::effect_type)
+	  \sa openmpt::ext::pattern_vis::get_pattern_row_channel_volume_effect_type
+	*/
+	virtual effect_type get_pattern_row_channel_effect_type( std::int32_t pattern, std::int32_t row, std::int32_t channel ) const = 0;
+
+}; // class pattern_vis
+
+
+#ifndef LIBOPENMPT_EXT_INTERFACE_INTERACTIVE
+#define LIBOPENMPT_EXT_INTERFACE_INTERACTIVE
+#endif
+
+LIBOPENMPT_DECLARE_EXT_CXX_INTERFACE(interactive)
+
+class interactive {
+
+	LIBOPENMPT_EXT_CXX_INTERFACE(interactive)
+
+	//! Set the current ticks per row (speed)
+	/*!
+	  \param speed The new tick count in range [1, 65535].
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the speed is outside the specified range.
+	  \remarks The tick count may be reset by pattern commands at any time.
+	  \sa openmpt::module::get_current_speed
+	*/
+	virtual void set_current_speed( std::int32_t speed ) = 0;
+
+	//! Set the current module tempo
+	/*!
+	  \param tempo The new tempo in range [32, 512]. The exact meaning of the value depends on the tempo mode used by the module.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the tempo is outside the specified range.
+	  \remarks The tempo may be reset by pattern commands at any time. Use openmpt::ext:interactive::set_tempo_factor to apply a tempo factor that is independent of pattern commands.
+	  \sa openmpt::module::get_current_tempo
+	*/
+	virtual void set_current_tempo( std::int32_t tempo ) = 0;
+
+	//! Set the current module tempo factor without affecting playback pitch
+	/*!
+	  \param factor The new tempo factor in range ]0.0, 4.0] - 1.0 means unmodified tempo.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the factor is outside the specified range.
+	  \remarks Modifying the tempo without applying the same pitch factor using openmpt::ext::interactive::set_pitch_factor may cause rhythmic samples (e.g. drum loops) to go out of sync.
+	  \sa openmpt::ext::interactive::get_tempo_factor
+	*/
+	virtual void set_tempo_factor( double factor ) = 0;
+
+	//! Gets the current module tempo factor
+	/*!
+	  \return The current tempo factor.
+	  \sa openmpt::ext::interactive::set_tempo_factor
+	*/
+	virtual double get_tempo_factor( ) const = 0;
+	
+	//! Set the current module pitch factor without affecting playback speed
+	/*!
+	  \param factor The new pitch factor in range ]0.0, 4.0] - 1.0 means unmodified pitch.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the factor is outside the specified range.
+	  \remarks Modifying the pitch without applying the the same tempo factor using openmpt::ext::interactive::set_tempo_factor may cause rhythmic samples (e.g. drum loops) to go out of sync.
+	  \remarks To shift the pich by `n` semitones, the parameter can be calculated as follows: `pow( 2.0, n / 12.0 )`
+	  \sa openmpt::ext::interactive::get_pitch_factor
+	*/
+	virtual void set_pitch_factor( double factor ) = 0;
+
+	//! Gets the current module pitch factor
+	/*!
+	  \return The current pitch factor.
+	  \sa openmpt::ext::interactive::set_pitch_factor
+	*/
+	virtual double get_pitch_factor( ) const = 0;
+
+	//! Set the current global volume
+	/*!
+	  \param volume The new global volume in range [0.0, 1.0]
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the volume is outside the specified range.
+	  \remarks The global volume may be reset by pattern commands at any time. Use openmpt::module::set_render_param to apply a global overall volume factor that is independent of pattern commands.
+	  \sa openmpt::ext::interactive::get_global_volume
+	*/
+	virtual void set_global_volume( double volume ) = 0;
+
+	//! Get the current global volume
+	/*!
+	  \return The current global volume in range [0.0, 1.0]
+	  \sa openmpt::ext::interactive::set_global_volume
+	*/
+	virtual double get_global_volume( ) const = 0;
+	
+	//! Set the current channel volume for a channel
+	/*!
+	  \param channel The channel whose volume should be set, in range [0, openmpt::module::get_num_channels()[
+	  \param volume The new channel volume in range [0.0, 1.0]
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the channel or volume is outside the specified range.
+	  \remarks The channel volume may be reset by pattern commands at any time.
+	  \sa openmpt::ext::interactive::get_channel_volume
+	*/
+	virtual void set_channel_volume( std::int32_t channel, double volume ) = 0;
+
+	//! Get the current channel volume for a channel
+	/*!
+	  \param channel The channel whose volume should be retrieved, in range [0, openmpt::module::get_num_channels()[
+	  \return The current channel volume in range [0.0, 1.0]
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the channel is outside the specified range.
+	  \sa openmpt::ext::interactive::set_channel_volume
+	*/
+	virtual double get_channel_volume( std::int32_t channel ) const = 0;
+
+	//! Set the current mute status for a channel
+	/*!
+	  \param channel The channel whose mute status should be set, in range [0, openmpt::module::get_num_channels()[
+	  \param mute The new mute status. true is muted, false is unmuted.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the channel is outside the specified range.
+	  \sa openmpt::ext::interactive::get_channel_mute_status
+	*/
+	virtual void set_channel_mute_status( std::int32_t channel, bool mute ) = 0;
+
+	//! Get the current mute status for a channel
+	/*!
+	  \param channel The channel whose mute status should be retrieved, in range [0, openmpt::module::get_num_channels()[
+	  \return The current channel mute status. true is muted, false is unmuted.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the channel is outside the specified range.
+	  \sa openmpt::ext::interactive::set_channel_mute_status
+	*/
+	virtual bool get_channel_mute_status( std::int32_t channel ) const = 0;
+	
+	//! Set the current mute status for an instrument
+	/*!
+	  \param instrument The instrument whose mute status should be set, in range [0, openmpt::module::get_num_instruments()[ if openmpt::module::get_num_instruments is not 0, otherwise in [0, openmpt::module::get_num_samples()[
+	  \param mute The new mute status. true is muted, false is unmuted.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the instrument is outside the specified range.
+	  \sa openmpt::ext::interactive::get_instrument_mute_status
+	*/
+	virtual void set_instrument_mute_status( std::int32_t instrument, bool mute ) = 0;
+
+	//! Get the current mute status for an instrument
+	/*!
+	  \param instrument The instrument whose mute status should be retrieved, in range [0, openmpt::module::get_num_instruments()[ if openmpt::module::get_num_instruments is not 0, otherwise in [0, openmpt::module::get_num_samples()[
+	  \return The current instrument mute status. true is muted, false is unmuted.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the instrument is outside the specified range.
+	  \sa openmpt::ext::interactive::set_instrument_mute_status
+	*/
+	virtual bool get_instrument_mute_status( std::int32_t instrument ) const = 0;
+
+	//! Play a note using the specified instrument
+	/*!
+	  \param instrument The instrument that should be played, in range [0, openmpt::module::get_num_instruments()[ if openmpt::module::get_num_instruments is not 0, otherwise in [0, openmpt::module::get_num_samples()[
+	  \param note The note to play, in rage [0, 119]. 60 is the middle C.
+	  \param volume The volume at which the note should be triggered, in range [0.0, 1.0]
+	  \param panning The panning position at which the note should be triggered, in range [-1.0, 1.0], 0.0 is center.
+	  \return The channel on which the note is played. This can pe be passed to openmpt::ext::interactive::stop_note to stop the note.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the instrument or note is outside the specified range.
+	  \sa openmpt::ext::interactive::stop_note
+	*/
+	virtual std::int32_t play_note( std::int32_t instrument, std::int32_t note, double volume, double panning ) = 0;
+
+	//! Stop the note playing on the specified channel
+	/*!
+	  \param channel The channel on which the note should be stopped.
+	  \throws openmpt::exception Throws an exception derived from openmpt::exception if the channel index is invalid.
+	  \sa openmpt::ext::interactive::play_note
+	*/
+	virtual void stop_note( std::int32_t channel ) = 0;
+
+}; // class interactive
+
+
+/* add stuff here */
+
+
+
+#undef LIBOPENMPT_DECLARE_EXT_CXX_INTERFACE
+#undef LIBOPENMPT_EXT_CXX_INTERFACE
+
+} // namespace ext
+
+} // namespace openmpt
+
+/*!
+  @}
+*/
+
+#endif // LIBOPENMPT_EXT_HPP
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_buffer.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_buffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..575049e4d97789549a99be6cb8fe6463845fcb17
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_buffer.h
@@ -0,0 +1,198 @@
+/*
+ * libopenmpt_stream_callbacks_buffer.h
+ * ------------------------------------
+ * Purpose: libopenmpt public c interface
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_STREAM_CALLBACKS_BUFFER_H
+#define LIBOPENMPT_STREAM_CALLBACKS_BUFFER_H
+
+#include "libopenmpt.h"
+
+/* The use of this header requires:
+
+#include <libopenmpt/libopenmpt.h>
+#if defined( LIBOPENMPT_STREAM_CALLBACKS_BUFFER )
+#include <libopenmpt/libopenmpt_stream_callbacks_buffer.h>
+#else
+#error "libopenmpt too old."
+#endif
+
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*! \addtogroup libopenmpt_c
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct openmpt_stream_buffer {
+	const void * file_data; /* or prefix data IFF prefix_size < file_size */
+	int64_t file_size;
+	int64_t file_pos;
+	int64_t prefix_size;
+	int overflow;
+} openmpt_stream_buffer;
+
+static size_t openmpt_stream_buffer_read_func( void * stream, void * dst, size_t bytes ) {
+	openmpt_stream_buffer * s = (openmpt_stream_buffer*)stream;
+	int64_t offset = 0;
+	int64_t begpos = 0;
+	int64_t endpos = 0;
+	size_t valid_bytes = 0;
+	if ( !s ) {
+		return 0;
+	}
+	offset = bytes;
+	begpos = s->file_pos;
+	endpos = s->file_pos;
+	valid_bytes = 0;
+	endpos = (uint64_t)endpos + (uint64_t)offset;
+	if ( ( offset > 0 ) && !( (uint64_t)endpos > (uint64_t)begpos ) ) {
+		/* integer wrapped */
+		return 0;
+	}
+	if ( bytes == 0 ) {
+		return 0;
+	}
+	if ( begpos >= s->file_size ) {
+		return 0;
+	}
+	if ( endpos > s->file_size ) {
+		/* clip to eof */
+		bytes = bytes - (size_t)( endpos - s->file_size );
+		endpos = endpos - ( endpos - s->file_size );
+	}
+	memset( dst, 0, bytes );
+	if ( begpos >= s->prefix_size ) {
+		s->overflow = 1;
+		valid_bytes = 0;
+	} else if ( endpos > s->prefix_size ) {
+		s->overflow = 1;
+		valid_bytes = bytes - (size_t)( endpos - s->prefix_size );
+	} else {
+		valid_bytes = bytes;
+	}
+	memcpy( dst, (const char*)s->file_data + s->file_pos, valid_bytes );
+	s->file_pos = s->file_pos + bytes;
+	return bytes;
+}
+
+static int openmpt_stream_buffer_seek_func( void * stream, int64_t offset, int whence ) {
+	openmpt_stream_buffer * s = (openmpt_stream_buffer*)stream;
+	int result = -1;
+	if ( !s ) {
+		return -1;
+	}
+	switch ( whence ) {
+		case OPENMPT_STREAM_SEEK_SET:
+			if ( offset < 0 ) {
+				return -1;
+			}
+			if ( offset > s->file_size ) {
+				return -1;
+			}
+			s->file_pos = offset;
+			result = 0;
+			break;
+		case OPENMPT_STREAM_SEEK_CUR:
+			do {
+				int64_t oldpos = s->file_pos;
+				int64_t pos = s->file_pos;
+				pos = (uint64_t)pos + (uint64_t)offset;
+				if ( ( offset > 0 ) && !( (uint64_t)pos > (uint64_t)oldpos ) ) {
+					/* integer wrapped */
+					return -1;
+				}
+				if ( ( offset < 0 ) && !( (uint64_t)pos < (uint64_t)oldpos ) ) {
+					/* integer wrapped */
+					return -1;
+				}
+				s->file_pos = pos;
+			} while(0);
+			result = 0;
+			break;
+		case OPENMPT_STREAM_SEEK_END:
+			if ( offset > 0 ) {
+				return -1;
+			}
+			do {
+				int64_t oldpos = s->file_pos;
+				int64_t pos = s->file_pos;
+				pos = s->file_size;
+				pos = (uint64_t)pos + (uint64_t)offset;
+				if ( ( offset < 0 ) && !( (uint64_t)pos < (uint64_t)oldpos ) ) {
+					/* integer wrapped */
+					return -1;
+				}
+				s->file_pos = pos;
+			} while(0);
+			result = 0;
+			break;
+	}
+	return result;
+}
+
+static int64_t openmpt_stream_buffer_tell_func( void * stream ) {
+	openmpt_stream_buffer * s = (openmpt_stream_buffer*)stream;
+	if ( !s ) {
+		return -1;
+	}
+	return s->file_pos;
+}
+
+static void openmpt_stream_buffer_init( openmpt_stream_buffer * buffer, const void * file_data, int64_t file_size ) {
+	memset( buffer, 0, sizeof( openmpt_stream_buffer ) );
+	buffer->file_data = file_data;
+	buffer->file_size = file_size;
+	buffer->file_pos = 0;
+	buffer->prefix_size = file_size;
+	buffer->overflow = 0;
+}
+
+#define openmpt_stream_buffer_init_prefix_only( buffer_, prefix_data_, prefix_size_, file_size_ ) do { \
+	openmpt_stream_buffer_init( (buffer_), (prefix_data_), (file_size_) ); \
+	(buffer_)->prefix_size = (prefix_size_); \
+} while(0)
+
+#define openmpt_stream_buffer_overflowed( buffer_ ) ( (buffer_)->overflow )
+
+/*! \brief Provide openmpt_stream_callbacks for in-memoy buffers
+ *
+ * Fills openmpt_stream_callbacks suitable for passing an in-memory buffer as a stream parameter to functions doing file input/output.
+ *
+ * \remarks The stream argument must be passed as `(void*)(openmpt_stream_buffer*)stream_buffer`.
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \sa openmpt_could_open_probability2
+ * \sa openmpt_probe_file_header_from_stream
+ * \sa openmpt_module_create2
+ */
+static openmpt_stream_callbacks openmpt_stream_get_buffer_callbacks(void) {
+	openmpt_stream_callbacks retval;
+	memset( &retval, 0, sizeof( openmpt_stream_callbacks ) );
+	retval.read = openmpt_stream_buffer_read_func;
+	retval.seek = openmpt_stream_buffer_seek_func;
+	retval.tell = openmpt_stream_buffer_tell_func;
+	return retval;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * @}
+ */
+
+#endif /* LIBOPENMPT_STREAM_CALLBACKS_BUFFER_H */
+
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_fd.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_fd.h
new file mode 100644
index 0000000000000000000000000000000000000000..46b39f12981ba3bb73b3b644a8d7e69f67bbc000
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_fd.h
@@ -0,0 +1,101 @@
+/*
+ * libopenmpt_stream_callbacks_fd.h
+ * --------------------------------
+ * Purpose: libopenmpt public c interface
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_STREAM_CALLBACKS_FD_H
+#define LIBOPENMPT_STREAM_CALLBACKS_FD_H
+
+#include "libopenmpt.h"
+
+#ifdef _MSC_VER
+#include <io.h>
+#endif
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+/*! \addtogroup libopenmpt_c
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This stuff has to be in a header file because of possibly different MSVC CRTs which cause problems for fd crossing CRT boundaries. */
+
+static size_t openmpt_stream_fd_read_func( void * stream, void * dst, size_t bytes ) {
+	int fd = 0;
+	#if defined(_MSC_VER)
+		size_t retval = 0;
+		int to_read = 0;
+		int ret_read = 0;
+	#else
+		ssize_t retval = 0;
+	#endif
+	fd = (int)(uintptr_t)stream;
+	if ( fd < 0 ) {
+		return 0;
+	}
+	#if defined(_MSC_VER)
+		retval = 0;
+		while ( bytes > 0 ) {
+			to_read = 0;
+			if ( bytes < (size_t)INT_MAX ) {
+				to_read = (int)bytes;
+			} else {
+				to_read = INT_MAX;
+			}
+			ret_read = _read( fd, dst, to_read );
+			if ( ret_read <= 0 ) {
+				return retval;
+			}
+			bytes -= ret_read;
+			retval += ret_read;
+		}
+	#else
+		retval = read( fd, dst, bytes );
+	#endif
+	if ( retval <= 0 ) {
+		return 0;
+	}
+	return retval;
+}
+
+/*! \brief Provide openmpt_stream_callbacks for standard POSIX file descriptors
+ *
+ * Fills openmpt_stream_callbacks suitable for passing a POSIX filer descriptor as a stream parameter to functions doing file input/output.
+ *
+ * \remarks The stream argument must be passed as `(void*)(uintptr_t)(int)fd`.
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \sa openmpt_could_open_probability2
+ * \sa openmpt_probe_file_header_from_stream
+ * \sa openmpt_module_create2
+ */
+static openmpt_stream_callbacks openmpt_stream_get_fd_callbacks(void) {
+	openmpt_stream_callbacks retval;
+	memset( &retval, 0, sizeof( openmpt_stream_callbacks ) );
+	retval.read = openmpt_stream_fd_read_func;
+	return retval;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * @}
+ */
+
+#endif /* LIBOPENMPT_STREAM_CALLBACKS_FD_H */
+
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_file.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_file.h
new file mode 100644
index 0000000000000000000000000000000000000000..643049153e75fb233104c2cb5ad6eb0b9005dfe7
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_stream_callbacks_file.h
@@ -0,0 +1,132 @@
+/*
+ * libopenmpt_stream_callbacks_file.h
+ * ----------------------------------
+ * Purpose: libopenmpt public c interface
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_STREAM_CALLBACKS_FILE_H
+#define LIBOPENMPT_STREAM_CALLBACKS_FILE_H
+
+#include "libopenmpt.h"
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _MSC_VER
+#include <wchar.h> /* off_t */
+#endif
+
+/*! \addtogroup libopenmpt_c
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This stuff has to be in a header file because of possibly different MSVC CRTs which cause problems for FILE * crossing CRT boundaries. */
+
+static size_t openmpt_stream_file_read_func( void * stream, void * dst, size_t bytes ) {
+	FILE * f = 0;
+	size_t retval = 0;
+	f = (FILE*)stream;
+	if ( !f ) {
+		return 0;
+	}
+	retval = fread( dst, 1, bytes, f );
+	if ( retval <= 0 ) {
+		return 0;
+	}
+	return retval;
+}
+
+static int openmpt_stream_file_seek_func( void * stream, int64_t offset, int whence ) {
+	FILE * f = 0;
+	int fwhence = 0;
+	f = (FILE*)stream;
+	if ( !f ) {
+		return -1;
+	}
+	switch ( whence ) {
+#if defined(SEEK_SET)
+		case OPENMPT_STREAM_SEEK_SET:
+			fwhence = SEEK_SET;
+			break;
+#endif
+#if defined(SEEK_CUR)
+		case OPENMPT_STREAM_SEEK_CUR:
+			fwhence = SEEK_CUR;
+			break;
+#endif
+#if defined(SEEK_END)
+		case OPENMPT_STREAM_SEEK_END:
+			fwhence = SEEK_END;
+			break;
+#endif
+		default:
+			return -1;
+			break;
+	}
+	#if defined(_MSC_VER)
+		return _fseeki64( f, offset, fwhence ) ? -1 : 0;
+	#elif defined(_POSIX_SOURCE) && (_POSIX_SOURCE == 1) 
+		return fseeko( f, offset, fwhence ) ? -1 : 0;
+	#else
+		return fseek( f, offset, fwhence ) ? -1 : 0;
+	#endif
+}
+
+static int64_t openmpt_stream_file_tell_func( void * stream ) {
+	FILE * f = 0;
+	int64_t retval = 0;
+	f = (FILE*)stream;
+	if ( !f ) {
+		return -1;
+	}
+	#if defined(_MSC_VER)
+		retval = _ftelli64( f );
+	#elif defined(_POSIX_SOURCE) && (_POSIX_SOURCE == 1) 
+		retval = ftello( f );
+	#else
+		retval = ftell( f );
+	#endif
+	if ( retval < 0 ) {
+		return -1;
+	}
+	return retval;
+}
+
+/*! \brief Provide openmpt_stream_callbacks for standard C FILE objects
+ *
+ * Fills openmpt_stream_callbacks suitable for passing a standard C FILE object as a stream parameter to functions doing file input/output.
+ *
+ * \remarks The stream argument must be passed as `(void*)(FILE*)file`.
+ * \sa \ref libopenmpt_c_fileio
+ * \sa openmpt_stream_callbacks
+ * \sa openmpt_could_open_probability2
+ * \sa openmpt_probe_file_header_from_stream
+ * \sa openmpt_module_create2
+ */
+static openmpt_stream_callbacks openmpt_stream_get_file_callbacks(void) {
+	openmpt_stream_callbacks retval;
+	memset( &retval, 0, sizeof( openmpt_stream_callbacks ) );
+	retval.read = openmpt_stream_file_read_func;
+	retval.seek = openmpt_stream_file_seek_func;
+	retval.tell = openmpt_stream_file_tell_func;
+	return retval;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * @}
+ */
+
+#endif /* LIBOPENMPT_STREAM_CALLBACKS_FILE_H */
+
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h
new file mode 100644
index 0000000000000000000000000000000000000000..5ca7f21d0ff4a776d8011fc72b67d140aa237b9d
--- /dev/null
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h
@@ -0,0 +1,75 @@
+/*
+ * libopenmpt_version.h
+ * --------------------
+ * Purpose: libopenmpt public interface version
+ * Notes  : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+#ifndef LIBOPENMPT_VERSION_H
+#define LIBOPENMPT_VERSION_H
+
+/*! \addtogroup libopenmpt
+  @{
+*/
+
+/*! \brief libopenmpt major version number */
+#define OPENMPT_API_VERSION_MAJOR 0
+/*! \brief libopenmpt minor version number */
+#define OPENMPT_API_VERSION_MINOR 4
+/*! \brief libopenmpt patch version number */
+#define OPENMPT_API_VERSION_PATCH 4
+/*! \brief libopenmpt pre-release tag */
+#define OPENMPT_API_VERSION_PREREL ""
+/*! \brief libopenmpt pre-release flag */
+#define OPENMPT_API_VERSION_IS_PREREL 0
+
+/*! \brief libopenmpt version number as a single integer value
+ *  \since 0.3
+ *  \remarks Use the following shim if you need to support earlier libopenmpt versions:
+ *           \code
+ *           #include <libopenmpt/libopenmpt_version.h>
+ *           #if !defined(OPENMPT_API_VERSION_MAKE)
+ *           #define OPENMPT_API_VERSION_MAKE(major, minor, patch) (((major)<<24)|((minor)<<16)|((patch)<<0))
+ *           #endif
+ *           \endcode
+ */
+#define OPENMPT_API_VERSION_MAKE(major, minor, patch) (((major)<<24)|((minor)<<16)|((patch)<<0))
+
+/*! \brief libopenmpt API version number */
+#define OPENMPT_API_VERSION OPENMPT_API_VERSION_MAKE(OPENMPT_API_VERSION_MAJOR, OPENMPT_API_VERSION_MINOR, OPENMPT_API_VERSION_PATCH)
+
+/*! \brief Check whether the libopenmpt API is at least the provided version
+ *  \since 0.3
+ *  \remarks Use the following shim if you need to support earlier libopenmpt versions:
+ *           \code
+ *           #include <libopenmpt/libopenmpt_version.h>
+ *           #if !defined(OPENMPT_API_VERSION_AT_LEAST)
+ *           #define OPENMPT_API_VERSION_AT_LEAST(major, minor, patch) (OPENMPT_API_VERSION >= OPENMPT_API_VERSION_MAKE((major), (minor), (patch)))
+ *           #endif
+ *           \endcode
+ */
+#define OPENMPT_API_VERSION_AT_LEAST(major, minor, patch) (OPENMPT_API_VERSION >= OPENMPT_API_VERSION_MAKE((major), (minor), (patch)))
+
+/*! \brief Check whether the libopenmpt API is before the provided version
+ *  \since 0.3
+ *  \remarks Use the following shim if you need to support earlier libopenmpt versions:
+ *           \code
+ *           #include <libopenmpt/libopenmpt_version.h>
+ *           #if !defined(OPENMPT_API_VERSION_BEFORE)
+ *           #define OPENMPT_API_VERSION_BEFORE(major, minor, patch) (OPENMPT_API_VERSION < OPENMPT_API_VERSION_MAKE((major), (minor), (patch)))
+ *           #endif
+ *           \endcode
+ */
+#define OPENMPT_API_VERSION_BEFORE(major, minor, patch) (OPENMPT_API_VERSION < OPENMPT_API_VERSION_MAKE((major), (minor), (patch)))
+
+#define OPENMPT_API_VERSION_HELPER_STRINGIZE(x) #x
+#define OPENMPT_API_VERSION_STRINGIZE(x) OPENMPT_API_VERSION_HELPER_STRINGIZE(x)
+#define OPENMPT_API_VERSION_STRING OPENMPT_API_VERSION_STRINGIZE(OPENMPT_API_VERSION_MAJOR) "." OPENMPT_API_VERSION_STRINGIZE(OPENMPT_API_VERSION_MINOR) "." OPENMPT_API_VERSION_STRINGIZE(OPENMPT_API_VERSION_PATCH) OPENMPT_API_VERSION_PREREL
+
+/*!
+  @}
+*/
+
+#endif /* LIBOPENMPT_VERSION_H */
diff --git a/libs/libopenmpt/lib/x86/libopenmpt.lib b/libs/libopenmpt/lib/x86/libopenmpt.lib
new file mode 100644
index 0000000000000000000000000000000000000000..3f814528acc851cec1e764547570756c24d65225
Binary files /dev/null and b/libs/libopenmpt/lib/x86/libopenmpt.lib differ
diff --git a/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a b/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..e3fa0c58fcd082319ea3b6365cb566dd7a3efa24
Binary files /dev/null and b/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a differ
diff --git a/libs/libopenmpt/lib/x86_64/libopenmpt.lib b/libs/libopenmpt/lib/x86_64/libopenmpt.lib
new file mode 100644
index 0000000000000000000000000000000000000000..75e3849fac966542b457684ca6f55a41237211d3
Binary files /dev/null and b/libs/libopenmpt/lib/x86_64/libopenmpt.lib differ
diff --git a/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a b/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..72940372211829f26b413075496f7e201d5fb357
Binary files /dev/null and b/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a differ
diff --git a/libs/libpng-src/projects/visualc10/.gitignore b/libs/libpng-src/projects/visualc10/.gitignore
index 118a15cbb370c7f3c92eed49790cb3f9d641b0b0..e1bec81fcb4d7d25a7b176b6585f24a752bd0e56 100644
--- a/libs/libpng-src/projects/visualc10/.gitignore
+++ b/libs/libpng-src/projects/visualc10/.gitignore
@@ -1,3 +1,5 @@
 /Win32
 /x64
 /libpng.vcproj.*.*.user
+/ARM
+/ARM64
diff --git a/libs/libpng-src/projects/visualc10/libpng.vcxproj b/libs/libpng-src/projects/visualc10/libpng.vcxproj
index fb53826ecfe631e919e44c4cdd627ce7e89a6d86..eaa3d4ffb9d169a4127d882bc69e053e9fd81349 100644
--- a/libs/libpng-src/projects/visualc10/libpng.vcxproj
+++ b/libs/libpng-src/projects/visualc10/libpng.vcxproj
@@ -1,6 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
@@ -9,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -21,7 +37,7 @@
   <PropertyGroup Label="Globals">
     <ProjectGuid>{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}</ProjectGuid>
     <RootNamespace>libpng</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@@ -29,21 +45,45 @@
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -51,29 +91,53 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <ClCompile>
@@ -107,6 +171,38 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <AdditionalIncludeDirectories>..\..;..\..\..\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;NDEBUG;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+      <AdditionalIncludeDirectories>..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\libpng.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <Midl>
       <TargetEnvironment>X64</TargetEnvironment>
@@ -143,6 +239,40 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <Midl />
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <AdditionalIncludeDirectories>..\..;..\..\..\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;NDEBUG;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+      <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+      <AdditionalIncludeDirectories>..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\libpng.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM64</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <Optimization>Disabled</Optimization>
@@ -174,6 +304,36 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>..\..;..\..\..\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>false</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\libpng.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Midl>
       <TargetEnvironment>X64</TargetEnvironment>
@@ -209,164 +369,320 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <Midl />
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>..\..;..\..\..\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+      <DisableSpecificWarnings>4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <MultiProcessorCompilation>false</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\libpng.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM64</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\libpng.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\png.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngerror.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngget.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngmem.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngpread.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngread.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngrio.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngrtran.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngrutil.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngset.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngtrans.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngwio.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngwrite.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngwtran.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\pngwutil.c">
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <CustomBuildStep Include="..\..\scripts\pngw32.def">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
     </CustomBuildStep>
     <None Include="README.txt" />
   </ItemGroup>
@@ -377,17 +693,29 @@
   <ItemGroup>
     <ResourceCompile Include="..\..\scripts\pngw32.rc">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\Oogaland\Projects\orospakr.ca\srb2\tools\libpng-src\scripts;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ResourceCompile>
   </ItemGroup>
   <ItemGroup>
diff --git a/libs/zlib/projects/visualc10/.gitignore b/libs/zlib/projects/visualc10/.gitignore
index 488a5428bcc97e1516bc2d4053a1f49b1a45e695..1c5340cd4b75b86cbb14171c8cdceea824cf07fb 100644
--- a/libs/zlib/projects/visualc10/.gitignore
+++ b/libs/zlib/projects/visualc10/.gitignore
@@ -1,3 +1,5 @@
 /Win32
 /x64
 /zlib.vcproj.*.*.user
+/ARM
+/ARM64
diff --git a/libs/zlib/projects/visualc10/zlib.vcxproj b/libs/zlib/projects/visualc10/zlib.vcxproj
index 814641d344b35f22ffd4608ad30f342318e1ff83..a7055ddb8156e3bf2af4c67e9ea069b3135e65a4 100644
--- a/libs/zlib/projects/visualc10/zlib.vcxproj
+++ b/libs/zlib/projects/visualc10/zlib.vcxproj
@@ -1,6 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
@@ -9,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -21,7 +37,7 @@
   <PropertyGroup Label="Globals">
     <ProjectGuid>{73A5729C-7323-41D4-AB48-8A03C9F81603}</ProjectGuid>
     <RootNamespace>zlib</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
@@ -29,21 +45,45 @@
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>StaticLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+    <ConfigurationType>StaticLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
     <Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
@@ -52,29 +92,53 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir)$(Platform)\$(Configuration)\</IntDir>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
@@ -106,6 +170,36 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>Default</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>false</MultiProcessorCompilation>
+      <UndefinePreprocessorDefinitions>ASMV;ASMINF</UndefinePreprocessorDefinitions>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\zlib.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Midl>
       <TargetEnvironment>X64</TargetEnvironment>
@@ -139,6 +233,36 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <Midl />
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>false</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\zlib.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM64</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <ClCompile>
       <Optimization>MaxSpeed</Optimization>
@@ -169,6 +293,37 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>WIN32;NDEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <UndefinePreprocessorDefinitions>ASMV;ASMINF</UndefinePreprocessorDefinitions>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\zlib.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <Midl>
       <TargetEnvironment>X64</TargetEnvironment>
@@ -202,30 +357,77 @@
       <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <Midl />
+    <ClCompile>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(ProjectDir)$(Platform)\$(Configuration)\</AssemblerListingLocation>
+      <ObjectFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ObjectFileName>
+      <ProgramDataBaseFileName>$(ProjectDir)$(Platform)\$(Configuration)\</ProgramDataBaseFileName>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Lib>
+      <OutputFile>$(ProjectDir)$(Platform)\$(Configuration)\zlib.lib</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TargetMachine>MachineARM64</TargetMachine>
+    </Lib>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(ProjectDir)$(PlatformName)\$(ConfigurationName)\zlib.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\..\adler32.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\compress.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\crc32.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\deflate.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\gzclose.c" />
     <ClCompile Include="..\..\gzlib.c" />
@@ -233,60 +435,97 @@
     <ClCompile Include="..\..\gzwrite.c" />
     <ClCompile Include="..\..\infback.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\inffast.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\inflate.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\inftrees.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\trees.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\uncompr.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\zutil.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="..\..\contrib\masmx64\inffas8664.c">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <CustomBuildStep Include="..\..\win32\zlib.def">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
     </CustomBuildStep>
     <None Include="README.txt" />
   </ItemGroup>
@@ -306,39 +545,67 @@
   <ItemGroup>
     <ResourceCompile Include="..\..\win32\zlib1.rc">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\Oogaland\Projects\orospakr.ca\srb2\tools\zlib\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ResourceCompile>
   </ItemGroup>
   <ItemGroup>
     <MASM Include="..\..\contrib\masmx86\inffas32.asm">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
     </MASM>
     <MASM Include="..\..\contrib\masmx86\match686.asm">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
     </MASM>
     <MASM Include="..\..\contrib\masmx64\gvmat64.asm">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</EnableMASM51Compatibility>
+      <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</EnableMASM51Compatibility>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
       <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</EnableMASM51Compatibility>
+      <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</EnableMASM51Compatibility>
     </MASM>
     <MASM Include="..\..\contrib\masmx64\inffasx64.asm">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
       <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</EnableMASM51Compatibility>
+      <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</EnableMASM51Compatibility>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
       <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</EnableMASM51Compatibility>
+      <EnableMASM51Compatibility Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</EnableMASM51Compatibility>
     </MASM>
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
diff --git a/srb2-vc10.sln b/srb2-vc10.sln
index ecceafd56b6d9d722ddd48d8561f1f8c714f73d3..b4415bfc056e94fd36f79cee403d7b168f6131d8 100644
--- a/srb2-vc10.sln
+++ b/srb2-vc10.sln
@@ -1,9 +1,9 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25123.0
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.136
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Srb2win", "src\win32\Srb2win-vc10.vcxproj", "{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Srb2DD", "src\win32\Srb2win-vc10.vcxproj", "{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "libs\libpng-src\projects\visualc10\libpng.vcxproj", "{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}"
 EndProject
@@ -13,56 +13,104 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "libs\zlib\projects\
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "s_openal", "src\hardware\s_openal\s_openal-vc10.vcxproj", "{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}"
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Srb2SDL", "src\sdl\Srb2SDL-vc10.vcxproj", "{61BA7D3C-F77D-4D31-B718-1177FE482CF2}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Srb2Win", "src\sdl\Srb2SDL-vc10.vcxproj", "{61BA7D3C-F77D-4D31-B718-1177FE482CF2}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|ARM = Debug|ARM
+		Debug|ARM64 = Debug|ARM64
 		Debug|Win32 = Debug|Win32
 		Debug|x64 = Debug|x64
+		Release|ARM = Release|ARM
+		Release|ARM64 = Release|ARM64
 		Release|Win32 = Release|Win32
 		Release|x64 = Release|x64
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|ARM.ActiveCfg = Debug|ARM
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|ARM.Build.0 = Debug|ARM
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|ARM64.Build.0 = Debug|ARM64
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|Win32.ActiveCfg = Debug|Win32
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|Win32.Build.0 = Debug|Win32
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|x64.ActiveCfg = Debug|x64
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Debug|x64.Build.0 = Debug|x64
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|ARM.ActiveCfg = Release|ARM
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|ARM.Build.0 = Release|ARM
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|ARM64.ActiveCfg = Release|ARM64
+		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|ARM64.Build.0 = Release|ARM64
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|Win32.ActiveCfg = Release|Win32
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|Win32.Build.0 = Release|Win32
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|x64.ActiveCfg = Release|x64
 		{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}.Release|x64.Build.0 = Release|x64
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|ARM.ActiveCfg = Debug|ARM
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|ARM.Build.0 = Debug|ARM
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|ARM64.Build.0 = Debug|ARM64
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|Win32.ActiveCfg = Debug|Win32
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|Win32.Build.0 = Debug|Win32
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|x64.ActiveCfg = Debug|x64
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Debug|x64.Build.0 = Debug|x64
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|ARM.ActiveCfg = Release|ARM
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|ARM.Build.0 = Release|ARM
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|ARM64.ActiveCfg = Release|ARM64
+		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|ARM64.Build.0 = Release|ARM64
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|Win32.ActiveCfg = Release|Win32
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|Win32.Build.0 = Release|Win32
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|x64.ActiveCfg = Release|x64
 		{72B01ACA-7A1A-4F7B-ACEF-2607299CF052}.Release|x64.Build.0 = Release|x64
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|ARM.ActiveCfg = Debug|ARM
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|ARM.Build.0 = Debug|ARM
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|ARM64.Build.0 = Debug|ARM64
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|Win32.ActiveCfg = Debug|Win32
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|Win32.Build.0 = Debug|Win32
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|x64.ActiveCfg = Debug|x64
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Debug|x64.Build.0 = Debug|x64
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|ARM.ActiveCfg = Release|ARM
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|ARM.Build.0 = Release|ARM
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|ARM64.ActiveCfg = Release|ARM64
+		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|ARM64.Build.0 = Release|ARM64
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|Win32.ActiveCfg = Release|Win32
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|Win32.Build.0 = Release|Win32
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|x64.ActiveCfg = Release|x64
 		{51137D5C-4E81-4955-AACF-EA3092006051}.Release|x64.Build.0 = Release|x64
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|ARM.ActiveCfg = Debug|ARM
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|ARM.Build.0 = Debug|ARM
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|ARM64.Build.0 = Debug|ARM64
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|Win32.ActiveCfg = Debug|Win32
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|Win32.Build.0 = Debug|Win32
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|x64.ActiveCfg = Debug|x64
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Debug|x64.Build.0 = Debug|x64
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|ARM.ActiveCfg = Release|ARM
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|ARM.Build.0 = Release|ARM
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|ARM64.ActiveCfg = Release|ARM64
+		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|ARM64.Build.0 = Release|ARM64
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|Win32.ActiveCfg = Release|Win32
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|Win32.Build.0 = Release|Win32
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|x64.ActiveCfg = Release|x64
 		{73A5729C-7323-41D4-AB48-8A03C9F81603}.Release|x64.Build.0 = Release|x64
+		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Debug|ARM.ActiveCfg = Debug|ARM
+		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Debug|ARM64.ActiveCfg = Debug|ARM64
 		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Debug|Win32.ActiveCfg = Debug|Win32
 		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Debug|x64.ActiveCfg = Debug|x64
+		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Release|ARM.ActiveCfg = Release|ARM
+		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Release|ARM64.ActiveCfg = Release|ARM64
 		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Release|Win32.ActiveCfg = Release|Win32
 		{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}.Release|x64.ActiveCfg = Release|x64
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|ARM.ActiveCfg = Debug|ARM
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|ARM.Build.0 = Debug|ARM
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|ARM64.Build.0 = Debug|ARM64
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|Win32.ActiveCfg = Debug|Win32
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|Win32.Build.0 = Debug|Win32
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|x64.ActiveCfg = Debug|x64
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Debug|x64.Build.0 = Debug|x64
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|ARM.ActiveCfg = Release|ARM
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|ARM.Build.0 = Release|ARM
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|ARM64.ActiveCfg = Release|ARM64
+		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|ARM64.Build.0 = Release|ARM64
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|Win32.ActiveCfg = Release|Win32
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|Win32.Build.0 = Release|Win32
 		{61BA7D3C-F77D-4D31-B718-1177FE482CF2}.Release|x64.ActiveCfg = Release|x64
@@ -71,4 +119,7 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {8C0B5F99-D9B8-4CB2-BA67-5D350E71C6FC}
+	EndGlobalSection
 EndGlobal
diff --git a/srb2.png b/srb2.png
index 9c13eae9a5d1ca26167abfe56486e2e7a642cd6c..72a08f6648b8c8849d5804889977a412b35e6deb 100644
Binary files a/srb2.png and b/srb2.png differ
diff --git a/srb2banner.png b/srb2banner.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c13eae9a5d1ca26167abfe56486e2e7a642cd6c
Binary files /dev/null and b/srb2banner.png differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6a8b7e3f1228f2e7028a9f9cd324768c85182025..723407d86fa17529d138634c3e6b48f2338f2557 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -123,6 +123,7 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_sky.c
 	r_splats.c
 	r_things.c
+	r_portal.c
 
 	r_bsp.h
 	r_data.h
@@ -136,6 +137,7 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_splats.h
 	r_state.h
 	r_things.h
+	r_portal.h
 )
 
 set(SRB2_CORE_GAME_SOURCES
@@ -218,6 +220,8 @@ set(SRB2_CONFIG_HAVE_ZLIB ON CACHE BOOL
 	"Enable zlib support.")
 set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL
 	"Enable GME support.")
+set(SRB2_CONFIG_HAVE_OPENMPT ON CACHE BOOL
+	"Enable OpenMPT support.")
 set(SRB2_CONFIG_HWRENDER ON CACHE BOOL
 	"Enable hardware rendering through OpenGL.")
 set(SRB2_CONFIG_USEASM OFF CACHE BOOL
@@ -227,6 +231,12 @@ set(SRB2_CONFIG_YASM OFF CACHE BOOL
 set(SRB2_CONFIG_STATIC_OPENGL OFF CACHE BOOL
 	"Use statically linked OpenGL. NOT RECOMMENDED.")
 
+### use internal libraries?
+if(${CMAKE_SYSTEM} MATCHES "Windows") ###set on Windows only
+	set(SRB2_CONFIG_USE_INTERNAL_LIBRARIES OFF CACHE BOOL
+	"Use SRB2's internal copies of required dependencies (SDL2, PNG, zlib, GME, OpenMPT).")
+endif()
+
 if(${SRB2_CONFIG_HAVE_BLUA})
 	add_definitions(-DHAVE_BLUA)
 	set(SRB2_LUA_SOURCES
@@ -315,7 +325,17 @@ if(${SRB2_CONFIG_HAVE_BLUA})
 endif()
 
 if(${SRB2_CONFIG_HAVE_GME})
-	find_package(GME)
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(GME_FOUND ON)
+		set(GME_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/gme/include)
+        if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win64 -lgme")
+		else() # 32-bit
+			set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win32 -lgme")
+		endif()
+	else()
+		find_package(GME)
+	endif()
 	if(${GME_FOUND})
 		set(SRB2_HAVE_GME ON)
 		add_definitions(-DHAVE_LIBGME)
@@ -324,10 +344,41 @@ if(${SRB2_CONFIG_HAVE_GME})
 	endif()
 endif()
 
+if(${SRB2_CONFIG_HAVE_OPENMPT})
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(OPENMPT_FOUND ON)
+		set(OPENMPT_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/inc)
+        if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(OPENMPT_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/libopenmpt/lib/x86_64/mingw -lopenmpt")
+		else() # 32-bit
+			set(OPENMPT_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/libopenmpt/lib/x86/mingw -lopenmpt")
+		endif()
+	else()
+		find_package(OPENMPT)
+	endif()
+	if(${OPENMPT_FOUND})
+		set(SRB2_HAVE_OPENMPT ON)
+		add_definitions(-DHAVE_OPENMPT)
+	else()
+		message(WARNING "You have specified that OpenMPT is available but it was not found.")
+	endif()
+endif()
+
 if(${SRB2_CONFIG_HAVE_ZLIB})
-	find_package(ZLIB)
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(ZLIB_FOUND ON)
+		set(ZLIB_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/zlib)
+		if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(ZLIB_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/zlib/win32 -lz64")
+		else() # 32-bit
+			set(ZLIB_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/zlib/win32 -lz32")
+		endif()
+	else()
+		find_package(ZLIB)
+	endif()
 	if(${ZLIB_FOUND})
 		set(SRB2_HAVE_ZLIB ON)
+		add_definitions(-DHAVE_ZLIB)
 	else()
 		message(WARNING "You have specified that ZLIB is available but it was not found. SRB2 may not compile correctly.")
 	endif()
@@ -335,11 +386,27 @@ endif()
 
 if(${SRB2_CONFIG_HAVE_PNG} AND ${SRB2_CONFIG_HAVE_ZLIB})
 	if (${ZLIB_FOUND})
-		find_package(PNG)
+		if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+			set(PNG_FOUND ON)
+			set(PNG_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/libpng-src)
+			if(${SRB2_SYSTEM_BITS} EQUAL 64)
+				set(PNG_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/libpng-src/projects -lpng64")
+			else() # 32-bit
+				set(PNG_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/libpng-src/projects -lpng32")
+			endif()
+		else()
+			find_package(PNG)
+		endif()
 		if(${PNG_FOUND})
 			set(SRB2_HAVE_PNG ON)
 			add_definitions(-DHAVE_PNG)
 			add_definitions(-D_LARGEFILE64_SOURCE)
+			set(SRB2_PNG_SOURCES apng.c)
+			set(SRB2_PNG_HEADERS apng.h)
+			prepend_sources(SRB2_PNG_SOURCES)
+			prepend_sources(SRB2_PNG_HEADERS)
+			source_group("Main" FILES ${SRB2_CORE_SOURCES} ${SRB2_CORE_HEADERS}
+				${SRB2_PNG_SOURCES} ${SRB2_PNG_HEADERS})
 		else()
 			message(WARNING "You have specified that PNG is available but it was not found. SRB2 may not compile correctly.")
 		endif()
diff --git a/src/Makefile b/src/Makefile
index 017bd2442b6563b64210702087418fb16f013938..3015aa5d6b40bfa231e5faa08d7d997cb4986ecc 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,7 +1,8 @@
+
 #     GNU Make makefile for SRB2
 #############################################################################
 # Copyright (C) 1998-2000 by DooM Legacy Team.
-# Copyright (C) 2003-2014 by Sonic Team Junior.
+# Copyright (C) 2003-2018 by Sonic Team Junior.
 #
 # This program is free software distributed under the
 # terms of the GNU General Public License, version 2.
@@ -63,13 +64,17 @@
 #     Compile without 3D sound support, add 'NOHS=1'
 #     Compile with GDBstubs, add 'RDB=1'
 #     Compile without PNG, add 'NOPNG=1'
+#     Compile without zlib, add 'NOZLIB=1'
 #
 # Addon for SDL:
 #     To Cross-Compile, add 'SDL_CONFIG=/usr/*/bin/sdl-config'
 #     Compile without SDL_Mixer, add 'NOMIXER=1'
+#     Compile without SDL_Mixer_X, add 'NOMIXERX=1' (Win32 only)
+#     Compile without GME, add 'NOGME=1'
 #     Compile without BSD API, add 'NONET=1'
 #     Compile without IPX/SPX, add 'NOIPX=1'
 #     Compile Mingw/SDL with S_DS3S, add 'DS3D=1'
+#     Compile without libopenmpt, add 'NOOPENMPT=1'
 #     Compile with S_FMOD3D, add 'FMOD=1' (WIP)
 #     Compile with S_OPENAL, add 'OPENAL=1' (WIP)
 #     To link with the whole SDL_Image lib to load Icons, add 'SDL_IMAGE=1' but it isn't not realy needed
@@ -79,7 +84,7 @@
 
 # SRB2 data files
 D_DIR?=../bin/Resources
-D_FILES=$(D_DIR)/srb2.srb \
+D_FILES=$(D_DIR)/srb2.pk3 \
 	$(D_DIR)/player.dta \
 	$(D_DIR)/rings.wpn \
 	$(D_DIR)/drill.dta \
@@ -96,8 +101,18 @@ endif
 ifdef LINUX64
 LINUX=1
 NONX86=1
+# LINUX64 does not imply X86_64=1; could mean ARM64 or Itanium
 endif
 
+ifdef MINGW64
+MINGW=1
+NONX86=1
+NOASM=1
+# MINGW64 should not necessarily imply X86_64=1, but we make that assumption elsewhere
+# Once that changes, remove this
+X86_64=1
+endif #ifdef MINGW64
+
 ifdef HAIKU
 SDL=1
 endif
@@ -106,6 +121,7 @@ include Makefile.cfg
 
 ifdef DUMMY
 NOPNG=1
+NOZLIB=1
 NONET=1
 NOHW=1
 NOHS=1
@@ -137,6 +153,10 @@ ifdef DJGPPDOS
 include djgppdos/Makefile.cfg
 endif
 
+ifndef NOOPENMPT
+HAVE_OPENMPT=1
+endif
+
 ifdef MINGW
 include win32/Makefile.cfg
 endif #ifdef MINGW
@@ -223,11 +243,13 @@ OPTS += -DCOMPVERSION
 
 ifndef NONX86
 ifndef GCC29
-	M5=-march=pentium
-	M4=-march=i486
+	ARCHOPTS?=-march=pentium
+else
+	ARCHOPTS?=-mpentium
+endif
 else
-	M5=-mpentium
-	M4=-m486
+ifdef X86_64
+	ARCHOPTS?=-march=nocona
 endif
 endif
 
@@ -266,14 +288,9 @@ endif
 
 LIBS+=$(PNG_LDFLAGS)
 CFLAGS+=$(PNG_CFLAGS)
-endif
-
-ZLIB_PKGCONFIG?=zlib
-ZLIB_CFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --cflags)
-ZLIB_LDFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --libs)
 
-LIBS+=$(ZLIB_LDFLAGS)
-CFLAGS+=$(ZLIB_CFLAGS)
+OBJS+=$(OBJDIR)/apng.o
+endif
 
 ifdef HAVE_LIBGME
 OPTS+=-DHAVE_LIBGME
@@ -286,6 +303,29 @@ LIBS+=$(LIBGME_LDFLAGS)
 CFLAGS+=$(LIBGME_CFLAGS)
 endif
 
+ifdef HAVE_OPENMPT
+OPTS+=-DHAVE_OPENMPT
+
+LIBOPENMPT_PKGCONFIG?=libopenmpt
+LIBOPENMPT_CFLAGS?=$(shell $(PKG_CONFIG) $(LIBOPENMPT_PKGCONFIG) --cflags)
+LIBOPENMPT_LDFLAGS?=$(shell $(PKG_CONFIG) $(LIBOPENMPT_PKGCONFIG) --libs)
+
+LIBS+=$(LIBOPENMPT_LDFLAGS)
+CFLAGS+=$(LIBOPENMPT_CFLAGS)
+endif
+
+ifndef NOZLIB
+OPTS+=-DHAVE_ZLIB
+ZLIB_PKGCONFIG?=zlib
+ZLIB_CFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --cflags)
+ZLIB_LDFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --libs)
+
+LIBS+=$(ZLIB_LDFLAGS)
+CFLAGS+=$(ZLIB_CFLAGS)
+else
+NOPNG=1
+endif
+
 ifdef STATIC
 LIBS:=-static $(LIBS)
 endif
@@ -343,7 +383,7 @@ else
 	WINDRESFLAGS = -DNDEBUG
 	CFLAGS+=-O3
 endif
-	CFLAGS+=-g $(OPTS) $(M5) $(WINDRESFLAGS)
+	CFLAGS+=-g $(OPTS) $(ARCHOPTS) $(WINDRESFLAGS)
 
 ifdef YASM
 ifdef STABS
@@ -357,7 +397,8 @@ endif
 
 ifdef PROFILEMODE
 	# build with profiling information
-	CFLAGS:=-pg $(CFLAGS)
+	CFLAGS+=-pg
+	LDFLAGS+=-pg
 endif
 
 ifdef ZDEBUG
@@ -432,6 +473,7 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/r_sky.o    \
 		$(OBJDIR)/r_splats.o \
 		$(OBJDIR)/r_things.o \
+		$(OBJDIR)/r_portal.o \
 		$(OBJDIR)/screen.o   \
 		$(OBJDIR)/v_video.o  \
 		$(OBJDIR)/s_sound.o  \
@@ -599,7 +641,6 @@ $(BIN)/s_openal.so: $(OBJDIR)/s_openal.o
 	-$(MKDIR) $(BIN)
 	@echo Linking S_OpenAL.so...
 	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lopenal
-
 endif
 
 else
@@ -712,15 +753,15 @@ endif
 ifndef NOHS
 $(OBJDIR)/s_ds3d.o: hardware/s_ds3d/s_ds3d.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
-	$(CC) $(M5) -Os -o $(OBJDIR)/s_ds3d.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_ds3d/s_ds3d.c
+	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_ds3d.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_ds3d/s_ds3d.c
 
 $(OBJDIR)/s_fmod.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
-	$(CC) $(M5) -Os -o $(OBJDIR)/s_fmod.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_fmod/s_fmod.c
+	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_fmod.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_fmod/s_fmod.c
 
 $(OBJDIR)/s_openal.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
-	$(CC) $(M5) -Os -o $(OBJDIR)/s_openal.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_openal/s_openal.c
+	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_openal.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_openal/s_openal.c
 endif
 endif
 endif
@@ -750,11 +791,11 @@ else
 
 $(OBJDIR)/s_fmod.o: hardware/s_fmod/s_fmod.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
-	$(CC) $(M5) -Os -o $(OBJDIR)/s_fmod.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_fmod/s_fmod.c
+	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_fmod.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_fmod/s_fmod.c
 
 $(OBJDIR)/s_openal.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
-	$(CC) $(M5) -Os -o $(OBJDIR)/s_openal.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_openal/s_openal.c
+	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_openal.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_openal/s_openal.c
 endif
 endif
 
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index da4f220a974c938cfe64903758f6823de6c8aa94..cbbbf426f7cd0125b7bb7e5d9ffb2514c120828f 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -7,7 +7,19 @@
 # and other things
 #
 
-ifdef GCC80
+ifdef GCC91
+GCC83=1
+endif
+
+ifdef GCC83
+GCC82=1
+endif
+
+ifdef GCC82
+GCC81=1
+endif
+
+ifdef GCC81
 GCC72=1
 endif
 
@@ -20,7 +32,7 @@ GCC64=1
 endif
 
 ifdef GCC64
-GCC64=1
+GCC63=1
 endif
 
 ifdef GCC63
@@ -108,12 +120,11 @@ ifndef GCC295
  WFLAGS+=-Wno-div-by-zero
 endif
 #WFLAGS+=-Wsystem-headers
-ifndef ERRORMODE
-#WFLAGS+=-Wfloat-equal
-endif
+WFLAGS+=-Wfloat-equal
 #WFLAGS+=-Wtraditional
 ifdef VCHELP
  WFLAGS+=-Wdeclaration-after-statement
+ WFLAGS+=-Wno-error=declaration-after-statement
 endif
  WFLAGS+=-Wundef
 ifndef GCC295
@@ -139,6 +150,9 @@ ifdef GCC43
 endif
 endif
  WFLAGS+=-Wsign-compare
+ifdef GCC91
+ WFLAGS+=-Wno-error=address-of-packed-member
+endif
 ifdef GCC45
  WFLAGS+=-Wlogical-op
 endif
@@ -175,6 +189,9 @@ ifndef GCC295
 endif
 endif
 WFLAGS+=-Wformat-y2k
+ifdef GCC71
+WFLAGS+=-Wno-error=format-overflow=2
+endif
 WFLAGS+=-Wformat-security
 ifndef GCC29
 #WFLAGS+=-Winit-self
@@ -182,15 +199,6 @@ endif
 ifdef GCC46
 WFLAGS+=-Wno-suggest-attribute=noreturn
 endif
-ifdef GCC71
-WFLAGS+=-Wno-error=implicit-fallthrough -Wimplicit-fallthrough=3
-endif
-
-ifndef MINGW
-ifdef GCC45
-WFLAGS+=-Wunsuffixed-float-constants
-endif
-endif
 
 ifdef NOLDWARNING
 LDFLAGS+=-Wl,--as-needed
@@ -205,6 +213,9 @@ WFLAGS+=$(OLDWFLAGS)
 ifdef GCC43
  #WFLAGS+=-Wno-error=clobbered
 endif
+ifdef GCC44
+ WFLAGS+=-Wno-error=array-bounds
+endif
 ifdef GCC46
  WFLAGS+=-Wno-error=suggest-attribute=noreturn
 endif
@@ -214,6 +225,19 @@ endif
 ifdef GCC61
  WFLAGS+=-Wno-tautological-compare -Wno-error=tautological-compare
 endif
+ifdef GCC71
+ WFLAGS+=-Wno-error=implicit-fallthrough
+ WFLAGS+=-Wno-implicit-fallthrough
+endif
+ifdef GCC81
+ WFLAGS+=-Wno-error=format-overflow
+ WFLAGS+=-Wno-error=stringop-truncation
+ WFLAGS+=-Wno-error=stringop-overflow
+ WFLAGS+=-Wno-format-overflow
+ WFLAGS+=-Wno-stringop-truncation
+ WFLAGS+=-Wno-stringop-overflow
+ WFLAGS+=-Wno-error=multistatement-macros
+endif
 
 
 #indicate platform and what interface use with
@@ -221,6 +245,7 @@ ifndef LINUX
 ifndef FREEBSD
 ifndef CYGWIN32
 ifndef MINGW
+ifndef MINGW64
 ifndef SDL
 ifndef DUMMY
 	DJGPPDOS=1
@@ -230,6 +255,7 @@ endif
 endif
 endif
 endif
+endif
 
 #determine the interface directory (where you put all i_*.c)
 i_cdmus_o=$(OBJDIR)/i_cdmus.o
diff --git a/src/am_map.c b/src/am_map.c
index 7e012a3043f72ccd0d139351b1f97f21f7174396..6d7042f1c9009720d8cd8aa73bdefd08996f7fa7 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -11,8 +11,8 @@
 /// \file  am_map.c
 /// \brief Code for the 'automap', former Doom feature used for DEVMODE testing
 
-#include "g_game.h"
 #include "am_map.h"
+#include "g_game.h"
 #include "g_input.h"
 #include "p_local.h"
 #include "p_slopes.h"
@@ -33,7 +33,6 @@ static const UINT8 GRAYSRANGE  = 16;
 static const UINT8 BROWNS      = (3*16);
 static const UINT8 YELLOWS     = (7*16);
 static const UINT8 GREENS      = (10*16);
-static const UINT8 GREENRANGE  = 16;
 static const UINT8 DBLACK      = 31;
 static const UINT8 DWHITE      = 0;
 
@@ -46,8 +45,6 @@ static const UINT8 NOCLIMBYELLOWS     = (11*16);
 
 // Automap colors
 #define BACKGROUND            DBLACK
-#define YOURCOLORS            DWHITE
-#define YOURRANGE             0
 #define WALLCOLORS            (REDS + REDRANGE/2)
 #define WALLRANGE             (REDRANGE/2)
 #define NOCLIMBWALLCOLORS     (NOCLIMBREDS + NOCLIMBREDRANGE/2)
@@ -64,31 +61,23 @@ static const UINT8 NOCLIMBYELLOWS     = (11*16);
 #define CDWALLCOLORS          YELLOWS
 #define NOCLIMBCDWALLCOLORS   NOCLIMBYELLOWS
 #define THINGCOLORS           GREENS
-#define THINGRANGE            GREENRANGE
-#define SECRETWALLCOLORS      WALLCOLORS
-#define SECRETWALLRANGE       WALLRANGE
 #define GRIDCOLORS            (GRAYS + GRAYSRANGE/2)
-#define GRIDRANGE             0
 #define XHAIRCOLORS           GRAYS
 
-// drawing stuff
-#define FB 0
-
-#define AM_PANDOWNKEY   KEY_DOWNARROW
+// controls
 #define AM_PANUPKEY     KEY_UPARROW
-#define AM_PANRIGHTKEY  KEY_RIGHTARROW
+#define AM_PANDOWNKEY   KEY_DOWNARROW
 #define AM_PANLEFTKEY   KEY_LEFTARROW
+#define AM_PANRIGHTKEY  KEY_RIGHTARROW
+
 #define AM_ZOOMINKEY    '='
 #define AM_ZOOMOUTKEY   '-'
-#define AM_STARTKEY     KEY_TAB
-#define AM_ENDKEY       KEY_TAB
 #define AM_GOBIGKEY     '0'
+
 #define AM_FOLLOWKEY    'f'
 #define AM_GRIDKEY      'g'
-#define AM_MARKKEY      'm'
-#define AM_CLEARMARKKEY 'c'
 
-#define AM_NUMMARKPOINTS 10
+#define AM_TOGGLEKEY    KEY_TAB
 
 // scale on entry
 #define INITSCALEMTOF (FRACUNIT/5)
@@ -109,6 +98,9 @@ static const UINT8 NOCLIMBYELLOWS     = (11*16);
 #define CXMTOF(x) (f_x + MTOF((x)-m_x))
 #define CYMTOF(y) (f_y + (f_h - MTOF((y)-m_y)))
 
+#define MAPBITS (FRACBITS-4)
+#define FRACTOMAPBITS (FRACBITS-MAPBITS)
+
 typedef struct
 {
 	fixed_t x, y;
@@ -129,7 +121,10 @@ typedef struct
 // A line drawing of the player pointing right,
 //   starting from the middle.
 //
+
+#define PLAYERRADIUS (16*(1<<MAPBITS))
 #define R ((8*PLAYERRADIUS)/7)
+
 static const mline_t player_arrow[] = {
 	{ { -R+R/8, 0 }, { R, 0 } }, // -----
 	{ { R, 0 }, { R-R/2, R/4 } }, // ----->
@@ -162,27 +157,15 @@ static const mline_t thintriangle_guy[] = {
 #undef R
 #define NUMTHINTRIANGLEGUYLINES (sizeof (thintriangle_guy)/sizeof (mline_t))
 
-static INT32 bigstate; //added : 24-01-98 : moved here, toggle between
-                     // user view and large view (full map view)
-
-static INT32 grid = 0;
-
-static INT32 leveljuststarted = 1; // kluge until AM_LevelInit() is called
+static boolean bigstate;	// user view and large view (full map view)
+static boolean draw_grid = false;
 
 boolean automapactive = false;
 boolean am_recalc = false; //added : 05-02-98 : true when screen size changes
+static boolean am_stopped = true;
 
-// location of window on screen
-static INT32 f_x;
-static INT32 f_y;
-
-// size of window on screen
-static INT32 f_w;
-static INT32 f_h;
-
-static INT32 lightlev; // used for funky strobing effect
-static UINT8 *fb; // pseudo-frame buffer
-static INT32 amclock;
+static INT32 f_x, f_y;	// location of window on screen (always zero for both)
+static INT32 f_w, f_h;	// size of window on screen (always the screen width and height respectively)
 
 static mpoint_t m_paninc; // how far the window pans each tic (map coords)
 static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords)
@@ -206,11 +189,6 @@ static fixed_t max_y;
 static fixed_t max_w; // max_x-min_x,
 static fixed_t max_h; // max_y-min_y
 
-// based on player size
-static fixed_t min_w;
-static fixed_t min_h;
-
-
 static fixed_t min_scale_mtof; // used to tell when to stop zooming out
 static fixed_t max_scale_mtof; // used to tell when to stop zooming in
 
@@ -228,13 +206,7 @@ static fixed_t scale_ftom;
 
 static player_t *plr; // the player represented by an arrow
 
-static patch_t *marknums[10];                   // numbers used for marking by the automap
-static mpoint_t markpoints[AM_NUMMARKPOINTS];   // where the points are
-static INT32 markpointnum = 0;                    // next point to be assigned
-
-static INT32 followplayer = 1; // specifies whether to follow the player around
-
-static boolean stopped = true;
+static INT32 followplayer = true; // specifies whether to follow the player around
 
 // function for drawing lines, depends on rendermode
 typedef void (*AMDRAWFLINEFUNC) (const fline_t *fl, INT32 color);
@@ -273,8 +245,8 @@ static inline void AM_restoreScaleAndLoc(void)
 	}
 	else
 	{
-		m_x = plr->mo->x - m_w/2;
-		m_y = plr->mo->y - m_h/2;
+		m_x = (plr->mo->x >> FRACTOMAPBITS) - m_w/2;
+		m_y = (plr->mo->y >> FRACTOMAPBITS) - m_h/2;
 	}
 	m_x2 = m_x + m_w;
 	m_y2 = m_y + m_h;
@@ -284,15 +256,6 @@ static inline void AM_restoreScaleAndLoc(void)
 	scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
 }
 
-/** Adds a marker at the current location.
-  */
-static inline void AM_addMark(void)
-{
-	markpoints[markpointnum].x = m_x + m_w/2;
-	markpoints[markpointnum].y = m_y + m_h/2;
-	markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
-}
-
 /** Determines the bounding box around all vertices.
   * This is used to set global variables controlling the zoom range.
   */
@@ -318,11 +281,8 @@ static void AM_findMinMaxBoundaries(void)
 			max_y = vertexes[i].y;
 	}
 
-	max_w = max_x - min_x;
-	max_h = max_y - min_y;
-
-	min_w = 2*PLAYERRADIUS; // const? never changed?
-	min_h = 2*PLAYERRADIUS;
+	max_w = (max_x >>= FRACTOMAPBITS) - (min_x >>= FRACTOMAPBITS);
+	max_h = (max_y >>= FRACTOMAPBITS) - (min_y >>= FRACTOMAPBITS);
 
 	a = FixedDiv(f_w<<FRACBITS, max_w);
 	b = FixedDiv(f_h<<FRACBITS, max_h);
@@ -335,7 +295,7 @@ static void AM_changeWindowLoc(void)
 {
 	if (m_paninc.x || m_paninc.y)
 	{
-		followplayer = 0;
+		followplayer = false;
 		f_oldloc.x = INT32_MAX;
 	}
 
@@ -361,11 +321,7 @@ static void AM_initVariables(void)
 	INT32 pnum;
 
 	automapactive = true;
-	fb = screens[0];
-
 	f_oldloc.x = INT32_MAX;
-	amclock = 0;
-	lightlev = 0;
 
 	m_paninc.x = m_paninc.y = 0;
 	ftom_zoommul = FRACUNIT;
@@ -381,8 +337,11 @@ static void AM_initVariables(void)
 				break;
 
 	plr = &players[pnum];
-	m_x = plr->mo->x - m_w/2;
-	m_y = plr->mo->y - m_h/2;
+	if (plr != NULL && plr->mo != NULL)
+	{
+		m_x = (plr->mo->x >> FRACTOMAPBITS) - m_w/2;
+		m_y = (plr->mo->y >> FRACTOMAPBITS) - m_h/2;
+	}
 	AM_changeWindowLoc();
 
 	// for saving & restoring
@@ -392,41 +351,21 @@ static void AM_initVariables(void)
 	old_m_h = m_h;
 }
 
-static const UINT8 *maplump; // pointer to the raw data for the automap background.
-
-/** Clears all map markers.
-  */
-static void AM_clearMarks(void)
-{
-	INT32 i;
-
-	for (i = 0; i < AM_NUMMARKPOINTS; i++)
-		markpoints[i].x = -1; // means empty
-	markpointnum = 0;
-}
-
 //
 // should be called at the start of every level
 // right now, i figure it out myself
 //
 static void AM_LevelInit(void)
 {
-	leveljuststarted = 0;
-
 	f_x = f_y = 0;
 	f_w = vid.width;
 	f_h = vid.height;
 
-	if (rendermode == render_soft)
-		AM_drawFline = AM_drawFline_soft;
-#ifdef HWRENDER // not win32 only 19990829 by Kin
-	else if (rendermode != render_none)
+	AM_drawFline = AM_drawFline_soft;
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
 		AM_drawFline = HWR_drawAMline;
 #endif
-	else
-		I_Error("Automap can't run without a render system");
-
-	AM_clearMarks();
 
 	AM_findMinMaxBoundaries();
 	scale_mtof = FixedDiv(min_scale_mtof*10, 7*FRACUNIT);
@@ -442,7 +381,7 @@ static void AM_LevelInit(void)
 void AM_Stop(void)
 {
 	automapactive = false;
-	stopped = true;
+	am_stopped = true;
 }
 
 /** Enables automap.
@@ -453,15 +392,14 @@ static inline void AM_Start(void)
 {
 	static INT32 lastlevel = -1;
 
-	if (!stopped)
+	if (!am_stopped)
 		AM_Stop();
-	stopped = false;
+	am_stopped = false;
 	if (lastlevel != gamemap || am_recalc) // screen size changed
 	{
-		am_recalc = false;
-
 		AM_LevelInit();
 		lastlevel = gamemap;
+		am_recalc = false;
 	}
 	AM_initVariables();
 }
@@ -499,7 +437,7 @@ boolean AM_Responder(event_t *ev)
 	{
 		if (!automapactive)
 		{
-			if (ev->type == ev_keydown && ev->data1 == AM_STARTKEY)
+			if (ev->type == ev_keydown && ev->data1 == AM_TOGGLEKEY)
 			{
 				//faB: prevent alt-tab in win32 version to activate automap just before
 				//     minimizing the app; doesn't do any harm to the DOS version
@@ -511,10 +449,8 @@ boolean AM_Responder(event_t *ev)
 				}
 			}
 		}
-
 		else if (ev->type == ev_keydown)
 		{
-
 			rc = true;
 			switch (ev->data1)
 			{
@@ -550,7 +486,7 @@ boolean AM_Responder(event_t *ev)
 					mtof_zoommul = M_ZOOMIN;
 					ftom_zoommul = M_ZOOMOUT;
 					break;
-				case AM_ENDKEY:
+				case AM_TOGGLEKEY:
 					AM_Stop();
 					break;
 				case AM_GOBIGKEY:
@@ -568,13 +504,7 @@ boolean AM_Responder(event_t *ev)
 					f_oldloc.x = INT32_MAX;
 					break;
 				case AM_GRIDKEY:
-					grid = !grid;
-					break;
-				case AM_MARKKEY:
-					AM_addMark();
-					break;
-				case AM_CLEARMARKKEY:
-					AM_clearMarks();
+					draw_grid = !draw_grid;
 					break;
 				default:
 					rc = false;
@@ -628,8 +558,8 @@ static inline void AM_doFollowPlayer(void)
 {
 	if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y)
 	{
-		m_x = FTOM(MTOF(plr->mo->x)) - m_w/2;
-		m_y = FTOM(MTOF(plr->mo->y)) - m_h/2;
+		m_x = FTOM(MTOF(plr->mo->x >> FRACTOMAPBITS)) - m_w/2;
+		m_y = FTOM(MTOF(plr->mo->y >> FRACTOMAPBITS)) - m_h/2;
 		m_x2 = m_x + m_w;
 		m_y2 = m_y + m_h;
 		f_oldloc.x = plr->mo->x;
@@ -647,8 +577,6 @@ void AM_Ticker(void)
 	if (dedicated || !automapactive)
 		return;
 
-	amclock++;
-
 	if (followplayer)
 		AM_doFollowPlayer();
 
@@ -667,72 +595,7 @@ void AM_Ticker(void)
   */
 static void AM_clearFB(INT32 color)
 {
-#ifdef HWRENDER
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_clearAutomap();
-		return;
-	}
-#endif
-
-	if (!maplump)
-		memset(fb, color, f_w*f_h*vid.bpp);
-	else
-	{
-		INT32 dmapx, dmapy, i, y;
-		static INT32 mapxstart, mapystart;
-		UINT8 *dest = screens[0];
-		const UINT8 *src;
-#define MAPLUMPHEIGHT (200 - 42)
-
-		if (followplayer)
-		{
-			static vertex_t oldplr;
-
-			dmapx = MTOF(plr->mo->x) - MTOF(oldplr.x); //fixed point
-			dmapy = MTOF(oldplr.y) - MTOF(plr->mo->y);
-
-			oldplr.x = plr->mo->x;
-			oldplr.y = plr->mo->y;
-			mapxstart += dmapx>>1;
-			mapystart += dmapy>>1;
-
-			while (mapxstart >= BASEVIDWIDTH)
-				mapxstart -= BASEVIDWIDTH;
-			while (mapxstart < 0)
-				mapxstart += BASEVIDWIDTH;
-			while (mapystart >= MAPLUMPHEIGHT)
-				mapystart -= MAPLUMPHEIGHT;
-			while (mapystart < 0)
-				mapystart += MAPLUMPHEIGHT;
-		}
-		else
-		{
-			mapxstart += (MTOF(m_paninc.x)>>1);
-			mapystart -= (MTOF(m_paninc.y)>>1);
-			if (mapxstart >= BASEVIDWIDTH)
-				mapxstart -= BASEVIDWIDTH;
-			if (mapxstart < 0)
-				mapxstart += BASEVIDWIDTH;
-			if (mapystart >= MAPLUMPHEIGHT)
-				mapystart -= MAPLUMPHEIGHT;
-			if (mapystart < 0)
-				mapystart += MAPLUMPHEIGHT;
-		}
-
-		//blit the automap background to the screen.
-		for (y = 0; y < f_h; y++)
-		{
-			src = maplump + mapxstart + (y + mapystart)*BASEVIDWIDTH;
-			for (i = 0; i < BASEVIDWIDTH*vid.dupx; i++)
-			{
-				while (src > maplump + BASEVIDWIDTH*MAPLUMPHEIGHT)
-					src -= BASEVIDWIDTH*MAPLUMPHEIGHT;
-				*dest++ = *src++;
-			}
-			dest += vid.width - vid.dupx*BASEVIDWIDTH;
-		}
-	}
+	V_DrawFill(f_x, f_y, f_w, f_h, color|V_NOSCALESTART);
 }
 
 /** Performs automap clipping of lines.
@@ -867,7 +730,7 @@ static boolean AM_clipMline(const mline_t *ml, fline_t *fl)
 //
 static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 {
-	register INT32 x, y, dx, dy, sx, sy, ax, ay, d;
+	INT32 x, y, dx, dy, sx, sy, ax, ay, d;
 
 #ifdef _DEBUG
 	static INT32 num = 0;
@@ -883,7 +746,7 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 	}
 #endif
 
-#define PUTDOT(xx,yy,cc) fb[(yy)*f_w + (xx)]=(UINT8)(cc)
+	#define PUTDOT(xx,yy,cc) V_DrawFill(xx,yy,1,1,cc|V_NOSCALESTART);
 
 	dx = fl->b.x - fl->a.x;
 	ax = 2 * (dx < 0 ? -dx : dx);
@@ -901,7 +764,7 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 		d = ay - ax/2;
 		for (;;)
 		{
-			PUTDOT(x, y, color);
+			PUTDOT(x, y, color)
 			if (x == fl->b.x)
 				return;
 			if (d >= 0)
@@ -918,7 +781,7 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 		d = ax - ay/2;
 		for (;;)
 		{
-			PUTDOT(x, y, color);
+			PUTDOT(x, y, color)
 			if (y == fl->b.y)
 				return;
 			if (d >= 0)
@@ -930,6 +793,8 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 			d += ax;
 		}
 	}
+
+	#undef PUTDOT
 }
 
 //
@@ -1000,15 +865,15 @@ static inline void AM_drawWalls(void)
 
 	for (i = 0; i < numlines; i++)
 	{
-		l.a.x = lines[i].v1->x;
-		l.a.y = lines[i].v1->y;
-		l.b.x = lines[i].v2->x;
-		l.b.y = lines[i].v2->y;
+		l.a.x = lines[i].v1->x >> FRACTOMAPBITS;
+		l.a.y = lines[i].v1->y >> FRACTOMAPBITS;
+		l.b.x = lines[i].v2->x >> FRACTOMAPBITS;
+		l.b.y = lines[i].v2->y >> FRACTOMAPBITS;
 #ifdef ESLOPE
 #define SLOPEPARAMS(slope, end1, end2, normalheight) \
 		if (slope) { \
-			end1 = P_GetZAt(slope, l.a.x, l.a.y); \
-			end2 = P_GetZAt(slope, l.b.x, l.b.y); \
+			end1 = P_GetZAt(slope, lines[i].v1->x, lines[i].v1->y); \
+			end2 = P_GetZAt(slope, lines[i].v2->x, lines[i].v2->y); \
 		} else \
 			end1 = end2 = normalheight;
 
@@ -1021,17 +886,12 @@ static inline void AM_drawWalls(void)
 #undef SLOPEPARAMS
 #endif
 
-//		AM_drawMline(&l, GRAYS + 3); // Old, everything-is-gray automap
 		if (!lines[i].backsector) // 1-sided
 		{
 			if (lines[i].flags & ML_NOCLIMB)
-			{
-				AM_drawMline(&l, NOCLIMBWALLCOLORS+lightlev);
-			}
+				AM_drawMline(&l, NOCLIMBWALLCOLORS);
 			else
-			{
-				AM_drawMline(&l, WALLCOLORS+lightlev);
-			}
+				AM_drawMline(&l, WALLCOLORS);
 		}
 #ifdef ESLOPE
 		else if ((backf1 == backc1 && backf2 == backc2) // Back is thok barrier
@@ -1048,24 +908,16 @@ static inline void AM_drawWalls(void)
 #endif
 			{
 				if (lines[i].flags & ML_NOCLIMB)
-				{
-					AM_drawMline(&l, NOCLIMBTSWALLCOLORS+lightlev);
-				}
+					AM_drawMline(&l, NOCLIMBTSWALLCOLORS);
 				else
-				{
-					AM_drawMline(&l, TSWALLCOLORS+lightlev);
-				}
+					AM_drawMline(&l, TSWALLCOLORS);
 			}
 			else
 			{
 				if (lines[i].flags & ML_NOCLIMB)
-				{
-					AM_drawMline(&l, NOCLIMBTHOKWALLCOLORS+lightlev);
-				}
+					AM_drawMline(&l, NOCLIMBTHOKWALLCOLORS);
 				else
-				{
-					AM_drawMline(&l, THOKWALLCOLORS+lightlev);
-				}
+					AM_drawMline(&l, THOKWALLCOLORS);
 			}
 		}
 		else
@@ -1077,7 +929,7 @@ static inline void AM_drawWalls(void)
 				if (lines[i].backsector->floorheight
 						!= lines[i].frontsector->floorheight) {
 #endif
-					AM_drawMline(&l, NOCLIMBFDWALLCOLORS + lightlev); // floor level change
+					AM_drawMline(&l, NOCLIMBFDWALLCOLORS); // floor level change
 				}
 #ifdef ESLOPE
 				else if (backc1 != frontc1 || backc2 != frontc2) {
@@ -1085,11 +937,10 @@ static inline void AM_drawWalls(void)
 				else if (lines[i].backsector->ceilingheight
 						!= lines[i].frontsector->ceilingheight) {
 #endif
-					AM_drawMline(&l, NOCLIMBCDWALLCOLORS+lightlev); // ceiling level change
-				}
-				else {
-					AM_drawMline(&l, NOCLIMBTSWALLCOLORS+lightlev);
+					AM_drawMline(&l, NOCLIMBCDWALLCOLORS); // ceiling level change
 				}
+				else
+					AM_drawMline(&l, NOCLIMBTSWALLCOLORS);
 			}
 			else
 			{
@@ -1099,7 +950,7 @@ static inline void AM_drawWalls(void)
 				if (lines[i].backsector->floorheight
 						!= lines[i].frontsector->floorheight) {
 #endif
-					AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change
+					AM_drawMline(&l, FDWALLCOLORS); // floor level change
 				}
 #ifdef ESLOPE
 				else if (backc1 != frontc1 || backc2 != frontc2) {
@@ -1107,11 +958,10 @@ static inline void AM_drawWalls(void)
 				else if (lines[i].backsector->ceilingheight
 						!= lines[i].frontsector->ceilingheight) {
 #endif
-					AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change
-				}
-				else {
-					AM_drawMline(&l, TSWALLCOLORS+lightlev);
+					AM_drawMline(&l, CDWALLCOLORS); // ceiling level change
 				}
+				else
+					AM_drawMline(&l, TSWALLCOLORS);
 			}
 		}
 	}
@@ -1172,6 +1022,11 @@ static void AM_drawLineCharacter(const mline_t *lineguy, size_t lineguylines, fi
 		l.b.x += x;
 		l.b.y += y;
 
+		l.a.x >>= FRACTOMAPBITS;
+		l.a.y >>= FRACTOMAPBITS;
+		l.b.x >>= FRACTOMAPBITS;
+		l.b.y >>= FRACTOMAPBITS;
+
 		AM_drawMline(&l, color);
 	}
 }
@@ -1180,83 +1035,51 @@ static inline void AM_drawPlayers(void)
 {
 	INT32 i;
 	player_t *p;
-	INT32 color;
+	INT32 color = GREENS;
 
 	if (!multiplayer)
 	{
-		AM_drawLineCharacter(player_arrow, NUMPLYRLINES, 0,
-			plr->mo->angle, DWHITE, plr->mo->x, plr->mo->y);
+		AM_drawLineCharacter(player_arrow, NUMPLYRLINES, 0, plr->mo->angle, DWHITE, plr->mo->x, plr->mo->y);
 		return;
 	}
 
-	// multiplayer
+	// multiplayer (how??)
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (!playeringame[i] || players[i].spectator)
 			continue;
 
 		p = &players[i];
-		if (p->skincolor == 0)
-			color = GREENS;
-		else
+		if (p->skincolor > 0)
 			color = R_GetTranslationColormap(TC_DEFAULT, p->skincolor, GTC_CACHE)[GREENS + 8];
 
-		AM_drawLineCharacter(player_arrow, NUMPLYRLINES, 0, p->mo->angle,
-			color, p->mo->x, p->mo->y);
+		AM_drawLineCharacter(player_arrow, NUMPLYRLINES, 0, p->mo->angle, color, p->mo->x, p->mo->y);
 	}
 }
 
-static inline void AM_drawThings(INT32 colors, INT32 colorrange)
+static inline void AM_drawThings(UINT8 colors)
 {
 	size_t i;
 	mobj_t *t;
 
-	(void)colorrange;
 	for (i = 0; i < numsectors; i++)
 	{
 		t = sectors[i].thinglist;
 		while (t)
 		{
-			AM_drawLineCharacter(thintriangle_guy, NUMTHINTRIANGLEGUYLINES,
-				16<<FRACBITS, t->angle, colors + lightlev, t->x, t->y);
+			AM_drawLineCharacter(thintriangle_guy, NUMTHINTRIANGLEGUYLINES, 16<<FRACBITS, t->angle, colors, t->x, t->y);
 			t = t->snext;
 		}
 	}
 }
 
-static inline void AM_drawMarks(void)
-{
-	INT32 i, fx, fy, w, h;
-
-	for (i = 0; i < AM_NUMMARKPOINTS; i++)
-	{
-		if (markpoints[i].x != -1 && marknums[i])
-		{
-			// w = SHORT(marknums[i]->width);
-			// h = SHORT(marknums[i]->height);
-			w = 5; // because something's wrong with the wad, i guess
-			h = 6; // because something's wrong with the wad, i guess
-			fx = CXMTOF(markpoints[i].x);
-			fy = CYMTOF(markpoints[i].y);
-			if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
-				V_DrawPatch(fx, fy, FB, marknums[i]);
-		}
-	}
-}
-
 /** Draws the crosshair, actually just a dot in software mode.
   *
   * \param color Color for the crosshair.
   */
-static inline void AM_drawCrosshair(INT32 color)
+static inline void AM_drawCrosshair(UINT8 color)
 {
-	if (rendermode != render_soft)
-		return; // BP: should be putpixel here
-
-	if (scr_bpp == 1)
-		fb[(f_w*(f_h + 1))/2] = (UINT8)color; // single point for now
-	else
-		*((INT16 *)(void *)fb + (f_w*(f_h + 1))/2) = (INT16)color;
+	V_DrawFill(f_w/2 + f_x, f_h/2 + f_y, 1, 1, color|V_NOSCALESTART);
 }
 
 /** Draws the automap.
@@ -1267,13 +1090,10 @@ void AM_Drawer(void)
 		return;
 
 	AM_clearFB(BACKGROUND);
-	if (grid)
-		AM_drawGrid(GRIDCOLORS);
+	if (draw_grid) AM_drawGrid(GRIDCOLORS);
 	AM_drawWalls();
 	AM_drawPlayers();
-	AM_drawThings(THINGCOLORS, THINGRANGE);
+	AM_drawThings(THINGCOLORS);
 
 	AM_drawCrosshair(XHAIRCOLORS);
-
-	AM_drawMarks();
 }
diff --git a/src/am_map.h b/src/am_map.h
index df145848b6fc24c2fef52f223646a94e57c3cf49..4e8c782a9cf21c70476e96d2a1e6e1f6891b174c 100644
--- a/src/am_map.h
+++ b/src/am_map.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/android/i_cdmus.c b/src/android/i_cdmus.c
index c5aac8f18a30143eca66f48be31de81ab1fb80e6..426bc5dc9ebdbf130614725e96fadbba42f6dd2c 100644
--- a/src/android/i_cdmus.c
+++ b/src/android/i_cdmus.c
@@ -8,7 +8,7 @@
 
 UINT8 cdaudio_started = 0;
 
-consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 
diff --git a/src/android/i_sound.c b/src/android/i_sound.c
index ecf96f2f053cf2ca8e3cb135b34cfc7e2dccd0c0..b5a1c364618fbde5b8142eb74f7541ff2ecd18be 100644
--- a/src/android/i_sound.c
+++ b/src/android/i_sound.c
@@ -21,13 +21,14 @@ void I_ShutdownSound(void){}
 //  SFX I/O
 //
 
-INT32 I_StartSound(sfxenum_t id, INT32 vol, INT32 sep, INT32 pitch, INT32 priority)
+INT32 I_StartSound(sfxenum_t id, INT32 vol, INT32 sep, INT32 pitch, INT32 priority, INT32 channel)
 {
         (void)id;
         (void)vol;
         (void)sep;
         (void)pitch;
         (void)priority;
+        (void)channel;
         return -1;
 }
 
@@ -55,90 +56,159 @@ void I_SetSfxVolume(INT32 volume)
         (void)volume;
 }
 
-//
-//  MUSIC I/O
-//
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
+
 UINT8 music_started = 0;
+UINT8 digmusic_started = 0;
 
 void I_InitMusic(void){}
 
 void I_ShutdownMusic(void){}
 
-void I_PauseSong(INT32 handle)
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
+
+musictype_t I_SongType(void)
 {
-        (void)handle;
+	return MU_NONE;
 }
 
-void I_ResumeSong(INT32 handle)
+boolean I_SongPlaying(void)
 {
-        (void)handle;
+	return false;
 }
 
-//
-//  MIDI I/O
-//
+boolean I_SongPaused(void)
+{
+	return false;
+}
 
-UINT8 midimusic_started = 0;
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
 
-void I_InitMIDIMusic(void){}
+boolean I_SetSongSpeed(float speed)
+{
+        (void)speed;
+        return false;
+}
 
-void I_ShutdownMIDIMusic(void){}
+/// ------------------------
+//  MUSIC SEEKING
+/// ------------------------
 
-void I_SetMIDIMusicVolume(INT32 volume)
+UINT32 I_GetSongLength(void)
 {
-        (void)volume;
+        return 0;
+}
+
+boolean I_SetSongLoopPoint(UINT32 looppoint)
+{
+        (void)looppoint;
+        return false;
 }
 
-INT32 I_RegisterSong(void *data, size_t len)
+UINT32 I_GetSongLoopPoint(void)
+{
+	return 0;
+}
+
+boolean I_SetSongPosition(UINT32 position)
+{
+        (void)position;
+        return false;
+}
+
+UINT32 I_GetSongPosition(void)
+{
+        return 0;
+}
+
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
+
+UINT8 midimusic_started = 0;
+
+boolean I_LoadSong(char *data, size_t len)
 {
         (void)data;
         (void)len;
         return -1;
 }
 
-boolean I_PlaySong(INT32 handle, INT32 looping)
+void I_UnloadSong()
+{
+
+}
+
+boolean I_PlaySong(boolean looping)
 {
         (void)handle;
         (void)looping;
         return false;
 }
 
-void I_StopSong(INT32 handle)
+void I_StopSong(void)
 {
         (void)handle;
 }
 
-void I_UnRegisterSong(INT32 handle)
+void I_PauseSong(void)
 {
         (void)handle;
 }
 
-//
-//  DIGMUSIC I/O
-//
+void I_ResumeSong(void)
+{
+        (void)handle;
+}
 
-UINT8 digmusic_started = 0;
+void I_SetMusicVolume(INT32 volume)
+{
+        (void)volume;
+}
 
-void I_InitDigMusic(void){}
+/// ------------------------
+//  MUSIC FADING
+/// ------------------------
+
+void I_SetInternalMusicVolume(UINT8 volume)
+{
+	(void)volume;
+}
 
-void I_ShutdownDigMusic(void){}
+void I_StopFadingSong(void)
+{
+}
 
-boolean I_StartDigSong(const char *musicname, INT32 looping)
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void));
 {
-        (void)musicname;
-        (void)looping;
+	(void)target_volume;
+	(void)source_volume;
+	(void)ms;
         return false;
 }
 
-void I_StopDigSong(void){}
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
+{
+	(void)target_volume;
+	(void)ms;
+	return false;
+}
 
-void I_SetDigMusicVolume(INT32 volume)
+boolean I_FadeOutStopSong(UINT32 ms)
 {
-        (void)volume;
+        (void)ms;
+        return false;
 }
 
-boolean I_SetSongSpeed(float speed)
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
 {
-        (void)speed;
+        (void)ms;
+        (void)looping;
         return false;
 }
diff --git a/src/apng.c b/src/apng.c
new file mode 100644
index 0000000000000000000000000000000000000000..694b3d1e8b5d90efb45bb0145960e3061b9cf5aa
--- /dev/null
+++ b/src/apng.c
@@ -0,0 +1,289 @@
+/*
+Copyright 2019, James R.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "apng.h"
+
+#define APNG_INFO_acTL 0x20000U
+
+#define APNG_WROTE_acTL 0x10000U
+
+struct apng_info_def
+{
+	png_uint_32 mode;
+	png_uint_32 valid;
+
+	png_uint_32 num_frames;
+	png_uint_32 num_plays;
+
+	long start_acTL;/* acTL is written here */
+
+	png_flush_ptr output_flush_fn;
+	apng_seek_ptr output_seek_fn;
+	apng_tell_ptr output_tell_fn;
+
+	apng_set_acTL_ptr set_acTL_fn;
+};
+
+/* PROTOS (FUCK COMPILER) */
+void   apng_seek  (png_structp, apng_const_infop, size_t);
+size_t apng_tell  (png_structp, apng_const_infop);
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+void   apng_flush (png_structp, apng_infop);
+#ifdef PNG_STDIO_SUPPORTED
+void   apng_default_flush (png_structp);
+#endif/* PNG_STDIO_SUPPORTED */
+#endif/* PNG_WRITE_FLUSH_SUPPORTED */
+#ifdef PNG_STDIO_SUPPORTED
+void   apng_default_seek  (png_structp, size_t);
+size_t apng_default_tell  (png_structp);
+#endif/* PNG_STDIO_SUPPORTED */
+void   apng_write_IEND (png_structp);
+void   apng_write_acTL (png_structp, png_uint_32, png_uint_32);
+#ifndef PNG_WRITE_APNG_SUPPORTED
+png_uint_32 apng_set_acTL_dummy (png_structp, png_infop,
+		png_uint_32, png_uint_32);
+#endif/* PNG_WRITE_APNG_SUPPORTED */
+
+apng_infop
+apng_create_info_struct (png_structp pngp)
+{
+	apng_infop ainfop;
+	(void)pngp;
+	if (( ainfop = calloc(sizeof (apng_info),1) ))
+	{
+		apng_set_write_fn(pngp, ainfop, 0, 0, 0, 0, 0);
+		apng_set_set_acTL_fn(pngp, ainfop, 0);
+	}
+	return ainfop;
+}
+
+void
+apng_destroy_info_struct (png_structp pngp, apng_infopp ainfopp)
+{
+	(void)pngp;
+	if (!( pngp && ainfopp ))
+		return;
+
+	free((*ainfopp));
+}
+
+void
+apng_seek (png_structp pngp, apng_const_infop ainfop, size_t l)
+{
+	(*(ainfop->output_seek_fn))(pngp, l);
+}
+
+size_t
+apng_tell (png_structp pngp, apng_const_infop ainfop)
+{
+	return (*(ainfop->output_tell_fn))(pngp);
+}
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+void
+apng_flush (png_structp pngp, apng_infop ainfop)
+{
+	if (ainfop->output_flush_fn)
+		(*(ainfop->output_flush_fn))(pngp);
+}
+
+#ifdef PNG_STDIO_SUPPORTED
+void
+apng_default_flush (png_structp pngp)
+{
+	if (!( pngp ))
+		return;
+
+	fflush((png_FILE_p)png_get_io_ptr);
+}
+#endif/* PNG_STDIO_SUPPORTED */
+#endif/* PNG_WRITE_FLUSH_SUPPORTED */
+
+#ifdef PNG_STDIO_SUPPORTED
+void
+apng_default_seek (png_structp pngp, size_t l)
+{
+	if (!( pngp ))
+		return;
+
+	if (fseek((png_FILE_p)png_get_io_ptr(pngp), (long)l, SEEK_SET) == -1)
+		png_error(pngp, "Seek Error");
+}
+
+size_t
+apng_default_tell (png_structp pngp)
+{
+	long l;
+
+	if (!( pngp ))
+	{
+		png_error(pngp, "Call to apng_default_tell with NULL pngp failed");
+	}
+
+	if (( l = ftell((png_FILE_p)png_get_io_ptr(pngp)) ) == -1)
+		png_error(pngp, "Tell Error");
+
+	return (size_t)l;
+}
+#endif/* PNG_STDIO_SUPPORTED */
+
+void
+apng_set_write_fn (png_structp pngp, apng_infop ainfop, png_voidp iop,
+		png_rw_ptr write_f, png_flush_ptr flush_f,
+		apng_seek_ptr seek_f, apng_tell_ptr tell_f)
+{
+	if (!( pngp && ainfop ))
+		return;
+
+	png_set_write_fn(pngp, iop, write_f, flush_f);
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+#ifdef PNG_STDIO_SUPPORTED
+	if (!flush_f)
+		ainfop->output_flush_fn = &apng_default_flush;
+	else
+#endif/* PNG_STDIO_SUPPORTED */
+		ainfop->output_flush_fn = flush_f;
+#endif/* PNG_WRITE_FLUSH_SUPPORTED */
+#ifdef PNG_STDIO_SUPPORTED
+	if (!seek_f)
+		ainfop->output_seek_fn = &apng_default_seek;
+	else
+#endif/* PNG_STDIO_SUPPORTED */
+		ainfop->output_seek_fn  = seek_f;
+#ifdef PNG_STDIO_SUPPORTED
+	if (!seek_f)
+		ainfop->output_tell_fn  = apng_default_tell;
+	else
+#endif/* PNG_STDIO_SUPPORTED */
+		ainfop->output_tell_fn  = tell_f;
+}
+
+void
+apng_write_IEND (png_structp pngp)
+{
+	png_byte chunkc[] = "IEND";
+	png_write_chunk(pngp, chunkc, 0, 0);
+}
+
+void
+apng_write_acTL (png_structp pngp, png_uint_32 frames, png_uint_32 plays)
+{
+	png_byte chunkc[] = "acTL";
+	png_byte buf[8];
+	png_save_uint_32(buf, frames);
+	png_save_uint_32(buf + 4, plays);
+	png_write_chunk(pngp, chunkc, buf, 8);
+}
+
+png_uint_32
+apng_set_acTL (png_structp pngp, png_infop infop, apng_infop ainfop,
+		png_uint_32 frames, png_uint_32 plays)
+{
+	(void)pngp;
+	(void)infop;
+	if (!( pngp && infop && ainfop ))
+		return 0;
+
+	ainfop->num_frames = frames;
+	ainfop->num_plays  = plays;
+
+	ainfop->valid |= APNG_INFO_acTL;
+
+	return 1;
+}
+
+void
+apng_write_info_before_PLTE (png_structp pngp, png_infop infop,
+		apng_infop ainfop)
+{
+	if (!( pngp && infop && ainfop ))
+		return;
+
+	png_write_info_before_PLTE(pngp, infop);
+
+	if (( ainfop->valid & APNG_INFO_acTL )&&!( ainfop->mode & APNG_WROTE_acTL ))
+	{
+		ainfop->start_acTL = apng_tell(pngp, ainfop);
+
+		apng_write_acTL(pngp, 0, 0);
+		/* modified for runtime dynamic linking */
+		(*(ainfop->set_acTL_fn))(pngp, infop, PNG_UINT_31_MAX, 0);
+
+		ainfop->mode |= APNG_WROTE_acTL;
+	}
+}
+
+void
+apng_write_info (png_structp pngp, png_infop infop,
+		apng_infop ainfop)
+{
+	apng_write_info_before_PLTE(pngp, infop, ainfop);
+	png_write_info(pngp, infop);
+}
+
+void
+apng_write_end (png_structp pngp, png_infop infop, apng_infop ainfop)
+{
+	(void)infop;
+	apng_write_IEND(pngp);
+	apng_seek(pngp, ainfop, ainfop->start_acTL);
+	apng_write_acTL(pngp, ainfop->num_frames, ainfop->num_plays);
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+#ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED
+	apng_flush(pngp, infop);
+#endif/* PNG_WRITE_FLUSH_SUPPORTED */
+#endif/* PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED */
+}
+
+#ifndef PNG_WRITE_APNG_SUPPORTED
+png_uint_32
+apng_set_acTL_dummy (png_structp pngp, png_infop infop,
+		png_uint_32 frames, png_uint_32 plays)
+{
+	(void)pngp;
+	(void)infop;
+	(void)frames;
+	(void)plays;
+	return 0;
+}
+#endif/* PNG_WRITE_APNG_SUPPORTED */
+
+/* Dynamic runtime linking capable! (Hopefully.) */
+void
+apng_set_set_acTL_fn (png_structp pngp, apng_infop ainfop,
+		apng_set_acTL_ptr set_acTL_f)
+{
+	(void)pngp;
+	if (!ainfop->set_acTL_fn)
+#ifndef PNG_WRITE_APNG_SUPPORTED
+		ainfop->set_acTL_fn = &apng_set_acTL_dummy;
+#else
+		ainfop->set_acTL_fn = &png_set_acTL;
+#endif/* PNG_WRITE_APNG_SUPPORTED */
+	else
+		ainfop->set_acTL_fn = set_acTL_f;
+}
diff --git a/src/apng.h b/src/apng.h
new file mode 100644
index 0000000000000000000000000000000000000000..aa7fac3df34e7ee14fe9504722a87005cf0f4ae9
--- /dev/null
+++ b/src/apng.h
@@ -0,0 +1,82 @@
+/*
+Copyright 2019, James R.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef APNG_H
+#define APNG_H
+
+#ifndef _MSC_VER
+#ifndef _WII
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#endif
+#endif
+
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
+#endif
+
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 0
+#endif
+
+#include <png.h>
+
+typedef struct apng_info_def apng_info;
+typedef apng_info * apng_infop;
+typedef const apng_info * apng_const_infop;
+typedef apng_info * * apng_infopp;
+
+typedef void   (*apng_seek_ptr)(png_structp, size_t);
+typedef size_t (*apng_tell_ptr)(png_structp);
+
+typedef png_uint_32 (*apng_set_acTL_ptr)(png_structp, png_infop,
+		png_uint_32, png_uint_32);
+
+apng_infop apng_create_info_struct (png_structp png_ptr);
+
+void apng_destroy_info_struct (png_structp png_ptr,
+		apng_infopp info_ptr_ptr);
+
+/* Call the following functions in place of the libpng counterparts. */
+
+png_uint_32 apng_set_acTL (png_structp png_ptr, png_infop info_ptr,
+		apng_infop ainfo_ptr,
+		png_uint_32 num_frames, png_uint_32 num_plays);
+
+void apng_write_info_before_PLTE (png_structp png_ptr, png_infop info_ptr,
+		apng_infop ainfo_ptr);
+void apng_write_info (png_structp png_ptr, png_infop info_ptr,
+		apng_infop ainfo_ptr);
+
+void apng_write_end (png_structp png_ptr, png_infop info_ptr,
+		apng_infop ainfo_ptr);
+
+void apng_set_write_fn (png_structp png_ptr, apng_infop ainfo_ptr,
+		png_voidp io_ptr,
+		png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn,
+		apng_seek_ptr output_seek_fn, apng_tell_ptr output_tell_fn);
+
+void apng_set_set_acTL_fn (png_structp png_ptr, apng_infop ainfo_ptr,
+		apng_set_acTL_ptr set_acTL_fn);
+
+#endif/* APNG_H */
diff --git a/src/asm_defs.inc b/src/asm_defs.inc
index db59d2c696df1024b161324d963461e2d16aee0c..e494a676e7f62aac28f12f616b9dfed2fc4bdc06 100644
--- a/src/asm_defs.inc
+++ b/src/asm_defs.inc
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2014 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/b_bot.c b/src/b_bot.c
index 543bcb183000d42db13f6a534c5bfad0d96460a2..17211b3532d2ace7448fd3a7f1cafac82a1bef9e 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2007-2016 by John "JTE" Muniz.
-// Copyright (C) 2011-2016 by Sonic Team Junior.
+// Copyright (C) 2011-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -275,8 +275,7 @@ void B_RespawnBot(INT32 playernum)
 	player->accelstart = sonic->player->accelstart;
 	player->thrustfactor = sonic->player->thrustfactor;
 	player->normalspeed = sonic->player->normalspeed;
-	player->pflags |= PF_AUTOBRAKE;
-	player->pflags &= ~PF_DIRECTIONCHAR;
+	player->pflags |= PF_AUTOBRAKE|(sonic->player->pflags & PF_DIRECTIONCHAR);
 
 	P_TeleportMove(tails, x, y, z);
 	if (player->charability == CA_FLY)
diff --git a/src/b_bot.h b/src/b_bot.h
index 259405f38ad3df3708294849f9515c80fe40151f..20b2803b69ccf4af24432f4f7f7058aaf7cd271c 100644
--- a/src/b_bot.h
+++ b/src/b_bot.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2007-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/byteptr.h b/src/byteptr.h
index 410d7c00442f77767a436a9f252b8082f67aa818..aa09d6be41f5251400cc902929c2ab87397f3802 100644
--- a/src/byteptr.h
+++ b/src/byteptr.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,7 +15,9 @@
 #define DEALIGNED
 #endif
 
-#ifndef _BIG_ENDIAN
+#include "endian.h"
+
+#ifndef SRB2_BIG_ENDIAN
 //
 // Little-endian machines
 //
@@ -75,7 +77,7 @@
 #define READANGLE(p)        *((angle_t *)p)++
 #endif
 
-#else //_BIG_ENDIAN
+#else //SRB2_BIG_ENDIAN
 //
 // definitions for big-endian machines with alignment constraints.
 //
@@ -144,7 +146,7 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr)
 #define READCHAR(p)         ({    char *p_tmp = (   char *)p;    char b =        *p_tmp; p_tmp++; p = (void *)p_tmp; b; })
 #define READFIXED(p)        ({ fixed_t *p_tmp = (fixed_t *)p; fixed_t b =   readlong(p); p_tmp++; p = (void *)p_tmp; b; })
 #define READANGLE(p)        ({ angle_t *p_tmp = (angle_t *)p; angle_t b =  readulong(p); p_tmp++; p = (void *)p_tmp; b; })
-#endif //_BIG_ENDIAN
+#endif //SRB2_BIG_ENDIAN
 
 #undef DEALIGNED
 
diff --git a/src/command.c b/src/command.c
index 62431b6647b5185f2d7596c93155962970e7ef65..600803cdbc49033ab7ab7ecdcbb0a77efa8fd551 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -32,6 +32,7 @@
 #include "hu_stuff.h"
 #include "p_setup.h"
 #include "lua_script.h"
+#include "d_netfil.h" // findfile
 
 //========
 // protos.
@@ -49,6 +50,8 @@ static void COM_Wait_f(void);
 static void COM_Help_f(void);
 static void COM_Toggle_f(void);
 
+static void CV_EnforceExecVersion(void);
+static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
 static consvar_t *CV_FindVar(const char *name);
 static const char *CV_StringValue(const char *var_name);
@@ -62,7 +65,20 @@ 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}};
 
+// Filter consvars by EXECVERSION
+// First implementation is 26 (2.1.21), so earlier configs default at 25 (2.1.20)
+// Also set CV_HIDEN during runtime, after config is loaded
+static boolean execversion_enabled = false;
+consvar_t cv_execversion = {"execversion","25",CV_CALL,CV_Unsigned, CV_EnforceExecVersion, 0, NULL, NULL, 0, 0, NULL};
+
+// for default joyaxis detection
+static boolean joyaxis_default = false;
+static boolean joyaxis2_default = false;
+static INT32 joyaxis_count = 0;
+static INT32 joyaxis2_count = 0;
+
 #define COM_BUF_SIZE 8192 // command buffer size
+#define MAX_ALIAS_RECURSION 100 // max recursion allowed for aliases
 
 static INT32 com_wait; // one command per frame (for cmd sequences)
 
@@ -132,6 +148,20 @@ void COM_BufInsertText(const char *ptext)
 	}
 }
 
+/** Progress the wait timer and flush waiting console commands when ready.
+  */
+void
+COM_BufTicker(void)
+{
+	if (com_wait)
+	{
+		com_wait--;
+		return;
+	}
+
+	COM_BufExecute();
+}
+
 /** Flushes (executes) console commands in the buffer.
   */
 void COM_BufExecute(void)
@@ -141,12 +171,6 @@ void COM_BufExecute(void)
 	char line[1024] = "";
 	INT32 quotes;
 
-	if (com_wait)
-	{
-		com_wait--;
-		return;
-	}
-
 	while (com_text.cursize)
 	{
 		// find a '\n' or; line break
@@ -485,6 +509,7 @@ static void COM_ExecuteString(char *ptext)
 {
 	xcommand_t *cmd;
 	cmdalias_t *a;
+	static INT32 recursion = 0; // detects recursion and stops it if it goes too far
 
 	COM_TokenizeString(ptext);
 
@@ -507,7 +532,14 @@ static void COM_ExecuteString(char *ptext)
 	{
 		if (!stricmp(com_argv[0], a->name))
 		{
-			COM_BufInsertText(a->value);
+			if (recursion > MAX_ALIAS_RECURSION)
+				CONS_Alert(CONS_WARNING, M_GetText("Alias recursion cycle detected!\n"));
+			else
+			{ // Monster Iestyn: keep track of how many levels of recursion we're in
+				recursion++;
+				COM_BufInsertText(a->value);
+				recursion--;
+			}
 			return;
 		}
 	}
@@ -617,6 +649,7 @@ static void COM_CEchoDuration_f(void)
 static void COM_Exec_f(void)
 {
 	UINT8 *buf = NULL;
+	char filename[256];
 
 	if (COM_Argc() < 2 || COM_Argc() > 3)
 	{
@@ -625,13 +658,23 @@ static void COM_Exec_f(void)
 	}
 
 	// load file
+	// Try with Argv passed verbatim first, for back compat
 	FIL_ReadFile(COM_Argv(1), &buf);
 
 	if (!buf)
 	{
-		if (!COM_CheckParm("-noerror"))
-			CONS_Printf(M_GetText("couldn't execute file %s\n"), COM_Argv(1));
-		return;
+		// Now try by searching the file path
+		// filename is modified with the full found path
+		strcpy(filename, COM_Argv(1));
+		if (findfile(filename, NULL, true) != FS_NOTFOUND)
+			FIL_ReadFile(filename, &buf);
+
+		if (!buf)
+		{
+			if (!COM_CheckParm("-noerror"))
+				CONS_Printf(M_GetText("couldn't execute file %s\n"), COM_Argv(1));
+			return;
+		}
 	}
 
 	if (!COM_CheckParm("-silent"))
@@ -1066,7 +1109,7 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 		if (var->flags & CV_FLOAT)
 		{
 			double d = atof(valstr);
-			if (!d && valstr[0] != '0')
+			if (fpclassify(d) == FP_ZERO && valstr[0] != '0')
 				v = INT32_MIN;
 			else
 				v = (INT32)(d * FRACUNIT);
@@ -1229,7 +1272,7 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
 	char *svalue;
 	UINT8 stealth = false;
 
-	if (playernum != serverplayer && playernum != adminplayer && !serverloading)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum) && !serverloading)
 	{
 		// not from server or remote admin, must be hacked/buggy client
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal netvar command received from %s\n"), player_names[playernum]);
@@ -1355,7 +1398,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 		// send the value of the variable
 		UINT8 buf[128];
 		UINT8 *p = buf;
-		if (!(server || (adminplayer == consoleplayer)))
+		if (!(server || (IsPlayerAdmin(consoleplayer))))
 		{
 			CONS_Printf(M_GetText("Only the server or admin can change: %s %s\n"), var->name, var->string);
 			return;
@@ -1599,6 +1642,210 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 	var->changed = 1; // user has changed it now
 }
 
+void CV_InitFilterVar(void)
+{
+	joyaxis_default = joyaxis2_default = true;
+	joyaxis_count = joyaxis2_count = 0;
+}
+
+void CV_ToggleExecVersion(boolean enable)
+{
+	execversion_enabled = enable;
+}
+
+static void CV_EnforceExecVersion(void)
+{
+	if (!execversion_enabled)
+		CV_StealthSetValue(&cv_execversion, EXECVERSION);
+}
+
+static boolean CV_FilterJoyAxisVars(consvar_t *v, const char *valstr)
+{
+	// If ALL axis settings are previous defaults, set them to the new defaults
+	// EXECVERSION < 26 (2.1.21)
+
+	if (joyaxis_default)
+	{
+#if !defined (_WII) && !defined  (WMINPUT)
+		if (!stricmp(v->name, "joyaxis_turn"))
+		{
+			if (joyaxis_count > 6) return false;
+			// we're currently setting the new defaults, don't interfere
+			else if (joyaxis_count == 6) return true;
+
+			if (!stricmp(valstr, "X-Axis")) joyaxis_count++;
+			else joyaxis_default = false;
+		}
+#if !defined (PSP)
+		if (!stricmp(v->name, "joyaxis_move"))
+		{
+			if (joyaxis_count > 6) return false;
+			else if (joyaxis_count == 6) return true;
+
+			if (!stricmp(valstr, "Y-Axis")) joyaxis_count++;
+			else joyaxis_default = false;
+		}
+#endif
+#if !defined (_arch_dreamcast) && !defined (_XBOX) && !defined (PSP)
+		if (!stricmp(v->name, "joyaxis_side"))
+		{
+			if (joyaxis_count > 6) return false;
+			else if (joyaxis_count == 6) return true;
+
+			if (!stricmp(valstr, "Z-Axis")) joyaxis_count++;
+			else joyaxis_default = false;
+		}
+#endif
+#if !defined (_XBOX) && !defined (PSP)
+		if (!stricmp(v->name, "joyaxis_look"))
+		{
+			if (joyaxis_count > 6) return false;
+			else if (joyaxis_count == 6) return true;
+
+			if (!stricmp(valstr, "None")) joyaxis_count++;
+			else joyaxis_default = false;
+		}
+#endif
+		if (!stricmp(v->name, "joyaxis_fire")
+			|| !stricmp(v->name, "joyaxis_firenormal"))
+		{
+			if (joyaxis_count > 6) return false;
+			else if (joyaxis_count == 6) return true;
+
+			if (!stricmp(valstr, "None")) joyaxis_count++;
+			else joyaxis_default = false;
+		}
+#endif
+		// reset all axis settings to defaults
+		if (joyaxis_count == 6)
+		{
+			COM_BufInsertText(va("%s \"%s\"\n", cv_turnaxis.name, cv_turnaxis.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_moveaxis.name, cv_moveaxis.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_sideaxis.name, cv_sideaxis.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_lookaxis.name, cv_lookaxis.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_fireaxis.name, cv_fireaxis.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_firenaxis.name, cv_firenaxis.defaultvalue));
+			joyaxis_count++;
+			return false;
+		}
+	}
+
+	if (joyaxis2_default)
+	{
+#if !defined (_WII) && !defined  (WMINPUT)
+		if (!stricmp(v->name, "joyaxis2_turn"))
+		{
+			if (joyaxis2_count > 6) return false;
+			// we're currently setting the new defaults, don't interfere
+			else if (joyaxis2_count == 6) return true;
+
+			if (!stricmp(valstr, "X-Axis")) joyaxis2_count++;
+			else joyaxis2_default = false;
+		}
+// #if !defined (PSP)
+		if (!stricmp(v->name, "joyaxis2_move"))
+		{
+			if (joyaxis2_count > 6) return false;
+			else if (joyaxis2_count == 6) return true;
+
+			if (!stricmp(valstr, "Y-Axis")) joyaxis2_count++;
+			else joyaxis2_default = false;
+		}
+// #endif
+#if !defined (_arch_dreamcast) && !defined (_XBOX) && !defined (PSP)
+		if (!stricmp(v->name, "joyaxis2_side"))
+		{
+			if (joyaxis2_count > 6) return false;
+			else if (joyaxis2_count == 6) return true;
+
+			if (!stricmp(valstr, "Z-Axis")) joyaxis2_count++;
+			else joyaxis2_default = false;
+		}
+#endif
+#if !defined (_XBOX) // && !defined (PSP)
+		if (!stricmp(v->name, "joyaxis2_look"))
+		{
+			if (joyaxis2_count > 6) return false;
+			else if (joyaxis2_count == 6) return true;
+
+			if (!stricmp(valstr, "None")) joyaxis2_count++;
+			else joyaxis2_default = false;
+		}
+#endif
+		if (!stricmp(v->name, "joyaxis2_fire")
+			|| !stricmp(v->name, "joyaxis2_firenormal"))
+		{
+			if (joyaxis2_count > 6) return false;
+			else if (joyaxis2_count == 6) return true;
+
+			if (!stricmp(valstr, "None")) joyaxis2_count++;
+			else joyaxis2_default = false;
+		}
+#endif
+
+		// reset all axis settings to defaults
+		if (joyaxis2_count == 6)
+		{
+			COM_BufInsertText(va("%s \"%s\"\n", cv_turnaxis2.name, cv_turnaxis2.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_moveaxis2.name, cv_moveaxis2.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_sideaxis2.name, cv_sideaxis2.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_lookaxis2.name, cv_lookaxis2.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_fireaxis2.name, cv_fireaxis2.defaultvalue));
+			COM_BufInsertText(va("%s \"%s\"\n", cv_firenaxis2.name, cv_firenaxis2.defaultvalue));
+			joyaxis2_count++;
+			return false;
+		}
+	}
+
+	// we haven't reached our counts yet, or we're not default
+	return true;
+}
+
+static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr)
+{
+	// True means allow the CV change, False means block it
+
+	// We only care about CV_SAVE because this filters the user's config files
+	// We do this same check in CV_Command
+	if (!(v->flags & CV_SAVE))
+		return true;
+
+	if (GETMAJOREXECVERSION(cv_execversion.value) < 26) // 26 = 2.1.21
+	{
+		// MOUSE SETTINGS
+		// alwaysfreelook split between first and third person (chasefreelook)
+		// mousemove was on by default, which invalidates the current approach
+		if (!stricmp(v->name, "alwaysmlook")
+			|| !stricmp(v->name, "alwaysmlook2")
+			|| !stricmp(v->name, "mousemove")
+			|| !stricmp(v->name, "mousemove2"))
+			return false;
+
+		// mousesens was changed from 35 to 20 due to oversensitivity
+		if ((!stricmp(v->name, "mousesens")
+			|| !stricmp(v->name, "mousesens2")
+			|| !stricmp(v->name, "mouseysens")
+			|| !stricmp(v->name, "mouseysens2"))
+			&& atoi(valstr) == 35)
+			return false;
+
+		// JOYSTICK DEFAULTS
+		// use_joystick was changed from 0 to 1 to automatically use a joystick if available
+#if defined(HAVE_SDL) || defined(_WINDOWS)
+		if ((!stricmp(v->name, "use_joystick")
+			|| !stricmp(v->name, "use_joystick2"))
+			&& atoi(valstr) == 0)
+			return false;
+#endif
+
+		// axis defaults were changed to be friendly to 360 controllers
+		// if ALL axis settings are defaults, then change them to new values
+		if (!CV_FilterJoyAxisVars(v, valstr))
+			return false;
+	}
+	return true;
+}
+
 /** Displays or changes a variable from the console.
   * Since the user is presumed to have been directly responsible
   * for this change, the variable is marked as changed this game.
@@ -1623,8 +1870,11 @@ static boolean CV_Command(void)
 		return true;
 	}
 
-	CV_Set(v, COM_Argv(1));
-	v->changed = 1; // now it's been changed by (presumably) the user
+	if (!(v->flags & CV_SAVE) || CV_FilterVarByVersion(v, COM_Argv(1)))
+	{
+		CV_Set(v, COM_Argv(1));
+		v->changed = 1; // now it's been changed by (presumably) the user
+	}
 	return true;
 }
 
diff --git a/src/command.h b/src/command.h
index 989ead8cf6366a88eb87954a710ab511135e3e05..4682ba4a4cf4f066599f8426c04b27dc05fa966c 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -45,6 +45,9 @@ void COM_ImmedExecute(const char *ptext);
 // Execute commands in buffer, flush them
 void COM_BufExecute(void);
 
+// As above; and progress the wait timer.
+void COM_BufTicker(void);
+
 // setup command buffer, at game tartup
 void COM_Init(void);
 
@@ -125,6 +128,13 @@ extern CV_PossibleValue_t CV_OnOff[];
 extern CV_PossibleValue_t CV_YesNo[];
 extern CV_PossibleValue_t CV_Unsigned[];
 extern CV_PossibleValue_t CV_Natural[];
+
+// Filter consvars by version
+extern consvar_t cv_execversion;
+
+void CV_InitFilterVar(void);
+void CV_ToggleExecVersion(boolean enable);
+
 // register a variable for use at the console
 void CV_RegisterVar(consvar_t *variable);
 
diff --git a/src/config.h.in b/src/config.h.in
index 5f06ec45df306429a53eb26dd883939d5173df76..fc32aef823e27f61abf1e22859073112646158ef 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -11,32 +11,29 @@
 
 #ifdef CMAKECONFIG
 
-#define ASSET_HASH_SRB2_SRB   "${SRB2_ASSET_srb2.srb_HASH}"
+#define ASSET_HASH_SRB2_PK3   "${SRB2_ASSET_srb2.pk3_HASH}"
 #define ASSET_HASH_PLAYER_DTA "${SRB2_ASSET_player.dta_HASH}"
-#define ASSET_HASH_RINGS_DTA  "${SRB2_ASSET_rings.dta_HASH}"
 #define ASSET_HASH_ZONES_DTA  "${SRB2_ASSET_zones.dta_HASH}"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_DTA  "${SRB2_ASSET_patch.dta_HASH}"
+#define ASSET_HASH_PATCH_PK3  "${SRB2_ASSET_patch.pk3_HASH}"
 #endif
 
 #define SRB2_COMP_REVISION    "${SRB2_COMP_REVISION}"
 #define SRB2_COMP_BRANCH      "${SRB2_COMP_BRANCH}"
-#define SRB2_GIT_DESCRIBE     "${SRB2_GIT_DESCRIBE}"
-#define SRB2_GIT_BRANCH       "${SRB2_GIT_BRANCH}"
 
 #define CMAKE_ASSETS_DIR      "${CMAKE_SOURCE_DIR}/assets"
 
 #else
 
 /* Manually defined asset hashes for non-CMake builds
- * Last updated 2015 / 05 / 03
+ * Last updated 2015 / 05 / 03 - v2.1.15 - main assets
+ * Last updated 2018 / ?? / ?? - v2.2 - patch.pk3
  */
-#define ASSET_HASH_SRB2_SRB   "c1b9577687f8a795104aef4600720ea7"
+#define ASSET_HASH_SRB2_PK3   "c1b9577687f8a795104aef4600720ea7"
 #define ASSET_HASH_ZONES_DTA  "303838c6c534d9540288360fa49cca60"
 #define ASSET_HASH_PLAYER_DTA "cfca0f1c73023cbbd8f844f45480f799"
-#define ASSET_HASH_RINGS_DTA  "85901ad4bf94637e5753d2ac2c03ea26"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_DTA  "dbbf8bc6121618ee3be2d5b14650429b"
+#define ASSET_HASH_PATCH_PK3  "dbbf8bc6121618ee3be2d5b14650429b"
 #endif
 
 #endif
diff --git a/src/console.c b/src/console.c
index a504af06d7f4d28f88c0d1f1c40e806c9f5e2b2a..09a6cab453b288f83d9d5778ab88cc878837642a 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -56,10 +56,7 @@ static boolean consoleready;  // console prompt is ready
        INT32 con_destlines; // vid lines used by console at final position
 static INT32 con_curlines;  // vid lines currently used by console
 
-       INT32 con_clipviewtop; // clip value for planes & sprites, so that the
-                            // part of the view covered by the console is not
-                            // drawn when not needed, this must be -1 when
-                            // console is off
+       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
@@ -96,11 +93,10 @@ static size_t input_len; // length of current line, used to bound cursor and suc
 // protos.
 static void CON_InputInit(void);
 static void CON_RecalcSize(void);
+static void CON_ChangeHeight(void);
 
 static void CONS_hudlines_Change(void);
 static void CONS_backcolor_Change(void);
-static void CON_DrawBackpic(patch_t *pic, INT32 startx, INT32 destwidth);
-//static void CON_DrawBackpic2(pic_t *pic, INT32 startx, INT32 destwidth);
 
 //======================================================================
 //                   CONSOLE VARS AND COMMANDS
@@ -131,11 +127,16 @@ static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"}
 // whether to use console background picture, or translucent mode
 static consvar_t cons_backpic = {"con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, 	{1, "Gray"},	{2, "Brown"},
-												{3, "Red"},		{4, "Orange"},	{5, "Yellow"},
-												{6, "Green"},	{7, "Blue"},	{8, "Purple"},
-												{9, "Magenta"},	{10, "Aqua"},
+static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, 		{1, "Black"},		{2, "Sepia"},
+												{3, "Brown"},		{4, "Pink"},		{5, "Raspberry"},
+												{6, "Red"},			{7, "Creamsicle"},	{8, "Orange"},
+												{9, "Gold"},		{10,"Yellow"},		{11,"Emerald"},
+												{12,"Green"},		{13,"Cyan"},		{14,"Steel"},
+												{15,"Periwinkle"},	{16,"Blue"},		{17,"Purple"},
+												{18,"Lavender"},
 												{0, NULL}};
+
+
 consvar_t cons_backcolor = {"con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change, 0, NULL, NULL, 0, 0, NULL};
 
 static void CON_Print(char *msg);
@@ -226,99 +227,139 @@ static void CONS_Bind_f(void)
 // Font colormap colors
 // TODO: This could probably be improved somehow...
 // These colormaps are 99% identical, with just a few changed bytes
-UINT8 *yellowmap;
-UINT8 *purplemap;
-UINT8 *lgreenmap;
-UINT8 *bluemap;
-UINT8 *graymap;
-UINT8 *redmap;
-UINT8 *orangemap;
+// This could EASILY be handled by modifying a centralised colormap
+// for software depending on the prior state - but yknow, OpenGL...
+UINT8 *yellowmap, *magentamap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap, *skymap, *purplemap, *aquamap, *peridotmap, *azuremap, *brownmap, *rosymap, *invertmap;
 
 // Console BG color
 UINT8 *consolebgmap = NULL;
+UINT8 *promptbgmap = NULL;
+static UINT8 promptbgcolor = UINT8_MAX;
 
-void CON_SetupBackColormap(void)
+void CON_SetupBackColormapEx(INT32 color, boolean prompt)
 {
 	UINT16 i, palsum;
-	UINT8 j, palindex, shift;
+	UINT8 j, palindex;
 	UINT8 *pal = W_CacheLumpName(GetPalette(), PU_CACHE);
+	INT32 shift = 6;
 
-	if (!consolebgmap)
-		consolebgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+	if (color == INT32_MAX)
+		color = cons_backcolor.value;
 
 	shift = 6; // 12 colors -- shift of 7 means 6 colors
-	switch (cons_backcolor.value)
+
+	switch (color)
 	{
-		case 0:		palindex = 15; 	break; // White
-		case 1:		palindex = 31;	break; // Gray
-		case 2:		palindex = 239;	break; // Brown
-		case 3:		palindex = 47;	break; // Red
-		case 4:		palindex = 63;	break; // Orange
-		case 5:		palindex = 79;	shift = 7;	break; // Yellow
-		case 6:		palindex = 111;	break; // Green
-		case 7:		palindex = 159;	break; // Blue
-		case 8:		palindex = 199;	shift = 7; break; // Purple
-		case 9:		palindex = 187;	break; // Magenta
-		case 10:	palindex = 139;	break; // Aqua
+		case 0:		palindex = 15; 	break; 	// White
+		case 1:		palindex = 31;	break; 	// Black
+		case 2:		palindex = 251;	break;	// Sepia
+		case 3:		palindex = 239;	break; 	// Brown
+		case 4:		palindex = 215; shift = 7; 	break; 	// Pink
+		case 5:		palindex = 37; shift = 7;	break; 	// Raspberry
+		case 6:		palindex = 47; shift = 7;	break; 	// Red
+		case 7:		palindex = 53;	shift = 7;	break;	// Creamsicle
+		case 8:		palindex = 63;	break; 	// Orange
+		case 9:		palindex = 56; shift = 7;	break; 	// Gold
+		case 10:	palindex = 79; shift = 7;	break; 	// Yellow
+		case 11:	palindex = 119; shift = 7; 	break; 	// Emerald
+		case 12:	palindex = 111;	break; 	// Green
+		case 13:	palindex = 136;	shift = 7; break; 	// Cyan
+		case 14:	palindex = 175; shift = 7;	break; 	// Steel
+		case 15:	palindex = 166;	shift = 7; 	break; 	// Periwinkle
+		case 16:	palindex = 159;	break; 	// Blue
+		case 17:	palindex = 187; shift = 7; 	break; 	// Purple
+		case 18:	palindex = 199; shift = 7; 	break; 	// Lavender
 		// Default green
-		default:	palindex = 175; break;
-}
+		default:	palindex = 111; break;
+	}
+
+	if (prompt)
+	{
+		if (!promptbgmap)
+			promptbgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+
+		if (color == promptbgcolor)
+			return;
+		else
+			promptbgcolor = color;
+	}
+	else if (!consolebgmap)
+		consolebgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
 
 	// setup background colormap
 	for (i = 0, j = 0; i < 768; i += 3, j++)
 	{
 		palsum = (pal[i] + pal[i+1] + pal[i+2]) >> shift;
-		consolebgmap[j] = (UINT8)(palindex - palsum);
+		if (prompt)
+			promptbgmap[j] = (UINT8)(palindex - palsum);
+		else
+			consolebgmap[j] = (UINT8)(palindex - palsum);
 	}
 }
 
+void CON_SetupBackColormap(void)
+{
+	CON_SetupBackColormapEx(cons_backcolor.value, false);
+	CON_SetupBackColormapEx(1, true); // default to gray
+}
+
 static void CONS_backcolor_Change(void)
 {
-	CON_SetupBackColormap();
+	CON_SetupBackColormapEx(cons_backcolor.value, false);
 }
 
 static void CON_SetupColormaps(void)
 {
 	INT32 i;
-
-	yellowmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	graymap   = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	purplemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	lgreenmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	bluemap   = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	redmap    = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	orangemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+	UINT8 *memorysrc = (UINT8 *)Z_Malloc((256*15), PU_STATIC, NULL);
+
+	magentamap = memorysrc;
+	yellowmap  = (magentamap+256);
+	lgreenmap  = (yellowmap+256);
+	bluemap    = (lgreenmap+256);
+	redmap     = (bluemap+256);
+	graymap    = (redmap+256);
+	orangemap  = (graymap+256);
+	skymap     = (orangemap+256);
+	purplemap  = (skymap+256);
+	aquamap    = (purplemap+256);
+	peridotmap = (aquamap+256);
+	azuremap   = (peridotmap+256);
+	brownmap   = (azuremap+256);
+	rosymap    = (brownmap+256);
+	invertmap  = (rosymap+256);
 
 	// setup the other colormaps, for console text
 
 	// these don't need to be aligned, unless you convert the
 	// V_DrawMappedPatch() into optimised asm.
 
-	for (i = 0; i < 256; i++)
-	{
-		yellowmap[i] = (UINT8)i; // remap each color to itself...
-		graymap[i] = (UINT8)i;
-		purplemap[i] = (UINT8)i;
-		lgreenmap[i] = (UINT8)i;
-		bluemap[i] = (UINT8)i;
-		redmap[i] = (UINT8)i;
-		orangemap[i] = (UINT8)i;
-	}
-
-	yellowmap[3] = (UINT8)73;
-	yellowmap[9] = (UINT8)66;
-	purplemap[3] = (UINT8)184;
-	purplemap[9] = (UINT8)186;
-	lgreenmap[3] = (UINT8)98;
-	lgreenmap[9] = (UINT8)106;
-	bluemap[3]   = (UINT8)147;
-	bluemap[9]   = (UINT8)158;
-	graymap[3]   = (UINT8)10;
-	graymap[9]   = (UINT8)15;
-	redmap[3]    = (UINT8)210;
-	redmap[9]    = (UINT8)32;
-	orangemap[3] = (UINT8)52;
-	orangemap[9] = (UINT8)57;
+	for (i = 0; i < (256*15); i++, ++memorysrc)
+		*memorysrc = (UINT8)(i & 0xFF); // remap each color to itself...
+
+#define colset(map, a, b, c) \
+	map[1] = (UINT8)a;\
+	map[3] = (UINT8)b;\
+	map[9] = (UINT8)c
+
+	colset(magentamap, 177, 178, 184);
+	colset(yellowmap,   82,  73,  66);
+	colset(lgreenmap,   97,  98, 106);
+	colset(bluemap,    146, 147, 155);
+	colset(redmap,     210,  32,  39);
+	colset(graymap,      6,  8,   14);
+	colset(orangemap,   51,  52,  57);
+	colset(skymap,     129, 130, 133);
+	colset(purplemap,  160, 161, 163);
+	colset(aquamap,    120, 121, 123);
+	colset(peridotmap,  88, 188, 190);
+	colset(azuremap,   144, 145, 170);
+	colset(brownmap,   219, 221, 224);
+	colset(rosymap,    200, 201, 203);
+	colset(invertmap,   27,  26,  22);
+	invertmap[26] = (UINT8)3;
+
+#undef colset
 
 	// Init back colormap
 	CON_SetupBackColormap();
@@ -431,6 +472,12 @@ static void CON_RecalcSize(void)
 		con_destlines = vid.height;
 	}
 
+	if (con_destlines > 0) // Resize console if already open
+	{
+		CON_ChangeHeight();
+		con_curlines = con_destlines;
+	}
+
 	// check for change of video width
 	if (conw == con_width)
 		return; // didn't change
@@ -480,6 +527,20 @@ static void CON_RecalcSize(void)
 	Z_Free(tmp_buffer);
 }
 
+static void CON_ChangeHeight(void)
+{
+	INT32 minheight = 20 * con_scalefactor;	// 20 = 8+8+4
+
+	// toggle console in
+	con_destlines = (cons_height.value*vid.height)/100;
+	if (con_destlines < minheight)
+		con_destlines = minheight;
+	else if (con_destlines > vid.height)
+		con_destlines = vid.height;
+
+	con_destlines &= ~0x3; // multiple of text row height
+}
+
 // Handles Console moves in/out of screen (per frame)
 //
 static void CON_MoveConsole(void)
@@ -568,16 +629,7 @@ void CON_Ticker(void)
 			CON_ClearHUD();
 		}
 		else
-		{
-			// toggle console in
-			con_destlines = (cons_height.value*vid.height)/100;
-			if (con_destlines < minheight)
-				con_destlines = minheight;
-			else if (con_destlines > vid.height)
-				con_destlines = vid.height;
-
-			con_destlines &= ~0x3; // multiple of text row height
-		}
+			CON_ChangeHeight();
 	}
 
 	// console movement
@@ -839,8 +891,9 @@ boolean CON_Responder(event_t *ev)
 			return true;
 		}
 
-		// don't eat the key
-		return false;
+		// ...why shouldn't it eat the key? if it doesn't, it just means you
+		// can control Sonic from the console, which is silly
+		return true;//return false;
 	}
 
 	// command completion forward (tab) and backward (shift-tab)
@@ -1034,15 +1087,20 @@ boolean CON_Responder(event_t *ev)
 	else if (key == KEY_KPADSLASH)
 		key = '/';
 
-	if (shiftdown)
+	if (key >= 'a' && key <= 'z')
+	{
+		if (capslock ^ shiftdown)
+			key = shiftxform[key];
+	}
+	else if (shiftdown)
 		key = shiftxform[key];
 
 	// enter a char into the command prompt
 	if (key < 32 || key > 127)
-		return false;
+		return true;
 
 	// add key to cmd line here
-	if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
+	if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers
 		key = key + 'a' - 'A';
 
 	if (input_sel != input_cur)
@@ -1074,6 +1132,7 @@ static void CON_Print(char *msg)
 {
 	size_t l;
 	INT32 controlchars = 0; // for color changing
+	char color = '\x80';  // keep color across lines
 
 	if (msg == NULL)
 		return;
@@ -1099,7 +1158,7 @@ static void CON_Print(char *msg)
 		{
 			if (*msg & 0x80)
 			{
-				con_line[con_cx++] = *(msg++);
+				color = con_line[con_cx++] = *(msg++);
 				controlchars++;
 				continue;
 			}
@@ -1107,12 +1166,14 @@ static void CON_Print(char *msg)
 			{
 				con_cy--;
 				CON_Linefeed();
+				color = '\x80';
 				controlchars = 0;
 			}
 			else if (*msg == '\n') // linefeed
 			{
 				CON_Linefeed();
-				controlchars = 0;
+				con_line[con_cx++] = color;
+				controlchars = 1;
 			}
 			else if (*msg == ' ') // space
 			{
@@ -1120,7 +1181,8 @@ static void CON_Print(char *msg)
 				if (con_cx - controlchars >= con_width-11)
 				{
 					CON_Linefeed();
-					controlchars = 0;
+					con_line[con_cx++] = color;
+					controlchars = 1;
 				}
 			}
 			else if (*msg == '\t')
@@ -1135,7 +1197,8 @@ static void CON_Print(char *msg)
 				if (con_cx - controlchars >= con_width-11)
 				{
 					CON_Linefeed();
-					controlchars = 0;
+					con_line[con_cx++] = color;
+					controlchars = 1;
 				}
 			}
 			msg++;
@@ -1152,7 +1215,8 @@ static void CON_Print(char *msg)
 		if ((con_cx - controlchars) + l > con_width-11)
 		{
 			CON_Linefeed();
-			controlchars = 0;
+			con_line[con_cx++] = color;
+			controlchars = 1;
 		}
 
 		// a word at a time
@@ -1224,24 +1288,15 @@ void CONS_Printf(const char *fmt, ...)
 	if (con_startup)
 	{
 #ifdef _WINDOWS
-		static lumpnum_t con_backpic_lumpnum = UINT32_MAX;
-		patch_t *con_backpic;
+		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_CACHE);
 
-		if (con_backpic_lumpnum == UINT32_MAX)
-			con_backpic_lumpnum = W_GetNumForName("CONSBACK");
+		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
+		V_DrawScaledPatch(0, 0, 0, con_backpic);
 
-		// We load the raw lump, even in hardware mode
-		con_backpic = (patch_t*)W_CacheLumpNum(con_backpic_lumpnum, PU_CACHE);
-
-		// show startup screen and message using only 'software' graphics
-		// (rendermode may be hardware accelerated, but the video mode is not set yet)
-		CON_DrawBackpic(con_backpic, 0, vid.width); // put console background
-		I_LoadingScreen(txt);
-
-		Z_Unlock(con_backpic);
+		W_UnlockCachedPatch(con_backpic);
+		I_LoadingScreen(txt);				// Win32/OS2 only
 #else
-		// here we display the console background and console text
-		// (no hardware accelerated support for these versions)
+		// here we display the console text
 		CON_Drawer();
 		I_FinishUpdate(); // page flip or blit buffer
 #endif
@@ -1427,8 +1482,8 @@ static void CON_DrawHudlines(void)
 	if (con_hudlines <= 0)
 		return;
 
-	if (chat_on)
-		y = charheight; // leave place for chat input in the first row of text
+	if (chat_on && OLDCHAT)
+		y = charheight; // leave place for chat input in the first row of text (only do it if consolechat is on.)
 	else
 		y = 0;
 
@@ -1468,64 +1523,6 @@ static void CON_DrawHudlines(void)
 	con_clearlines = y; // this is handled by HU_Erase();
 }
 
-// Scale a pic_t at 'startx' pos, to 'destwidth' columns.
-//   startx, destwidth is resolution dependent
-// Used to draw console borders, console background.
-// The pic must be sized BASEVIDHEIGHT height.
-static void CON_DrawBackpic(patch_t *pic, INT32 startx, INT32 destwidth)
-{
-	(void)startx;
-	(void)destwidth;
-	V_DrawScaledPatch(0, 0, 0, pic);
-}
-
-#if 0
-static inline void CON_DrawBackpic2(pic_t *pic, INT32 startx, INT32 destwidth)
-{
-	INT32 x, y;
-	INT32 v;
-	UINT8 *src, *dest;
-	const UINT8 *deststop;
-	INT32 frac, fracstep;
-
-	dest = screens[0]+startx;
-	deststop = screens[0] + vid.rowbytes * vid.height;
-
-	for (y = 0; y < con_curlines; y++, dest += vid.width)
-	{
-		// scale the picture to the resolution
-		v = SHORT(pic->height) - ((con_curlines - y) * (BASEVIDHEIGHT-1) / vid.height) - 1;
-
-		src = pic->data + v*SHORT(pic->width);
-
-		// in case of the console backpic, simplify
-		if (SHORT(pic->width) == destwidth)
-			M_Memcpy(dest, src, destwidth);
-		else
-		{
-			// scale pic to screen width
-			frac = 0;
-			fracstep = (SHORT(pic->width)<<16)/destwidth;
-			for (x = 0; x < destwidth; x += 4)
-			{
-				if (dest+x > deststop) break;
-				dest[x] = src[frac>>FRACBITS];
-				frac += fracstep;
-				if (dest+x+1 > deststop) break;
-				dest[x+1] = src[frac>>FRACBITS];
-				frac += fracstep;
-				if (dest+x+2 > deststop) break;
-				dest[x+2] = src[frac>>FRACBITS];
-				frac += fracstep;
-				if (dest+x+3 > deststop) break;
-				dest[x+3] = src[frac>>FRACBITS];
-				frac += fracstep;
-			}
-		}
-	}
-}
-#endif
-
 // draw the console background, text, and prompt if enough place
 //
 static void CON_DrawConsole(void)
@@ -1548,18 +1545,10 @@ static void CON_DrawConsole(void)
 	// draw console background
 	if (cons_backpic.value || con_forcepic)
 	{
-		static lumpnum_t con_backpic_lumpnum = UINT32_MAX;
-		patch_t *con_backpic;
-
-		if (con_backpic_lumpnum == UINT32_MAX)
-			con_backpic_lumpnum = W_GetNumForName("CONSBACK");
-
-		con_backpic = (patch_t*)W_CachePatchNum(con_backpic_lumpnum, PU_CACHE);
+		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_CACHE);
 
-		if (rendermode != render_soft)
-			V_DrawScaledPatch(0, 0, 0, con_backpic);
-		else if (rendermode != render_none)
-			CON_DrawBackpic(con_backpic, 0, vid.width); // picture as background
+		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
+		V_DrawScaledPatch(0, 0, 0, con_backpic);
 
 		W_UnlockCachedPatch(con_backpic);
 	}
@@ -1577,8 +1566,7 @@ static void CON_DrawConsole(void)
 	i = con_cy - con_scrollup;
 
 	// skip the last empty line due to the cursor being at the start of a new line
-	if (!con_scrollup && !con_cx)
-		i--;
+	i--;
 
 	i -= (con_curlines - minheight) / charheight;
 
@@ -1620,7 +1608,7 @@ void CON_Drawer(void)
 	if (con_curlines > 0)
 		CON_DrawConsole();
 	else if (gamestate == GS_LEVEL
-	|| gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
+	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
 	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
 		CON_DrawHudlines();
 }
diff --git a/src/console.h b/src/console.h
index 1e510e89a204c2774edf779fa77efe770638183d..f628080330f80a6b855af539f3e7e73efb83150e 100644
--- a/src/console.h
+++ b/src/console.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -34,11 +34,13 @@ extern UINT32 con_scalefactor; // console text scale factor
 
 extern consvar_t cons_backcolor;
 
-extern UINT8 *yellowmap, *purplemap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap;
+extern UINT8 *yellowmap, *magentamap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap, *skymap, *purplemap, *aquamap, *peridotmap, *azuremap, *brownmap, *rosymap, *invertmap;
 
 // Console bg color (auto updated to match)
 extern UINT8 *consolebgmap;
+extern UINT8 *promptbgmap;
 
+void CON_SetupBackColormapEx(INT32 color, boolean prompt);
 void CON_SetupBackColormap(void);
 void CON_ClearHUD(void); // clear heads up messages
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 72f60aa9f68c6ec57e9bc9605dfcb8e25500f454..74b3339fc2252a362f615ad8b7f66e697d660942 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -23,6 +23,7 @@
 #include "g_game.h"
 #include "hu_stuff.h"
 #include "keys.h"
+#include "g_input.h" // JOY1
 #include "m_menu.h"
 #include "console.h"
 #include "d_netfil.h"
@@ -395,8 +396,7 @@ static void ExtraDataTicker(void)
 							DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic));
 						}
 						CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
-						D_FreeTextcmd(gametic);
-						return;
+						break;
 					}
 				}
 			}
@@ -513,7 +513,8 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 		rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
 
 	// Score is resynched in the rspfirm resync packet
-	rsp->rings = LONG(players[i].rings);
+	rsp->rings = SHORT(players[i].rings);
+	rsp->spheres = SHORT(players[i].spheres);
 	rsp->lives = players[i].lives;
 	rsp->continues = players[i].continues;
 	rsp->scoreadd = players[i].scoreadd;
@@ -643,7 +644,8 @@ static void resynch_read_player(resynch_pak *rsp)
 		players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]);
 
 	// Score is resynched in the rspfirm resync packet
-	players[i].rings = LONG(rsp->rings);
+	players[i].rings = SHORT(rsp->rings);
+	players[i].spheres = SHORT(rsp->spheres);
 	players[i].lives = rsp->lives;
 	players[i].continues = rsp->continues;
 	players[i].scoreadd = rsp->scoreadd;
@@ -1125,7 +1127,8 @@ static inline void CL_DrawConnectionStatus(void)
 	INT32 ccstime = I_GetTime();
 
 	// Draw background fade
-	V_DrawFadeScreen();
+	if (!menuactive) // menu already draws its own fade
+		V_DrawFadeScreen(0xFF00, 16); // force default
 
 	// Draw the bottom box.
 	M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1);
@@ -1173,22 +1176,37 @@ static inline void CL_DrawConnectionStatus(void)
 		if (lastfilenum != -1)
 		{
 			INT32 dldlength;
-			static char tempname[32];
+			static char tempname[28];
+			fileneeded_t *file = &fileneeded[lastfilenum];
+			char *filename = file->filename;
 
 			Net_GetNetStat();
-			dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
+			dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
 			if (dldlength > 256)
 				dldlength = 256;
 			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111);
 			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 96);
 
 			memset(tempname, 0, sizeof(tempname));
-			nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
+			// offset filename to just the name only part
+			filename += strlen(filename) - nameonlylength(filename);
+
+			if (strlen(filename) > sizeof(tempname)-1) // too long to display fully
+			{
+				size_t endhalfpos = strlen(filename)-10;
+				// display as first 14 chars + ... + last 10 chars
+				// which should add up to 27 if our math(s) is correct
+				snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos);
+			}
+			else // we can copy the whole thing in safely
+			{
+				strncpy(tempname, filename, sizeof(tempname)-1);
+			}
 
 			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
 				va(M_GetText("Downloading \"%s\""), tempname));
 			V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-				va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
+				va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10));
 			V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
 				va("%3.1fK/s ", ((double)getbps)/1024));
 		}
@@ -1367,7 +1385,6 @@ 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.adminplayer = (SINT8)adminplayer;
 
 	// we fill these structs with FFs so that any players not in game get sent as 0xFFFF
 	// which is nice and easy for us to detect
@@ -1375,8 +1392,12 @@ static boolean SV_SendServerConfig(INT32 node)
 	memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
 	memset(netbuffer->u.servercfg.playeravailabilities, 0xFF, sizeof(netbuffer->u.servercfg.playeravailabilities));
 
+	memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers));
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i];
+
 		if (!playeringame[i])
 			continue;
 		netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
@@ -1627,6 +1648,8 @@ static void SendAskInfo(INT32 node, boolean viams)
 serverelem_t serverlist[MAXSERVERLIST];
 UINT32 serverlistcount = 0;
 
+#define FORCECLOSE 0x8000
+
 static void SL_ClearServerList(INT32 connectedserver)
 {
 	UINT32 i;
@@ -1634,7 +1657,7 @@ static void SL_ClearServerList(INT32 connectedserver)
 	for (i = 0; i < serverlistcount; i++)
 		if (connectedserver != serverlist[i].node)
 		{
-			Net_CloseConnection(serverlist[i].node);
+			Net_CloseConnection(serverlist[i].node|FORCECLOSE);
 			serverlist[i].node = 0;
 		}
 	serverlistcount = 0;
@@ -1716,12 +1739,25 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
 				// Make sure MS version matches our own, to
 				// thwart nefarious servers who lie to the MS.
 
-				if(strcmp(version, server_list[i].version) == 0)
+				if (strcmp(version, server_list[i].version) == 0)
 				{
 					INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
 					if (node == -1)
 						break; // no more node free
 					SendAskInfo(node, true);
+					// Force close the connection so that servers can't eat
+					// up nodes forever if we never get a reply back from them
+					// (usually when they've not forwarded their ports).
+					//
+					// Don't worry, we'll get in contact with the working
+					// servers again when they send SERVERINFO to us later!
+					//
+					// (Note: as a side effect this probably means every
+					// server in the list will probably be using the same node (e.g. node 1),
+					// not that it matters which nodes they use when
+					// the connections are closed afterwards anyway)
+					// -- Monster Iestyn 12/11/18
+					Net_CloseConnection(node|FORCECLOSE);
 				}
 			}
 		}
@@ -1948,7 +1984,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 
 		I_OsPolling();
 		key = I_GetKey();
-		if (key == KEY_ESCAPE)
+		if (key == KEY_ESCAPE || key == KEY_JOY1+1)
 		{
 			CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
 //				M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
@@ -1967,6 +2003,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 #ifdef CLIENT_LOADINGSCREEN
 		if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
 		{
+			F_MenuPresTicker(true); // title sky
 			F_TitleScreenTicker(true);
 			F_TitleScreenDrawer();
 			CL_DrawConnectionStatus();
@@ -2031,7 +2068,7 @@ static void CL_ConnectToServer(boolean viams)
 	G_SetGamestate(GS_WAITINGPLAYERS);
 	wipegamestate = GS_WAITINGPLAYERS;
 
-	adminplayer = -1;
+	ClearAdminPlayers();
 	pnumnodes = 1;
 	oldtic = I_GetTime() - 1;
 #ifndef NONET
@@ -2239,7 +2276,7 @@ static void Command_connect(void)
 	// Assume we connect directly.
 	boolean viams = false;
 
-	if (COM_Argc() < 2)
+	if (COM_Argc() < 2 || *COM_Argv(1) == 0)
 	{
 		CONS_Printf(M_GetText(
 			"Connect <serveraddress> (port): connect to a server\n"
@@ -2337,7 +2374,7 @@ void CL_ClearPlayer(INT32 playernum)
 //
 // Removes a player from the current game
 //
-static void CL_RemovePlayer(INT32 playernum)
+static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 {
 	// Sanity check: exceptional cases (i.e. c-fails) can cause multiple
 	// kick commands to be issued for the same player.
@@ -2362,11 +2399,11 @@ static void CL_RemovePlayer(INT32 playernum)
 	if (gametype == GT_CTF)
 		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
 
-	// If in a special stage, redistribute the player's rings across
+	// If in a special stage, redistribute the player's spheres across
 	// the remaining players.
 	if (G_IsSpecialStage(gamemap))
 	{
-		INT32 i, count, increment, rings;
+		INT32 i, count, increment, spheres;
 
 		for (i = 0, count = 0; i < MAXPLAYERS; i++)
 		{
@@ -2375,23 +2412,29 @@ static void CL_RemovePlayer(INT32 playernum)
 		}
 
 		count--;
-		rings = players[playernum].rings;
-		increment = rings/count;
+		spheres = players[playernum].spheres;
+		increment = spheres/count;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && i != playernum)
 			{
-				if (rings < increment)
-					P_GivePlayerRings(&players[i], rings);
+				if (spheres < increment)
+					P_GivePlayerSpheres(&players[i], spheres);
 				else
-					P_GivePlayerRings(&players[i], increment);
+					P_GivePlayerSpheres(&players[i], increment);
 
-				rings -= increment;
+				spheres -= increment;
 			}
 		}
 	}
 
+#ifdef HAVE_BLUA
+	LUAh_PlayerQuit(&players[playernum], reason); // Lua hook for player quitting
+#else
+	(void)reason;
+#endif
+
 	// Reset player data
 	CL_ClearPlayer(playernum);
 
@@ -2404,8 +2447,10 @@ static void CL_RemovePlayer(INT32 playernum)
 	// Reset the name
 	sprintf(player_names[playernum], "Player %d", playernum+1);
 
-	if (playernum == adminplayer)
-		adminplayer = -1; // don't stay admin after you're gone
+	if (IsPlayerAdmin(playernum))
+	{
+		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
+	}
 
 	if (playernum == displayplayer)
 		displayplayer = consoleplayer; // don't look through someone's view who isn't there
@@ -2523,7 +2568,7 @@ static void Command_Nodes(void)
 			if (I_GetNodeAddress && (address = I_GetNodeAddress(playernode[i])) != NULL)
 				CONS_Printf(" - %s", address);
 
-			if (i == adminplayer)
+			if (IsPlayerAdmin(i))
 				CONS_Printf(M_GetText(" (verified admin)"));
 
 			if (players[i].spectator)
@@ -2548,7 +2593,7 @@ static void Command_Ban(void)
 		return;
 	}
 
-	if (server || adminplayer == consoleplayer)
+	if (server || IsPlayerAdmin(consoleplayer))
 	{
 		UINT8 buf[3 + MAX_REASONLENGTH];
 		UINT8 *p = buf;
@@ -2569,7 +2614,10 @@ static void Command_Ban(void)
 		else
 		{
 			if (server) // only the server is allowed to do this right now
+			{
 				Ban_Add(COM_Argv(2));
+				D_SaveBan(); // save the ban list
+			}
 
 			if (COM_Argc() == 2)
 			{
@@ -2600,6 +2648,42 @@ static void Command_Ban(void)
 
 }
 
+static void Command_BanIP(void)
+{
+	if (COM_Argc() < 2)
+	{
+		CONS_Printf(M_GetText("banip <ip> <reason>: ban an ip address\n"));
+		return;
+	}
+
+	if (server) // Only the server can use this, otherwise does nothing.
+	{
+		const char *address = (COM_Argv(1));
+		const char *reason;
+
+		if (COM_Argc() == 2)
+			reason = NULL;
+		else
+			reason = COM_Argv(2);
+
+
+		if (I_SetBanAddress && I_SetBanAddress(address, NULL))
+		{
+			if (reason)
+				CONS_Printf("Banned IP address %s for: %s\n", address, reason);
+			else
+				CONS_Printf("Banned IP address %s\n", address);
+
+			Ban_Add(reason);
+			D_SaveBan();
+		}
+		else
+		{
+			return;
+		}
+	}
+}
+
 static void Command_Kick(void)
 {
 	if (COM_Argc() < 2)
@@ -2614,7 +2698,7 @@ static void Command_Kick(void)
 		return;
 	}
 
-	if (server || adminplayer == consoleplayer)
+	if (server || IsPlayerAdmin(consoleplayer))
 	{
 		UINT8 buf[3 + MAX_REASONLENGTH];
 		UINT8 *p = buf;
@@ -2623,13 +2707,16 @@ static void Command_Kick(void)
 		if (pn == -1 || pn == 0)
 			return;
 
-		// Special case if we are trying to kick a player who is downloading the game state:
-		// trigger a timeout instead of kicking them, because a kick would only
-		// take effect after they have finished downloading
-		if (sendingsavegame[playernode[pn]])
+		if (server)
 		{
-			Net_ConnectionTimeout(playernode[pn]);
-			return;
+			// Special case if we are trying to kick a player who is downloading the game state:
+			// trigger a timeout instead of kicking them, because a kick would only
+			// take effect after they have finished downloading
+			if (sendingsavegame[playernode[pn]])
+			{
+				Net_ConnectionTimeout(playernode[pn]);
+				return;
+			}
 		}
 
 		WRITESINT8(p, pn);
@@ -2667,11 +2754,12 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	INT32 pnum, msg;
 	char buf[3 + MAX_REASONLENGTH];
 	char *reason = buf;
+	kickreason_t kickreason = KR_KICK;
 
 	pnum = READUINT8(*p);
 	msg = READUINT8(*p);
 
-	if (pnum == serverplayer && playernum == adminplayer)
+	if (pnum == serverplayer && IsPlayerAdmin(playernum))
 	{
 		CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n"));
 
@@ -2682,7 +2770,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	}
 
 	// Is playernum authorized to make this kick?
-	if (playernum != serverplayer && playernum != adminplayer
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum)
 		&& !(playerpernode[playernode[playernum]] == 2
 		&& nodetoplayer2[playernode[playernum]] == pnum))
 	{
@@ -2731,7 +2819,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		msg = KICK_MSG_CON_FAIL;
 	}
 
-	CONS_Printf("\x82%s ", player_names[pnum]);
+	//CONS_Printf("\x82%s ", player_names[pnum]);
 
 	// If a verified admin banned someone, the server needs to know about it.
 	// If the playernum isn't zero (the server) then the server needs to record the ban.
@@ -2748,15 +2836,18 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	switch (msg)
 	{
 		case KICK_MSG_GO_AWAY:
-			CONS_Printf(M_GetText("has been kicked (Go away)\n"));
+			HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
+			kickreason = KR_KICK;
 			break;
 #ifdef NEWPING
 		case KICK_MSG_PING_HIGH:
-			CONS_Printf(M_GetText("left the game (Broke ping limit)\n"));
+			HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false);
+			kickreason = KR_PINGLIMIT;
 			break;
 #endif
 		case KICK_MSG_CON_FAIL:
-			CONS_Printf(M_GetText("left the game (Synch failure)\n"));
+			HU_AddChatText(va("\x82*%s left the game (Synch Failure)", player_names[pnum]), false);
+			kickreason = KR_SYNCH;
 
 			if (M_CheckParm("-consisdump")) // Helps debugging some problems
 			{
@@ -2792,22 +2883,27 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			}
 			break;
 		case KICK_MSG_TIMEOUT:
-			CONS_Printf(M_GetText("left the game (Connection timeout)\n"));
+			HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false);
+			kickreason = KR_TIMEOUT;
 			break;
 		case KICK_MSG_PLAYER_QUIT:
 			if (netgame) // not splitscreen/bots
-				CONS_Printf(M_GetText("left the game\n"));
+				HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
+			kickreason = KR_LEAVE;
 			break;
 		case KICK_MSG_BANNED:
-			CONS_Printf(M_GetText("has been banned (Don't come back)\n"));
+			HU_AddChatText(va("\x82*%s has been banned (Don't come back)", player_names[pnum]), false);
+			kickreason = KR_BAN;
 			break;
 		case KICK_MSG_CUSTOM_KICK:
 			READSTRINGN(*p, reason, MAX_REASONLENGTH+1);
-			CONS_Printf(M_GetText("has been kicked (%s)\n"), reason);
+			HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false);
+			kickreason = KR_KICK;
 			break;
 		case KICK_MSG_CUSTOM_BAN:
 			READSTRINGN(*p, reason, MAX_REASONLENGTH+1);
-			CONS_Printf(M_GetText("has been banned (%s)\n"), reason);
+			HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false);
+			kickreason = KR_BAN;
 			break;
 	}
 
@@ -2835,7 +2931,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
 	}
 	else
-		CL_RemovePlayer(pnum);
+		CL_RemovePlayer(pnum, kickreason);
 }
 
 consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
@@ -2867,6 +2963,7 @@ void D_ClientServerInit(void)
 	COM_AddCommand("getplayernum", Command_GetPlayerNum);
 	COM_AddCommand("kick", Command_Kick);
 	COM_AddCommand("ban", Command_Ban);
+	COM_AddCommand("banip", Command_BanIP);
 	COM_AddCommand("clearbans", Command_ClearBans);
 	COM_AddCommand("showbanlist", Command_ShowBan);
 	COM_AddCommand("reloadbans", Command_ReloadBan);
@@ -2945,6 +3042,7 @@ void SV_ResetServer(void)
 		playeringame[i] = false;
 		playernode[i] = UINT8_MAX;
 		sprintf(player_names[i], "Player %d", i + 1);
+		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
 	}
 
 	mynode = 0;
@@ -3019,7 +3117,7 @@ void D_QuitNetGame(void)
 	}
 
 	D_CloseConnection();
-	adminplayer = -1;
+	ClearAdminPlayers();
 
 	DEBFILE("===========================================================================\n"
 	        "                         Log finish\n"
@@ -3050,7 +3148,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 	INT16 node, newplayernum;
 	boolean splitscreenplayer;
 
-	if (playernum != serverplayer && playernum != adminplayer)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		// protect against hacked/buggy client
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
@@ -3079,9 +3177,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 	if (newplayernum+1 > doomcom->numslots)
 		doomcom->numslots = (INT16)(newplayernum+1);
 
-	if (netgame)
-		CONS_Printf(M_GetText("Player %d has joined the game (node %d)\n"), newplayernum+1, node);
-
 	// the server is creating my player
 	if (node == mynode)
 	{
@@ -3092,16 +3187,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			displayplayer = newplayernum;
 			secondarydisplayplayer = newplayernum;
 			DEBFILE("spawning me\n");
-			// Apply player flags as soon as possible!
-			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
-			if (cv_flipcam.value)
-				players[newplayernum].pflags |= PF_FLIPCAM;
-			if (cv_analog.value)
-				players[newplayernum].pflags |= PF_ANALOGMODE;
-			if (cv_directionchar.value)
-				players[newplayernum].pflags |= PF_DIRECTIONCHAR;
-			if (cv_autobrake.value)
-				players[newplayernum].pflags |= PF_AUTOBRAKE;
 		}
 		else
 		{
@@ -3109,25 +3194,21 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			DEBFILE("spawning my brother\n");
 			if (botingame)
 				players[newplayernum].bot = 1;
-			// Same goes for player 2 when relevant
-			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
-			if (cv_flipcam2.value)
-				players[newplayernum].pflags |= PF_FLIPCAM;
-			if (cv_analog2.value)
-				players[newplayernum].pflags |= PF_ANALOGMODE;
-			if (cv_directionchar2.value)
-				players[newplayernum].pflags |= PF_DIRECTIONCHAR;
-			if (cv_autobrake2.value)
-				players[newplayernum].pflags |= PF_AUTOBRAKE;
 		}
 		D_SendPlayerConfig();
 		addedtogame = true;
 	}
-	else if (server && netgame && cv_showjoinaddress.value)
+
+	if (netgame)
 	{
-		const char *address;
-		if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL)
-			CONS_Printf(M_GetText("Player Address is %s\n"), address);
+		if (server && cv_showjoinaddress.value)
+		{
+			const char *address;
+			if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL)
+				HU_AddChatText(va("\x82*Player %d has joined the game (node %d) (%s)", newplayernum+1, node, address), false);	// merge join notification + IP to avoid clogging console/chat.
+		}
+		else
+			HU_AddChatText(va("\x82*Player %d has joined the game (node %d)", newplayernum+1, node), false);	// if you don't wanna see the join address.
 	}
 
 	if (server && multiplayer && motd[0] != '\0')
@@ -3311,7 +3392,7 @@ void SV_StopServer(void)
 	localtextcmd[0] = 0;
 	localtextcmd2[0] = 0;
 
-	for (i = 0; i < BACKUPTICS; i++)
+	for (i = firstticstosend; i < firstticstosend + BACKUPTICS; i++)
 		D_Clearticcmd(i);
 
 	consoleplayer = 0;
@@ -3595,7 +3676,8 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
 				gametype = netbuffer->u.servercfg.gametype;
 				modifiedgame = netbuffer->u.servercfg.modifiedgame;
-				adminplayer = netbuffer->u.servercfg.adminplayer;
+				for (j = 0; j < MAXPLAYERS; j++)
+					adminplayers[j] = netbuffer->u.servercfg.adminplayers[j];
 				memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
 			}
 
@@ -4003,7 +4085,8 @@ static void HandlePacketFromPlayer(SINT8 node)
 						INT32 k = *txtpak++; // playernum
 						const size_t txtsize = txtpak[0]+1;
 
-						M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
+						if (i >= gametic) // Don't copy old net commands
+							M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
 						txtpak += txtsize;
 					}
 				}
@@ -4187,14 +4270,9 @@ static INT16 Consistancy(void)
 		ret += P_GetRandSeed();
 
 #ifdef MOBJCONSISTANCY
-	if (!thinkercap.next)
-	{
-		DEBFILE(va("Consistancy = %u\n", ret));
-		return ret;
-	}
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -4463,6 +4541,7 @@ static void Local_Maketic(INT32 realtics)
 void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
 {
 	tic_t tic;
+	UINT8 numadjust = 0;
 
 	(void)x;
 	(void)y;
@@ -4472,7 +4551,21 @@ void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
 	// spawning, but will be applied afterwards.
 
 	for (tic = server ? maketic : (neededtic - 1); tic >= gametic; tic--)
+	{
+		if (numadjust++ == BACKUPTICS)
+		{
+			DEBFILE(va("SV_SpawnPlayer: All netcmds for player %d adjusted!\n", playernum));
+			// We already adjusted them all, waste of time doing the same thing over and over
+			// This shouldn't happen normally though, either gametic was 0 (which is handled now anyway)
+			// or maketic >= gametic + BACKUPTICS
+			// -- Monster Iestyn 16/01/18
+			break;
+		}
 		netcmds[tic%BACKUPTICS][playernum].angleturn = (INT16)((angle>>16) | TICCMD_RECEIVED);
+
+		if (!tic) // failsafe for gametic == 0 -- Monster Iestyn 16/01/18
+			break;
+	}
 }
 
 // create missed tic
@@ -4521,7 +4614,7 @@ void TryRunTics(tic_t realtics)
 
 	if (realtics >= 1)
 	{
-		COM_BufExecute();
+		COM_BufTicker();
 		if (mapchangepending)
 			D_MapChange(-1, 0, ultimatemode, false, 2, false, fromlevelselect); // finish the map change
 	}
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index bdf3326658bd0f15c03ecf2a2744f519f6238295..4ed7ff95725d9af2c8afe07bf53a25171869bafd 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -164,7 +164,8 @@ typedef struct
 	UINT16 powers[NUMPOWERS];
 
 	// Score is resynched in the confirm resync packet
-	INT32 rings;
+	INT16 rings;
+	INT16 spheres;
 	SINT8 lives;
 	SINT8 continues;
 	UINT8 scoreadd;
@@ -290,7 +291,7 @@ typedef struct
 
 	UINT8 gametype;
 	UINT8 modifiedgame;
-	SINT8 adminplayer; // Needs to be signed
+	SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed
 
 	char server_context[8]; // Unique context id, generated at server startup.
 
@@ -438,9 +439,9 @@ extern doomdata_t *netbuffer;
 
 extern consvar_t cv_playbackspeed;
 
-#define BASEPACKETSIZE ((size_t)&(((doomdata_t *)0)->u))
-#define FILETXHEADER ((size_t)((filetx_pak *)0)->data)
-#define BASESERVERTICSSIZE ((size_t)&(((doomdata_t *)0)->u.serverpak.cmds[0]))
+#define BASEPACKETSIZE      offsetof(doomdata_t, u)
+#define FILETXHEADER        offsetof(filetx_pak, data)
+#define BASESERVERTICSSIZE  offsetof(doomdata_t, u.serverpak.cmds[0])
 
 #define KICK_MSG_GO_AWAY     1
 #define KICK_MSG_CON_FAIL    2
@@ -453,6 +454,17 @@ extern consvar_t cv_playbackspeed;
 #define KICK_MSG_CUSTOM_KICK 7
 #define KICK_MSG_CUSTOM_BAN  8
 
+typedef enum
+{
+	KR_KICK          = 1, //Kicked by server
+	KR_PINGLIMIT     = 2, //Broke Ping Limit
+	KR_SYNCH         = 3, //Synch Failure
+	KR_TIMEOUT       = 4, //Connection Timeout
+	KR_BAN           = 5, //Banned by server
+	KR_LEAVE         = 6, //Quit the game
+
+} kickreason_t;
+
 extern boolean server;
 #define client (!server)
 extern boolean dedicated; // For dedicated server
diff --git a/src/d_event.h b/src/d_event.h
index b0d0e3c58f7426512ee4feb2b2cd0914786e38de..e9374efaf539b7970cab644f1ff4365bd488af49 100644
--- a/src/d_event.h
+++ b/src/d_event.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_main.c b/src/d_main.c
index 45f9d67639ea7395ccd5f5c8bd68156a76aefe5a..eaeae4b10f09580b108e46fbc3e0ff80e56b8bdc 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -71,6 +71,7 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #include "fastcmp.h"
 #include "keys.h"
 #include "filesrch.h" // refreshdirmenu, mainwadstally
+#include "g_input.h" // tutorial mode control scheming
 
 #ifdef CMAKECONFIG
 #include "config.h"
@@ -112,13 +113,10 @@ INT32 postimgparam;
 postimg_t postimgtype2 = postimg_none;
 INT32 postimgparam2;
 
-boolean nomidimusic = false, nosound = false;
-boolean nodigimusic = false; // No fmod-based music
-
-// These variables are only true if
-// the respective sound system is initialized
-// and active, but no sounds/music should play.
-boolean music_disabled = false;
+// These variables are in effect
+// whether the respective sound system is disabled
+// or they're init'ed, but the player just toggled them
+boolean midi_disabled = false;
 boolean sound_disabled = false;
 boolean digital_disabled = false;
 
@@ -154,41 +152,16 @@ void D_PostEvent(const event_t *ev)
 	eventhead = (eventhead+1) & (MAXEVENTS-1);
 }
 // just for lock this function
-#ifndef DOXYGEN
+#if defined (PC_DOS) && !defined (DOXYGEN)
 void D_PostEvent_end(void) {};
 #endif
 
 // modifier keys
+// Now handled in I_OsPolling
 UINT8 shiftdown = 0; // 0x1 left, 0x2 right
 UINT8 ctrldown = 0; // 0x1 left, 0x2 right
 UINT8 altdown = 0; // 0x1 left, 0x2 right
-//
-// D_ModifierKeyResponder
-// Sets global shift/ctrl/alt variables, never actually eats events
-//
-static inline void D_ModifierKeyResponder(event_t *ev)
-{
-	if (ev->type == ev_keydown || ev->type == ev_console) switch (ev->data1)
-	{
-		case KEY_LSHIFT: shiftdown |= 0x1; return;
-		case KEY_RSHIFT: shiftdown |= 0x2; return;
-		case KEY_LCTRL: ctrldown |= 0x1; return;
-		case KEY_RCTRL: ctrldown |= 0x2; return;
-		case KEY_LALT: altdown |= 0x1; return;
-		case KEY_RALT: altdown |= 0x2; return;
-		default: return;
-	}
-	else if (ev->type == ev_keyup) switch (ev->data1)
-	{
-		case KEY_LSHIFT: shiftdown &= ~0x1; return;
-		case KEY_RSHIFT: shiftdown &= ~0x2; return;
-		case KEY_LCTRL: ctrldown &= ~0x1; return;
-		case KEY_RCTRL: ctrldown &= ~0x2; return;
-		case KEY_LALT: altdown &= ~0x1; return;
-		case KEY_RALT: altdown &= ~0x2; return;
-		default: return;
-	}
-}
+boolean capslock = 0;	// gee i wonder what this does.
 
 //
 // D_ProcessEvents
@@ -202,9 +175,6 @@ void D_ProcessEvents(void)
 	{
 		ev = &events[eventtail];
 
-		// Set global shift/ctrl/alt down variables
-		D_ModifierKeyResponder(ev); // never eats events
-
 		// Screenshots over everything so that they can be taken anywhere.
 		if (M_ScreenshotResponder(ev))
 			continue; // ate the event
@@ -235,6 +205,9 @@ void D_ProcessEvents(void)
 // wipegamestate can be set to -1 to force a wipe on the next draw
 // added comment : there is a wipe eatch change of the gamestate
 gamestate_t wipegamestate = GS_LEVEL;
+// -1: Default; 0-n: Wipe index; INT16_MAX: do not wipe
+INT16 wipetypepre = -1;
+INT16 wipetypepost = -1;
 
 static void D_Display(void)
 {
@@ -268,7 +241,7 @@ static void D_Display(void)
 
 	// save the current screen if about to wipe
 	wipe = (gamestate != wipegamestate);
-	if (wipe)
+	if (wipe && wipetypepre != INT16_MAX)
 	{
 		// set for all later
 		wipedefindex = gamestate; // wipe_xxx_toblack
@@ -280,27 +253,37 @@ static void D_Display(void)
 				wipedefindex = wipe_multinter_toblack;
 		}
 
+		if (wipetypepre < 0 || !F_WipeExists(wipetypepre))
+			wipetypepre = wipedefs[wipedefindex];
+
 		if (rendermode != render_none)
 		{
 			// Fade to black first
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)) // fades to black on its own timing, always
-			 && wipedefs[wipedefindex] != UINT8_MAX)
+			if ((wipegamestate == (gamestate_t)FORCEWIPE ||
+			        (wipegamestate != (gamestate_t)FORCEWIPEOFF
+						&& !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+					) // fades to black on its own timing, always
+			 && wipetypepre != UINT8_MAX)
 			{
 				F_WipeStartScreen();
 				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 				F_WipeEndScreen();
-				F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK);
+				F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 			}
 
 			F_WipeStartScreen();
 		}
+
+		wipetypepre = -1;
 	}
+	else
+		wipetypepre = -1;
 
 	// do buffered drawing
 	switch (gamestate)
 	{
 		case GS_TITLESCREEN:
-			if (!titlemapinaction) {
+			if (!titlemapinaction || !curbghide) {
 				F_TitleScreenDrawer();
 				break;
 			}
@@ -309,8 +292,7 @@ static void D_Display(void)
 			if (!gametic)
 				break;
 			HU_Erase();
-			if (automapactive)
-				AM_Drawer();
+			AM_Drawer();
 			break;
 
 		case GS_INTERMISSION:
@@ -328,6 +310,12 @@ static void D_Display(void)
 				wipe = true;
 			break;
 
+		case GS_ENDING:
+			F_EndingDrawer();
+			HU_Erase();
+			HU_Drawer();
+			break;
+
 		case GS_CUTSCENE:
 			F_CutsceneDrawer();
 			HU_Erase();
@@ -361,88 +349,96 @@ static void D_Display(void)
 			break;
 	}
 
-	// clean up border stuff
-	// see if the border needs to be initially drawn
-	if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))
+	// STUPID race condition...
+	if (wipegamestate == GS_INTRO && gamestate == GS_TITLESCREEN)
+		wipegamestate = FORCEWIPEOFF;
+	else
 	{
-		// draw the view directly
+		wipegamestate = gamestate;
 
-		if (!automapactive && !dedicated && cv_renderview.value)
+		// clean up border stuff
+		// see if the border needs to be initially drawn
+		if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide))
 		{
-			if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
-			{
-				topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
-				objectsdrawn = 0;
-#ifdef HWRENDER
-				if (rendermode != render_soft)
-					HWR_RenderPlayerView(0, &players[displayplayer]);
-				else
-#endif
-				if (rendermode != render_none)
-					R_RenderPlayerView(&players[displayplayer]);
-			}
+			// draw the view directly
 
-			// render the second screen
-			if (splitscreen && players[secondarydisplayplayer].mo)
+			if (!automapactive && !dedicated && cv_renderview.value)
 			{
-#ifdef HWRENDER
-				if (rendermode != render_soft)
-					HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
-				else
-#endif
-				if (rendermode != render_none)
+				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
 				{
-					viewwindowy = vid.height / 2;
-					M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
-
 					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
+					objectsdrawn = 0;
+	#ifdef HWRENDER
+					if (rendermode != render_soft)
+						HWR_RenderPlayerView(0, &players[displayplayer]);
+					else
+	#endif
+					if (rendermode != render_none)
+						R_RenderPlayerView(&players[displayplayer]);
+				}
 
-					R_RenderPlayerView(&players[secondarydisplayplayer]);
+				// render the second screen
+				if (splitscreen && players[secondarydisplayplayer].mo)
+				{
+	#ifdef HWRENDER
+					if (rendermode != render_soft)
+						HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
+					else
+	#endif
+					if (rendermode != render_none)
+					{
+						viewwindowy = vid.height / 2;
+						M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
+
+						topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
+
+						R_RenderPlayerView(&players[secondarydisplayplayer]);
+
+						viewwindowy = 0;
+						M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
+					}
+				}
 
-					viewwindowy = 0;
-					M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
+				// Image postprocessing effect
+				if (rendermode == render_soft)
+				{
+					if (postimgtype)
+						V_DoPostProcessor(0, postimgtype, postimgparam);
+					if (postimgtype2)
+						V_DoPostProcessor(1, postimgtype2, postimgparam2);
 				}
 			}
 
-			// Image postprocessing effect
-			if (rendermode == render_soft)
+			if (lastdraw)
 			{
-				if (postimgtype)
-					V_DoPostProcessor(0, postimgtype, postimgparam);
-				if (postimgtype2)
-					V_DoPostProcessor(1, postimgtype2, postimgparam2);
+				if (rendermode == render_soft)
+				{
+					VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+					usebuffer = true;
+				}
+				lastdraw = false;
 			}
-		}
 
-		if (lastdraw)
-		{
-			if (rendermode == render_soft)
+			if (gamestate == GS_LEVEL)
 			{
-				VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
-				usebuffer = true;
+				ST_Drawer();
+				F_TextPromptDrawer();
+				HU_Drawer();
 			}
-			lastdraw = false;
-		}
-
-		if (gamestate == GS_LEVEL)
-		{
-			ST_Drawer();
-			HU_Drawer();
+			else
+				F_TitleScreenDrawer();
 		}
-		else
-			F_TitleScreenDrawer();
 	}
 
 	// change gamma if needed
 	// (GS_LEVEL handles this already due to level-specific palettes)
-	if (forcerefresh && gamestate != GS_LEVEL)
+	if (forcerefresh && !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
 		V_SetPalette(0);
 
-	wipegamestate = gamestate;
-
 	// draw pause pic
 	if (paused && cv_showhud.value && (!menuactive || netgame))
 	{
+#if 0
 		INT32 py;
 		patch_t *patch;
 		if (automapactive)
@@ -451,6 +447,11 @@ static void D_Display(void)
 			py = viewwindowy + 4;
 		patch = W_CachePatchName("M_PAUSE", PU_CACHE);
 		V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch);
+#else
+		INT32 y = ((automapactive) ? (32) : (BASEVIDHEIGHT/2));
+		M_DrawTextBox((BASEVIDWIDTH/2) - (60), y - (16), 13, 2);
+		V_DrawCenteredString(BASEVIDWIDTH/2, y - (4), V_YELLOWMAP, "Game Paused");
+#endif
 	}
 
 	// vid size change is now finished if it was on...
@@ -466,18 +467,25 @@ static void D_Display(void)
 	//
 	// wipe update
 	//
-	if (wipe)
+	if (wipe && wipetypepost != INT16_MAX)
 	{
 		// note: moved up here because NetUpdate does input changes
 		// and input during wipe tends to mess things up
 		wipedefindex += WIPEFINALSHIFT;
 
+		if (wipetypepost < 0 || !F_WipeExists(wipetypepost))
+			wipetypepost = wipedefs[wipedefindex];
+
 		if (rendermode != render_none)
 		{
 			F_WipeEndScreen();
-			F_RunWipe(wipedefs[wipedefindex], gamestate != GS_TIMEATTACK);
+			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
+
+		wipetypepost = -1;
 	}
+	else
+		wipetypepost = -1;
 
 	NetUpdate(); // send out any new accumulation
 
@@ -730,17 +738,27 @@ void D_StartTitle(void)
 	gametype = GT_COOP;
 	paused = false;
 	advancedemo = false;
+	F_InitMenuPresValues();
 	F_StartTitleScreen();
-	CON_ToggleOff();
+
+	currentMenu = &MainDef; // reset the current menu ID
 
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
+
+	// The title screen is obviously not a tutorial! (Unless I'm mistaken)
+	if (tutorialmode && tutorialgcs)
+	{
+		G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage
+		CV_SetValue(&cv_usemouse, tutorialusemouse);
+		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
+		CV_SetValue(&cv_mousemove, tutorialmousemove);
+		CV_SetValue(&cv_analog, tutorialanalog);
+		M_StartMessage("Do you want to \x82save the recommended \x82movement controls?\x80\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls",
+			M_TutorialSaveControlResponse, MM_YESNO);
+	}
+	tutorialmode = false;
 }
 
 //
@@ -774,21 +792,17 @@ static inline void D_CleanFile(void)
 	}
 }
 
-#ifndef _MAX_PATH
-#define _MAX_PATH MAX_WADPATH
-#endif
-
 // ==========================================================================
 // Identify the SRB2 version, and IWAD file to use.
 // ==========================================================================
 
 static void IdentifyVersion(void)
 {
-	char *srb2wad1, *srb2wad2;
+	char *srb2wad;
 	const char *srb2waddir = NULL;
 
 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	// change to the directory where 'srb2.srb' is found
+	// change to the directory where 'srb2.pk3' is found
 	srb2waddir = I_LocateWad();
 #endif
 
@@ -813,31 +827,24 @@ static void IdentifyVersion(void)
 		srb2waddir = I_GetWadDir();
 #endif
 	// Commercial.
-	srb2wad1 = malloc(strlen(srb2waddir)+1+8+1);
-	srb2wad2 = malloc(strlen(srb2waddir)+1+8+1);
-	if (srb2wad1 == NULL && srb2wad2 == NULL)
+	srb2wad = malloc(strlen(srb2waddir)+1+8+1);
+	if (srb2wad == NULL)
 		I_Error("No more free memory to look in %s", srb2waddir);
-	if (srb2wad1 != NULL)
-		sprintf(srb2wad1, pandf, srb2waddir, "srb2.pk3");
-	if (srb2wad2 != NULL)
-		sprintf(srb2wad2, pandf, srb2waddir, "srb2.wad");
+	else
+		sprintf(srb2wad, pandf, srb2waddir, "srb2.pk3");
 
 	// will be overwritten in case of -cdrom or unix/win home
 	snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir);
 	configfile[sizeof configfile - 1] = '\0';
 
 	// Load the IWAD
-	if (srb2wad2 != NULL && FIL_ReadFileOK(srb2wad2))
-		D_AddFile(srb2wad2);
-	else if (srb2wad1 != NULL && FIL_ReadFileOK(srb2wad1))
-		D_AddFile(srb2wad1);
+	if (srb2wad != NULL && FIL_ReadFileOK(srb2wad))
+		D_AddFile(srb2wad);
 	else
-		I_Error("SRB2.SRB/SRB2.WAD not found! Expected in %s, ss files: %s and %s\n", srb2waddir, srb2wad1, srb2wad2);
+		I_Error("srb2.pk3 not found! Expected in %s, ss file: %s\n", srb2waddir, srb2wad);
 
-	if (srb2wad1)
-		free(srb2wad1);
-	if (srb2wad2)
-		free(srb2wad2);
+	if (srb2wad)
+		free(srb2wad);
 
 	// if you change the ordering of this or add/remove a file, be sure to update the md5
 	// checking in D_SRB2Main
@@ -850,30 +857,25 @@ static void IdentifyVersion(void)
 
 #ifdef USE_PATCH_DTA
 	// Add our crappy patches to fix our bugs
-	D_AddFile(va(pandf,srb2waddir,"patch.dta"));
+	D_AddFile(va(pandf,srb2waddir,"patch.pk3"));
 #endif
 
 #if !defined (HAVE_SDL) || defined (HAVE_MIXER)
 	{
-		const char *musicfile = "music.dta";
-		const char *musicpath = va(pandf,srb2waddir,musicfile);
-		int ms = W_VerifyNMUSlumps(musicpath); // Don't forget the music!
-		if (ms == 1)
-			D_AddFile(musicpath);
-		else if (ms == 0)
-			I_Error("File %s has been modified with non-music lumps",musicfile);
-	}
-#endif
+#define MUSICTEST(str) \
+		{\
+			const char *musicpath = va(pandf,srb2waddir,str);\
+			int ms = W_VerifyNMUSlumps(musicpath); \
+			if (ms == 1) \
+				D_AddFile(musicpath); \
+			else if (ms == 0) \
+				I_Error("File "str" has been modified with non-music/sound lumps"); \
+		}
 
-#ifdef DEVELOP // This section can be deleted when music_new is merged with music.dta
-	{
-		const char *musicfile = "music_new.dta";
-		const char *musicpath = va(pandf,srb2waddir,musicfile);
-		int ms = W_VerifyNMUSlumps(musicpath); // Don't forget the music!
-		if (ms == 1)
-			D_AddFile(musicpath);
-		else if (ms == 0)
-			I_Error("File %s has been modified with non-music lumps",musicfile);
+		MUSICTEST("music.dta")
+#ifdef DEVELOP // remove when music_new.dta is merged into music.dta
+		MUSICTEST("music_new.dta")
+#endif
 	}
 #endif
 }
@@ -946,6 +948,20 @@ void D_SRB2Main(void)
 	INT32 pstartmap = 1;
 	boolean autostart = false;
 
+	// Print GPL notice for our console users (Linux)
+	CONS_Printf(
+	"\n\nSonic Robo Blast 2\n"
+	"Copyright (C) 1998-2018 by Sonic Team Junior\n\n"
+	"This program comes with ABSOLUTELY NO WARRANTY.\n\n"
+	"This is free software, and you are welcome to redistribute it\n"
+	"and/or modify it under the terms of the GNU General Public License\n"
+	"as published by the Free Software Foundation; either version 2 of\n"
+	"the License, or (at your option) any later version.\n"
+	"See the 'LICENSE.txt' file for details.\n\n"
+	"Sonic the Hedgehog and related characters are trademarks of SEGA.\n"
+	"We do not claim ownership of SEGA's intellectual property used\n"
+	"in this program.\n\n");
+
 	// keep error messages until the final flush(stderr)
 #if !defined (PC_DOS) && !defined(NOTERMIOS)
 	if (setvbuf(stderr, NULL, _IOFBF, 1000))
@@ -1044,19 +1060,10 @@ void D_SRB2Main(void)
 
 	if (M_CheckParm("-password") && M_IsNextParm())
 		D_SetPassword(M_GetNextParm());
-	else
-	{
-		size_t z;
-		char junkpw[25];
-		for (z = 0; z < 24; z++)
-			junkpw[z] = (char)(rand() & 64)+32;
-		junkpw[24] = '\0';
-		D_SetPassword(junkpw);
-	}
 
 	// add any files specified on the command line with -file wadfile
 	// to the wad list
-	if (!(M_CheckParm("-connect")))
+	if (!(M_CheckParm("-connect") && !M_CheckParm("-server")))
 	{
 		if (M_CheckParm("-file"))
 		{
@@ -1107,6 +1114,13 @@ void D_SRB2Main(void)
 	// adapt tables to SRB2's needs, including extra slots for dehacked file support
 	P_PatchInfoTables();
 
+	// initiate menu metadata before SOCcing them
+	M_InitMenuPresTables();
+
+	// init title screen display params
+	if (M_CheckParm("-connect"))
+		F_InitMenuPresValues();
+
 	//---------------------------------------------------- READY TIME
 	// we need to check for dedicated before initialization of some subsystems
 
@@ -1116,40 +1130,49 @@ void D_SRB2Main(void)
 	// Make backups of some SOCcable tables.
 	P_BackupTables();
 
-	// Setup default unlockable conditions
-	M_SetupDefaultConditionSets();
+	// Setup character tables
+	// Have to be done here before files are loaded
+	M_InitCharacterTables();
 
-	// load wad, including the main wad file
-	CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
-	if (!W_InitMultipleFiles(startupwadfiles))
-#ifdef _DEBUG
-		CONS_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
-#else
-		I_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
-#endif
-	D_CleanFile();
+	mainwads = 0;
 
 #ifndef DEVELOP // md5s last updated 12/14/14
 
 	// Check MD5s of autoloaded files
-	//W_VerifyFileMD5(0, ASSET_HASH_SRB2_SRB); // srb2.srb/srb2.wad
-	//W_VerifyFileMD5(1, ASSET_HASH_ZONES_DTA); // zones.dta
-	//W_VerifyFileMD5(2, ASSET_HASH_PLAYER_DTA); // player.dta
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_SRB2_PK3); // srb2.pk3
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_ZONES_DTA); // zones.dta
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_PLAYER_DTA); // player.dta
 #ifdef USE_PATCH_DTA
-	W_VerifyFileMD5(3, ASSET_HASH_PATCH_DTA); // patch.dta
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_PATCH_DTA); // patch.dta
 #endif
-
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
-#endif //ifndef DEVELOP
+	//mainwads++; // music.dta does not increment mainwads (see <= 2.1.21)
+	//mainwads++; // neither does music_new.dta
+#else
 
-	mainwads = 3; // there are 3 wads not to unload
+	mainwads++;	// srb2.pk3
+	mainwads++; // zones.dta
+	mainwads++; // player.dta
 #ifdef USE_PATCH_DTA
-	++mainwads; // patch.dta adds one more
+	mainwads++; // patch.dta
 #endif
-#ifdef DEVELOP
-	++mainwads; // music_new, too
+	//mainwads++; // music.dta does not increment mainwads (see <= 2.1.21)
+	//mainwads++; // neither does music_new.dta
+
+	// load wad, including the main wad file
+	CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
+	if (!W_InitMultipleFiles(startupwadfiles, mainwads))
+#ifdef _DEBUG
+		CONS_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
+#else
+		I_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
 #endif
+	D_CleanFile();
+
+#endif //ifndef DEVELOP
+
+	mainwadstally = packetsizetally;
 
 	mainwadstally = packetsizetally;
 
@@ -1211,21 +1234,29 @@ void D_SRB2Main(void)
 	R_Init();
 
 	// setting up sound
-	CONS_Printf("S_Init(): Setting up sound.\n");
+	if (dedicated)
+	{
+		sound_disabled = true;
+		midi_disabled = digital_disabled = true;
+	}
+	else
+	{
+		CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
+	}
 	if (M_CheckParm("-nosound"))
-		nosound = true;
+		sound_disabled = true;
 	if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
-		nomidimusic = nodigimusic = true;
+		midi_disabled = digital_disabled = true;
 	else
 	{
 		if (M_CheckParm("-nomidimusic"))
-			nomidimusic = true; ; // WARNING: DOS version initmusic in I_StartupSound
+			midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
 		if (M_CheckParm("-nodigmusic"))
-			nodigimusic = true; // WARNING: DOS version initmusic in I_StartupSound
+			digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
 	}
 	I_StartupSound();
 	I_InitMusic();
-	S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
+	S_InitSfxChannels(cv_soundvolume.value);
 
 	CONS_Printf("ST_Init(): Init status bar.\n");
 	ST_Init();
@@ -1323,7 +1354,7 @@ void D_SRB2Main(void)
 		}
 	}
 
-	if (autostart || netgame || M_CheckParm("+connect") || M_CheckParm("-connect"))
+	if (autostart || netgame)
 	{
 		gameaction = ga_nothing;
 
@@ -1357,8 +1388,7 @@ void D_SRB2Main(void)
 			}
 		}
 
-		if (server && !M_CheckParm("+map") && !M_CheckParm("+connect")
-			&& !M_CheckParm("-connect"))
+		if (server && !M_CheckParm("+map"))
 		{
 			// Prevent warping to nonexistent levels
 			if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
@@ -1377,13 +1407,14 @@ void D_SRB2Main(void)
 	}
 	else if (M_CheckParm("-skipintro"))
 	{
-		CON_ToggleOff();
-		CON_ClearHUD();
+		F_InitMenuPresValues();
 		F_StartTitleScreen();
 	}
 	else
 		F_StartIntro(); // Tails 03-03-2002
 
+	CON_ToggleOff();
+
 	if (dedicated && server)
 	{
 		levelstarttic = gametic;
diff --git a/src/d_main.h b/src/d_main.h
index d73b19d1f6e5fd34ab5fd0470a5c762379c6fd8b..d67a5bb498ba0f45d37b2be8c6bd15fde96cc78a 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -40,8 +40,8 @@ void D_SRB2Main(void);
 
 // Called by IO functions when input is detected.
 void D_PostEvent(const event_t *ev);
-#ifndef DOXYGEN
-FUNCMATH void D_PostEvent_end(void);    // delimiter for locking memory
+#if defined (PC_DOS) && !defined (DOXYGEN)
+void D_PostEvent_end(void);    // delimiter for locking memory
 #endif
 
 void D_ProcessEvents(void);
diff --git a/src/d_net.c b/src/d_net.c
index 2c0a8a3292bdd01047059ab7cbc3cd9a4e34b059..9f68c187c472e7cf73175c0f1ed55dcaccdba31b 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,6 +27,7 @@
 #include "d_clisrv.h"
 #include "z_zone.h"
 #include "i_tcp.h"
+#include "d_main.h" // srb2home
 
 //
 // NETWORKING
@@ -1369,12 +1370,12 @@ boolean D_CheckNetGame(void)
 		{
 			k++;
 			sprintf(filename, "debug%d.txt", k);
-			debugfile = fopen(filename, "w");
+			debugfile = fopen(va("%s" PATHSEP "%s", srb2home, filename), "w");
 		}
 		if (debugfile)
-			CONS_Printf(M_GetText("debug output to: %s\n"), filename);
+			CONS_Printf(M_GetText("debug output to: %s\n"), va("%s" PATHSEP "%s", srb2home, filename));
 		else
-			CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), filename);
+			CONS_Alert(CONS_WARNING, M_GetText("cannot debug output to file %s!\n"), va("%s" PATHSEP "%s", srb2home, filename));
 	}
 #endif
 
diff --git a/src/d_net.h b/src/d_net.h
index 84814ce39327573a176716a02eec9d77bab52643..3d1058702101fef5d250d1c210b2756dd47ea408 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -19,9 +19,10 @@
 #define __D_NET__
 
 // Max computers in a game
-#define MAXNETNODES 32
+#define MAXNETNODES (MAXPLAYERS+4)
 #define BROADCASTADDR MAXNETNODES
 #define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer
+//#define NETSPLITSCREEN // Kart's splitscreen netgame feature
 
 #define STATLENGTH (TICRATE*2)
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index b1e3bc9d2540e032a11dfe0a0aa384719bec3ebc..e6f5244f019a72cc0f8e0c859f82146f9ec73b95 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -126,8 +126,6 @@ FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void);
 static void Command_Playintro_f(void);
 
 static void Command_Displayplayer_f(void);
-static void Command_Tunes_f(void);
-static void Command_RestartAudio_f(void);
 
 static void Command_ExitLevel_f(void);
 static void Command_Showmap_f(void);
@@ -144,7 +142,9 @@ static void Command_Changepassword_f(void);
 static void Command_Login_f(void);
 static void Got_Login(UINT8 **cp, INT32 playernum);
 static void Got_Verification(UINT8 **cp, INT32 playernum);
+static void Got_Removal(UINT8 **cp, INT32 playernum);
 static void Command_Verify_f(void);
+static void Command_RemoveAdmin_f(void);
 static void Command_MotD_f(void);
 static void Got_MotD_f(UINT8 **cp, INT32 playernum);
 
@@ -246,9 +246,9 @@ INT32 cv_debug;
 consvar_t cv_usemouse = {"use_mouse", "On", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_usemouse2 = {"use_mouse2", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse2, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_usejoystick = {"use_joystick", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
+consvar_t cv_usejoystick = {"use_joystick", "1", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick2 = {"use_joystick2", "0", CV_SAVE|CV_CALL, usejoystick_cons_t,
+consvar_t cv_usejoystick2 = {"use_joystick2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t,
 	I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL};
 #if (defined (LJOYSTICK) || defined (HAVE_SDL))
 #ifdef LJOYSTICK
@@ -309,10 +309,11 @@ consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL,
 
 consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Tics"}, {2, "Centiseconds"}, {3, "Mania"}, {0, NULL}};
-consvar_t cv_timetic = {"timerres", "Normal", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
+static CV_PossibleValue_t timetic_cons_t[] = {{0, "Classic"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
+consvar_t cv_timetic = {"timerres", "Classic", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_resetmusic = {"resetmusic", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
+consvar_t cv_powerupdisplay = {"powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t pointlimit_cons_t[] = {{0, "MIN"}, {999999990, "MAX"}, {0, NULL}};
 consvar_t cv_pointlimit = {"pointlimit", "0", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t,
@@ -367,7 +368,7 @@ consvar_t cv_sleep = {"cpusleep", "-1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL
 INT16 gametype = GT_COOP;
 boolean splitscreen = false;
 boolean circuitmap = false;
-INT32 adminplayer = -1;
+INT32 adminplayers[MAXPLAYERS];
 
 /// \warning Keep this up-to-date if you add/remove/rename net text commands
 const char *netxcmdnames[MAXNETXCMD - 1] =
@@ -436,8 +437,10 @@ void D_RegisterServerCommands(void)
 	COM_AddCommand("password", Command_Changepassword_f);
 	RegisterNetXCmd(XD_LOGIN, Got_Login);
 	COM_AddCommand("login", Command_Login_f); // useful in dedicated to kick off remote admin
-	COM_AddCommand("verify", Command_Verify_f);
+	COM_AddCommand("promote", Command_Verify_f);
 	RegisterNetXCmd(XD_VERIFIED, Got_Verification);
+	COM_AddCommand("demote", Command_RemoveAdmin_f);
+	RegisterNetXCmd(XD_DEMOTED, Got_Removal);
 
 	COM_AddCommand("motd", Command_MotD_f);
 	RegisterNetXCmd(XD_SETMOTD, Got_MotD_f); // For remote admin
@@ -586,7 +589,6 @@ void D_RegisterServerCommands(void)
   */
 void D_RegisterClientCommands(void)
 {
-	const char *username;
 	INT32 i;
 
 	for (i = 0; i < MAXSKINCOLORS; i++)
@@ -644,8 +646,6 @@ void D_RegisterClientCommands(void)
 #endif
 
 	// register these so it is saved to config
-	if ((username = I_GetUserName()))
-		cv_playername.defaultvalue = cv_defaultplayername.defaultvalue = username;
 	CV_RegisterVar(&cv_playername);
 	CV_RegisterVar(&cv_playercolor);
 	CV_RegisterVar(&cv_skin); // r_things.c (skin NAME)
@@ -673,6 +673,7 @@ void D_RegisterClientCommands(void)
 
 	// HUD
 	CV_RegisterVar(&cv_timetic);
+	CV_RegisterVar(&cv_powerupdisplay);
 	CV_RegisterVar(&cv_itemfinder);
 	CV_RegisterVar(&cv_showinputjoy);
 
@@ -684,9 +685,6 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_ghost_guest);
 
 	COM_AddCommand("displayplayer", Command_Displayplayer_f);
-	COM_AddCommand("tunes", Command_Tunes_f);
-	COM_AddCommand("restartaudio", Command_RestartAudio_f);
-	CV_RegisterVar(&cv_resetmusic);
 
 	// FIXME: not to be here.. but needs be done for config loading
 	CV_RegisterVar(&cv_globalgamma);
@@ -714,10 +712,21 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_msaturation);
 
 	// m_menu.c
+	CV_RegisterVar(&cv_compactscoreboard);
+	CV_RegisterVar(&cv_chatheight);
+	CV_RegisterVar(&cv_chatwidth);
+	CV_RegisterVar(&cv_chattime);
+	CV_RegisterVar(&cv_chatspamprotection);
+	CV_RegisterVar(&cv_chatbacktint);
+	CV_RegisterVar(&cv_consolechat);
+	CV_RegisterVar(&cv_chatnotifications);
 	CV_RegisterVar(&cv_crosshair);
 	CV_RegisterVar(&cv_crosshair2);
 	CV_RegisterVar(&cv_alwaysfreelook);
 	CV_RegisterVar(&cv_alwaysfreelook2);
+	CV_RegisterVar(&cv_chasefreelook);
+	CV_RegisterVar(&cv_chasefreelook2);
+	CV_RegisterVar(&cv_tutorialprompt);
 
 	// g_input.c
 	CV_RegisterVar(&cv_sideaxis);
@@ -810,6 +819,7 @@ void D_RegisterClientCommands(void)
 	COM_AddCommand("writethings", Command_Writethings_f);
 	CV_RegisterVar(&cv_speed);
 	CV_RegisterVar(&cv_opflags);
+	CV_RegisterVar(&cv_ophoopflags);
 	CV_RegisterVar(&cv_mapthingnum);
 //	CV_RegisterVar(&cv_grid);
 //	CV_RegisterVar(&cv_snapto);
@@ -1042,8 +1052,8 @@ static void SetPlayerName(INT32 playernum, char *newname)
 		if (strcasecmp(newname, player_names[playernum]) != 0)
 		{
 			if (netgame)
-				CONS_Printf(M_GetText("%s renamed to %s\n"),
-					player_names[playernum], newname);
+				HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
+
 			strcpy(player_names[playernum], newname);
 		}
 	}
@@ -1221,7 +1231,7 @@ static void SendNameAndColor(void)
 	snacpending++;
 
 	// Don't change name if muted
-	if (cv_mute.value && !(server || adminplayer == consoleplayer))
+	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
 	else // Cleanup name if changing it
 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
@@ -1443,13 +1453,13 @@ void SendWeaponPref(void)
 	UINT8 buf[1];
 
 	buf[0] = 0;
-	if (players[consoleplayer].pflags & PF_FLIPCAM)
+	if (cv_flipcam.value)
 		buf[0] |= 1;
-	if (players[consoleplayer].pflags & PF_ANALOGMODE)
+	if (cv_analog.value)
 		buf[0] |= 2;
-	if (players[consoleplayer].pflags & PF_DIRECTIONCHAR)
+	if (cv_directionchar.value)
 		buf[0] |= 4;
-	if (players[consoleplayer].pflags & PF_AUTOBRAKE)
+	if (cv_autobrake.value)
 		buf[0] |= 8;
 	SendNetXCmd(XD_WEAPONPREF, buf, 1);
 }
@@ -1459,13 +1469,13 @@ void SendWeaponPref2(void)
 	UINT8 buf[1];
 
 	buf[0] = 0;
-	if (players[secondarydisplayplayer].pflags & PF_FLIPCAM)
+	if (cv_flipcam2.value)
 		buf[0] |= 1;
-	if (players[secondarydisplayplayer].pflags & PF_ANALOGMODE)
+	if (cv_analog2.value)
 		buf[0] |= 2;
-	if (players[secondarydisplayplayer].pflags & PF_DIRECTIONCHAR)
+	if (cv_directionchar2.value)
 		buf[0] |= 4;
-	if (players[secondarydisplayplayer].pflags & PF_AUTOBRAKE)
+	if (cv_autobrake2.value)
 		buf[0] |= 8;
 	SendNetXCmd2(XD_WEAPONPREF, buf, 1);
 }
@@ -1535,7 +1545,12 @@ static void Command_Playdemo_f(void)
 
 	CONS_Printf(M_GetText("Playing back demo '%s'.\n"), name);
 
-	G_DoPlayDemo(name);
+	// 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))
+		G_DoPlayDemo(va("%s"PATHSEP"%s", srb2home, name));
+	else
+		G_DoPlayDemo(name);
 }
 
 static void Command_Timedemo_f(void)
@@ -1652,7 +1667,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 		mapchangepending = 0;
 		// spawn the server if needed
 		// reset players if there is a new one
-		if (!(adminplayer == consoleplayer))
+		if (!IsPlayerAdmin(consoleplayer))
 		{
 			if (SV_SpawnServer())
 				buf[0] &= ~(1<<1);
@@ -1710,7 +1725,7 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (client && !(adminplayer == consoleplayer))
+	if (client && !IsPlayerAdmin(consoleplayer))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -1808,6 +1823,16 @@ static void Command_Map_f(void)
 	else
 		fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
 
+	if (tutorialmode && tutorialgcs)
+	{
+		G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage
+		CV_SetValue(&cv_usemouse, tutorialusemouse);
+		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
+		CV_SetValue(&cv_mousemove, tutorialmousemove);
+		CV_SetValue(&cv_analog, tutorialanalog);
+	}
+	tutorialmode = false; // warping takes us out of tutorial mode
+
 	D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
 }
 
@@ -1824,8 +1849,11 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	UINT8 flags;
 	INT32 resetplayer = 1, lastgametype;
 	UINT8 skipprecutscene, FLS;
+#ifdef HAVE_BLUA
+	INT16 mapnumber;
+#endif
 
-	if (playernum != serverplayer && playernum != adminplayer)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]);
 		if (server)
@@ -1871,6 +1899,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 			mapname, resetplayer, lastgametype, gametype, chmappending));
 		CONS_Printf(M_GetText("Speeding off to level...\n"));
 	}
+
 	if (demoplayback && !timingdemo)
 		precache = false;
 
@@ -1885,13 +1914,13 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	}
 
 #ifdef HAVE_BLUA
-	LUAh_MapChange();
+	mapnumber = M_MapNumber(mapname[3], mapname[4]);
+	LUAh_MapChange(mapnumber);
 #endif
 
 	G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
 	if (demoplayback && !timingdemo)
 		precache = true;
-	CON_ToggleOff();
 	if (timingdemo)
 		G_DoneLevelLoad();
 
@@ -1917,7 +1946,7 @@ static void Command_Pause(void)
 	else
 		WRITEUINT8(cp, 0);
 
-	if (cv_pause.value || server || (adminplayer == consoleplayer))
+	if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
 	{
 		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
 		{
@@ -1935,7 +1964,7 @@ static void Got_Pause(UINT8 **cp, INT32 playernum)
 	UINT8 dedicatedpause = false;
 	const char *playername;
 
-	if (netgame && !cv_pause.value && playernum != serverplayer && playernum != adminplayer)
+	if (netgame && !cv_pause.value && playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]);
 		if (server)
@@ -2064,7 +2093,7 @@ static void Got_RandomSeed(UINT8 **cp, INT32 playernum)
   */
 static void Command_Clearscores_f(void)
 {
-	if (!(server || (adminplayer == consoleplayer)))
+	if (!(server || (IsPlayerAdmin(consoleplayer))))
 		return;
 
 	SendNetXCmd(XD_CLEARSCORES, NULL, 1);
@@ -2084,7 +2113,7 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum)
 	INT32 i;
 
 	(void)cp;
-	if (playernum != serverplayer && playernum != adminplayer)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]);
 		if (server)
@@ -2305,7 +2334,7 @@ static void Command_ServerTeamChange_f(void)
 	UINT16 usvalue;
 	NetPacket.value.l = NetPacket.value.b = 0;
 
-	if (!(server || (adminplayer == consoleplayer)))
+	if (!(server || (IsPlayerAdmin(consoleplayer))))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -2452,7 +2481,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 
 	if (NetPacket.packet.verification) // Special marker that the server sent the request
 	{
-		if (playernum != serverplayer && (playernum != adminplayer))
+		if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
 			if (server)
@@ -2491,7 +2520,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	}
 	else
 	{
-		if (playernum != serverplayer && (playernum != adminplayer))
+		if (playernum != serverplayer && (!IsPlayerAdmin(playernum)))
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
 			if (server)
@@ -2676,8 +2705,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	// Clear player score and rings if a spectator.
 	if (players[playernum].spectator)
 	{
-		players[playernum].score = 0;
-		players[playernum].rings = 0;
+		players[playernum].score = players[playernum].rings = 0;
 		if (players[playernum].mo)
 			players[playernum].mo->health = 1;
 	}
@@ -2719,10 +2747,12 @@ static void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt,
 
 #define BASESALT "basepasswordstorage"
 static UINT8 adminpassmd5[16];
+static boolean adminpasswordset = false;
 
 void D_SetPassword(const char *pw)
 {
 	D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5);
+	adminpasswordset = true;
 }
 
 // Remote Administration
@@ -2758,6 +2788,12 @@ static void Command_Login_f(void)
 	UINT8 finalmd5[16];
 	const char *pw;
 
+	if (!netgame)
+	{
+		CONS_Printf(M_GetText("This only works in a netgame.\n"));
+		return;
+	}
+
 	// If the server uses login, it will effectively just remove admin privileges
 	// from whoever has them. This is good.
 	if (COM_Argc() != 2)
@@ -2794,19 +2830,68 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
 	if (client)
 		return;
 
+	if (!adminpasswordset)
+	{
+		CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[playernum]);
+		return;
+	}
+
 	// Do the final pass to compare with the sent md5
 	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", playernum), &finalmd5);
 
 	if (!memcmp(sentmd5, finalmd5, 16))
 	{
 		CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[playernum]);
-		COM_BufInsertText(va("verify %d\n", playernum)); // do this immediately
+		COM_BufInsertText(va("promote %d\n", playernum)); // do this immediately
 	}
 	else
 		CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[playernum]);
 #endif
 }
 
+boolean IsPlayerAdmin(INT32 playernum)
+{
+	INT32 i;
+	for (i = 0; i < MAXPLAYERS; i++)
+		if (playernum == adminplayers[i])
+			return true;
+
+	return false;
+}
+
+void SetAdminPlayer(INT32 playernum)
+{
+	INT32 i;
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playernum == adminplayers[i])
+			return; // Player is already admin
+
+		if (adminplayers[i] == -1)
+		{
+			adminplayers[i] = playernum; // Set the player to a free spot
+			break; // End the loop now. If it keeps going, the same player might get assigned to two slots.
+		}
+
+
+	}
+}
+
+void ClearAdminPlayers(void)
+{
+	INT32 i;
+	for (i = 0; i < MAXPLAYERS; i++)
+		adminplayers[i] = -1;
+}
+
+void RemoveAdminPlayer(INT32 playernum)
+{
+	INT32 i;
+	for (i = 0; i < MAXPLAYERS; i++)
+		if (playernum == adminplayers[i])
+			adminplayers[i] = -1;
+}
+
 static void Command_Verify_f(void)
 {
 	char buf[8]; // Should be plenty
@@ -2819,9 +2904,15 @@ static void Command_Verify_f(void)
 		return;
 	}
 
+	if (!netgame)
+	{
+		CONS_Printf(M_GetText("This only works in a netgame.\n"));
+		return;
+	}
+
 	if (COM_Argc() != 2)
 	{
-		CONS_Printf(M_GetText("verify <node>: give admin privileges to a node\n"));
+		CONS_Printf(M_GetText("promote <node>: give admin privileges to a node\n"));
 		return;
 	}
 
@@ -2855,7 +2946,7 @@ static void Got_Verification(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	adminplayer = num;
+	SetAdminPlayer(num);
 
 	if (num != consoleplayer)
 		return;
@@ -2863,6 +2954,62 @@ static void Got_Verification(UINT8 **cp, INT32 playernum)
 	CONS_Printf(M_GetText("You are now a server administrator.\n"));
 }
 
+static void Command_RemoveAdmin_f(void)
+{
+	char buf[8]; // Should be plenty
+	char *temp;
+	INT32 playernum;
+
+	if (client)
+	{
+		CONS_Printf(M_GetText("Only the server can use this.\n"));
+		return;
+	}
+
+	if (COM_Argc() != 2)
+	{
+		CONS_Printf(M_GetText("demote <node>: remove admin privileges from a node\n"));
+		return;
+	}
+
+	strlcpy(buf, COM_Argv(1), sizeof(buf));
+
+	playernum = atoi(buf);
+
+	temp = buf;
+
+	WRITEUINT8(temp, playernum);
+
+	if (playeringame[playernum])
+		SendNetXCmd(XD_DEMOTED, buf, 1);
+}
+
+static void Got_Removal(UINT8 **cp, INT32 playernum)
+{
+	INT16 num = READUINT8(*cp);
+
+	if (playernum != serverplayer) // it's not from the server (hacker or bug)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
+		if (server)
+		{
+			UINT8 buf[2];
+
+			buf[0] = (UINT8)playernum;
+			buf[1] = KICK_MSG_CON_FAIL;
+			SendNetXCmd(XD_KICK, &buf, 2);
+		}
+		return;
+	}
+
+	RemoveAdminPlayer(num);
+
+	if (num != consoleplayer)
+		return;
+
+	CONS_Printf(M_GetText("You are no longer a server administrator.\n"));
+}
+
 static void Command_MotD_f(void)
 {
 	size_t i, j;
@@ -2874,7 +3021,7 @@ static void Command_MotD_f(void)
 		return;
 	}
 
-	if (!(server || (adminplayer == consoleplayer)))
+	if (!(server || (IsPlayerAdmin(consoleplayer))))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -2898,7 +3045,7 @@ static void Command_MotD_f(void)
 		}
 
 	if ((netgame || multiplayer) && client)
-		SendNetXCmd(XD_SETMOTD, mymotd, sizeof(motd));
+		SendNetXCmd(XD_SETMOTD, mymotd, i); // send the actual size of the motd string, not the full buffer's size
 	else
 	{
 		strcpy(motd, mymotd);
@@ -2921,7 +3068,7 @@ static void Got_MotD_f(UINT8 **cp, INT32 playernum)
 		if (!isprint(mymotd[i]) || mymotd[i] == ';')
 			kick = true;
 
-	if ((playernum != serverplayer && playernum != adminplayer) || kick)
+	if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]);
 		if (server)
@@ -2958,7 +3105,7 @@ static void Command_RunSOC(void)
 	else
 		fn = COM_Argv(1);
 
-	if (netgame && !(server || consoleplayer == adminplayer))
+	if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -2984,7 +3131,7 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
 	char filename[256];
 	filestatus_t ncs = FS_NOTFOUND;
 
-	if (playernum != serverplayer && playernum != adminplayer)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]);
 		if (server)
@@ -3055,7 +3202,7 @@ static void Command_Addfile(void)
 	if (!musiconly)
 	{
 		// ... But only so long as they contain nothing more then music and sprites.
-		if (netgame && !(server || adminplayer == consoleplayer))
+		if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
 		{
 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 			return;
@@ -3066,7 +3213,7 @@ static void Command_Addfile(void)
 	// Add file on your client directly if it is trivial, or you aren't in a netgame.
 	if (!(netgame || multiplayer) || musiconly)
 	{
-		P_AddWadFile(fn, NULL);
+		P_AddWadFile(fn);
 		return;
 	}
 
@@ -3075,26 +3222,14 @@ static void Command_Addfile(void)
 		if (*p == '\\' || *p == '/' || *p == ':')
 			break;
 	++p;
+
 	// check total packet size and no of files currently loaded
+	// See W_LoadWadFile in w_wad.c
+	if ((numwadfiles >= MAX_WADFILES)
+	|| ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8)))
 	{
-		size_t packetsize = 0;
-		serverinfo_pak *dummycheck = NULL;
-
-		// Shut the compiler up.
-		(void)dummycheck;
-
-		// See W_LoadWadFile in w_wad.c
-		for (i = 0; i < numwadfiles; i++)
-			packetsize += nameonlylength(wadfiles[i]->filename) + 22;
-
-		packetsize += nameonlylength(fn) + 22;
-
-		if ((numwadfiles >= MAX_WADFILES)
-		|| (packetsize > sizeof(dummycheck->fileneeded)))
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
-			return;
-		}
+		CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
+		return;
 	}
 
 	WRITESTRINGN(buf_p,p,240);
@@ -3130,7 +3265,7 @@ static void Command_Addfile(void)
 		WRITEMEM(buf_p, md5sum, 16);
 	}
 
-	if (adminplayer == consoleplayer) // Request to add file
+	if (IsPlayerAdmin(consoleplayer) && (!server)) // Request to add file
 		SendNetXCmd(XD_REQADDFILE, buf, buf_p - buf);
 	else
 		SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
@@ -3143,8 +3278,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	UINT8 md5sum[16];
 	boolean kick = false;
 	boolean toomany = false;
-	INT32 i;
-	size_t packetsize = 0;
+	INT32 i,j;
 	serverinfo_pak *dummycheck = NULL;
 
 	// Shut the compiler up.
@@ -3162,7 +3296,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 		if (!isprint(filename[i]) || filename[i] == ';')
 			kick = true;
 
-	if ((playernum != serverplayer && playernum != adminplayer) || kick)
+	if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
 	{
 		UINT8 buf[2];
 
@@ -3175,13 +3309,8 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	}
 
 	// See W_LoadWadFile in w_wad.c
-	for (i = 0; i < numwadfiles; i++)
-		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
-
-	packetsize += nameonlylength(filename) + 22;
-
 	if ((numwadfiles >= MAX_WADFILES)
-	|| (packetsize > sizeof(dummycheck->fileneeded)))
+	|| ((packetsizetally + nameonlylength(filename) + 22) > MAXFILENEEDED*sizeof(UINT8)))
 		toomany = true;
 	else
 		ncs = findfile(filename,md5sum,true);
@@ -3201,8 +3330,9 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 
 		CONS_Printf("%s",message);
 
-		if (adminplayer)
-			COM_BufAddText(va("sayto %d %s", adminplayer, message));
+		for (j = 0; j < MAXPLAYERS; j++)
+			if (adminplayers[j])
+				COM_BufAddText(va("sayto %d %s", adminplayers[j], message));
 
 		return;
 	}
@@ -3235,7 +3365,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 
 	ncs = findfile(filename,md5sum,true);
 
-	if (ncs != FS_FOUND || !P_AddWadFile(filename, NULL))
+	if (ncs != FS_FOUND || !P_AddWadFile(filename))
 	{
 		Command_ExitGame_f();
 		if (ncs == FS_FOUND)
@@ -3292,10 +3422,56 @@ static void Command_ListWADS_f(void)
 static void Command_Version_f(void)
 {
 #ifdef DEVELOP
-	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s)\n", compbranch, comprevision, compdate, comptime);
+	CONS_Printf("Sonic Robo Blast 2 %s-%s (%s %s) ", compbranch, comprevision, compdate, comptime);
 #else
-	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s)\n", VERSIONSTRING, compdate, comptime, comprevision);
+	CONS_Printf("Sonic Robo Blast 2 %s (%s %s %s) ", VERSIONSTRING, compdate, comptime, comprevision);
+#endif
+
+	// Base library
+#if defined( HAVE_SDL)
+	CONS_Printf("SDL ");
+#elif defined(_WINDOWS)
+	CONS_Printf("DD ");
+#endif
+
+	// OS
+	// Would be nice to use SDL_GetPlatform for this
+#if defined (_WIN32) || defined (_WIN64)
+	CONS_Printf("Windows ");
+#elif defined(__linux__)
+	CONS_Printf("Linux ");
+#elif defined(MACOSX)
+	CONS_Printf("macOS ");
+#elif defined(UNIXCOMMON)
+	CONS_Printf("Unix (Common) ");
+#else
+	CONS_Printf("Other OS ");
+#endif
+
+	// Bitness
+	if (sizeof(void*) == 4)
+		CONS_Printf("32-bit ");
+	else if (sizeof(void*) == 8)
+		CONS_Printf("64-bit ");
+	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");
+#endif
+
+	// DEVELOP build
+#ifdef DEVELOP
+	CONS_Printf("\x87" "DEVELOP " "\x80");
 #endif
+
+	CONS_Printf("\n");
 }
 
 #ifdef UPDATE_ALERT
@@ -3334,6 +3510,9 @@ static void Command_Playintro_f(void)
 	if (netgame)
 		return;
 
+	if (dirmenu)
+		closefilemenu(true);
+
 	F_StartIntro();
 }
 
@@ -3660,7 +3839,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 			if (playeringame[i])
 				players[i].ctfteam = 0;
 
-		if (server || (adminplayer == consoleplayer))
+		if (server || (IsPlayerAdmin(consoleplayer)))
 		{
 			CV_StealthSetValue(&cv_teamscramble, 0);
 			teamscramble = 0;
@@ -3743,7 +3922,7 @@ static void TeamScramble_OnChange(void)
 	if (!cv_teamscramble.value)
 		teamscramble = 0;
 
-	if (!G_GametypeHasTeams() && (server || consoleplayer == adminplayer))
+	if (!G_GametypeHasTeams() && (server || IsPlayerAdmin(consoleplayer)))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n"));
 		CV_StealthSetValue(&cv_teamscramble, 0);
@@ -3919,33 +4098,12 @@ 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 || (adminplayer == consoleplayer)))
+	else if (!(server || (IsPlayerAdmin(consoleplayer))))
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
-	else if (gamestate != GS_LEVEL || demoplayback)
+	else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback)
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
 	else
-	{
-		if ((netgame || multiplayer)
-		&& ((mapheaderinfo[gamemap-1]->nextlevel <= 0)
-		|| (mapheaderinfo[gamemap-1]->nextlevel > NUMMAPS)
-		|| !(mapvisited[mapheaderinfo[gamemap-1]->nextlevel-1])))
-		{
-			UINT8 i;
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (playeringame[i] && players[i].exiting)
-					break;
-			}
-
-			if (i == MAXPLAYERS)
-			{
-				CONS_Printf(M_GetText("Someone must finish the level for you to use this.\n"));
-				return;
-			}
-		}
-
 		SendNetXCmd(XD_EXITLEVEL, NULL, 0);
-	}
 }
 
 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
@@ -3956,7 +4114,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 	if (gameaction == ga_completed)
 		return;
 
-	if (playernum != serverplayer && playernum != adminplayer)
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]);
 		if (server)
@@ -3982,95 +4140,6 @@ static void Command_Displayplayer_f(void)
 	CONS_Printf(M_GetText("Displayplayer is %d\n"), displayplayer);
 }
 
-static void Command_Tunes_f(void)
-{
-	const char *tunearg;
-	UINT16 tunenum, track = 0;
-	const size_t argc = COM_Argc();
-
-	if (argc < 2) //tunes slot ...
-	{
-		CONS_Printf("tunes <name/num> [track] [speed] / <-show> / <-default> / <-none>:\n");
-		CONS_Printf(M_GetText("Play an arbitrary music lump. If a map number is used, 'MAP##M' is played.\n"));
-		CONS_Printf(M_GetText("If the format supports multiple songs, you can specify which one to play.\n\n"));
-		CONS_Printf(M_GetText("* With \"-show\", shows the currently playing tune and track.\n"));
-		CONS_Printf(M_GetText("* With \"-default\", returns to the default music for the map.\n"));
-		CONS_Printf(M_GetText("* With \"-none\", any music playing will be stopped.\n"));
-		return;
-	}
-
-	tunearg = COM_Argv(1);
-	tunenum = (UINT16)atoi(tunearg);
-	track = 0;
-
-	if (!strcasecmp(tunearg, "-show"))
-	{
-		CONS_Printf(M_GetText("The current tune is: %s [track %d]\n"),
-			mapmusname, (mapmusflags & MUSIC_TRACKMASK));
-		return;
-	}
-	if (!strcasecmp(tunearg, "-none"))
-	{
-		S_StopMusic();
-		return;
-	}
-	else if (!strcasecmp(tunearg, "-default"))
-	{
-		tunearg = mapheaderinfo[gamemap-1]->musname;
-		track = mapheaderinfo[gamemap-1]->mustrack;
-	}
-	else if (!tunearg[2] && toupper(tunearg[0]) >= 'A' && toupper(tunearg[0]) <= 'Z')
-		tunenum = (UINT16)M_MapNumber(tunearg[0], tunearg[1]);
-
-	if (tunenum && tunenum >= 1036)
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Valid music slots are 1 to 1035.\n"));
-		return;
-	}
-	if (!tunenum && strlen(tunearg) > 6) // This is automatic -- just show the error just in case
-		CONS_Alert(CONS_NOTICE, M_GetText("Music name too long - truncated to six characters.\n"));
-
-	if (argc > 2)
-		track = (UINT16)atoi(COM_Argv(2))-1;
-
-	if (tunenum)
-		snprintf(mapmusname, 7, "%sM", G_BuildMapName(tunenum));
-	else
-		strncpy(mapmusname, tunearg, 7);
-	mapmusname[6] = 0;
-	mapmusflags = (track & MUSIC_TRACKMASK);
-
-	S_ChangeMusic(mapmusname, mapmusflags, true);
-
-	if (argc > 3)
-	{
-		float speed = (float)atof(COM_Argv(3));
-		if (speed > 0.0f)
-			S_SpeedMusic(speed);
-	}
-}
-
-static void Command_RestartAudio_f(void)
-{
-	if (dedicated)  // No point in doing anything if game is a dedicated server.
-		return;
-
-	S_StopMusic();
-	I_ShutdownMusic();
-	I_ShutdownSound();
-	I_StartupSound();
-	I_InitMusic();
-	
-// These must be called or no sound and music until manually set.
-
-	I_SetSfxVolume(cv_soundvolume.value);
-	I_SetDigMusicVolume(cv_digmusicvolume.value);
-	I_SetMIDIMusicVolume(cv_midimusicvolume.value);
-	if (Playing()) // Gotta make sure the player is in a level
-		P_RestoreMusic(&players[consoleplayer]);
-	
-}
-
 /** Quits a game and returns to the title screen.
   *
   */
@@ -4092,6 +4161,9 @@ void Command_ExitGame_f(void)
 	cv_debug = 0;
 	emeralds = 0;
 
+	if (dirmenu)
+		closefilemenu(true);
+
 	if (!modeattacking)
 		D_StartTitle();
 }
@@ -4150,7 +4222,7 @@ static void Command_Cheats_f(void)
 {
 	if (COM_CheckParm("off"))
 	{
-		if (!(server || (adminplayer == consoleplayer)))
+		if (!(server || (IsPlayerAdmin(consoleplayer))))
 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		else
 			CV_ResetCheatNetVars();
@@ -4160,7 +4232,7 @@ static void Command_Cheats_f(void)
 	if (CV_CheatsEnabled())
 	{
 		CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed -- Cheats are enabled.\n"));
-		if (server || (adminplayer == consoleplayer))
+		if (server || (IsPlayerAdmin(consoleplayer)))
 			CONS_Printf(M_GetText("Type CHEATS OFF to reset all cheat variables to default.\n"));
 	}
 	else
@@ -4188,8 +4260,8 @@ static void Command_Archivetest_f(void)
 
 	// assign mobjnum
 	i = 1;
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			((mobj_t *)th)->mobjnum = i++;
 
 	// allocate buffer
@@ -4229,6 +4301,20 @@ static void Command_Archivetest_f(void)
   */
 static void ForceSkin_OnChange(void)
 {
+	if ((server || IsPlayerAdmin(consoleplayer)) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins))
+	{
+		if (cv_forceskin.value == -2)
+			CV_SetValue(&cv_forceskin, numskins-1);
+		else
+		{
+			// hack because I can't restrict this and still allow added skins to be used with forceskin.
+			if (!menuactive)
+				CONS_Printf(M_GetText("Valid skin numbers are 0 to %d (-1 disables)\n"), numskins - 1);
+			CV_SetValue(&cv_forceskin, -1);
+		}
+		return;
+	}
+
 	// NOT in SP, silly!
 	if (!(netgame || multiplayer))
 		return;
@@ -4245,7 +4331,7 @@ static void ForceSkin_OnChange(void)
 //Allows the player's name to be changed if cv_mute is off.
 static void Name_OnChange(void)
 {
-	if (cv_mute.value && !(server || adminplayer == consoleplayer))
+	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("You may not change your name when chat is muted.\n"));
 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
@@ -4368,7 +4454,7 @@ static void Color2_OnChange(void)
   */
 static void Mute_OnChange(void)
 {
-	if (server || (adminplayer == consoleplayer))
+	if (server || (IsPlayerAdmin(consoleplayer)))
 		return;
 
 	if (cv_mute.value)
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 57e23b0f1e0752d95576e197fe3de8aaadb1806b..d9889d9791f3f5c6d81821148b457bffc2aa01ac 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -103,8 +103,6 @@ extern consvar_t cv_startinglives;
 // for F_finale.c
 extern consvar_t cv_rollingdemos;
 
-extern consvar_t cv_resetmusic;
-
 extern consvar_t cv_ringslinger, cv_soundtest;
 
 extern consvar_t cv_specialrings, cv_powerstones, cv_matchboxes, cv_competitionboxes;
@@ -139,9 +137,10 @@ typedef enum
 	XD_DELFILE,     // 18 - replace next time we add an XD
 	XD_SETMOTD,     // 19
 	XD_SUICIDE,     // 20
+	XD_DEMOTED,     // 21
 #ifdef HAVE_BLUA
-	XD_LUACMD,      // 21
-	XD_LUAVAR,      // 22
+	XD_LUACMD,      // 22
+	XD_LUAVAR,      // 23
 #endif
 	MAXNETXCMD
 } netxcmd_t;
@@ -196,6 +195,10 @@ void Command_ExitGame_f(void);
 void Command_Retry_f(void);
 void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
 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);
+void ClearAdminPlayers(void);
+void RemoveAdminPlayer(INT32 playernum);
 void ItemFinder_OnChange(void);
 void D_SetPassword(const char *pw);
 
@@ -203,5 +206,3 @@ void D_SetPassword(const char *pw);
 UINT8 CanChangeSkin(INT32 playernum);
 
 #endif
-
-
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 72ac0dd320bbfa284134d0e240c80b955415ec4e..db9fa12e4957c86be3bd0579907bb9ea33af8230 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -87,7 +87,7 @@ static filetran_t transfer[MAXNETNODES];
 // Receiver structure
 INT32 fileneedednum; // Number of files needed to join the server
 fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files
-char downloaddir[256] = "DOWNLOAD";
+char downloaddir[512] = "DOWNLOAD";
 
 #ifdef CLIENT_LOADINGSCREEN
 // for cl loading screen
@@ -324,16 +324,12 @@ INT32 CL_CheckFiles(void)
 	INT32 ret = 1;
 	size_t packetsize = 0;
 	size_t filestoget = 0;
-	serverinfo_pak *dummycheck = NULL;
-
-	// Shut the compiler up.
-	(void)dummycheck;
 
 //	if (M_CheckParm("-nofiles"))
 //		return 1;
 
 	// the first is the iwad (the main wad file)
-	// we don't care if it's called srb2.srb or srb2.wad.
+	// we don't care if it's called srb2.pk3 or not.
 	// Never download the IWAD, just assume it's there and identical
 	fileneeded[0].status = FS_OPEN;
 
@@ -371,8 +367,7 @@ INT32 CL_CheckFiles(void)
 	}
 
 	// See W_LoadWadFile in w_wad.c
-	for (i = 0; i < numwadfiles; i++)
-		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+	packetsize = packetsizetally;
 
 	for (i = 1; i < fileneedednum; i++)
 	{
@@ -396,7 +391,7 @@ INT32 CL_CheckFiles(void)
 		packetsize += nameonlylength(fileneeded[i].filename) + 22;
 
 		if ((numwadfiles+filestoget >= MAX_WADFILES)
-		|| (packetsize > sizeof(dummycheck->fileneeded)))
+		|| (packetsize > MAXFILENEEDED*sizeof(UINT8)))
 			return 3;
 
 		filestoget++;
@@ -423,7 +418,7 @@ void CL_LoadServerFiles(void)
 			continue; // Already loaded
 		else if (fileneeded[i].status == FS_FOUND)
 		{
-			P_AddWadFile(fileneeded[i].filename, NULL);
+			P_AddWadFile(fileneeded[i].filename);
 			G_SetGameModified(true);
 			fileneeded[i].status = FS_OPEN;
 		}
@@ -754,6 +749,7 @@ void Got_Filetxpak(void)
 	static INT32 filetime = 0;
 
 	if (!(strcmp(filename, "srb2.pk3")
+		&& strcmp(filename, "srb2.srb")
 		&& strcmp(filename, "srb2.wad")
 		&& strcmp(filename, "zones.dta")
 		&& strcmp(filename, "player.dta")
@@ -898,10 +894,11 @@ void nameonly(char *s)
 		{
 			ns = &(s[j+1]);
 			len = strlen(ns);
-			if (false)
-				M_Memcpy(s, ns, len+1);
-			else
-				memmove(s, ns, len+1);
+#if 0
+			M_Memcpy(s, ns, len+1);
+#else
+			memmove(s, ns, len+1);
+#endif
 			return;
 		}
 }
@@ -949,15 +946,37 @@ filestatus_t checkfilemd5(char *filename, const UINT8 *wantedmd5sum)
 	return FS_FOUND; // will never happen, but makes the compiler shut up
 }
 
+// Rewritten by Monster Iestyn to be less stupid
+// Note: if completepath is true, "filename" is modified, but only if FS_FOUND is going to be returned
+// (Don't worry about WinCE's version of filesearch, nobody cares about that OS anymore)
 filestatus_t findfile(char *filename, const UINT8 *wantedmd5sum, boolean completepath)
 {
-	filestatus_t homecheck = filesearch(filename, srb2home, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
+	filestatus_t homecheck; // store result of last file search
+	boolean badmd5 = false; // store whether md5 was bad from either of the first two searches (if nothing was found in the third)
+
+	// first, check SRB2's "home" directory
+	homecheck = filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
+
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// next, check SRB2's "path" directory
+	homecheck = filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// finally check "." directory
+	homecheck = filesearch(filename, ".", wantedmd5sum, completepath, 10);
 
-	homecheck = filesearch(filename, srb2path, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+	if (homecheck != FS_NOTFOUND) // if not found this time, fall back on the below return statement
+		return homecheck; // otherwise return the result we got
 
-	return filesearch(filename, ".", wantedmd5sum, completepath, 10);
+	return (badmd5 ? FS_MD5SUMBAD : FS_NOTFOUND); // md5 sum bad or file not found
 }
diff --git a/src/d_netfil.h b/src/d_netfil.h
index 6fdd0a8a1e3cbf2995a748c91e865fe7a06ff1fb..3d7c2ed599dafb0b5eb4c772403b42a771d8b236 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -47,7 +47,7 @@ typedef struct
 
 extern INT32 fileneedednum;
 extern fileneeded_t fileneeded[MAX_WADFILES];
-extern char downloaddir[256];
+extern char downloaddir[512];
 
 #ifdef CLIENT_LOADINGSCREEN
 extern INT32 lastfilenum;
diff --git a/src/d_player.h b/src/d_player.h
index e1350fe67db507ea2de57c0a146ecb49657a778e..c133af7039cac5b02ef609be63b8f1fadbc19453 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -196,6 +196,7 @@ typedef enum
 	SH_PITY = 1, // the world's most basic shield ever, given to players who suck at Match
 	SH_WHIRLWIND,
 	SH_ARMAGEDDON,
+	SH_PINK, // PITY IN PINK!
 
 	// Normal shields that use flags
 	SH_ATTRACT = SH_PITY|SH_PROTECTELECTRIC,
@@ -232,7 +233,8 @@ typedef enum
 	// Specific level gimmicks.
 	CR_ZOOMTUBE,
 	CR_ROPEHANG,
-	CR_MACESPIN
+	CR_MACESPIN,
+	CR_MINECART
 } carrytype_t; // pw_carry
 
 // Player powers. (don't edit this comment)
@@ -324,7 +326,8 @@ typedef struct player_s
 	angle_t drawangle;
 
 	// player's ring count
-	INT32 rings;
+	INT16 rings;
+	INT16 spheres;
 
 	SINT8 pity; // i pity the fool.
 	INT32 currentweapon; // current weapon selected.
@@ -381,7 +384,7 @@ typedef struct player_s
 	fixed_t height; // Bounding box changes.
 	fixed_t spinheight;
 
-	SINT8 lives;
+	SINT8 lives; // number of lives - if == INFLIVES, the player has infinite lives
 	SINT8 continues; // continues that player has acquired
 
 	SINT8 xtralife; // Ring Extra Life counter
@@ -454,16 +457,28 @@ typedef struct player_s
 	UINT8 drilldelay;
 	boolean bonustime; // Capsule destroyed, now it's bonus time!
 	mobj_t *capsule; // Go inside the capsule
+	mobj_t *drone; // Move center to the drone
+	fixed_t oldscale; // Pre-Nightserize scale
 	UINT8 mare; // Current mare
+	UINT8 marelap; // Current mare lap
+	UINT8 marebonuslap; // Current mare lap starting from bonus time
 
 	// Statistical purposes.
 	tic_t marebegunat; // Leveltime when mare begun
 	tic_t startedtime; // Time which you started this mare with.
 	tic_t finishedtime; // Time it took you to finish the mare (used for display)
-	INT16 finishedrings; // The rings you had left upon finishing the mare
+	tic_t lapbegunat; // Leveltime when lap begun
+	tic_t lapstartedtime; // Time which you started this lap with.
+	INT16 finishedspheres; // The spheres you had left upon finishing the mare
+	INT16 finishedrings; // The rings/stars you had left upon finishing the mare
 	UINT32 marescore; // score for this nights stage
 	UINT32 lastmarescore; // score for the last mare
+	UINT32 totalmarescore; // score for all mares
 	UINT8 lastmare; // previous mare
+	UINT8 lastmarelap; // previous mare lap
+	UINT8 lastmarebonuslap; // previous mare bonus lap
+	UINT8 totalmarelap; // total mare lap
+	UINT8 totalmarebonuslap; // total mare bonus lap
 	INT32 maxlink; // maximum link obtained
 	UINT8 texttimer; // nights_texttime should not be local
 	UINT8 textvar; // which line of NiGHTS text to show -- let's not use cheap hacks
@@ -489,4 +504,7 @@ typedef struct player_s
 #endif
 } player_t;
 
+// Value for infinite lives
+#define INFLIVES 0x7F
+
 #endif
diff --git a/src/d_think.h b/src/d_think.h
index 2831b9f2b2e7b8b84fd7e030826c4ffd61256e9f..b907c17fd77d6f231c1813df056867322bcf56e4 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 83f684b6fa1bdb50098f3d4b9051c970d6c92500..1ea015439c0fbbae529cc3a98ec21975b27aefb5 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/dehacked.c b/src/dehacked.c
index b4dee1b46f41995f43004662da981810bc50da31..d6ad009623e6baff2938e602321636c3c173a51f 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,10 +28,12 @@
 #include "p_local.h" // for var1 and var2, and some constants
 #include "p_setup.h"
 #include "r_data.h"
+#include "r_draw.h"
 #include "r_sky.h"
 #include "fastcmp.h"
 #include "lua_script.h"
 #include "lua_hook.h"
+#include "d_clisrv.h"
 
 #include "m_cond.h"
 
@@ -71,6 +73,7 @@ static sfxenum_t get_sfx(const char *word);
 static UINT16 get_mus(const char *word, UINT8 dehacked_mode);
 #endif
 static hudnum_t get_huditem(const char *word);
+static menutype_t get_menutype(const char *word);
 #ifndef HAVE_BLUA
 static powertype_t get_power(const char *word);
 #endif
@@ -165,9 +168,14 @@ static char *myhashfgets(char *buf, size_t bufsize, MYFILE *f)
 		if (c == '\n') // Ensure debug line is right...
 			dbg_line++;
 		if (c == '#')
+		{
+			if (i > 0) // don't let i wrap past 0
+				i--; // don't include hash char in string
 			break;
+		}
 	}
-	i++;
+	if (buf[i] != '#') // don't include hash char in string
+		i++;
 	buf[i] = '\0';
 
 	return buf;
@@ -298,11 +306,11 @@ static void clear_levels(void)
 static boolean findFreeSlot(INT32 *num)
 {
 	// Send the character select entry to a free slot.
-	while (*num < 32 && (description[*num].used))
+	while (*num < MAXSKINS && (description[*num].used))
 		*num = *num+1;
 
 	// No more free slots. :(
-	if (*num >= 32)
+	if (*num >= MAXSKINS)
 		return false;
 
 	description[*num].picname[0] = '\0'; // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
@@ -509,7 +517,9 @@ static void readfreeslots(MYFILE *f)
 					continue;
 				// Copy in the spr2 name and increment free_spr2.
 				if (free_spr2 < NUMPLAYERSPRITES) {
+					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
 					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
 					spr2names[free_spr2++][4] = 0;
 				} else
 					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
@@ -853,25 +863,27 @@ static const struct {
 	const char *name;
 	const mobjtype_t type;
 } FLICKYTYPES[] = {
-	{"BLUEBIRD", MT_FLICKY_01},
-	{"RABBIT",   MT_FLICKY_02},
-	{"CHICKEN",  MT_FLICKY_03},
-	{"SEAL",     MT_FLICKY_04},
-	{"PIG",      MT_FLICKY_05},
-	{"CHIPMUNK", MT_FLICKY_06},
-	{"PENGUIN",  MT_FLICKY_07},
-	{"FISH",     MT_FLICKY_08},
-	{"RAM",      MT_FLICKY_09},
-	{"PUFFIN",   MT_FLICKY_10},
-	{"COW",      MT_FLICKY_11},
-	{"RAT",      MT_FLICKY_12},
-	{"BEAR",     MT_FLICKY_13},
-	{"DOVE",     MT_FLICKY_14},
-	{"CAT",      MT_FLICKY_15},
-	{"CANARY",   MT_FLICKY_16},
+	{"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky)
+	{"RABBIT",   MT_FLICKY_02}, // Pocky (1)
+	{"CHICKEN",  MT_FLICKY_03}, // Cucky (1)
+	{"SEAL",     MT_FLICKY_04}, // Rocky (1)
+	{"PIG",      MT_FLICKY_05}, // Picky (1)
+	{"CHIPMUNK", MT_FLICKY_06}, // Ricky (1)
+	{"PENGUIN",  MT_FLICKY_07}, // Pecky (1)
+	{"FISH",     MT_FLICKY_08}, // Nicky (CD)
+	{"RAM",      MT_FLICKY_09}, // Flocky (CD)
+	{"PUFFIN",   MT_FLICKY_10}, // Wicky (CD)
+	{"COW",      MT_FLICKY_11}, // Macky (SRB2)
+	{"RAT",      MT_FLICKY_12}, // Micky (2)
+	{"BEAR",     MT_FLICKY_13}, // Becky (2)
+	{"DOVE",     MT_FLICKY_14}, // Docky (CD)
+	{"CAT",      MT_FLICKY_15}, // Nyannyan (Flicky)
+	{"CANARY",   MT_FLICKY_16}, // Lucky (CD)
 	{"a", 0}, // End of normal flickies - a lower case character so will never fastcmp valid with uppercase tmp
-	//{"FLICKER",  MT_FLICKER},
-	{"SEED",          MT_SEED},
+	//{"FLICKER",          MT_FLICKER}, // Flacky (SRB2)
+	{"SPIDER",   MT_SECRETFLICKY_01}, // Sticky (SRB2)
+	{"BAT",      MT_SECRETFLICKY_02}, // Backy (SRB2)
+	{"SEED",                MT_SEED}, // Seed (CD)
 	{NULL, 0}
 };
 
@@ -912,7 +924,10 @@ static void readlevelheader(MYFILE *f, INT32 num)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -1095,6 +1110,7 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				if      (fastcmp(word2, "TITLE"))      i = 1100;
 				else if (fastcmp(word2, "EVALUATION")) i = 1101;
 				else if (fastcmp(word2, "CREDITS"))    i = 1102;
+				else if (fastcmp(word2, "ENDING"))     i = 1103;
 				else
 				// Support using the actual map name,
 				// i.e., Nextlevel = AB, Nextlevel = FZ, etc.
@@ -1149,6 +1165,22 @@ static void readlevelheader(MYFILE *f, INT32 num)
 #endif
 			else if (fastcmp(word, "MUSICTRACK"))
 				mapheaderinfo[num-1]->mustrack = ((UINT16)i - 1);
+			else if (fastcmp(word, "MUSICPOS"))
+				mapheaderinfo[num-1]->muspos = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTERFADEOUT"))
+				mapheaderinfo[num-1]->musinterfadeout = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTER"))
+				deh_strlcpy(mapheaderinfo[num-1]->musintername, word2,
+					sizeof(mapheaderinfo[num-1]->musintername), va("Level header %d: intermission music", num));
+			else if (fastcmp(word, "MUSICPOSTBOSS"))
+				deh_strlcpy(mapheaderinfo[num-1]->muspostbossname, word2,
+					sizeof(mapheaderinfo[num-1]->muspostbossname), va("Level header %d: post-boss music", num));
+			else if (fastcmp(word, "MUSICPOSTBOSSTRACK"))
+				mapheaderinfo[num-1]->muspostbosstrack = ((UINT16)i - 1);
+			else if (fastcmp(word, "MUSICPOSTBOSSPOS"))
+				mapheaderinfo[num-1]->muspostbosspos = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICPOSTBOSSFADEIN"))
+				mapheaderinfo[num-1]->muspostbossfadein = (UINT32)get_number(word2);
 			else if (fastcmp(word, "FORCECHARACTER"))
 			{
 				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
@@ -1194,13 +1226,17 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				else if (fastcmp(word2, "NORMAL")) i =  0;
 				else if (fastcmp(word2, "BOSS"))   i =  1;
 				else if (fastcmp(word2, "ERZ3"))   i =  2;
+				else if (fastcmp(word2, "NIGHTS")) i =  3;
+				else if (fastcmp(word2, "NIGHTSLINK")) i = 4;
 
-				if (i >= -1 && i <= 2) // -1 for no bonus. Max is 2.
+				if (i >= -1 && i <= 4) // -1 for no bonus. Max is 4.
 					mapheaderinfo[num-1]->bonustype = (SINT8)i;
 				else
 					deh_warning("Level header %d: invalid bonus type number %d", num, i);
 			}
 
+			else if (fastcmp(word, "MAXBONUSLIVES"))
+				mapheaderinfo[num-1]->maxbonuslives = (SINT8)i;
 			else if (fastcmp(word, "LEVELFLAGS"))
 				mapheaderinfo[num-1]->levelflags = (UINT8)i;
 			else if (fastcmp(word, "MENUFLAGS"))
@@ -1249,6 +1285,13 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				else
 					mapheaderinfo[num-1]->levelflags &= ~LF_SAVEGAME;
 			}
+			else if (fastcmp(word, "MIXNIGHTSCOUNTDOWN"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_MIXNIGHTSCOUNTDOWN;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_MIXNIGHTSCOUNTDOWN;
+			}
 
 			// Individual triggers for menu flags
 			else if (fastcmp(word, "HIDDEN"))
@@ -1293,6 +1336,8 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				else
 					mapheaderinfo[num-1]->menuflags &= ~LF2_WIDEICON;
 			}
+			else if (fastcmp(word, "STARTRINGS"))
+				mapheaderinfo[num-1]->startrings = (UINT16)i;
 			else
 				deh_warning("Level header %d: unknown word '%s'", num, word);
 		}
@@ -1445,6 +1490,10 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 			{
 				cutscenes[num]->scene[scenenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
 			}
+			else if (fastcmp(word, "MUSICPOS"))
+			{
+				cutscenes[num]->scene[scenenum].musswitchposition = (UINT32)get_number(word2);
+			}
 			else if (fastcmp(word, "MUSICLOOP"))
 			{
 				cutscenes[num]->scene[scenenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
@@ -1523,16 +1572,538 @@ static void readcutscene(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "SCENE"))
 			{
-				if (1 <= value && value <= 128)
-				{
-					readcutscenescene(f, num, value - 1);
-				}
-				else
-					deh_warning("Scene number %d out of range (1 - 128)", value);
-
+				if (1 <= value && value <= 128)
+				{
+					readcutscenescene(f, num, value - 1);
+				}
+				else
+					deh_warning("Scene number %d out of range (1 - 128)", value);
+
+			}
+			else
+				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
+{
+	char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	INT32 i;
+	UINT16 usi;
+	UINT8 picid;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "PAGETEXT"))
+			{
+				char *pagetext = NULL;
+				char *buffer;
+				const int bufferlen = 4096;
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '=')
+					{
+						pagetext = &s[i+2];
+						break;
+					}
+				}
+
+				if (!pagetext)
+				{
+					Z_Free(textprompts[num]->page[pagenum].text);
+					textprompts[num]->page[pagenum].text = NULL;
+					continue;
+				}
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '\0')
+					{
+						s[i] = '\n';
+						s[i+1] = '\0';
+						break;
+					}
+				}
+
+				buffer = Z_Malloc(4096, PU_STATIC, NULL);
+				strcpy(buffer, pagetext);
+
+				// \todo trim trailing whitespace before the #
+				// and also support # at the end of a PAGETEXT with no line break
+
+				strcat(buffer,
+					myhashfgets(pagetext, bufferlen
+					- strlen(buffer) - 1, f));
+
+				// A text prompt overwriting another one...
+				Z_Free(textprompts[num]->page[pagenum].text);
+
+				textprompts[num]->page[pagenum].text = Z_StrDup(buffer);
+
+				Z_Free(buffer);
+
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+			usi = (UINT16)i;
+
+			// copypasta from readcutscenescene
+			if (fastcmp(word, "NUMBEROFPICS"))
+			{
+				textprompts[num]->page[pagenum].numpics = (UINT8)i;
+			}
+			else if (fastcmp(word, "PICMODE"))
+			{
+				UINT8 picmode = 0; // PROMPT_PIC_PERSIST
+				if (usi == 1 || word2[0] == 'L') picmode = PROMPT_PIC_LOOP;
+				else if (usi == 2 || word2[0] == 'D' || word2[0] == 'H') picmode = PROMPT_PIC_DESTROY;
+				textprompts[num]->page[pagenum].picmode = picmode;
+			}
+			else if (fastcmp(word, "PICTOLOOP"))
+				textprompts[num]->page[pagenum].pictoloop = (UINT8)i;
+			else if (fastcmp(word, "PICTOSTART"))
+				textprompts[num]->page[pagenum].pictostart = (UINT8)i;
+			else if (fastcmp(word, "PICSMETAPAGE"))
+			{
+				if (usi && usi <= textprompts[num]->numpages)
+				{
+					UINT8 metapagenum = usi - 1;
+
+					textprompts[num]->page[pagenum].numpics = textprompts[num]->page[metapagenum].numpics;
+					textprompts[num]->page[pagenum].picmode = textprompts[num]->page[metapagenum].picmode;
+					textprompts[num]->page[pagenum].pictoloop = textprompts[num]->page[metapagenum].pictoloop;
+					textprompts[num]->page[pagenum].pictostart = textprompts[num]->page[metapagenum].pictostart;
+
+					for (picid = 0; picid < MAX_PROMPT_PICS; picid++)
+					{
+						strncpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], 8);
+						textprompts[num]->page[pagenum].pichires[picid] = textprompts[num]->page[metapagenum].pichires[picid];
+						textprompts[num]->page[pagenum].picduration[picid] = textprompts[num]->page[metapagenum].picduration[picid];
+						textprompts[num]->page[pagenum].xcoord[picid] = textprompts[num]->page[metapagenum].xcoord[picid];
+						textprompts[num]->page[pagenum].ycoord[picid] = textprompts[num]->page[metapagenum].ycoord[picid];
+					}
+				}
+			}
+			else if (fastncmp(word, "PIC", 3))
+			{
+				picid = (UINT8)atoi(word + 3);
+				if (picid > MAX_PROMPT_PICS || picid == 0)
+				{
+					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+					continue;
+				}
+				--picid;
+
+				if (fastcmp(word+4, "NAME"))
+				{
+					strncpy(textprompts[num]->page[pagenum].picname[picid], word2, 8);
+				}
+				else if (fastcmp(word+4, "HIRES"))
+				{
+					textprompts[num]->page[pagenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+				}
+				else if (fastcmp(word+4, "DURATION"))
+				{
+					textprompts[num]->page[pagenum].picduration[picid] = usi;
+				}
+				else if (fastcmp(word+4, "XCOORD"))
+				{
+					textprompts[num]->page[pagenum].xcoord[picid] = usi;
+				}
+				else if (fastcmp(word+4, "YCOORD"))
+				{
+					textprompts[num]->page[pagenum].ycoord[picid] = usi;
+				}
+				else
+					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7);
+				textprompts[num]->page[pagenum].musswitch[6] = 0;
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				i = get_mus(word2, true);
+				if (i && i <= 1035)
+					snprintf(textprompts[num]->page[pagenum].musswitch, 7, "%sM", G_BuildMapName(i));
+				else if (i && i <= 1050)
+					strncpy(textprompts[num]->page[pagenum].musswitch, compat_special_music_slots[i - 1036], 7);
+				else
+					textprompts[num]->page[pagenum].musswitch[0] = 0; // becomes empty string
+				textprompts[num]->page[pagenum].musswitch[6] = 0;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+			{
+				textprompts[num]->page[pagenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
+			}
+			else if (fastcmp(word, "MUSICLOOP"))
+			{
+				textprompts[num]->page[pagenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			// end copypasta from readcutscenescene
+			else if (fastcmp(word, "NAME"))
+			{
+				INT32 j;
+
+				// HACK: Add yellow control char now
+				// so the drawing function doesn't call it repeatedly
+				char name[34];
+				name[0] = '\x82'; // color yellow
+				name[1] = 0;
+				strncat(name, word2, 33);
+				name[33] = 0;
+
+				// Replace _ with ' '
+				for (j = 0; j < 32 && name[j]; j++)
+				{
+					if (name[j] == '_')
+						name[j] = ' ';
+				}
+
+				strncpy(textprompts[num]->page[pagenum].name, name, 32);
+			}
+			else if (fastcmp(word, "ICON"))
+				strncpy(textprompts[num]->page[pagenum].iconname, word2, 8);
+			else if (fastcmp(word, "ICONALIGN"))
+				textprompts[num]->page[pagenum].rightside = (i || word2[0] == 'R');
+			else if (fastcmp(word, "ICONFLIP"))
+				textprompts[num]->page[pagenum].iconflip = (i || word2[0] == 'T' || word2[0] == 'Y');
+			else if (fastcmp(word, "LINES"))
+				textprompts[num]->page[pagenum].lines = usi;
+			else if (fastcmp(word, "BACKCOLOR"))
+			{
+				INT32 backcolor;
+				if      (i == 0 || fastcmp(word2, "WHITE")) backcolor = 0;
+				else if (i == 1 || fastcmp(word2, "GRAY") || fastcmp(word2, "GREY") ||
+					fastcmp(word2, "BLACK")) backcolor = 1;
+				else if (i == 2 || fastcmp(word2, "SEPIA")) backcolor = 2;
+				else if (i == 3 || fastcmp(word2, "BROWN")) backcolor = 3;
+				else if (i == 4 || fastcmp(word2, "PINK")) backcolor = 4;
+				else if (i == 5 || fastcmp(word2, "RASPBERRY")) backcolor = 5;
+				else if (i == 6 || fastcmp(word2, "RED")) backcolor = 6;
+				else if (i == 7 || fastcmp(word2, "CREAMSICLE")) backcolor = 7;
+				else if (i == 8 || fastcmp(word2, "ORANGE")) backcolor = 8;
+				else if (i == 9 || fastcmp(word2, "GOLD")) backcolor = 9;
+				else if (i == 10 || fastcmp(word2, "YELLOW")) backcolor = 10;
+				else if (i == 11 || fastcmp(word2, "EMERALD")) backcolor = 11;
+				else if (i == 12 || fastcmp(word2, "GREEN")) backcolor = 12;
+				else if (i == 13 || fastcmp(word2, "CYAN") || fastcmp(word2, "AQUA")) backcolor = 13;
+				else if (i == 14 || fastcmp(word2, "STEEL")) backcolor = 14;
+				else if (i == 15 || fastcmp(word2, "PERIWINKLE")) backcolor = 15;
+				else if (i == 16 || fastcmp(word2, "BLUE")) backcolor = 16;
+				else if (i == 17 || fastcmp(word2, "PURPLE")) backcolor = 17;
+				else if (i == 18 || fastcmp(word2, "LAVENDER")) backcolor = 18;
+				else if (i < 0) backcolor = INT32_MAX; // CONS_BACKCOLOR user-configured
+				else backcolor = 1; // default gray
+				textprompts[num]->page[pagenum].backcolor = backcolor;
+			}
+			else if (fastcmp(word, "ALIGN"))
+			{
+				UINT8 align = 0; // left
+				if (usi == 1 || word2[0] == 'R') align = 1;
+				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
+				textprompts[num]->page[pagenum].align = align;
+			}
+			else if (fastcmp(word, "VERTICALALIGN"))
+			{
+				UINT8 align = 0; // top
+				if (usi == 1 || word2[0] == 'B') align = 1;
+				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
+				textprompts[num]->page[pagenum].verticalalign = align;
+			}
+			else if (fastcmp(word, "TEXTSPEED"))
+				textprompts[num]->page[pagenum].textspeed = get_number(word2);
+			else if (fastcmp(word, "TEXTSFX"))
+				textprompts[num]->page[pagenum].textsfx = get_number(word2);
+			else if (fastcmp(word, "HIDEHUD"))
+			{
+				UINT8 hidehud = 0;
+				if ((word2[0] == 'F' && (word2[1] == 'A' || !word2[1])) || word2[0] == 'N') hidehud = 0; // false
+				else if (usi == 1 || word2[0] == 'T' || word2[0] == 'Y') hidehud = 1; // true (hide appropriate HUD elements)
+				else if (usi == 2 || word2[0] == 'A' || (word2[0] == 'F' && word2[1] == 'O')) hidehud = 2; // force (hide all HUD elements)
+				textprompts[num]->page[pagenum].hidehud = hidehud;
+			}
+			else if (fastcmp(word, "METAPAGE"))
+			{
+				if (usi && usi <= textprompts[num]->numpages)
+				{
+					UINT8 metapagenum = usi - 1;
+
+					strncpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, 32);
+					strncpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, 8);
+					textprompts[num]->page[pagenum].rightside = textprompts[num]->page[metapagenum].rightside;
+					textprompts[num]->page[pagenum].iconflip = textprompts[num]->page[metapagenum].iconflip;
+					textprompts[num]->page[pagenum].lines = textprompts[num]->page[metapagenum].lines;
+					textprompts[num]->page[pagenum].backcolor = textprompts[num]->page[metapagenum].backcolor;
+					textprompts[num]->page[pagenum].align = textprompts[num]->page[metapagenum].align;
+					textprompts[num]->page[pagenum].verticalalign = textprompts[num]->page[metapagenum].verticalalign;
+					textprompts[num]->page[pagenum].textspeed = textprompts[num]->page[metapagenum].textspeed;
+					textprompts[num]->page[pagenum].textsfx = textprompts[num]->page[metapagenum].textsfx;
+					textprompts[num]->page[pagenum].hidehud = textprompts[num]->page[metapagenum].hidehud;
+
+					// music: don't copy, else each page change may reset the music
+				}
+			}
+			else if (fastcmp(word, "TAG"))
+				strncpy(textprompts[num]->page[pagenum].tag, word2, 33);
+			else if (fastcmp(word, "NEXTPROMPT"))
+				textprompts[num]->page[pagenum].nextprompt = usi;
+			else if (fastcmp(word, "NEXTPAGE"))
+				textprompts[num]->page[pagenum].nextpage = usi;
+			else if (fastcmp(word, "NEXTTAG"))
+				strncpy(textprompts[num]->page[pagenum].nexttag, word2, 33);
+			else if (fastcmp(word, "TIMETONEXT"))
+				textprompts[num]->page[pagenum].timetonext = get_number(word2);
+			else
+				deh_warning("PromptPage %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readtextprompt(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	// Allocate memory for this prompt if we don't yet have any
+	if (!textprompts[num])
+		textprompts[num] = Z_Calloc(sizeof (textprompt_t), PU_STATIC, NULL);
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " ");
+			if (word2)
+				value = atoi(word2);
+			else
+			{
+				deh_warning("No value for token %s", word);
+				continue;
+			}
+
+			if (fastcmp(word, "NUMPAGES"))
+			{
+				textprompts[num]->numpages = min(max(value, 0), MAX_PAGES);
+			}
+			else if (fastcmp(word, "PAGE"))
+			{
+				if (1 <= value && value <= MAX_PAGES)
+				{
+					textprompts[num]->page[value - 1].backcolor = 1; // default to gray
+					textprompts[num]->page[value - 1].hidehud = 1; // hide appropriate HUD elements
+					readtextpromptpage(f, num, value - 1);
+				}
+				else
+					deh_warning("Page number %d out of range (1 - %d)", value, MAX_PAGES);
+
+			}
+			else
+				deh_warning("Prompt %d: unknown word '%s', Page <num> expected.", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readmenu(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = (tmp += 2);
+			strupr(word2);
+
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "BACKGROUNDNAME"))
+			{
+				strncpy(menupres[num].bgname, word2, 8);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDEBACKGROUND"))
+			{
+				menupres[num].bghide = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "BACKGROUNDCOLOR"))
+			{
+				menupres[num].bgcolor = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS"))
+			{
+				// true by default, except MM_MAIN
+				menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED")
+				|| fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED"))
+			{
+				menupres[num].titlescrollxspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLYSPEED") || fastcmp(word, "SCROLLYSPEED"))
+			{
+				menupres[num].titlescrollyspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				strncpy(menupres[num].musname, word2, 7);
+				menupres[num].musname[6] = 0;
+				titlechanged = true;
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				value = get_mus(word2, true);
+				if (value && value <= 1035)
+					snprintf(menupres[num].musname, 7, "%sM", G_BuildMapName(value));
+				else if (value && value <= 1050)
+					strncpy(menupres[num].musname, compat_special_music_slots[value - 1036], 7);
+				else
+					menupres[num].musname[0] = 0; // becomes empty string
+				menupres[num].musname[6] = 0;
+				titlechanged = true;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+			{
+				menupres[num].mustrack = ((UINT16)value - 1);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "MUSICLOOP"))
+			{
+				// true by default except MM_MAIN
+				menupres[num].muslooping = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOMUSIC"))
+			{
+				menupres[num].musstop = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "IGNOREMUSIC"))
+			{
+				menupres[num].musignore = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "FADESTRENGTH"))
+			{
+				// one-based, <= 0 means use default value. 1-32
+				menupres[num].fadestrength = get_number(word2)-1;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOENTERBUBBLE"))
+			{
+				menupres[num].enterbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOEXITBUBBLE"))
+			{
+				menupres[num].exitbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "ENTERTAG"))
+			{
+				menupres[num].entertag = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "EXITTAG"))
+			{
+				menupres[num].exittag = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "ENTERWIPE"))
+			{
+				menupres[num].enterwipe = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "EXITWIPE"))
+			{
+				menupres[num].exitwipe = get_number(word2);
+				titlechanged = true;
 			}
-			else
-				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
@@ -1567,7 +2138,10 @@ static void readhuditem(MYFILE *f, INT32 num)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -1584,6 +2158,10 @@ static void readhuditem(MYFILE *f, INT32 num)
 			{
 				hudinfo[num].y = i;
 			}
+			else if (fastcmp(word, "F"))
+			{
+				hudinfo[num].f = i;
+			}
 			else
 				deh_warning("Level header %d: unknown word '%s'", num, word);
 		}
@@ -1612,202 +2190,248 @@ typedef struct
   */
 static actionpointer_t actionpointers[] =
 {
-	{{A_Explode},              "A_EXPLODE"},
-	{{A_Pain},                 "A_PAIN"},
-	{{A_Fall},                 "A_FALL"},
-	{{A_MonitorPop},           "A_MONITORPOP"},
-	{{A_GoldMonitorPop},       "A_GOLDMONITORPOP"},
-	{{A_GoldMonitorRestore},   "A_GOLDMONITORRESTORE"},
-	{{A_GoldMonitorSparkle},   "A_GOLDMONITORSPARKLE"},
-	{{A_Look},                 "A_LOOK"},
-	{{A_Chase},                "A_CHASE"},
-	{{A_FaceStabChase},        "A_FACESTABCHASE"},
-	{{A_FaceTarget},           "A_FACETARGET"},
-	{{A_FaceTracer},           "A_FACETRACER"},
-	{{A_Scream},               "A_SCREAM"},
-	{{A_BossDeath},            "A_BOSSDEATH"},
-	{{A_CustomPower},          "A_CUSTOMPOWER"},
-	{{A_GiveWeapon},           "A_GIVEWEAPON"},
-	{{A_RingBox},              "A_RINGBOX"},
-	{{A_Invincibility},        "A_INVINCIBILITY"},
-	{{A_SuperSneakers},        "A_SUPERSNEAKERS"},
-	{{A_BunnyHop},             "A_BUNNYHOP"},
-	{{A_BubbleSpawn},          "A_BUBBLESPAWN"},
-	{{A_FanBubbleSpawn},       "A_FANBUBBLESPAWN"},
-	{{A_BubbleRise},           "A_BUBBLERISE"},
-	{{A_BubbleCheck},          "A_BUBBLECHECK"},
-	{{A_AwardScore},           "A_AWARDSCORE"},
-	{{A_ExtraLife},            "A_EXTRALIFE"},
-	{{A_GiveShield},           "A_GIVESHIELD"},
-	{{A_GravityBox},           "A_GRAVITYBOX"},
-	{{A_ScoreRise},            "A_SCORERISE"},
-	{{A_ParticleSpawn},        "A_PARTICLESPAWN"},
-	{{A_AttractChase},         "A_ATTRACTCHASE"},
-	{{A_DropMine},             "A_DROPMINE"},
-	{{A_FishJump},             "A_FISHJUMP"},
-	{{A_ThrownRing},           "A_THROWNRING"},
-	{{A_SetSolidSteam},        "A_SETSOLIDSTEAM"},
-	{{A_UnsetSolidSteam},      "A_UNSETSOLIDSTEAM"},
-	{{A_SignPlayer},           "A_SIGNPLAYER"},
-	{{A_OverlayThink},         "A_OVERLAYTHINK"},
-	{{A_JetChase},             "A_JETCHASE"},
-	{{A_JetbThink},            "A_JETBTHINK"},
-	{{A_JetgThink},            "A_JETGTHINK"},
-	{{A_JetgShoot},            "A_JETGSHOOT"},
-	{{A_ShootBullet},          "A_SHOOTBULLET"},
-	{{A_MinusDigging},         "A_MINUSDIGGING"},
-	{{A_MinusPopup},           "A_MINUSPOPUP"},
-	{{A_MinusCheck},           "A_MINUSCHECK"},
-	{{A_ChickenCheck},         "A_CHICKENCHECK"},
-	{{A_MouseThink},           "A_MOUSETHINK"},
-	{{A_DetonChase},           "A_DETONCHASE"},
-	{{A_CapeChase},            "A_CAPECHASE"},
-	{{A_RotateSpikeBall},      "A_ROTATESPIKEBALL"},
-	{{A_SlingAppear},          "A_SLINGAPPEAR"},
-	{{A_UnidusBall},           "A_UNIDUSBALL"},
-	{{A_RockSpawn},            "A_ROCKSPAWN"},
-	{{A_SetFuse},              "A_SETFUSE"},
-	{{A_CrawlaCommanderThink}, "A_CRAWLACOMMANDERTHINK"},
-	{{A_SmokeTrailer},         "A_SMOKETRAILER"},
-	{{A_RingExplode},          "A_RINGEXPLODE"},
-	{{A_OldRingExplode},       "A_OLDRINGEXPLODE"},
-	{{A_MixUp},                "A_MIXUP"},
-	{{A_RecyclePowers},        "A_RECYCLEPOWERS"},
-	{{A_Boss1Chase},           "A_BOSS1CHASE"},
-	{{A_FocusTarget},          "A_FOCUSTARGET"},
-	{{A_Boss2Chase},           "A_BOSS2CHASE"},
-	{{A_Boss2Pogo},            "A_BOSS2POGO"},
-	{{A_BossZoom},             "A_BOSSZOOM"},
-	{{A_BossScream},           "A_BOSSSCREAM"},
-	{{A_Boss2TakeDamage},      "A_BOSS2TAKEDAMAGE"},
-	{{A_Boss7Chase},           "A_BOSS7CHASE"},
-	{{A_GoopSplat},            "A_GOOPSPLAT"},
-	{{A_Boss2PogoSFX},         "A_BOSS2POGOSFX"},
-	{{A_Boss2PogoTarget},      "A_BOSS2POGOTARGET"},
-	{{A_BossJetFume},          "A_BOSSJETFUME"},
-	{{A_EggmanBox},            "A_EGGMANBOX"},
-	{{A_TurretFire},           "A_TURRETFIRE"},
-	{{A_SuperTurretFire},      "A_SUPERTURRETFIRE"},
-	{{A_TurretStop},           "A_TURRETSTOP"},
-	{{A_JetJawRoam},           "A_JETJAWROAM"},
-	{{A_JetJawChomp},          "A_JETJAWCHOMP"},
-	{{A_PointyThink},          "A_POINTYTHINK"},
-	{{A_CheckBuddy},           "A_CHECKBUDDY"},
-	{{A_HoodThink},            "A_HOODTHINK"},
-	{{A_ArrowCheck},           "A_ARROWCHECK"},
-	{{A_SnailerThink},         "A_SNAILERTHINK"},
-	{{A_SharpChase},           "A_SHARPCHASE"},
-	{{A_SharpSpin},            "A_SHARPSPIN"},
-	{{A_VultureVtol},          "A_VULTUREVTOL"},
-	{{A_VultureCheck},         "A_VULTURECHECK"},
-	{{A_SkimChase},            "A_SKIMCHASE"},
-	{{A_1upThinker},           "A_1UPTHINKER"},
-	{{A_SkullAttack},          "A_SKULLATTACK"},
-	{{A_LobShot},              "A_LOBSHOT"},
-	{{A_FireShot},             "A_FIRESHOT"},
-	{{A_SuperFireShot},        "A_SUPERFIRESHOT"},
-	{{A_BossFireShot},         "A_BOSSFIRESHOT"},
-	{{A_Boss7FireMissiles},    "A_BOSS7FIREMISSILES"},
-	{{A_Boss1Laser},           "A_BOSS1LASER"},
-	{{A_Boss4Reverse},         "A_BOSS4REVERSE"},
-	{{A_Boss4SpeedUp},         "A_BOSS4SPEEDUP"},
-	{{A_Boss4Raise},           "A_BOSS4RAISE"},
-	{{A_SparkFollow},          "A_SPARKFOLLOW"},
-	{{A_BuzzFly},              "A_BUZZFLY"},
-	{{A_GuardChase},           "A_GUARDCHASE"},
-	{{A_EggShield},            "A_EGGSHIELD"},
-	{{A_SetReactionTime},      "A_SETREACTIONTIME"},
-	{{A_Boss1Spikeballs},      "A_BOSS1SPIKEBALLS"},
-	{{A_Boss3TakeDamage},      "A_BOSS3TAKEDAMAGE"},
-	{{A_Boss3Path},            "A_BOSS3PATH"},
-	{{A_LinedefExecute},       "A_LINEDEFEXECUTE"},
-	{{A_PlaySeeSound},         "A_PLAYSEESOUND"},
-	{{A_PlayAttackSound},      "A_PLAYATTACKSOUND"},
-	{{A_PlayActiveSound},      "A_PLAYACTIVESOUND"},
-	{{A_SpawnObjectAbsolute},  "A_SPAWNOBJECTABSOLUTE"},
-	{{A_SpawnObjectRelative},  "A_SPAWNOBJECTRELATIVE"},
-	{{A_ChangeAngleRelative},  "A_CHANGEANGLERELATIVE"},
-	{{A_ChangeAngleAbsolute},  "A_CHANGEANGLEABSOLUTE"},
-	{{A_PlaySound},            "A_PLAYSOUND"},
-	{{A_FindTarget},           "A_FINDTARGET"},
-	{{A_FindTracer},           "A_FINDTRACER"},
-	{{A_SetTics},              "A_SETTICS"},
-	{{A_SetRandomTics},        "A_SETRANDOMTICS"},
-	{{A_ChangeColorRelative},  "A_CHANGECOLORRELATIVE"},
-	{{A_ChangeColorAbsolute},  "A_CHANGECOLORABSOLUTE"},
-	{{A_MoveRelative},         "A_MOVERELATIVE"},
-	{{A_MoveAbsolute},         "A_MOVEABSOLUTE"},
-	{{A_Thrust},               "A_THRUST"},
-	{{A_ZThrust},              "A_ZTHRUST"},
-	{{A_SetTargetsTarget},     "A_SETTARGETSTARGET"},
-	{{A_SetObjectFlags},       "A_SETOBJECTFLAGS"},
-	{{A_SetObjectFlags2},      "A_SETOBJECTFLAGS2"},
-	{{A_RandomState},          "A_RANDOMSTATE"},
-	{{A_RandomStateRange},     "A_RANDOMSTATERANGE"},
-	{{A_DualAction},           "A_DUALACTION"},
-	{{A_RemoteAction},         "A_REMOTEACTION"},
-	{{A_ToggleFlameJet},       "A_TOGGLEFLAMEJET"},
-	{{A_OrbitNights},          "A_ORBITNIGHTS"},
-	{{A_GhostMe},              "A_GHOSTME"},
-	{{A_SetObjectState},       "A_SETOBJECTSTATE"},
-	{{A_SetObjectTypeState},   "A_SETOBJECTTYPESTATE"},
-	{{A_KnockBack},            "A_KNOCKBACK"},
-	{{A_PushAway},             "A_PUSHAWAY"},
-	{{A_RingDrain},            "A_RINGDRAIN"},
-	{{A_SplitShot},            "A_SPLITSHOT"},
-	{{A_MissileSplit},         "A_MISSILESPLIT"},
-	{{A_MultiShot},            "A_MULTISHOT"},
-	{{A_InstaLoop},            "A_INSTALOOP"},
-	{{A_Custom3DRotate},       "A_CUSTOM3DROTATE"},
-	{{A_SearchForPlayers},     "A_SEARCHFORPLAYERS"},
-	{{A_CheckRandom},          "A_CHECKRANDOM"},
-	{{A_CheckTargetRings},     "A_CHECKTARGETRINGS"},
-	{{A_CheckRings},           "A_CHECKRINGS"},
-	{{A_CheckTotalRings},      "A_CHECKTOTALRINGS"},
-	{{A_CheckHealth},          "A_CHECKHEALTH"},
-	{{A_CheckRange},           "A_CHECKRANGE"},
-	{{A_CheckHeight},          "A_CHECKHEIGHT"},
-	{{A_CheckTrueRange},       "A_CHECKTRUERANGE"},
-	{{A_CheckThingCount},      "A_CHECKTHINGCOUNT"},
-	{{A_CheckAmbush},          "A_CHECKAMBUSH"},
-	{{A_CheckCustomValue},     "A_CHECKCUSTOMVALUE"},
-	{{A_CheckCusValMemo},      "A_CHECKCUSVALMEMO"},
-	{{A_SetCustomValue},       "A_SETCUSTOMVALUE"},
-	{{A_UseCusValMemo},        "A_USECUSVALMEMO"},
-	{{A_RelayCustomValue},     "A_RELAYCUSTOMVALUE"},
-	{{A_CusValAction},         "A_CUSVALACTION"},
-	{{A_ForceStop},            "A_FORCESTOP"},
-	{{A_ForceWin},             "A_FORCEWIN"},
-	{{A_SpikeRetract},         "A_SPIKERETRACT"},
-	{{A_InfoState},            "A_INFOSTATE"},
-	{{A_Repeat},               "A_REPEAT"},
-	{{A_SetScale},             "A_SETSCALE"},
-	{{A_RemoteDamage},         "A_REMOTEDAMAGE"},
-	{{A_HomingChase},          "A_HOMINGCHASE"},
-	{{A_TrapShot},             "A_TRAPSHOT"},
-	{{A_VileTarget},           "A_VILETARGET"},
-	{{A_VileAttack},           "A_VILEATTACK"},
-	{{A_VileFire},             "A_VILEFIRE"},
-	{{A_BrakChase},            "A_BRAKCHASE"},
-	{{A_BrakFireShot},         "A_BRAKFIRESHOT"},
-	{{A_BrakLobShot},          "A_BRAKLOBSHOT"},
-	{{A_NapalmScatter},        "A_NAPALMSCATTER"},
-	{{A_SpawnFreshCopy},       "A_SPAWNFRESHCOPY"},
-	{{A_FlickySpawn},          "A_FLICKYSPAWN"},
-	{{A_FlickyAim},            "A_FLICKYAIM"},
-	{{A_FlickyFly},            "A_FLICKYFLY"},
-	{{A_FlickySoar},           "A_FLICKYSOAR"},
-	{{A_FlickyCoast},          "A_FLICKYCOAST"},
-	{{A_FlickyHop},            "A_FLICKYHOP"},
-	{{A_FlickyFlounder},       "A_FLICKYFLOUNDER"},
-	{{A_FlickyCheck},          "A_FLICKYCHECK"},
-	{{A_FlickyHeightCheck},    "A_FLICKYHEIGHTCHECK"},
-	{{A_FlickyFlutter},        "A_FLICKYFLUTTER"},
-	{{A_FlameParticle},        "A_FLAMEPARTICLE"},
-	{{A_FadeOverlay},          "A_FADEOVERLAY"},
-	{{A_Boss5Jump},            "A_BOSS5JUMP"},
-
-	{{NULL},                   "NONE"},
+	{{A_Explode},                "A_EXPLODE"},
+	{{A_Pain},                   "A_PAIN"},
+	{{A_Fall},                   "A_FALL"},
+	{{A_MonitorPop},             "A_MONITORPOP"},
+	{{A_GoldMonitorPop},         "A_GOLDMONITORPOP"},
+	{{A_GoldMonitorRestore},     "A_GOLDMONITORRESTORE"},
+	{{A_GoldMonitorSparkle},     "A_GOLDMONITORSPARKLE"},
+	{{A_Look},                   "A_LOOK"},
+	{{A_Chase},                  "A_CHASE"},
+	{{A_FaceStabChase},          "A_FACESTABCHASE"},
+	{{A_FaceStabRev},            "A_FACESTABREV"},
+	{{A_FaceStabHurl},           "A_FACESTABHURL"},
+	{{A_FaceStabMiss},           "A_FACESTABMISS"},
+	{{A_StatueBurst},            "A_STATUEBURST"},
+	{{A_FaceTarget},             "A_FACETARGET"},
+	{{A_FaceTracer},             "A_FACETRACER"},
+	{{A_Scream},                 "A_SCREAM"},
+	{{A_BossDeath},              "A_BOSSDEATH"},
+	{{A_CustomPower},            "A_CUSTOMPOWER"},
+	{{A_GiveWeapon},             "A_GIVEWEAPON"},
+	{{A_RingBox},                "A_RINGBOX"},
+	{{A_Invincibility},          "A_INVINCIBILITY"},
+	{{A_SuperSneakers},          "A_SUPERSNEAKERS"},
+	{{A_BunnyHop},               "A_BUNNYHOP"},
+	{{A_BubbleSpawn},            "A_BUBBLESPAWN"},
+	{{A_FanBubbleSpawn},         "A_FANBUBBLESPAWN"},
+	{{A_BubbleRise},             "A_BUBBLERISE"},
+	{{A_BubbleCheck},            "A_BUBBLECHECK"},
+	{{A_AwardScore},             "A_AWARDSCORE"},
+	{{A_ExtraLife},              "A_EXTRALIFE"},
+	{{A_GiveShield},             "A_GIVESHIELD"},
+	{{A_GravityBox},             "A_GRAVITYBOX"},
+	{{A_ScoreRise},              "A_SCORERISE"},
+	{{A_AttractChase},           "A_ATTRACTCHASE"},
+	{{A_DropMine},               "A_DROPMINE"},
+	{{A_FishJump},               "A_FISHJUMP"},
+	{{A_ThrownRing},             "A_THROWNRING"},
+	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
+	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
+	{{A_SignPlayer},             "A_SIGNPLAYER"},
+	{{A_OverlayThink},           "A_OVERLAYTHINK"},
+	{{A_JetChase},               "A_JETCHASE"},
+	{{A_JetbThink},              "A_JETBTHINK"},
+	{{A_JetgThink},              "A_JETGTHINK"},
+	{{A_JetgShoot},              "A_JETGSHOOT"},
+	{{A_ShootBullet},            "A_SHOOTBULLET"},
+	{{A_MinusDigging},           "A_MINUSDIGGING"},
+	{{A_MinusPopup},             "A_MINUSPOPUP"},
+	{{A_MinusCheck},             "A_MINUSCHECK"},
+	{{A_ChickenCheck},           "A_CHICKENCHECK"},
+	{{A_MouseThink},             "A_MOUSETHINK"},
+	{{A_DetonChase},             "A_DETONCHASE"},
+	{{A_CapeChase},              "A_CAPECHASE"},
+	{{A_RotateSpikeBall},        "A_ROTATESPIKEBALL"},
+	{{A_SlingAppear},            "A_SLINGAPPEAR"},
+	{{A_UnidusBall},             "A_UNIDUSBALL"},
+	{{A_RockSpawn},              "A_ROCKSPAWN"},
+	{{A_SetFuse},                "A_SETFUSE"},
+	{{A_CrawlaCommanderThink},   "A_CRAWLACOMMANDERTHINK"},
+	{{A_SmokeTrailer},           "A_SMOKETRAILER"},
+	{{A_RingExplode},            "A_RINGEXPLODE"},
+	{{A_OldRingExplode},         "A_OLDRINGEXPLODE"},
+	{{A_MixUp},                  "A_MIXUP"},
+	{{A_RecyclePowers},          "A_RECYCLEPOWERS"},
+	{{A_Boss1Chase},             "A_BOSS1CHASE"},
+	{{A_FocusTarget},            "A_FOCUSTARGET"},
+	{{A_Boss2Chase},             "A_BOSS2CHASE"},
+	{{A_Boss2Pogo},              "A_BOSS2POGO"},
+	{{A_BossZoom},               "A_BOSSZOOM"},
+	{{A_BossScream},             "A_BOSSSCREAM"},
+	{{A_Boss2TakeDamage},        "A_BOSS2TAKEDAMAGE"},
+	{{A_Boss7Chase},             "A_BOSS7CHASE"},
+	{{A_GoopSplat},              "A_GOOPSPLAT"},
+	{{A_Boss2PogoSFX},           "A_BOSS2POGOSFX"},
+	{{A_Boss2PogoTarget},        "A_BOSS2POGOTARGET"},
+	{{A_BossJetFume},            "A_BOSSJETFUME"},
+	{{A_EggmanBox},              "A_EGGMANBOX"},
+	{{A_TurretFire},             "A_TURRETFIRE"},
+	{{A_SuperTurretFire},        "A_SUPERTURRETFIRE"},
+	{{A_TurretStop},             "A_TURRETSTOP"},
+	{{A_JetJawRoam},             "A_JETJAWROAM"},
+	{{A_JetJawChomp},            "A_JETJAWCHOMP"},
+	{{A_PointyThink},            "A_POINTYTHINK"},
+	{{A_CheckBuddy},             "A_CHECKBUDDY"},
+	{{A_HoodFire},               "A_HOODFIRE"},
+	{{A_HoodThink},              "A_HOODTHINK"},
+	{{A_HoodFall},               "A_HOODFALL"},
+	{{A_ArrowBonks},             "A_ARROWBONKS"},
+	{{A_SnailerThink},           "A_SNAILERTHINK"},
+	{{A_SharpChase},             "A_SHARPCHASE"},
+	{{A_SharpSpin},              "A_SHARPSPIN"},
+	{{A_SharpDecel},             "A_SHARPDECEL"},
+	{{A_CrushstaceanWalk},       "A_CRUSHSTACEANWALK"},
+	{{A_CrushstaceanPunch},      "A_CRUSHSTACEANPUNCH"},
+	{{A_CrushclawAim},           "A_CRUSHCLAWAIM"},
+	{{A_CrushclawLaunch},        "A_CRUSHCLAWLAUNCH"},
+	{{A_VultureVtol},            "A_VULTUREVTOL"},
+	{{A_VultureCheck},           "A_VULTURECHECK"},
+	{{A_VultureHover},           "A_VULTUREHOVER"},
+	{{A_VultureBlast},           "A_VULTUREBLAST"},
+	{{A_VultureFly},             "A_VULTUREFLY"},
+	{{A_SkimChase},              "A_SKIMCHASE"},
+	{{A_1upThinker},             "A_1UPTHINKER"},
+	{{A_SkullAttack},            "A_SKULLATTACK"},
+	{{A_LobShot},                "A_LOBSHOT"},
+	{{A_FireShot},               "A_FIRESHOT"},
+	{{A_SuperFireShot},          "A_SUPERFIRESHOT"},
+	{{A_BossFireShot},           "A_BOSSFIRESHOT"},
+	{{A_Boss7FireMissiles},      "A_BOSS7FIREMISSILES"},
+	{{A_Boss1Laser},             "A_BOSS1LASER"},
+	{{A_Boss4Reverse},           "A_BOSS4REVERSE"},
+	{{A_Boss4SpeedUp},           "A_BOSS4SPEEDUP"},
+	{{A_Boss4Raise},             "A_BOSS4RAISE"},
+	{{A_SparkFollow},            "A_SPARKFOLLOW"},
+	{{A_BuzzFly},                "A_BUZZFLY"},
+	{{A_GuardChase},             "A_GUARDCHASE"},
+	{{A_EggShield},              "A_EGGSHIELD"},
+	{{A_SetReactionTime},        "A_SETREACTIONTIME"},
+	{{A_Boss1Spikeballs},        "A_BOSS1SPIKEBALLS"},
+	{{A_Boss3TakeDamage},        "A_BOSS3TAKEDAMAGE"},
+	{{A_Boss3Path},              "A_BOSS3PATH"},
+	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
+	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
+	{{A_PlayAttackSound},        "A_PLAYATTACKSOUND"},
+	{{A_PlayActiveSound},        "A_PLAYACTIVESOUND"},
+	{{A_SpawnObjectAbsolute},    "A_SPAWNOBJECTABSOLUTE"},
+	{{A_SpawnObjectRelative},    "A_SPAWNOBJECTRELATIVE"},
+	{{A_ChangeAngleRelative},    "A_CHANGEANGLERELATIVE"},
+	{{A_ChangeAngleAbsolute},    "A_CHANGEANGLEABSOLUTE"},
+	{{A_PlaySound},              "A_PLAYSOUND"},
+	{{A_FindTarget},             "A_FINDTARGET"},
+	{{A_FindTracer},             "A_FINDTRACER"},
+	{{A_SetTics},                "A_SETTICS"},
+	{{A_SetRandomTics},          "A_SETRANDOMTICS"},
+	{{A_ChangeColorRelative},    "A_CHANGECOLORRELATIVE"},
+	{{A_ChangeColorAbsolute},    "A_CHANGECOLORABSOLUTE"},
+	{{A_MoveRelative},           "A_MOVERELATIVE"},
+	{{A_MoveAbsolute},           "A_MOVEABSOLUTE"},
+	{{A_Thrust},                 "A_THRUST"},
+	{{A_ZThrust},                "A_ZTHRUST"},
+	{{A_SetTargetsTarget},       "A_SETTARGETSTARGET"},
+	{{A_SetObjectFlags},         "A_SETOBJECTFLAGS"},
+	{{A_SetObjectFlags2},        "A_SETOBJECTFLAGS2"},
+	{{A_RandomState},            "A_RANDOMSTATE"},
+	{{A_RandomStateRange},       "A_RANDOMSTATERANGE"},
+	{{A_DualAction},             "A_DUALACTION"},
+	{{A_RemoteAction},           "A_REMOTEACTION"},
+	{{A_ToggleFlameJet},         "A_TOGGLEFLAMEJET"},
+	{{A_OrbitNights},            "A_ORBITNIGHTS"},
+	{{A_GhostMe},                "A_GHOSTME"},
+	{{A_SetObjectState},         "A_SETOBJECTSTATE"},
+	{{A_SetObjectTypeState},     "A_SETOBJECTTYPESTATE"},
+	{{A_KnockBack},              "A_KNOCKBACK"},
+	{{A_PushAway},               "A_PUSHAWAY"},
+	{{A_RingDrain},              "A_RINGDRAIN"},
+	{{A_SplitShot},              "A_SPLITSHOT"},
+	{{A_MissileSplit},           "A_MISSILESPLIT"},
+	{{A_MultiShot},              "A_MULTISHOT"},
+	{{A_InstaLoop},              "A_INSTALOOP"},
+	{{A_Custom3DRotate},         "A_CUSTOM3DROTATE"},
+	{{A_SearchForPlayers},       "A_SEARCHFORPLAYERS"},
+	{{A_CheckRandom},            "A_CHECKRANDOM"},
+	{{A_CheckTargetRings},       "A_CHECKTARGETRINGS"},
+	{{A_CheckRings},             "A_CHECKRINGS"},
+	{{A_CheckTotalRings},        "A_CHECKTOTALRINGS"},
+	{{A_CheckHealth},            "A_CHECKHEALTH"},
+	{{A_CheckRange},             "A_CHECKRANGE"},
+	{{A_CheckHeight},            "A_CHECKHEIGHT"},
+	{{A_CheckTrueRange},         "A_CHECKTRUERANGE"},
+	{{A_CheckThingCount},        "A_CHECKTHINGCOUNT"},
+	{{A_CheckAmbush},            "A_CHECKAMBUSH"},
+	{{A_CheckCustomValue},       "A_CHECKCUSTOMVALUE"},
+	{{A_CheckCusValMemo},        "A_CHECKCUSVALMEMO"},
+	{{A_SetCustomValue},         "A_SETCUSTOMVALUE"},
+	{{A_UseCusValMemo},          "A_USECUSVALMEMO"},
+	{{A_RelayCustomValue},       "A_RELAYCUSTOMVALUE"},
+	{{A_CusValAction},           "A_CUSVALACTION"},
+	{{A_ForceStop},              "A_FORCESTOP"},
+	{{A_ForceWin},               "A_FORCEWIN"},
+	{{A_SpikeRetract},           "A_SPIKERETRACT"},
+	{{A_InfoState},              "A_INFOSTATE"},
+	{{A_Repeat},                 "A_REPEAT"},
+	{{A_SetScale},               "A_SETSCALE"},
+	{{A_RemoteDamage},           "A_REMOTEDAMAGE"},
+	{{A_HomingChase},            "A_HOMINGCHASE"},
+	{{A_TrapShot},               "A_TRAPSHOT"},
+	{{A_VileTarget},             "A_VILETARGET"},
+	{{A_VileAttack},             "A_VILEATTACK"},
+	{{A_VileFire},               "A_VILEFIRE"},
+	{{A_BrakChase},              "A_BRAKCHASE"},
+	{{A_BrakFireShot},           "A_BRAKFIRESHOT"},
+	{{A_BrakLobShot},            "A_BRAKLOBSHOT"},
+	{{A_NapalmScatter},          "A_NAPALMSCATTER"},
+	{{A_SpawnFreshCopy},         "A_SPAWNFRESHCOPY"},
+	{{A_FlickySpawn},            "A_FLICKYSPAWN"},
+	{{A_FlickyAim},              "A_FLICKYAIM"},
+	{{A_FlickyFly},              "A_FLICKYFLY"},
+	{{A_FlickySoar},             "A_FLICKYSOAR"},
+	{{A_FlickyCoast},            "A_FLICKYCOAST"},
+	{{A_FlickyHop},              "A_FLICKYHOP"},
+	{{A_FlickyFlounder},         "A_FLICKYFLOUNDER"},
+	{{A_FlickyCheck},            "A_FLICKYCHECK"},
+	{{A_FlickyHeightCheck},      "A_FLICKYHEIGHTCHECK"},
+	{{A_FlickyFlutter},          "A_FLICKYFLUTTER"},
+	{{A_FlameParticle},          "A_FLAMEPARTICLE"},
+	{{A_FadeOverlay},            "A_FADEOVERLAY"},
+	{{A_Boss5Jump},              "A_BOSS5JUMP"},
+	{{A_LightBeamReset},         "A_LIGHTBEAMRESET"},
+	{{A_MineExplode},            "A_MINEEXPLODE"},
+	{{A_MineRange},              "A_MINERANGE"},
+	{{A_ConnectToGround},        "A_CONNECTTOGROUND"},
+	{{A_SpawnParticleRelative},  "A_SPAWNPARTICLERELATIVE"},
+	{{A_MultiShotDist},          "A_MULTISHOTDIST"},
+	{{A_WhoCaresIfYourSonIsABee},"A_WHOCARESIFYOURSONISABEE"},
+	{{A_ParentTriesToSleep},     "A_PARENTTRIESTOSLEEP"},
+	{{A_CryingToMomma},          "A_CRYINGTOMOMMA"},
+	{{A_CheckFlags2},            "A_CHECKFLAGS2"},
+	{{A_Boss5FindWaypoint},      "A_BOSS5FINDWAYPOINT"},
+	{{A_DoNPCSkid},              "A_DONPCSKID"},
+	{{A_DoNPCPain},              "A_DONPCPAIN"},
+	{{A_PrepareRepeat},          "A_PREPAREREPEAT"},
+	{{A_Boss5ExtraRepeat},       "A_BOSS5EXTRAREPEAT"},
+	{{A_Boss5Calm},              "A_BOSS5CALM"},
+	{{A_Boss5CheckOnGround},     "A_BOSS5CHECKONGROUND"},
+	{{A_Boss5CheckFalling},      "A_BOSS5CHECKFALLING"},
+	{{A_Boss5PinchShot},         "A_BOSS5PINCHSHOT"},
+	{{A_Boss5MakeItRain},        "A_BOSS5MAKEITRAIN"},
+	{{A_LookForBetter},          "A_LOOKFORBETTER"},
+	{{A_Boss5BombExplode},       "A_BOSS5BOMBEXPLODE"},
+	{{A_DustDevilThink},         "A_DUSTDEVILTHINK"},
+	{{A_TNTExplode},             "A_TNTEXPLODE"},
+	{{A_DebrisRandom},           "A_DEBRISRANDOM"},
+	{{A_TrainCameo},             "A_TRAINCAMEO"},
+	{{A_TrainCameo2},            "A_TRAINCAMEO2"},
+	{{A_CanarivoreGas},          "A_CANARIVOREGAS"},
+	{{A_KillSegments},           "A_KILLSEGMENTS"},
+	{{A_SnapperSpawn},           "A_SNAPPERSPAWN"},
+	{{A_SnapperThinker},         "A_SNAPPERTHINKER"},
+	{{A_SaloonDoorSpawn},        "A_SALOONDOORSPAWN"},
+	{{A_MinecartSparkThink},     "A_MINECARTSPARKTHINK"},
+	{{A_ModuloToState},          "A_MODULOTOSTATE"},
+	{{NULL},                     "NONE"},
 
 	// This NULL entry must be the last in the list
 	{{NULL},                   NULL},
@@ -1819,7 +2443,6 @@ static void readframe(MYFILE *f, INT32 num)
 	char *word1;
 	char *word2 = NULL;
 	char *tmp;
-	INT32 j;
 
 	do
 	{
@@ -1834,16 +2457,6 @@ static void readframe(MYFILE *f, INT32 num)
 			if (s == tmp)
 				continue; // Skip comment lines, but don't break.
 
-			for (j = 0; s[j] != '\n'; j++)
-			{
-				if (s[j] == '=')
-				{
-					j += 2;
-					j = atoi(&s[j]);
-					break;
-				}
-			}
-
 			word1 = strtok(s, " ");
 			if (word1)
 				strupr(word1);
@@ -2060,7 +2673,10 @@ static void reademblemdata(MYFILE *f, INT32 num)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -2098,12 +2714,8 @@ static void reademblemdata(MYFILE *f, INT32 num)
 				else
 					emblemlocations[num-1].type = (UINT8)value;
 			}
-			else if (fastcmp(word, "X"))
-				emblemlocations[num-1].x = (INT16)value;
-			else if (fastcmp(word, "Y"))
-				emblemlocations[num-1].y = (INT16)value;
-			else if (fastcmp(word, "Z"))
-				emblemlocations[num-1].z = (INT16)value;
+			else if (fastcmp(word, "TAG"))
+				emblemlocations[num-1].tag = (INT16)value;
 			else if (fastcmp(word, "MAPNUM"))
 			{
 				// Support using the actual map name,
@@ -2195,7 +2807,10 @@ static void readextraemblemdata(MYFILE *f, INT32 num)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -2270,7 +2885,10 @@ static void readunlockable(MYFILE *f, INT32 num)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -2557,7 +3175,10 @@ static void readconditionset(MYFILE *f, UINT8 setnum)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -2618,7 +3239,10 @@ static void readmaincfg(MYFILE *f)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -2655,11 +3279,21 @@ static void readmaincfg(MYFILE *f)
 					value = get_number(word2);
 
 				sstage_start = (INT16)value;
-				sstage_end = (INT16)(sstage_start+6); // 7 special stages total
+				sstage_end = (INT16)(sstage_start+7); // 7 special stages total plus one weirdo
 			}
-			else if (fastcmp(word, "USENIGHTSSS"))
+			else if (fastcmp(word, "SMPSTAGE_START"))
 			{
-				useNightsSS = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				smpstage_start = (INT16)value;
+				smpstage_end = (INT16)(smpstage_start+6); // 7 special stages total
 			}
 			else if (fastcmp(word, "REDTEAM"))
 			{
@@ -2705,11 +3339,14 @@ static void readmaincfg(MYFILE *f)
 			{
 				extralifetics = (UINT16)get_number(word2);
 			}
+			else if (fastcmp(word, "NIGHTSLINKTICS"))
+			{
+				nightslinktics = (UINT16)get_number(word2);
+			}
 			else if (fastcmp(word, "GAMEOVERTICS"))
 			{
 				gameovertics = get_number(word2);
 			}
-
 			else if (fastcmp(word, "INTROTOPLAY"))
 			{
 				introtoplay = (UINT8)get_number(word2);
@@ -2720,7 +3357,7 @@ static void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "LOOPTITLE"))
 			{
-				looptitle = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				looptitle = (value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "TITLEMAP"))
@@ -2742,9 +3379,14 @@ static void readmaincfg(MYFILE *f)
 				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "TITLESCROLLSPEED"))
+			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED"))
+			{
+				titlescrollxspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLYSPEED"))
 			{
-				titlescrollspeed = get_number(word2);
+				titlescrollyspeed = get_number(word2);
 				titlechanged = true;
 			}
 			else if (fastcmp(word, "CREDITSCUTSCENE"))
@@ -2756,7 +3398,7 @@ static void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "DISABLESPEEDADJUST"))
 			{
-				disableSpeedAdjust = (UINT8)get_number(word2);
+				disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y');
 			}
 			else if (fastcmp(word, "NUMDEMOS"))
 			{
@@ -2801,7 +3443,7 @@ static void readmaincfg(MYFILE *f)
 				strncpy(timeattackfolder, gamedatafilename, min(filenamelen, sizeof (timeattackfolder)));
 				timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0';
 
-				strncpy(savegamename, timeattackfolder, sizeof (timeattackfolder));
+				strcpy(savegamename, timeattackfolder);
 				strlcat(savegamename, "%u.ssg", sizeof(savegamename));
 				// can't use sprintf since there is %u in savegamename
 				strcatbf(savegamename, srb2home, PATHSEP);
@@ -2838,6 +3480,19 @@ static void readmaincfg(MYFILE *f)
 				startchar = (INT16)value;
 				char_on = -1;
 			}
+			else if (fastcmp(word, "TUTORIALMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				tutorialmap = (INT16)value;
+			}
 			else
 				deh_warning("Maincfg: unknown word '%s'", word);
 		}
@@ -2876,7 +3531,10 @@ static void readwipes(MYFILE *f)
 
 			// Get the part before the " = "
 			tmp = strchr(s, '=');
-			*(tmp-1) = '\0';
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
 			strupr(word);
 
 			// Now get the part after
@@ -3021,7 +3679,7 @@ static void ignorelines(MYFILE *f)
 	Z_Free(s);
 }
 
-static void DEH_LoadDehackedFile(MYFILE *f)
+static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
 	char *word;
@@ -3081,15 +3739,16 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 				continue;
 			}
 			word2 = strtok(NULL, " ");
+			if (word2) {
+				strupr(word2);
+				if (word2[strlen(word2) - 1] == '\n')
+					word2[strlen(word2) - 1] = '\0';
+				i = atoi(word2);
+			}
+			else
+				i = 0;
 			if (fastcmp(word, "CHARACTER"))
 			{
-				if (word2) {
-					strupr(word2);
-					if (word2[strlen(word2)-1] == '\n')
-						word2[strlen(word2)-1] = '\0';
-					i = atoi(word2);
-				} else
-					i = 0;
 				if (i >= 0 && i < 32)
 					readPlayer(f, i);
 				else
@@ -3099,13 +3758,60 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 				}
 				continue;
 			}
-			if (word2)
+			else if (fastcmp(word, "EMBLEM"))
 			{
-				strupr(word2);
-				if (word2[strlen(word2)-1] == '\n')
-					word2[strlen(word2)-1] = '\0';
-				i = atoi(word2);
+				if (!mainfile && !gamedataadded)
+				{
+					deh_warning("You must define a custom gamedata to use \"%s\"", word);
+					ignorelines(f);
+				}
+				else
+				{
+					if (!word2)
+						i = numemblems + 1;
+
+					if (i > 0 && i <= MAXEMBLEMS)
+					{
+						if (numemblems < i)
+							numemblems = i;
+						reademblemdata(f, i);
+					}
+					else
+					{
+						deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
+						ignorelines(f);
+					}
+				}
+				continue;
+			}
+			else if (fastcmp(word, "EXTRAEMBLEM"))
+			{
+				if (!mainfile && !gamedataadded)
+				{
+					deh_warning("You must define a custom gamedata to use \"%s\"", word);
+					ignorelines(f);
+				}
+				else
+				{
+					if (!word2)
+						i = numextraemblems + 1;
 
+					if (i > 0 && i <= MAXEXTRAEMBLEMS)
+					{
+						if (numextraemblems < i)
+							numextraemblems = i;
+						readextraemblemdata(f, i);
+					}
+					else
+					{
+						deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
+						ignorelines(f);
+					}
+				}
+				continue;
+			}
+			if (word2)
+			{
 				if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
@@ -3182,6 +3888,16 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						ignorelines(f);
 					}
 				}
+				else if (fastcmp(word, "PROMPT"))
+				{
+					if (i > 0 && i < MAX_PROMPTS)
+						readtextprompt(f, i - 1);
+					else
+					{
+						deh_warning("Prompt number %d out of range (1 - %d)", i, MAX_PROMPTS);
+						ignorelines(f);
+					}
+				}
 				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
@@ -3218,47 +3934,22 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						ignorelines(f);
 					}
 				}
-				else if (fastcmp(word, "EMBLEM"))
-				{
-					if (!gamedataadded)
-					{
-						deh_warning("You must define a custom gamedata to use \"%s\"", word);
-						ignorelines(f);
-					}
-					else if (i > 0 && i <= MAXEMBLEMS)
-					{
-						if (numemblems < i)
-							numemblems = i;
-						reademblemdata(f, i);
-					}
-					else
-					{
-						deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "EXTRAEMBLEM"))
+				else if (fastcmp(word, "MENU"))
 				{
-					if (!gamedataadded)
-					{
-						deh_warning("You must define a custom gamedata to use \"%s\"", word);
-						ignorelines(f);
-					}
-					else if (i > 0 && i <= MAXEXTRAEMBLEMS)
-					{
-						if (numextraemblems < i)
-							numextraemblems = i;
-						readextraemblemdata(f, i);
-					}
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_menutype(word2); // find a huditem by name
+					if (i >= 1 && i < NUMMENUTYPES)
+						readmenu(f, i);
 					else
 					{
-						deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
+						// zero-based, but let's start at 1
+						deh_warning("Menu number %d out of range (1 - %d)", i, NUMMENUTYPES-1);
 						ignorelines(f);
 					}
 				}
 				else if (fastcmp(word, "UNLOCKABLE"))
 				{
-					if (!gamedataadded)
+					if (!mainfile && !gamedataadded)
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3273,7 +3964,7 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 				}
 				else if (fastcmp(word, "CONDITIONSET"))
 				{
-					if (!gamedataadded)
+					if (!mainfile && !gamedataadded)
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3304,7 +3995,7 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 				{
 					boolean clearall = (fastcmp(word2, "ALL"));
 
-					if (!gamedataadded)
+					if (!mainfile && !gamedataadded)
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						continue;
@@ -3375,7 +4066,7 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 
 // read dehacked lump in a wad (there is special trick for for deh
 // file that are converted to wad in w_wad.c)
-void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump)
+void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile)
 {
 	MYFILE f;
 	f.wad = wad;
@@ -3384,13 +4075,13 @@ void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump)
 	W_ReadLumpPwad(wad, lump, f.data);
 	f.curpos = f.data;
 	f.data[f.size] = 0;
-	DEH_LoadDehackedFile(&f);
+	DEH_LoadDehackedFile(&f, mainfile);
 	Z_Free(f.data);
 }
 
 void DEH_LoadDehackedLump(lumpnum_t lumpnum)
 {
-	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
+	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum), false);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -3584,10 +4275,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_RBUZZFLY1",
 	"S_RBUZZFLY2",
 
-	// AquaBuzz
-	"S_BBUZZFLY1",
-	"S_BBUZZFLY2",
-
 	// Jetty-Syn Bomber
 	"S_JETBLOOK1",
 	"S_JETBLOOK2",
@@ -3624,7 +4311,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_DETON13",
 	"S_DETON14",
 	"S_DETON15",
-	"S_DETON16",
 
 	// Skim Mine Dropper
 	"S_SKIM1",
@@ -3666,14 +4352,40 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_TURRETPOPDOWN7",
 	"S_TURRETPOPDOWN8",
 
-	// Sharp
-	"S_SHARP_ROAM1",
-	"S_SHARP_ROAM2",
-	"S_SHARP_AIM1",
-	"S_SHARP_AIM2",
-	"S_SHARP_AIM3",
-	"S_SHARP_AIM4",
-	"S_SHARP_SPIN",
+	// Spincushion
+	"S_SPINCUSHION_LOOK",
+	"S_SPINCUSHION_CHASE1",
+	"S_SPINCUSHION_CHASE2",
+	"S_SPINCUSHION_CHASE3",
+	"S_SPINCUSHION_CHASE4",
+	"S_SPINCUSHION_AIM1",
+	"S_SPINCUSHION_AIM2",
+	"S_SPINCUSHION_AIM3",
+	"S_SPINCUSHION_AIM4",
+	"S_SPINCUSHION_AIM5",
+	"S_SPINCUSHION_SPIN1",
+	"S_SPINCUSHION_SPIN2",
+	"S_SPINCUSHION_SPIN3",
+	"S_SPINCUSHION_SPIN4",
+	"S_SPINCUSHION_STOP1",
+	"S_SPINCUSHION_STOP2",
+	"S_SPINCUSHION_STOP3",
+	"S_SPINCUSHION_STOP4",
+
+	// Crushstacean
+	"S_CRUSHSTACEAN_ROAM1",
+	"S_CRUSHSTACEAN_ROAM2",
+	"S_CRUSHSTACEAN_ROAM3",
+	"S_CRUSHSTACEAN_ROAM4",
+	"S_CRUSHSTACEAN_ROAMPAUSE",
+	"S_CRUSHSTACEAN_PUNCH1",
+	"S_CRUSHSTACEAN_PUNCH2",
+	"S_CRUSHCLAW_AIM",
+	"S_CRUSHCLAW_OUT",
+	"S_CRUSHCLAW_STAY",
+	"S_CRUSHCLAW_IN",
+	"S_CRUSHCLAW_WAIT",
+	"S_CRUSHCHAIN",
 
 	// Jet Jaw
 	"S_JETJAW_ROAM1",
@@ -3706,15 +4418,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Vulture
 	"S_VULTURE_STND",
-	"S_VULTURE_VTOL1",
-	"S_VULTURE_VTOL2",
-	"S_VULTURE_VTOL3",
-	"S_VULTURE_VTOL4",
+	"S_VULTURE_DRIFT",
 	"S_VULTURE_ZOOM1",
 	"S_VULTURE_ZOOM2",
-	"S_VULTURE_ZOOM3",
-	"S_VULTURE_ZOOM4",
-	"S_VULTURE_ZOOM5",
+	"S_VULTURE_STUNNED",
 
 	// Pointy
 	"S_POINTY1",
@@ -3722,11 +4429,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Robo-Hood
 	"S_ROBOHOOD_LOOK",
-	"S_ROBOHOOD_STND",
-	"S_ROBOHOOD_SHOOT",
-	"S_ROBOHOOD_JUMP",
+	"S_ROBOHOOD_STAND",
+	"S_ROBOHOOD_FIRE1",
+	"S_ROBOHOOD_FIRE2",
+	"S_ROBOHOOD_JUMP1",
 	"S_ROBOHOOD_JUMP2",
-	"S_ROBOHOOD_FALL",
+	"S_ROBOHOOD_JUMP3",
 
 	// CastleBot FaceStabber
 	"S_FACESTABBER_STND1",
@@ -3739,6 +4447,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FACESTABBER_CHARGE2",
 	"S_FACESTABBER_CHARGE3",
 	"S_FACESTABBER_CHARGE4",
+	"S_FACESTABBER_PAIN",
+	"S_FACESTABBER_DIE1",
+	"S_FACESTABBER_DIE2",
+	"S_FACESTABBER_DIE3",
+	"S_FACESTABBERSPEAR",
 
 	// Egg Guard
 	"S_EGGGUARD_STND",
@@ -3756,17 +4469,34 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Egg Shield for Egg Guard
 	"S_EGGSHIELD",
+	"S_EGGSHIELDBREAK",
 
 	// Green Snapper
+	"S_SNAPPER_SPAWN",
+	"S_SNAPPER_SPAWN2",
 	"S_GSNAPPER_STND",
 	"S_GSNAPPER1",
 	"S_GSNAPPER2",
 	"S_GSNAPPER3",
 	"S_GSNAPPER4",
+	"S_SNAPPER_XPLD",
+	"S_SNAPPER_LEG",
+	"S_SNAPPER_LEGRAISE",
+	"S_SNAPPER_HEAD",
 
 	// Minus
+	"S_MINUS_INIT",
 	"S_MINUS_STND",
-	"S_MINUS_DIGGING",
+	"S_MINUS_DIGGING1",
+	"S_MINUS_DIGGING2",
+	"S_MINUS_DIGGING3",
+	"S_MINUS_DIGGING4",
+	"S_MINUS_BURST0",
+	"S_MINUS_BURST1",
+	"S_MINUS_BURST2",
+	"S_MINUS_BURST3",
+	"S_MINUS_BURST4",
+	"S_MINUS_BURST5",
 	"S_MINUS_POPUP",
 	"S_MINUS_UPWARD1",
 	"S_MINUS_UPWARD2",
@@ -3785,6 +4515,15 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_MINUS_DOWNWARD7",
 	"S_MINUS_DOWNWARD8",
 
+	// Minus dirt
+	"S_MINUSDIRT1",
+	"S_MINUSDIRT2",
+	"S_MINUSDIRT3",
+	"S_MINUSDIRT4",
+	"S_MINUSDIRT5",
+	"S_MINUSDIRT6",
+	"S_MINUSDIRT7",
+
 	// Spring Shell
 	"S_SSHELL_STND",
 	"S_SSHELL_RUN1",
@@ -3812,14 +4551,30 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_UNIDUS_RUN",
 	"S_UNIDUS_BALL",
 
+	// Canarivore
+	"S_CANARIVORE_LOOK",
+	"S_CANARIVORE_AWAKEN1",
+	"S_CANARIVORE_AWAKEN2",
+	"S_CANARIVORE_AWAKEN3",
+	"S_CANARIVORE_GAS1",
+	"S_CANARIVORE_GAS2",
+	"S_CANARIVORE_GAS3",
+	"S_CANARIVORE_GAS4",
+	"S_CANARIVORE_GAS5",
+	"S_CANARIVORE_GASREPEAT",
+	"S_CANARIVORE_CLOSE1",
+	"S_CANARIVORE_CLOSE2",
+	"S_CANARIVOREGAS_1",
+	"S_CANARIVOREGAS_2",
+	"S_CANARIVOREGAS_3",
+	"S_CANARIVOREGAS_4",
+	"S_CANARIVOREGAS_5",
+	"S_CANARIVOREGAS_6",
+	"S_CANARIVOREGAS_7",
+	"S_CANARIVOREGAS_8",
+
 	// Boss Explosion
-	"S_BPLD1",
-	"S_BPLD2",
-	"S_BPLD3",
-	"S_BPLD4",
-	"S_BPLD5",
-	"S_BPLD6",
-	"S_BPLD7",
+	"S_BOSSEXPLODE",
 
 	// S3&K Boss Explosion
 	"S_SONIC3KBOSSEXPLOSION1",
@@ -3922,6 +4677,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Boss 3
 	"S_EGGMOBILE3_STND",
+	"S_EGGMOBILE3_LAUGH1",
+	"S_EGGMOBILE3_LAUGH2",
+	"S_EGGMOBILE3_LAUGH3",
+	"S_EGGMOBILE3_LAUGH4",
+	"S_EGGMOBILE3_LAUGH5",
 	"S_EGGMOBILE3_ATK1",
 	"S_EGGMOBILE3_ATK2",
 	"S_EGGMOBILE3_ATK3A",
@@ -3930,11 +4690,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE3_ATK3D",
 	"S_EGGMOBILE3_ATK4",
 	"S_EGGMOBILE3_ATK5",
-	"S_EGGMOBILE3_LAUGH1",
-	"S_EGGMOBILE3_LAUGH2",
-	"S_EGGMOBILE3_LAUGH3",
-	"S_EGGMOBILE3_LAUGH4",
-	"S_EGGMOBILE3_LAUGH5",
 	"S_EGGMOBILE3_LAUGH6",
 	"S_EGGMOBILE3_LAUGH7",
 	"S_EGGMOBILE3_LAUGH8",
@@ -3987,8 +4742,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FAKEMOBILE_ATK3B",
 	"S_FAKEMOBILE_ATK3C",
 	"S_FAKEMOBILE_ATK3D",
-	"S_FAKEMOBILE_ATK4",
-	"S_FAKEMOBILE_ATK5",
+	"S_FAKEMOBILE_DIE1",
+	"S_FAKEMOBILE_DIE2",
 
 	// Boss 4
 	"S_EGGMOBILE4_STND",
@@ -4006,15 +4761,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE4_RATK6",
 	"S_EGGMOBILE4_RAISE1",
 	"S_EGGMOBILE4_RAISE2",
-	"S_EGGMOBILE4_RAISE3",
-	"S_EGGMOBILE4_RAISE4",
-	"S_EGGMOBILE4_RAISE5",
-	"S_EGGMOBILE4_RAISE6",
-	"S_EGGMOBILE4_RAISE7",
-	"S_EGGMOBILE4_RAISE8",
-	"S_EGGMOBILE4_RAISE9",
-	"S_EGGMOBILE4_RAISE10",
-	"S_EGGMOBILE4_PAIN",
+	"S_EGGMOBILE4_PAIN1",
+	"S_EGGMOBILE4_PAIN2",
 	"S_EGGMOBILE4_DIE1",
 	"S_EGGMOBILE4_DIE2",
 	"S_EGGMOBILE4_DIE3",
@@ -4032,10 +4780,111 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE4_FLEE1",
 	"S_EGGMOBILE4_FLEE2",
 	"S_EGGMOBILE4_MACE",
+	"S_EGGMOBILE4_MACE_DIE1",
+	"S_EGGMOBILE4_MACE_DIE2",
+	"S_EGGMOBILE4_MACE_DIE3",
 
 	// Boss 4 jet flame
-	"S_JETFLAME1",
-	"S_JETFLAME2",
+	"S_JETFLAME",
+
+	// Boss 4 Spectator Eggrobo
+	"S_EGGROBO1_IDLE",
+	"S_EGGROBO1_BSLAP1",
+	"S_EGGROBO2_BSLAP2",
+	"S_EGGROBO1_PISSED",
+
+	// Boss 4 Spectator Eggrobo jet flame
+	"S_EGGROBOJET",
+
+	// Boss 5
+	"S_FANG_IDLE1",
+	"S_FANG_IDLE2",
+	"S_FANG_IDLE3",
+	"S_FANG_IDLE4",
+	"S_FANG_IDLE5",
+	"S_FANG_IDLE6",
+	"S_FANG_IDLE7",
+	"S_FANG_IDLE8",
+	"S_FANG_PAIN1",
+	"S_FANG_PAIN2",
+	"S_FANG_PATHINGSTART1",
+	"S_FANG_PATHINGSTART2",
+	"S_FANG_PATHING",
+	"S_FANG_BOUNCE1",
+	"S_FANG_BOUNCE2",
+	"S_FANG_BOUNCE3",
+	"S_FANG_BOUNCE4",
+	"S_FANG_FALL1",
+	"S_FANG_FALL2",
+	"S_FANG_CHECKPATH1",
+	"S_FANG_CHECKPATH2",
+	"S_FANG_PATHINGCONT1",
+	"S_FANG_PATHINGCONT2",
+	"S_FANG_PATHINGCONT3",
+	"S_FANG_SKID1",
+	"S_FANG_SKID2",
+	"S_FANG_SKID3",
+	"S_FANG_CHOOSEATTACK",
+	"S_FANG_FIRESTART1",
+	"S_FANG_FIRESTART2",
+	"S_FANG_FIRE1",
+	"S_FANG_FIRE2",
+	"S_FANG_FIRE3",
+	"S_FANG_FIRE4",
+	"S_FANG_FIREREPEAT",
+	"S_FANG_LOBSHOT1",
+	"S_FANG_LOBSHOT2",
+	"S_FANG_WAIT1",
+	"S_FANG_WAIT2",
+	"S_FANG_WALLHIT",
+	"S_FANG_PINCHPATHINGSTART1",
+	"S_FANG_PINCHPATHINGSTART2",
+	"S_FANG_PINCHPATHING",
+	"S_FANG_PINCHBOUNCE1",
+	"S_FANG_PINCHBOUNCE2",
+	"S_FANG_PINCHBOUNCE3",
+	"S_FANG_PINCHBOUNCE4",
+	"S_FANG_PINCHFALL1",
+	"S_FANG_PINCHFALL2",
+	"S_FANG_PINCHSKID1",
+	"S_FANG_PINCHSKID2",
+	"S_FANG_PINCHLOBSHOT1",
+	"S_FANG_PINCHLOBSHOT2",
+	"S_FANG_PINCHLOBSHOT3",
+	"S_FANG_PINCHLOBSHOT4",
+	"S_FANG_DIE1",
+	"S_FANG_DIE2",
+	"S_FANG_DIE3",
+	"S_FANG_DIE4",
+	"S_FANG_DIE5",
+	"S_FANG_DIE6",
+	"S_FANG_DIE7",
+	"S_FANG_DIE8",
+	"S_FANG_FLEEPATHING1",
+	"S_FANG_FLEEPATHING2",
+	"S_FANG_FLEEBOUNCE1",
+	"S_FANG_FLEEBOUNCE2",
+	"S_FANG_KO",
+
+	"S_FBOMB1",
+	"S_FBOMB2",
+	"S_FBOMB_EXPL1",
+	"S_FBOMB_EXPL2",
+	"S_FBOMB_EXPL3",
+	"S_FBOMB_EXPL4",
+	"S_FBOMB_EXPL5",
+	"S_FBOMB_EXPL6",
+	"S_TNTDUST_1",
+	"S_TNTDUST_2",
+	"S_TNTDUST_3",
+	"S_TNTDUST_4",
+	"S_TNTDUST_5",
+	"S_TNTDUST_6",
+	"S_TNTDUST_7",
+	"S_TNTDUST_8",
+	"S_FSGNA",
+	"S_FSGNB",
+	"S_FSGNC",
 
 	// Black Eggman (Boss 7)
 	"S_BLACKEGG_STND",
@@ -4298,7 +5147,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_METALSONIC_BADBOUNCE",
 	"S_METALSONIC_SHOOT",
 	"S_METALSONIC_PAIN",
-	"S_METALSONIC_DEATH",
+	"S_METALSONIC_DEATH1",
+	"S_METALSONIC_DEATH2",
+	"S_METALSONIC_DEATH3",
+	"S_METALSONIC_DEATH4",
 	"S_METALSONIC_FLEE1",
 	"S_METALSONIC_FLEE2",
 	"S_METALSONIC_FLEE3",
@@ -4321,17 +5173,27 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_RING",
 
 	// Blue Sphere for special stages
-	"S_BLUEBALL",
-	"S_BLUEBALLSPARK",
+	"S_BLUESPHERE",
+	"S_BLUESPHEREBONUS",
+	"S_BLUESPHERESPARK",
+
+	// Bomb Sphere
+	"S_BOMBSPHERE1",
+	"S_BOMBSPHERE2",
+	"S_BOMBSPHERE3",
+	"S_BOMBSPHERE4",
+
+	// NiGHTS Chip
+	"S_NIGHTSCHIP",
+	"S_NIGHTSCHIPBONUS",
+
+	// NiGHTS Star
+	"S_NIGHTSSTAR",
+	"S_NIGHTSSTARXMAS",
 
 	// Gravity Wells for special stages
 	"S_GRAVWELLGREEN",
-	"S_GRAVWELLGREEN2",
-	"S_GRAVWELLGREEN3",
-
 	"S_GRAVWELLRED",
-	"S_GRAVWELLRED2",
-	"S_GRAVWELLRED3",
 
 	// Individual Team Rings
 	"S_TEAMRING",
@@ -4380,14 +5242,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CEMG6",
 	"S_CEMG7",
 
-	// Emeralds (for hunt)
-	"S_EMER1",
-
-	"S_FAN",
-	"S_FAN2",
-	"S_FAN3",
-	"S_FAN4",
-	"S_FAN5",
+	// Emerald hunt shards
+	"S_SHRD1",
+	"S_SHRD2",
+	"S_SHRD3",
 
 	// Bubble Source
 	"S_BUBBLES1",
@@ -4450,16 +5308,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SIGN52", // Eggman
 	"S_SIGN53",
 
-	// Steam Riser
-	"S_STEAM1",
-	"S_STEAM2",
-	"S_STEAM3",
-	"S_STEAM4",
-	"S_STEAM5",
-	"S_STEAM6",
-	"S_STEAM7",
-	"S_STEAM8",
-
 	// Spike Ball
 	"S_SPIKEBALL1",
 	"S_SPIKEBALL2",
@@ -4507,14 +5355,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_STARPOST_ENDSPIN",
 
 	// Big floating mine
-	"S_BIGMINE1",
-	"S_BIGMINE2",
-	"S_BIGMINE3",
-	"S_BIGMINE4",
-	"S_BIGMINE5",
-	"S_BIGMINE6",
-	"S_BIGMINE7",
-	"S_BIGMINE8",
+	"S_BIGMINE_IDLE",
+	"S_BIGMINE_ALERT1",
+	"S_BIGMINE_ALERT2",
+	"S_BIGMINE_ALERT3",
+	"S_BIGMINE_SET1",
+	"S_BIGMINE_SET2",
+	"S_BIGMINE_SET3",
+	"S_BIGMINE_BLAST1",
+	"S_BIGMINE_BLAST2",
+	"S_BIGMINE_BLAST3",
+	"S_BIGMINE_BLAST4",
+	"S_BIGMINE_BLAST5",
 
 	// Cannon Launcher
 	"S_CANNONLAUNCHER1",
@@ -4525,6 +5377,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BOXSPARKLE1",
 	"S_BOXSPARKLE2",
 	"S_BOXSPARKLE3",
+	"S_BOXSPARKLE4",
 
 	"S_BOX_FLICKER",
 	"S_BOX_POP1",
@@ -4645,6 +5498,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_THUNDERCOIN_ICON1",
 	"S_THUNDERCOIN_ICON2",
 
+	// ---
+
 	"S_ROCKET",
 
 	"S_LASER",
@@ -4674,22 +5529,17 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Arrow
 	"S_ARROW",
-	"S_ARROWUP",
-	"S_ARROWDOWN",
+	"S_ARROWBONK",
 
 	// Trapgoyle Demon fire
-	"S_DEMONFIRE1",
-	"S_DEMONFIRE2",
-	"S_DEMONFIRE3",
-	"S_DEMONFIRE4",
-	"S_DEMONFIRE5",
-	"S_DEMONFIRE6",
+	"S_DEMONFIRE",
 
 	// GFZ flowers
 	"S_GFZFLOWERA",
 	"S_GFZFLOWERB",
 	"S_GFZFLOWERC",
 
+	"S_BLUEBERRYBUSH",
 	"S_BERRYBUSH",
 	"S_BUSH",
 
@@ -4704,10 +5554,28 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_POLYGONTREE",
 	"S_BUSHTREE",
 	"S_BUSHREDTREE",
-
-	// THZ Plant
-	"S_THZFLOWERA",
-	"S_THZFLOWERB",
+	"S_SPRINGTREE",
+
+	// THZ flowers
+	"S_THZFLOWERA", // THZ1 Steam flower
+	"S_THZFLOWERB", // THZ1 Spin flower (red)
+	"S_THZFLOWERC", // THZ1 Spin flower (yellow)
+
+	// THZ Steam Whistle tree/bush
+	"S_THZTREE",
+	"S_THZTREEBRANCH1",
+	"S_THZTREEBRANCH2",
+	"S_THZTREEBRANCH3",
+	"S_THZTREEBRANCH4",
+	"S_THZTREEBRANCH5",
+	"S_THZTREEBRANCH6",
+	"S_THZTREEBRANCH7",
+	"S_THZTREEBRANCH8",
+	"S_THZTREEBRANCH9",
+	"S_THZTREEBRANCH10",
+	"S_THZTREEBRANCH11",
+	"S_THZTREEBRANCH12",
+	"S_THZTREEBRANCH13",
 
 	// THZ Alarm
 	"S_ALARM1",
@@ -4745,18 +5613,33 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// Blue Crystal
 	"S_BLUECRYSTAL1",
 
+	// Kelp,
+	"S_KELP",
+
+	// DSZ Stalagmites
+	"S_DSZSTALAGMITE",
+	"S_DSZ2STALAGMITE",
+
+	// DSZ Light beam
+	"S_LIGHTBEAM1",
+	"S_LIGHTBEAM2",
+	"S_LIGHTBEAM3",
+	"S_LIGHTBEAM4",
+	"S_LIGHTBEAM5",
+	"S_LIGHTBEAM6",
+	"S_LIGHTBEAM7",
+	"S_LIGHTBEAM8",
+	"S_LIGHTBEAM9",
+	"S_LIGHTBEAM10",
+	"S_LIGHTBEAM11",
+	"S_LIGHTBEAM12",
+
 	// CEZ Chain
 	"S_CEZCHAIN",
 
 	// Flame
-	"S_FLAME1",
-	"S_FLAME2",
-	"S_FLAME3",
-	"S_FLAME4",
-	"S_FLAME5",
-	"S_FLAME6",
+	"S_FLAME",
 	"S_FLAMEPARTICLE",
-
 	"S_FLAMEREST",
 
 	// Eggman Statue
@@ -4771,6 +5654,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BIGMACECHAIN",
 	"S_SMALLMACE",
 	"S_BIGMACE",
+	"S_SMALLGRABCHAIN",
+	"S_BIGGRABCHAIN",
 
 	// Yellow spring on a ball
 	"S_YELLOWSPRINGBALL",
@@ -4822,7 +5707,26 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BIGFIREBAR15",
 	"S_BIGFIREBAR16",
 
-	"S_CEZFLOWER1",
+	"S_CEZFLOWER",
+	"S_CEZPOLE",
+	"S_CEZBANNER1",
+	"S_CEZBANNER2",
+	"S_PINETREE",
+	"S_CEZBUSH1",
+	"S_CEZBUSH2",
+	"S_CANDLE",
+	"S_CANDLEPRICKET",
+	"S_FLAMEHOLDER",
+	"S_FIRETORCH",
+	"S_WAVINGFLAG",
+	"S_WAVINGFLAGSEG1",
+	"S_WAVINGFLAGSEG2",
+	"S_CRAWLASTATUE",
+	"S_FACESTABBERSTATUE",
+	"S_SUSPICIOUSFACESTABBERSTATUE_WAIT",
+	"S_SUSPICIOUSFACESTABBERSTATUE_BURST1",
+	"S_SUSPICIOUSFACESTABBERSTATUE_BURST2",
+	"S_BRAMBLES",
 
 	// Big Tumbleweed
 	"S_BIGTUMBLEWEED",
@@ -4851,6 +5755,95 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CACTI2",
 	"S_CACTI3",
 	"S_CACTI4",
+	"S_CACTI5",
+	"S_CACTI6",
+	"S_CACTI7",
+	"S_CACTI8",
+	"S_CACTI9",
+
+	// Warning signs sprites
+	"S_ARIDSIGN_CAUTION",
+	"S_ARIDSIGN_CACTI",
+	"S_ARIDSIGN_SHARPTURN",
+
+	// Oil lamp
+	"S_OILLAMP",
+	"S_OILLAMPFLARE",
+
+	// TNT barrel
+	"S_TNTBARREL_STND1",
+	"S_TNTBARREL_EXPL1",
+	"S_TNTBARREL_EXPL2",
+	"S_TNTBARREL_EXPL3",
+	"S_TNTBARREL_EXPL4",
+	"S_TNTBARREL_EXPL5",
+	"S_TNTBARREL_EXPL6",
+	"S_TNTBARREL_FLYING",
+
+	// TNT proximity shell
+	"S_PROXIMITY_TNT",
+	"S_PROXIMITY_TNT_TRIGGER1",
+	"S_PROXIMITY_TNT_TRIGGER2",
+	"S_PROXIMITY_TNT_TRIGGER3",
+	"S_PROXIMITY_TNT_TRIGGER4",
+	"S_PROXIMITY_TNT_TRIGGER5",
+	"S_PROXIMITY_TNT_TRIGGER6",
+	"S_PROXIMITY_TNT_TRIGGER7",
+	"S_PROXIMITY_TNT_TRIGGER8",
+	"S_PROXIMITY_TNT_TRIGGER9",
+	"S_PROXIMITY_TNT_TRIGGER10",
+	"S_PROXIMITY_TNT_TRIGGER11",
+	"S_PROXIMITY_TNT_TRIGGER12",
+	"S_PROXIMITY_TNT_TRIGGER13",
+	"S_PROXIMITY_TNT_TRIGGER14",
+	"S_PROXIMITY_TNT_TRIGGER15",
+	"S_PROXIMITY_TNT_TRIGGER16",
+	"S_PROXIMITY_TNT_TRIGGER17",
+	"S_PROXIMITY_TNT_TRIGGER18",
+	"S_PROXIMITY_TNT_TRIGGER19",
+	"S_PROXIMITY_TNT_TRIGGER20",
+	"S_PROXIMITY_TNT_TRIGGER21",
+	"S_PROXIMITY_TNT_TRIGGER22",
+	"S_PROXIMITY_TNT_TRIGGER23",
+
+	// Dust devil
+	"S_DUSTDEVIL",
+	"S_DUSTLAYER1",
+	"S_DUSTLAYER2",
+	"S_DUSTLAYER3",
+	"S_DUSTLAYER4",
+	"S_DUSTLAYER5",
+	"S_ARIDDUST1",
+	"S_ARIDDUST2",
+	"S_ARIDDUST3",
+
+	// Minecart
+	"S_MINECART_IDLE",
+	"S_MINECART_DTH1",
+	"S_MINECARTEND",
+	"S_MINECARTSEG_FRONT",
+	"S_MINECARTSEG_BACK",
+	"S_MINECARTSEG_LEFT",
+	"S_MINECARTSEG_RIGHT",
+	"S_MINECARTSIDEMARK1",
+	"S_MINECARTSIDEMARK2",
+	"S_MINECARTSPARK",
+
+	// Saloon door
+	"S_SALOONDOOR",
+	"S_SALOONDOORTHINKER",
+
+	// Train cameo
+	"S_TRAINCAMEOSPAWNER_1",
+	"S_TRAINCAMEOSPAWNER_2",
+	"S_TRAINCAMEOSPAWNER_3",
+	"S_TRAINCAMEOSPAWNER_4",
+	"S_TRAINCAMEOSPAWNER_5",
+	"S_TRAINPUFFMAKER",
+
+	// Train
+	"S_TRAINDUST",
+	"S_TRAINSTEAM",
 
 	// Flame jet
 	"S_FLAMEJETSTND",
@@ -4923,8 +5916,57 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_LAMPPOST2",  // with snow
 	"S_HANGSTAR",
 	// Xmas GFZ bushes
+	"S_XMASBLUEBERRYBUSH",
 	"S_XMASBERRYBUSH",
 	"S_XMASBUSH",
+	// FHZ
+	"S_FHZICE1",
+	"S_FHZICE2",
+
+	// Halloween Scenery
+	// Pumpkins
+	"S_JACKO1",
+	"S_JACKO1OVERLAY_1",
+	"S_JACKO1OVERLAY_2",
+	"S_JACKO1OVERLAY_3",
+	"S_JACKO1OVERLAY_4",
+	"S_JACKO2",
+	"S_JACKO2OVERLAY_1",
+	"S_JACKO2OVERLAY_2",
+	"S_JACKO2OVERLAY_3",
+	"S_JACKO2OVERLAY_4",
+	"S_JACKO3",
+	"S_JACKO3OVERLAY_1",
+	"S_JACKO3OVERLAY_2",
+	"S_JACKO3OVERLAY_3",
+	"S_JACKO3OVERLAY_4",
+	// Dr Seuss Trees
+	"S_HHZTREE_TOP",
+	"S_HHZTREE_TRUNK",
+	"S_HHZTREE_LEAF",
+	// Mushroom
+	"S_HHZSHROOM_1",
+	"S_HHZSHROOM_2",
+	"S_HHZSHROOM_3",
+	"S_HHZSHROOM_4",
+	"S_HHZSHROOM_5",
+	"S_HHZSHROOM_6",
+	"S_HHZSHROOM_7",
+	"S_HHZSHROOM_8",
+	"S_HHZSHROOM_9",
+	"S_HHZSHROOM_10",
+	"S_HHZSHROOM_11",
+	"S_HHZSHROOM_12",
+	"S_HHZSHROOM_13",
+	"S_HHZSHROOM_14",
+	"S_HHZSHROOM_15",
+	"S_HHZSHROOM_16",
+	// Misc
+	"S_HHZGRASS",
+	"S_HHZTENT1",
+	"S_HHZTENT2",
+	"S_HHZSTALAGMITE_TALL",
+	"S_HHZSTALAGMITE_SHORT",
 
 	// Botanic Serenity's loads of scenery states
 	"S_BSZTALLFLOWER_RED",
@@ -5018,6 +6060,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ARMF14",
 	"S_ARMF15",
 	"S_ARMF16",
+	"S_ARMF17",
+	"S_ARMF18",
+	"S_ARMF19",
+	"S_ARMF20",
+	"S_ARMF21",
+	"S_ARMF22",
+	"S_ARMF23",
+	"S_ARMF24",
+	"S_ARMF25",
+	"S_ARMF26",
+	"S_ARMF27",
+	"S_ARMF28",
+	"S_ARMF29",
+	"S_ARMF30",
+	"S_ARMF31",
+	"S_ARMF32",
 
 	"S_ARMB1",
 	"S_ARMB2",
@@ -5035,6 +6093,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ARMB14",
 	"S_ARMB15",
 	"S_ARMB16",
+	"S_ARMB17",
+	"S_ARMB18",
+	"S_ARMB19",
+	"S_ARMB20",
+	"S_ARMB21",
+	"S_ARMB22",
+	"S_ARMB23",
+	"S_ARMB24",
+	"S_ARMB25",
+	"S_ARMB26",
+	"S_ARMB27",
+	"S_ARMB28",
+	"S_ARMB29",
+	"S_ARMB30",
+	"S_ARMB31",
+	"S_ARMB32",
 
 	"S_WIND1",
 	"S_WIND2",
@@ -5116,6 +6190,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PITY4",
 	"S_PITY5",
 	"S_PITY6",
+	"S_PITY7",
+	"S_PITY8",
+	"S_PITY9",
+	"S_PITY10",
+	"S_PITY11",
+	"S_PITY12",
 
 	"S_FIRS1",
 	"S_FIRS2",
@@ -5213,6 +6293,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_01_FLAP1",
 	"S_FLICKY_01_FLAP2",
 	"S_FLICKY_01_FLAP3",
+	"S_FLICKY_01_STAND",
+	"S_FLICKY_01_CENTER",
 
 	// Rabbit
 	"S_FLICKY_02_OUT",
@@ -5220,6 +6302,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_02_HOP",
 	"S_FLICKY_02_UP",
 	"S_FLICKY_02_DOWN",
+	"S_FLICKY_02_STAND",
+	"S_FLICKY_02_CENTER",
 
 	// Chicken
 	"S_FLICKY_03_OUT",
@@ -5228,6 +6312,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_03_UP",
 	"S_FLICKY_03_FLAP1",
 	"S_FLICKY_03_FLAP2",
+	"S_FLICKY_03_STAND",
+	"S_FLICKY_03_CENTER",
 
 	// Seal
 	"S_FLICKY_04_OUT",
@@ -5239,6 +6325,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_04_SWIM2",
 	"S_FLICKY_04_SWIM3",
 	"S_FLICKY_04_SWIM4",
+	"S_FLICKY_04_STAND",
+	"S_FLICKY_04_CENTER",
 
 	// Pig
 	"S_FLICKY_05_OUT",
@@ -5246,6 +6334,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_05_HOP",
 	"S_FLICKY_05_UP",
 	"S_FLICKY_05_DOWN",
+	"S_FLICKY_05_STAND",
+	"S_FLICKY_05_CENTER",
 
 	// Chipmunk
 	"S_FLICKY_06_OUT",
@@ -5253,6 +6343,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_06_HOP",
 	"S_FLICKY_06_UP",
 	"S_FLICKY_06_DOWN",
+	"S_FLICKY_06_STAND",
+	"S_FLICKY_06_CENTER",
 
 	// Penguin
 	"S_FLICKY_07_OUT",
@@ -5267,6 +6359,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_07_SWIM1",
 	"S_FLICKY_07_SWIM2",
 	"S_FLICKY_07_SWIM3",
+	"S_FLICKY_07_STAND",
+	"S_FLICKY_07_CENTER",
 
 	// Fish
 	"S_FLICKY_08_OUT",
@@ -5280,6 +6374,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_08_SWIM2",
 	"S_FLICKY_08_SWIM3",
 	"S_FLICKY_08_SWIM4",
+	"S_FLICKY_08_STAND",
+	"S_FLICKY_08_CENTER",
 
 	// Ram
 	"S_FLICKY_09_OUT",
@@ -5287,11 +6383,15 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_09_HOP",
 	"S_FLICKY_09_UP",
 	"S_FLICKY_09_DOWN",
+	"S_FLICKY_09_STAND",
+	"S_FLICKY_09_CENTER",
 
 	// Puffin
 	"S_FLICKY_10_OUT",
 	"S_FLICKY_10_FLAP1",
 	"S_FLICKY_10_FLAP2",
+	"S_FLICKY_10_STAND",
+	"S_FLICKY_10_CENTER",
 
 	// Cow
 	"S_FLICKY_11_OUT",
@@ -5299,6 +6399,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_11_RUN1",
 	"S_FLICKY_11_RUN2",
 	"S_FLICKY_11_RUN3",
+	"S_FLICKY_11_STAND",
+	"S_FLICKY_11_CENTER",
 
 	// Rat
 	"S_FLICKY_12_OUT",
@@ -5306,6 +6408,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_12_RUN1",
 	"S_FLICKY_12_RUN2",
 	"S_FLICKY_12_RUN3",
+	"S_FLICKY_12_STAND",
+	"S_FLICKY_12_CENTER",
 
 	// Bear
 	"S_FLICKY_13_OUT",
@@ -5313,12 +6417,16 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_13_HOP",
 	"S_FLICKY_13_UP",
 	"S_FLICKY_13_DOWN",
+	"S_FLICKY_13_STAND",
+	"S_FLICKY_13_CENTER",
 
 	// Dove
 	"S_FLICKY_14_OUT",
 	"S_FLICKY_14_FLAP1",
 	"S_FLICKY_14_FLAP2",
 	"S_FLICKY_14_FLAP3",
+	"S_FLICKY_14_STAND",
+	"S_FLICKY_14_CENTER",
 
 	// Cat
 	"S_FLICKY_15_OUT",
@@ -5326,26 +6434,79 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLICKY_15_HOP",
 	"S_FLICKY_15_UP",
 	"S_FLICKY_15_DOWN",
+	"S_FLICKY_15_STAND",
+	"S_FLICKY_15_CENTER",
 
 	// Canary
 	"S_FLICKY_16_OUT",
 	"S_FLICKY_16_FLAP1",
 	"S_FLICKY_16_FLAP2",
 	"S_FLICKY_16_FLAP3",
+	"S_FLICKY_16_STAND",
+	"S_FLICKY_16_CENTER",
+
+	// Spider
+	"S_SECRETFLICKY_01_OUT",
+	"S_SECRETFLICKY_01_AIM",
+	"S_SECRETFLICKY_01_HOP",
+	"S_SECRETFLICKY_01_UP",
+	"S_SECRETFLICKY_01_DOWN",
+	"S_SECRETFLICKY_01_STAND",
+	"S_SECRETFLICKY_01_CENTER",
+
+	// Bat
+	"S_SECRETFLICKY_02_OUT",
+	"S_SECRETFLICKY_02_FLAP1",
+	"S_SECRETFLICKY_02_FLAP2",
+	"S_SECRETFLICKY_02_FLAP3",
+	"S_SECRETFLICKY_02_STAND",
+	"S_SECRETFLICKY_02_CENTER",
+
+	// Fan
+	"S_FAN",
+	"S_FAN2",
+	"S_FAN3",
+	"S_FAN4",
+	"S_FAN5",
+
+	// Steam Riser
+	"S_STEAM1",
+	"S_STEAM2",
+	"S_STEAM3",
+	"S_STEAM4",
+	"S_STEAM5",
+	"S_STEAM6",
+	"S_STEAM7",
+	"S_STEAM8",
+
+	// Bumpers
+	"S_BUMPER",
+	"S_BUMPERHIT",
 
+	// Balloons
+	"S_BALLOON",
+	"S_BALLOONPOP1",
+	"S_BALLOONPOP2",
+	"S_BALLOONPOP3",
+	"S_BALLOONPOP4",
+	"S_BALLOONPOP5",
+	"S_BALLOONPOP6",
+
+	// Yellow Spring
 	"S_YELLOWSPRING",
 	"S_YELLOWSPRING2",
 	"S_YELLOWSPRING3",
 	"S_YELLOWSPRING4",
 	"S_YELLOWSPRING5",
 
+	// Red Spring
 	"S_REDSPRING",
 	"S_REDSPRING2",
 	"S_REDSPRING3",
 	"S_REDSPRING4",
 	"S_REDSPRING5",
 
-	// Blue Springs
+	// Blue Spring
 	"S_BLUESPRING",
 	"S_BLUESPRING2",
 	"S_BLUESPRING3",
@@ -5372,6 +6533,16 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_RDIAG7",
 	"S_RDIAG8",
 
+	// Blue Diagonal Spring
+	"S_BDIAG1",
+	"S_BDIAG2",
+	"S_BDIAG3",
+	"S_BDIAG4",
+	"S_BDIAG5",
+	"S_BDIAG6",
+	"S_BDIAG7",
+	"S_BDIAG8",
+
 	// Yellow Side Spring
 	"S_YHORIZ1",
 	"S_YHORIZ2",
@@ -5477,7 +6648,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SEED",
 
 	"S_PARTICLE",
-	"S_PARTICLEGEN",
 
 	// Score Logos
 	"S_SCRA", // 100
@@ -5491,6 +6661,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SCRI", // 4000 (mario)
 	"S_SCRJ", // 8000 (mario)
 	"S_SCRK", // 1UP (mario)
+	"S_SCRL", // 10
 
 	// Drowning Timer Numbers
 	"S_ZERO1",
@@ -5509,16 +6680,21 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	"S_LOCKON1",
 	"S_LOCKON2",
+	"S_LOCKON3",
+	"S_LOCKON4",
+	"S_LOCKONINF1",
+	"S_LOCKONINF2",
+	"S_LOCKONINF3",
+	"S_LOCKONINF4",
 
 	// Tag Sign
 	"S_TTAG",
 
 	// Got Flag Sign
 	"S_GOTFLAG",
-	"S_GOTREDFLAG",
-	"S_GOTBLUEFLAG",
 
 	"S_CORK",
+	"S_LHRT",
 
 	// Red Ring
 	"S_RRNG1",
@@ -5719,8 +6895,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_TOAD",
 
 	// Nights-specific stuff
-	"S_NIGHTSDRONE1",
-	"S_NIGHTSDRONE2",
+	"S_NIGHTSDRONE_MAN1",
+	"S_NIGHTSDRONE_MAN2",
 	"S_NIGHTSDRONE_SPARKLING1",
 	"S_NIGHTSDRONE_SPARKLING2",
 	"S_NIGHTSDRONE_SPARKLING3",
@@ -5737,10 +6913,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTSDRONE_SPARKLING14",
 	"S_NIGHTSDRONE_SPARKLING15",
 	"S_NIGHTSDRONE_SPARKLING16",
-	"S_NIGHTSGOAL1",
-	"S_NIGHTSGOAL2",
-	"S_NIGHTSGOAL3",
-	"S_NIGHTSGOAL4",
+	"S_NIGHTSDRONE_GOAL1",
+	"S_NIGHTSDRONE_GOAL2",
+	"S_NIGHTSDRONE_GOAL3",
+	"S_NIGHTSDRONE_GOAL4",
 
 	"S_NIGHTSPARKLE1",
 	"S_NIGHTSPARKLE2",
@@ -5791,9 +6967,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTSCORE90_2",
 	"S_NIGHTSCORE100_2",
 
-	"S_NIGHTSWING",
-	"S_NIGHTSWING_XMAS",
-
 	// NiGHTS Paraloop Powerups
 	"S_NIGHTSSUPERLOOP",
 	"S_NIGHTSDRILLREFILL",
@@ -5811,14 +6984,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ORBITEM6",
 	"S_ORBITEM7",
 	"S_ORBITEM8",
-	"S_ORBITEM9",
-	"S_ORBITEM10",
-	"S_ORBITEM11",
-	"S_ORBITEM12",
-	"S_ORBITEM13",
-	"S_ORBITEM14",
-	"S_ORBITEM15",
-	"S_ORBITEM16",
+	"S_ORBIDYA1",
+	"S_ORBIDYA2",
+	"S_ORBIDYA3",
+	"S_ORBIDYA4",
+	"S_ORBIDYA5",
 
 	// "Flicky" helper
 	"S_NIGHTOPIANHELPER1",
@@ -5831,6 +7001,141 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTOPIANHELPER8",
 	"S_NIGHTOPIANHELPER9",
 
+	// Nightopian
+	"S_PIAN0",
+	"S_PIAN1",
+	"S_PIAN2",
+	"S_PIAN3",
+	"S_PIAN4",
+	"S_PIAN5",
+	"S_PIAN6",
+	"S_PIANSING",
+
+	// Shleep
+	"S_SHLEEP1",
+	"S_SHLEEP2",
+	"S_SHLEEP3",
+	"S_SHLEEP4",
+	"S_SHLEEPBOUNCE1",
+	"S_SHLEEPBOUNCE2",
+	"S_SHLEEPBOUNCE3",
+
+	// Secret badniks and hazards, shhhh
+	"S_PENGUINATOR_LOOK",
+	"S_PENGUINATOR_WADDLE1",
+	"S_PENGUINATOR_WADDLE2",
+	"S_PENGUINATOR_WADDLE3",
+	"S_PENGUINATOR_WADDLE4",
+	"S_PENGUINATOR_SLIDE1",
+	"S_PENGUINATOR_SLIDE2",
+	"S_PENGUINATOR_SLIDE3",
+	"S_PENGUINATOR_SLIDE4",
+	"S_PENGUINATOR_SLIDE5",
+
+	"S_POPHAT_LOOK",
+	"S_POPHAT_SHOOT1",
+	"S_POPHAT_SHOOT2",
+	"S_POPHAT_SHOOT3",
+
+	"S_HIVEELEMENTAL_LOOK",
+	"S_HIVEELEMENTAL_PREPARE1",
+	"S_HIVEELEMENTAL_PREPARE2",
+	"S_HIVEELEMENTAL_SHOOT1",
+	"S_HIVEELEMENTAL_SHOOT2",
+	"S_HIVEELEMENTAL_DORMANT",
+	"S_HIVEELEMENTAL_PAIN",
+	"S_HIVEELEMENTAL_DIE1",
+	"S_HIVEELEMENTAL_DIE2",
+	"S_HIVEELEMENTAL_DIE3",
+
+	"S_BUMBLEBORE_SPAWN",
+	"S_BUMBLEBORE_LOOK1",
+	"S_BUMBLEBORE_LOOK2",
+	"S_BUMBLEBORE_FLY1",
+	"S_BUMBLEBORE_FLY2",
+	"S_BUMBLEBORE_RAISE",
+	"S_BUMBLEBORE_FALL1",
+	"S_BUMBLEBORE_FALL2",
+	"S_BUMBLEBORE_STUCK1",
+	"S_BUMBLEBORE_STUCK2",
+	"S_BUMBLEBORE_DIE",
+
+	"S_BBUZZFLY1",
+	"S_BBUZZFLY2",
+
+	"S_SMASHSPIKE_FLOAT",
+	"S_SMASHSPIKE_EASE1",
+	"S_SMASHSPIKE_EASE2",
+	"S_SMASHSPIKE_FALL",
+	"S_SMASHSPIKE_STOMP1",
+	"S_SMASHSPIKE_STOMP2",
+	"S_SMASHSPIKE_RISE1",
+	"S_SMASHSPIKE_RISE2",
+
+	"S_CACO_LOOK",
+	"S_CACO_WAKE1",
+	"S_CACO_WAKE2",
+	"S_CACO_WAKE3",
+	"S_CACO_WAKE4",
+	"S_CACO_ROAR",
+	"S_CACO_CHASE",
+	"S_CACO_CHASE_REPEAT",
+	"S_CACO_RANDOM",
+	"S_CACO_PREPARE_SOUND",
+	"S_CACO_PREPARE1",
+	"S_CACO_PREPARE2",
+	"S_CACO_PREPARE3",
+	"S_CACO_SHOOT_SOUND",
+	"S_CACO_SHOOT1",
+	"S_CACO_SHOOT2",
+	"S_CACO_CLOSE",
+	"S_CACO_DIE_FLAGS",
+	"S_CACO_DIE_GIB1",
+	"S_CACO_DIE_GIB2",
+	"S_CACO_DIE_SCREAM",
+	"S_CACO_DIE_SHATTER",
+	"S_CACO_DIE_FALL",
+	"S_CACOSHARD_RANDOMIZE",
+	"S_CACOSHARD1_1",
+	"S_CACOSHARD1_2",
+	"S_CACOSHARD2_1",
+	"S_CACOSHARD2_2",
+	"S_CACOFIRE1",
+	"S_CACOFIRE2",
+	"S_CACOFIRE3",
+	"S_CACOFIRE_EXPLODE1",
+	"S_CACOFIRE_EXPLODE2",
+	"S_CACOFIRE_EXPLODE3",
+	"S_CACOFIRE_EXPLODE4",
+
+	"S_SPINBOBERT_MOVE_FLIPUP",
+	"S_SPINBOBERT_MOVE_UP",
+	"S_SPINBOBERT_MOVE_FLIPDOWN",
+	"S_SPINBOBERT_MOVE_DOWN",
+	"S_SPINBOBERT_FIRE_MOVE",
+	"S_SPINBOBERT_FIRE_GHOST",
+	"S_SPINBOBERT_FIRE_TRAIL1",
+	"S_SPINBOBERT_FIRE_TRAIL2",
+	"S_SPINBOBERT_FIRE_TRAIL3",
+
+	"S_HANGSTER_LOOK",
+	"S_HANGSTER_SWOOP1",
+	"S_HANGSTER_SWOOP2",
+	"S_HANGSTER_ARC1",
+	"S_HANGSTER_ARC2",
+	"S_HANGSTER_ARC3",
+	"S_HANGSTER_FLY1",
+	"S_HANGSTER_FLY2",
+	"S_HANGSTER_FLY3",
+	"S_HANGSTER_FLY4",
+	"S_HANGSTER_FLYREPEAT",
+	"S_HANGSTER_ARCUP1",
+	"S_HANGSTER_ARCUP2",
+	"S_HANGSTER_ARCUP3",
+	"S_HANGSTER_RETURN1",
+	"S_HANGSTER_RETURN2",
+	"S_HANGSTER_RETURN3",
+
 	"S_CRUMBLE1",
 	"S_CRUMBLE2",
 
@@ -5838,24 +7143,15 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SPRK1",
 	"S_SPRK2",
 	"S_SPRK3",
-	"S_SPRK4",
-	"S_SPRK5",
-	"S_SPRK6",
-	"S_SPRK7",
-	"S_SPRK8",
-	"S_SPRK9",
-	"S_SPRK10",
-	"S_SPRK11",
-	"S_SPRK12",
-	"S_SPRK13",
-	"S_SPRK14",
-	"S_SPRK15",
-	"S_SPRK16",
 
 	// Robot Explosion
 	"S_XPLD_FLICKY",
 	"S_XPLD1",
 	"S_XPLD2",
+	"S_XPLD3",
+	"S_XPLD4",
+	"S_XPLD5",
+	"S_XPLD6",
 	"S_XPLD_EGGTRAP",
 
 	// Underwater Explosion
@@ -5866,6 +7162,13 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_WPLD5",
 	"S_WPLD6",
 
+	"S_DUST1",
+	"S_DUST2",
+	"S_DUST3",
+	"S_DUST4",
+
+	"S_WOODDEBRIS",
+
 	"S_ROCKSPAWN",
 
 	"S_ROCKCRUMBLEA",
@@ -5884,6 +7187,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROCKCRUMBLEN",
 	"S_ROCKCRUMBLEO",
 	"S_ROCKCRUMBLEP",
+	"S_BRICKDEBRIS",
 
 #ifdef SEENAMES
 	"S_NAMECHECK",
@@ -5902,35 +7206,43 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_TAILSOVERLAY", // c:
 
 	// Enemies
-	"MT_BLUECRAWLA",
-	"MT_REDCRAWLA",
-	"MT_GFZFISH", // Greenflower Fish
-	"MT_GOLDBUZZ",
-	"MT_REDBUZZ",
-	"MT_AQUABUZZ",
+	"MT_BLUECRAWLA", // Crawla (Blue)
+	"MT_REDCRAWLA", // Crawla (Red)
+	"MT_GFZFISH", // SDURF
+	"MT_GOLDBUZZ", // Buzz (Gold)
+	"MT_REDBUZZ", // Buzz (Red)
 	"MT_JETTBOMBER", // Jetty-Syn Bomber
 	"MT_JETTGUNNER", // Jetty-Syn Gunner
 	"MT_CRAWLACOMMANDER", // Crawla Commander
 	"MT_DETON", // Deton
 	"MT_SKIM", // Skim mine dropper
-	"MT_TURRET",
-	"MT_POPUPTURRET",
-	"MT_SHARP", // Sharp
+	"MT_TURRET", // Industrial Turret
+	"MT_POPUPTURRET", // Pop-Up Turret
+	"MT_SPINCUSHION", // Spincushion
+	"MT_CRUSHSTACEAN", // Crushstacean
+	"MT_CRUSHCLAW", // Big meaty claw
+	"MT_CRUSHCHAIN", // Chain
 	"MT_JETJAW", // Jet Jaw
 	"MT_SNAILER", // Snailer
-	"MT_VULTURE", // Vulture
+	"MT_VULTURE", // BASH
 	"MT_POINTY", // Pointy
 	"MT_POINTYBALL", // Pointy Ball
 	"MT_ROBOHOOD", // Robo-Hood
-	"MT_FACESTABBER", // CastleBot FaceStabber
+	"MT_FACESTABBER", // Castlebot Facestabber
+	"MT_FACESTABBERSPEAR", // Castlebot Facestabber spear aura
 	"MT_EGGGUARD", // Egg Guard
-	"MT_EGGSHIELD", // Egg Shield for Egg Guard
+	"MT_EGGSHIELD", // Egg Guard's shield
 	"MT_GSNAPPER", // Green Snapper
+	"MT_SNAPPER_LEG", // Green Snapper leg
+	"MT_SNAPPER_HEAD", // Green Snapper head
 	"MT_MINUS", // Minus
-	"MT_SPRINGSHELL", // Spring Shell (no drop)
+	"MT_MINUSDIRT", // Minus dirt
+	"MT_SPRINGSHELL", // Spring Shell
 	"MT_YELLOWSHELL", // Spring Shell (yellow)
 	"MT_UNIDUS", // Unidus
 	"MT_UNIBALL", // Unidus Ball
+	"MT_CANARIVORE", // Canarivore
+	"MT_CANARIVORE_GAS", // Canarivore gas
 
 	// Generic Boss Items
 	"MT_BOSSEXPLODE",
@@ -5960,11 +7272,22 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_EGGMOBILE3",
 	"MT_PROPELLER",
 	"MT_FAKEMOBILE",
+	"MT_SHOCK",
 
 	// Boss 4
 	"MT_EGGMOBILE4",
 	"MT_EGGMOBILE4_MACE",
 	"MT_JETFLAME",
+	"MT_EGGROBO1",
+	"MT_EGGROBO1JET",
+
+	// Boss 5
+	"MT_FANG",
+	"MT_FBOMB",
+	"MT_TNTDUST", // also used by barrel
+	"MT_FSGNA",
+	"MT_FSGNB",
+	"MT_FANGWAYPOINT",
 
 	// Black Eggman (Boss 7)
 	"MT_BLACKEGGMAN",
@@ -5994,7 +7317,9 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// Collectible Items
 	"MT_RING",
 	"MT_FLINGRING", // Lost ring
-	"MT_BLUEBALL",  // Blue sphere replacement for special stages
+	"MT_BLUESPHERE",  // Blue sphere for special stages
+	"MT_FLINGBLUESPHERE", // Lost blue sphere
+	"MT_BOMBSPHERE",
 	"MT_REDTEAMRING",  //Rings collectable by red team.
 	"MT_BLUETEAMRING", //Rings collectable by blue team.
 	"MT_TOKEN", // Special Stage Token
@@ -6014,28 +7339,31 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 
 	// Springs and others
 	"MT_FAN",
-	"MT_STEAM", // Steam riser
-	"MT_BLUESPRING",
+	"MT_STEAM",
+	"MT_BUMPER",
+	"MT_BALLOON",
+
 	"MT_YELLOWSPRING",
 	"MT_REDSPRING",
-	"MT_YELLOWDIAG", // Yellow Diagonal Spring
-	"MT_REDDIAG", // Red Diagonal Spring
-	"MT_YELLOWHORIZ", // Yellow Side Spring
-	"MT_REDHORIZ", // Red Side Spring
-	"MT_BLUEHORIZ", // Blue Side Spring
+	"MT_BLUESPRING",
+	"MT_YELLOWDIAG",
+	"MT_REDDIAG",
+	"MT_BLUEDIAG",
+	"MT_YELLOWHORIZ",
+	"MT_REDHORIZ",
+	"MT_BLUEHORIZ",
 
 	// Interactive Objects
 	"MT_BUBBLES", // Bubble source
 	"MT_SIGN", // Level end sign
 	"MT_SPIKEBALL", // Spike Ball
-	"MT_SPECIALSPIKEBALL",
 	"MT_SPINFIRE",
 	"MT_SPIKE",
 	"MT_WALLSPIKE",
 	"MT_WALLSPIKEBASE",
 	"MT_STARPOST",
 	"MT_BIGMINE",
-	"MT_BIGAIRMINE",
+	"MT_BLASTEXECUTOR",
 	"MT_CANNONLAUNCHER",
 
 	// Monitor miscellany
@@ -6121,8 +7449,11 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_GFZFLOWER1",
 	"MT_GFZFLOWER2",
 	"MT_GFZFLOWER3",
+
+	"MT_BLUEBERRYBUSH",
 	"MT_BERRYBUSH",
 	"MT_BUSH",
+
 	// Trees (both GFZ and misc)
 	"MT_GFZTREE",
 	"MT_GFZBERRYTREE",
@@ -6134,10 +7465,14 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_POLYGONTREE",
 	"MT_BUSHTREE",
 	"MT_BUSHREDTREE",
+	"MT_SPRINGTREE",
 
 	// Techno Hill Scenery
 	"MT_THZFLOWER1",
 	"MT_THZFLOWER2",
+	"MT_THZFLOWER3",
+	"MT_THZTREE", // Steam whistle tree/bush
+	"MT_THZTREEBRANCH", // branch of said tree
 	"MT_ALARM",
 
 	// Deep Sea Scenery
@@ -6150,6 +7485,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CORAL2", // Coral 2
 	"MT_CORAL3", // Coral 3
 	"MT_BLUECRYSTAL", // Blue Crystal
+	"MT_KELP", // Kelp
+	"MT_DSZSTALAGMITE", // Deep Sea 1 Stalagmite
+	"MT_DSZ2STALAGMITE", // Deep Sea 2 Stalagmite
+	"MT_LIGHTBEAM", // DSZ Light beam
 
 	// Castle Eggman Scenery
 	"MT_CHAIN", // CEZ Chain
@@ -6167,11 +7506,32 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_BIGMACECHAIN", // Big Mace Chain
 	"MT_SMALLMACE", // Small Mace
 	"MT_BIGMACE", // Big Mace
+	"MT_SMALLGRABCHAIN", // Small Grab Chain
+	"MT_BIGGRABCHAIN", // Big Grab Chain
 	"MT_YELLOWSPRINGBALL", // Yellow spring on a ball
 	"MT_REDSPRINGBALL", // Red spring on a ball
 	"MT_SMALLFIREBAR", // Small Firebar
 	"MT_BIGFIREBAR", // Big Firebar
-	"MT_CEZFLOWER",
+	"MT_CEZFLOWER", // Flower
+	"MT_CEZPOLE1", // Pole (with red banner)
+	"MT_CEZPOLE2", // Pole (with blue banner)
+	"MT_CEZBANNER1", // Banner (red)
+	"MT_CEZBANNER2", // Banner (blue)
+	"MT_PINETREE", // Pine Tree
+	"MT_CEZBUSH1", // Bush 1
+	"MT_CEZBUSH2", // Bush 2
+	"MT_CANDLE", // Candle
+	"MT_CANDLEPRICKET", // Candle pricket
+	"MT_FLAMEHOLDER", // Flame holder
+	"MT_FIRETORCH", // Fire torch
+	"MT_WAVINGFLAG1", // Waving flag (red)
+	"MT_WAVINGFLAG2", // Waving flag (blue)
+	"MT_WAVINGFLAGSEG1", // Waving flag segment (red)
+	"MT_WAVINGFLAGSEG2", // Waving flag segment (blue)
+	"MT_CRAWLASTATUE", // Crawla statue
+	"MT_FACESTABBERSTATUE", // Facestabber statue
+	"MT_SUSPICIOUSFACESTABBERSTATUE", // :eggthinking:
+	"MT_BRAMBLES", // Brambles
 
 	// Arid Canyon Scenery
 	"MT_BIGTUMBLEWEED",
@@ -6180,6 +7540,34 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CACTI2",
 	"MT_CACTI3",
 	"MT_CACTI4",
+	"MT_CACTI5",
+	"MT_CACTI6",
+	"MT_CACTI7",
+	"MT_CACTI8",
+	"MT_CACTI9",
+	"MT_ARIDSIGN_CAUTION",
+	"MT_ARIDSIGN_CACTI",
+	"MT_ARIDSIGN_SHARPTURN",
+	"MT_OILLAMP",
+	"MT_TNTBARREL",
+	"MT_PROXIMITYTNT",
+	"MT_DUSTDEVIL",
+	"MT_DUSTLAYER",
+	"MT_ARIDDUST",
+	"MT_MINECART",
+	"MT_MINECARTSEG",
+	"MT_MINECARTSPAWNER",
+	"MT_MINECARTEND",
+	"MT_MINECARTENDSOLID",
+	"MT_MINECARTSIDEMARK",
+	"MT_MINECARTSPARK",
+	"MT_SALOONDOOR",
+	"MT_SALOONDOORTHINKER",
+	"MT_TRAINCAMEOSPAWNER",
+	"MT_TRAINSEG",
+	"MT_TRAINDUSTSPAWNER",
+	"MT_TRAINSTEAMSPAWNER",
+	"MT_MINECARTSWITCHPOINT",
 
 	// Red Volcano Scenery
 	"MT_FLAMEJET",
@@ -6223,8 +7611,28 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_LAMPPOST2",  // with snow
 	"MT_HANGSTAR",
 	// Xmas GFZ bushes
+	"MT_XMASBLUEBERRYBUSH",
 	"MT_XMASBERRYBUSH",
 	"MT_XMASBUSH",
+	// FHZ
+	"MT_FHZICE1",
+	"MT_FHZICE2",
+
+	// Halloween Scenery
+	// Pumpkins
+	"MT_JACKO1",
+	"MT_JACKO2",
+	"MT_JACKO3",
+	// Dr Seuss Trees
+	"MT_HHZTREE_TOP",
+	"MT_HHZTREE_PART",
+	// Misc
+	"MT_HHZSHROOM",
+	"MT_HHZGRASS",
+	"MT_HHZTENTACLE1",
+	"MT_HHZTENTACLE2",
+	"MT_HHZSTALAGMITE_TALL",
+	"MT_HHZSTALAGMITE_SHORT",
 
 	// Botanic Serenity
 	"MT_BSZTALLFLOWER_RED",
@@ -6296,21 +7704,42 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 
 	// Flickies
 	"MT_FLICKY_01", // Bluebird
+	"MT_FLICKY_01_CENTER",
 	"MT_FLICKY_02", // Rabbit
+	"MT_FLICKY_02_CENTER",
 	"MT_FLICKY_03", // Chicken
+	"MT_FLICKY_03_CENTER",
 	"MT_FLICKY_04", // Seal
+	"MT_FLICKY_04_CENTER",
 	"MT_FLICKY_05", // Pig
+	"MT_FLICKY_05_CENTER",
 	"MT_FLICKY_06", // Chipmunk
+	"MT_FLICKY_06_CENTER",
 	"MT_FLICKY_07", // Penguin
+	"MT_FLICKY_07_CENTER",
 	"MT_FLICKY_08", // Fish
+	"MT_FLICKY_08_CENTER",
 	"MT_FLICKY_09", // Ram
+	"MT_FLICKY_09_CENTER",
 	"MT_FLICKY_10", // Puffin
+	"MT_FLICKY_10_CENTER",
 	"MT_FLICKY_11", // Cow
+	"MT_FLICKY_11_CENTER",
 	"MT_FLICKY_12", // Rat
+	"MT_FLICKY_12_CENTER",
 	"MT_FLICKY_13", // Bear
+	"MT_FLICKY_13_CENTER",
 	"MT_FLICKY_14", // Dove
+	"MT_FLICKY_14_CENTER",
 	"MT_FLICKY_15", // Cat
+	"MT_FLICKY_15_CENTER",
 	"MT_FLICKY_16", // Canary
+	"MT_FLICKY_16_CENTER",
+	"MT_SECRETFLICKY_01", // Spider
+	"MT_SECRETFLICKY_01_CENTER",
+	"MT_SECRETFLICKY_02", // Bat
+	"MT_SECRETFLICKY_02_CENTER",
+	"MT_SEED",
 
 	// Environmental Effects
 	"MT_RAIN", // Rain
@@ -6323,7 +7752,6 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_WATERZAP",
 	"MT_SPINDUST", // Spindash dust
 	"MT_TFOG",
-	"MT_SEED",
 	"MT_PARTICLE",
 	"MT_PARTICLEGEN", // For fans, etc.
 
@@ -6332,6 +7760,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_DROWNNUMBERS", // Drowning Timer
 	"MT_GOTEMERALD", // Chaos Emerald (intangible)
 	"MT_LOCKON", // Target
+	"MT_LOCKONINF", // In-level Target
 	"MT_TAG", // Tag Sign
 	"MT_GOTFLAG", // Got Flag sign
 
@@ -6346,8 +7775,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_AWATERH", // Ambient Water Sound 8
 	"MT_RANDOMAMBIENT",
 	"MT_RANDOMAMBIENT2",
+	"MT_MACHINEAMBIENCE",
 
 	"MT_CORK",
+	"MT_LHRT",
 
 	// Ring Weapons
 	"MT_REDRING",
@@ -6396,7 +7827,9 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_AXISTRANSFER",
 	"MT_AXISTRANSFERLINE",
 	"MT_NIGHTSDRONE",
-	"MT_NIGHTSGOAL",
+	"MT_NIGHTSDRONE_MAN",
+	"MT_NIGHTSDRONE_SPARKLING",
+	"MT_NIGHTSDRONE_GOAL",
 	"MT_NIGHTSPARKLE",
 	"MT_NIGHTSLOOPHELPER",
 	"MT_NIGHTSBUMPER", // NiGHTS Bumper
@@ -6404,14 +7837,38 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_HOOPCOLLIDE", // Collision detection for NiGHTS hoops
 	"MT_HOOPCENTER", // Center of a hoop
 	"MT_NIGHTSCORE",
-	"MT_NIGHTSWING",
+	"MT_NIGHTSCHIP", // NiGHTS Chip
+	"MT_FLINGNIGHTSCHIP", // Lost NiGHTS Chip
+	"MT_NIGHTSSTAR", // NiGHTS Star
 	"MT_NIGHTSSUPERLOOP",
 	"MT_NIGHTSDRILLREFILL",
 	"MT_NIGHTSHELPER",
 	"MT_NIGHTSEXTRATIME",
 	"MT_NIGHTSLINKFREEZE",
 	"MT_EGGCAPSULE",
+	"MT_IDEYAANCHOR",
 	"MT_NIGHTOPIANHELPER", // the actual helper object that orbits you
+	"MT_PIAN", // decorative singing friend
+	"MT_SHLEEP", // almost-decorative sleeping enemy
+
+	// Secret badniks and hazards, shhhh
+	"MT_PENGUINATOR",
+	"MT_POPHAT",
+	"MT_POPSHOT",
+
+	"MT_HIVEELEMENTAL",
+	"MT_BUMBLEBORE",
+
+	"MT_BUBBLEBUZZ",
+
+	"MT_SMASHINGSPIKEBALL",
+	"MT_CACOLANTERN",
+	"MT_CACOSHARD",
+	"MT_CACOFIRE",
+	"MT_SPINBOBERT",
+	"MT_SPINBOBERT_FIRE1",
+	"MT_SPINBOBERT_FIRE2",
+	"MT_HANGSTER",
 
 	// Utility Objects
 	"MT_TELEPORTMAN",
@@ -6422,6 +7879,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_PULL",
 	"MT_GHOST",
 	"MT_OVERLAY",
+	"MT_ANGLEMAN",
 	"MT_POLYANCHOR",
 	"MT_POLYSPAWN",
 	"MT_POLYSPAWNCRUSH",
@@ -6433,6 +7891,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_SPARK", //spark
 	"MT_EXPLODE", // Robot Explosion
 	"MT_UWEXPLODE", // Underwater Explosion
+	"MT_DUST",
+	"MT_WOODDEBRIS",
 	"MT_ROCKSPAWNER",
 	"MT_FALLINGROCK",
 	"MT_ROCKCRUMBLE1",
@@ -6451,6 +7911,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_ROCKCRUMBLE14",
 	"MT_ROCKCRUMBLE15",
 	"MT_ROCKCRUMBLE16",
+	"MT_BRICKDEBRIS",
 
 #ifdef SEENAMES
 	"MT_NAMECHECK",
@@ -6504,8 +7965,8 @@ static const char *const MOBJFLAG2_LIST[] = {
 	"SCATTER",		  // Thrown ring has scatter properties
 	"BEYONDTHEGRAVE", // Source of this missile has died and has since respawned.
 	"SLIDEPUSH",	  // MF_PUSHABLE that pushes continuously.
-	"CLASSICPUSH",	  // Drops straight down when object has negative Z.
-	"STANDONME",	  // While not pushable, stand on me anyway.
+	"CLASSICPUSH",    // Drops straight down when object has negative momz.
+	"INVERTAIMABLE",  // Flips whether it's targetable by A_LookForEnemies (enemies no, decoys yes)
 	"INFLOAT",		  // Floating to a height for a move, don't auto float to target's height.
 	"DEBRIS",		  // Splash ring from explosion ring
 	"NIGHTSPULL",	  // Attracted from a paraloop
@@ -6523,7 +7984,6 @@ static const char *const MOBJFLAG2_LIST[] = {
 	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
 	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
-	"MACEROTATE",     // Thinker calls P_MaceRotate around tracer
 	NULL
 };
 
@@ -6796,32 +8256,22 @@ static const char *const POWERS_LIST[] = {
 };
 
 static const char *const HUDITEMS_LIST[] = {
-	"LIVESNAME",
-	"LIVESPIC",
-	"LIVESNUM",
-	"LIVESX",
+	"LIVES",
 
 	"RINGS",
-	"RINGSSPLIT",
 	"RINGSNUM",
-	"RINGSNUMSPLIT",
 
 	"SCORE",
 	"SCORENUM",
 
 	"TIME",
-	"TIMESPLIT",
 	"MINUTES",
-	"MINUTESSPLIT",
 	"TIMECOLON",
-	"TIMECOLONSPLIT",
 	"SECONDS",
-	"SECONDSSPLIT",
 	"TIMETICCOLON",
 	"TICS",
 
 	"SS_TOTALRINGS",
-	"SS_TOTALRINGS_SPLIT",
 
 	"GETRINGS",
 	"GETRINGSNUM",
@@ -6829,10 +8279,100 @@ static const char *const HUDITEMS_LIST[] = {
 	"TIMELEFTNUM",
 	"TIMEUP",
 	"HUNTPICS",
-	"GRAVBOOTSICO",
+	"POWERUPS",
 	"LAP"
 };
 
+static const char *const MENUTYPES_LIST[] = {
+	"NONE",
+
+	"MAIN",
+
+	// Single Player
+	"SP_MAIN",
+
+	"SP_LOAD",
+	"SP_PLAYER",
+
+	"SP_LEVELSELECT",
+	"SP_LEVELSTATS",
+
+	"SP_TIMEATTACK",
+	"SP_TIMEATTACK_LEVELSELECT",
+	"SP_GUESTREPLAY",
+	"SP_REPLAY",
+	"SP_GHOST",
+
+	"SP_NIGHTSATTACK",
+	"SP_NIGHTS_LEVELSELECT",
+	"SP_NIGHTS_GUESTREPLAY",
+	"SP_NIGHTS_REPLAY",
+	"SP_NIGHTS_GHOST",
+
+	// Multiplayer
+	"MP_MAIN",
+	"MP_SPLITSCREEN", // SplitServer
+	"MP_SERVER",
+	"MP_CONNECT",
+	"MP_ROOM",
+	"MP_PLAYERSETUP", // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
+
+	// Options
+	"OP_MAIN",
+
+	"OP_P1CONTROLS",
+	"OP_CHANGECONTROLS", // OP_ChangeControlsDef shared with P2
+	"OP_P1MOUSE",
+	"OP_P1JOYSTICK",
+	"OP_JOYSTICKSET", // OP_JoystickSetDef shared with P2
+
+	"OP_P2CONTROLS",
+	"OP_P2MOUSE",
+	"OP_P2JOYSTICK",
+
+	"OP_VIDEO",
+	"OP_VIDEOMODE",
+	"OP_COLOR",
+	"OP_OPENGL",
+	"OP_OPENGL_LIGHTING",
+	"OP_OPENGL_FOG",
+	"OP_OPENGL_COLOR",
+
+	"OP_SOUND",
+
+	"OP_SERVER",
+	"OP_MONITORTOGGLE",
+
+	"OP_DATA",
+	"OP_ADDONS",
+	"OP_SCREENSHOTS",
+	"OP_ERASEDATA",
+
+	// Secrets
+	"SR_MAIN",
+	"SR_PANDORA",
+	"SR_LEVELSELECT",
+	"SR_UNLOCKCHECKLIST",
+	"SR_EMBLEMHINT",
+
+	// Addons (Part of MISC, but let's make it our own)
+	"AD_MAIN",
+
+	// MISC
+	// "MESSAGE",
+	// "SPAUSE",
+
+	// "MPAUSE",
+	// "SCRAMBLETEAM",
+	// "CHANGETEAM",
+	// "CHANGELEVEL",
+
+	// "MAPAUSE",
+	// "HELP",
+
+	"SPECIAL"
+};
+
 struct {
 	const char *n;
 	// has to be able to hold both fixed_t and angle_t, so drastic measure!!
@@ -6866,12 +8406,15 @@ struct {
 
 	// doomdef.h constants
 	{"TICRATE",TICRATE},
+	{"MUSICRATE",MUSICRATE},
 	{"RING_DIST",RING_DIST},
 	{"PUSHACCEL",PUSHACCEL},
 	{"MODID",MODID}, // I don't know, I just thought it would be cool for a wad to potentially know what mod it was loaded into.
 	{"CODEBASE",CODEBASE}, // or what release of SRB2 this is.
 	{"VERSION",VERSION}, // Grab the game's version!
 	{"SUBVERSION",SUBVERSION}, // more precise version number
+	{"NEWTICRATE",NEWTICRATE}, // TICRATE*NEWTICRATERATIO
+	{"NEWTICRATERATIO",NEWTICRATERATIO},
 
 	// Special linedef executor tag numbers!
 	{"LE_PINCHPHASE",LE_PINCHPHASE}, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
@@ -6879,6 +8422,14 @@ struct {
 	{"LE_BOSSDEAD",LE_BOSSDEAD}, // A boss in the map died (Chaos mode boss tally)
 	{"LE_BOSS4DROP",LE_BOSS4DROP}, // CEZ boss dropped its cage
 	{"LE_BRAKVILEATACK",LE_BRAKVILEATACK}, // Brak's doing his LOS attack, oh noes
+	{"LE_TURRET",LE_TURRET}, // THZ turret
+	{"LE_BRAKPLATFORM",LE_BRAKPLATFORM}, // v2.0 Black Eggman destroys platform
+	{"LE_CAPSULE2",LE_CAPSULE2}, // Egg Capsule
+	{"LE_CAPSULE1",LE_CAPSULE1}, // Egg Capsule
+	{"LE_CAPSULE0",LE_CAPSULE0}, // Egg Capsule
+	{"LE_KOOPA",LE_KOOPA}, // Distant cousin to Gay Bowser
+	{"LE_AXE",LE_AXE}, // MKB Axe object
+	{"LE_PARAMWIDTH",LE_PARAMWIDTH},  // If an object that calls LinedefExecute has a nonzero parameter value, this times the parameter will be subtracted. (Mostly for the purpose of coexisting bosses...)
 
 	/// \todo Get all this stuff into its own sections, maybe. Maybe.
 
@@ -6950,6 +8501,7 @@ struct {
 	{"LF_NORELOAD",LF_NORELOAD},
 	{"LF_NOZONE",LF_NOZONE},
 	{"LF_SAVEGAME",LF_SAVEGAME},
+	{"LF_MIXNIGHTSCOUNTDOWN",LF_MIXNIGHTSCOUNTDOWN},
 	// And map flags
 	{"LF2_HIDEINMENU",LF2_HIDEINMENU},
 	{"LF2_HIDEINSTATS",LF2_HIDEINSTATS},
@@ -7000,6 +8552,7 @@ struct {
 	{"SH_PITY",SH_PITY},
 	{"SH_WHIRLWIND",SH_WHIRLWIND},
 	{"SH_ARMAGEDDON",SH_ARMAGEDDON},
+	{"SH_PINK",SH_PINK},
 	// normal shields that use flags
 	{"SH_ATTRACT",SH_ATTRACT},
 	{"SH_ELEMENTAL",SH_ELEMENTAL},
@@ -7025,6 +8578,7 @@ struct {
 	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
 	{"CR_ROPEHANG",CR_ROPEHANG},
 	{"CR_MACESPIN",CR_MACESPIN},
+	{"CR_MINECART",CR_MINECART},
 
 	// Ring weapons (ringweapons_t)
 	// Useful for A_GiveWeapon
@@ -7123,6 +8677,7 @@ struct {
 	{"DMG_CRUSHED",DMG_CRUSHED},
 	{"DMG_SPECTATOR",DMG_SPECTATOR},
 	//// Masks
+	{"DMG_CANHURTSELF",DMG_CANHURTSELF},
 	{"DMG_DEATHMASK",DMG_DEATHMASK},
 
 	// Gametypes, for use with global var "gametype"
@@ -7135,6 +8690,23 @@ struct {
 	{"GT_HIDEANDSEEK",GT_HIDEANDSEEK},
 	{"GT_CTF",GT_CTF},
 
+	// Jingles (jingletype_t)
+	{"JT_NONE",JT_NONE},
+	{"JT_OTHER",JT_OTHER},
+	{"JT_MASTER",JT_MASTER},
+	{"JT_1UP",JT_1UP},
+	{"JT_SHOES",JT_SHOES},
+	{"JT_INV",JT_INV},
+	{"JT_MINV",JT_MINV},
+	{"JT_DROWN",JT_DROWN},
+	{"JT_SUPER",JT_SUPER},
+	{"JT_GOVER",JT_GOVER},
+	{"JT_NIGHTSTIMEOUT",JT_NIGHTSTIMEOUT},
+	{"JT_SSTIMEOUT",JT_SSTIMEOUT},
+	// {"JT_LCLEAR",JT_LCLEAR},
+	// {"JT_RACENT",JT_RACENT},
+	// {"JT_CONTSC",JT_CONTSC},
+
 	// Player state (playerstate_t)
 	{"PST_LIVE",PST_LIVE}, // Playing or camping.
 	{"PST_DEAD",PST_DEAD}, // Dead on the ground, view follows killer.
@@ -7165,6 +8737,9 @@ struct {
 	{"WEP_RAIL",WEP_RAIL},
 	{"NUM_WEAPONS",NUM_WEAPONS},
 
+	// Value for infinite lives
+	{"INFLIVES", INFLIVES},
+
 	// Got Flags, for player->gotflag!
 	// Used to be MF_ for some stupid reason, now they're GF_ to stop them looking like mobjflags
 	{"GF_REDFLAG",GF_REDFLAG},
@@ -7236,6 +8811,11 @@ struct {
 	// Node flags
 	{"NF_SUBSECTOR",NF_SUBSECTOR}, // Indicate a leaf.
 #endif
+#ifdef ESLOPE
+	// Slope flags
+	{"SL_NOPHYSICS",SL_NOPHYSICS},
+	{"SL_DYNAMIC",SL_DYNAMIC},
+#endif
 
 	// Angles
 	{"ANG1",ANG1},
@@ -7318,13 +8898,23 @@ struct {
 	{"V_6WIDTHSPACE",V_6WIDTHSPACE},
 	{"V_OLDSPACING",V_OLDSPACING},
 	{"V_MONOSPACE",V_MONOSPACE},
-	{"V_PURPLEMAP",V_PURPLEMAP},
+
+	{"V_MAGENTAMAP",V_MAGENTAMAP},
 	{"V_YELLOWMAP",V_YELLOWMAP},
 	{"V_GREENMAP",V_GREENMAP},
 	{"V_BLUEMAP",V_BLUEMAP},
 	{"V_REDMAP",V_REDMAP},
 	{"V_GRAYMAP",V_GRAYMAP},
 	{"V_ORANGEMAP",V_ORANGEMAP},
+	{"V_SKYMAP",V_SKYMAP},
+	{"V_PURPLEMAP",V_PURPLEMAP},
+	{"V_AQUAMAP",V_AQUAMAP},
+	{"V_PERIDOTMAP",V_PERIDOTMAP},
+	{"V_AZUREMAP",V_AZUREMAP},
+	{"V_BROWNMAP",V_BROWNMAP},
+	{"V_ROSYMAP",V_ROSYMAP},
+	{"V_INVERTMAP",V_INVERTMAP},
+
 	{"V_TRANSLUCENT",V_TRANSLUCENT},
 	{"V_10TRANS",V_10TRANS},
 	{"V_20TRANS",V_20TRANS},
@@ -7350,7 +8940,7 @@ struct {
 	{"V_WRAPX",V_WRAPX},
 	{"V_WRAPY",V_WRAPY},
 	{"V_NOSCALESTART",V_NOSCALESTART},
-	{"V_SPLITSCREEN",V_SPLITSCREEN},
+	{"V_PERPLAYER",V_PERPLAYER},
 
 	{"V_PARAMMASK",V_PARAMMASK},
 	{"V_SCALEPATCHMASK",V_SCALEPATCHMASK},
@@ -7360,6 +8950,22 @@ struct {
 
 	{"V_CHARCOLORSHIFT",V_CHARCOLORSHIFT},
 	{"V_ALPHASHIFT",V_ALPHASHIFT},
+
+	//Kick Reasons
+	{"KR_KICK",KR_KICK},
+	{"KR_PINGLIMIT",KR_PINGLIMIT},
+	{"KR_SYNCH",KR_SYNCH},
+	{"KR_TIMEOUT",KR_TIMEOUT},
+	{"KR_BAN",KR_BAN},
+	{"KR_LEAVE",KR_LEAVE},
+
+	// translation colormaps
+	{"TC_DEFAULT",TC_DEFAULT},
+	{"TC_BOSS",TC_BOSS},
+	{"TC_METALSONIC",TC_METALSONIC},
+	{"TC_ALLWHITE",TC_ALLWHITE},
+	{"TC_RAINBOW",TC_RAINBOW},
+	{"TC_BLINK",TC_BLINK},
 #endif
 
 	{NULL,0}
@@ -7500,7 +9106,21 @@ static hudnum_t get_huditem(const char *word)
 		if (fastcmp(word, HUDITEMS_LIST[i]))
 			return i;
 	deh_warning("Couldn't find huditem named 'HUD_%s'",word);
-	return HUD_LIVESNAME;
+	return HUD_LIVES;
+}
+
+static menutype_t get_menutype(const char *word)
+{ // Returns the value of MN_ enumerations
+	menutype_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("MN_",word,3))
+		word += 3; // take off the MN_
+	for (i = 0; i < NUMMENUTYPES; i++)
+		if (fastcmp(word, MENUTYPES_LIST[i]))
+			return i;
+	deh_warning("Couldn't find menutype named 'MN_%s'",word);
+	return MN_NONE;
 }
 
 #ifndef HAVE_BLUA
@@ -7699,6 +9319,11 @@ static fixed_t find_const(const char **rword)
 		free(word);
 		return r;
 	}
+	else if (fastncmp("MN_",word,4)) {
+		r = get_menutype(word);
+		free(word);
+		return r;
+	}
 	else if (fastncmp("HUD_",word,4)) {
 		r = get_huditem(word);
 		free(word);
@@ -7748,7 +9373,7 @@ fixed_t get_number(const char *word)
 #endif
 }
 
-void FUNCMATH DEH_Check(void)
+void DEH_Check(void)
 {
 #if defined(_DEBUG) || defined(PARANOIA)
 	const size_t dehstates = sizeof(STATE_LIST)/sizeof(const char*);
@@ -7888,6 +9513,7 @@ static inline int lib_freeslot(lua_State *L)
 				{
 					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
 					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
 					spr2names[free_spr2++][4] = 0;
 				} else
 					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
@@ -8005,11 +9631,6 @@ static inline int lib_getenum(lua_State *L)
 				lua_pushinteger(L, ((lua_Integer)1<<i));
 				return 1;
 			}
-		if (fastcmp(p, "NETONLY"))
-		{
-			lua_pushinteger(L, (lua_Integer)ML_NETONLY);
-			return 1;
-		}
 		if (mathlib) return luaL_error(L, "linedef flag '%s' could not be found.\n", word);
 		return 0;
 	}
@@ -8167,6 +9788,16 @@ static inline int lib_getenum(lua_State *L)
 		if (mathlib) return luaL_error(L, "skincolor '%s' could not be found.\n", word);
 		return 0;
 	}
+	else if (fastncmp("MN_",word,3)) {
+		p = word+3;
+		for (i = 0; i < NUMMENUTYPES; i++)
+			if (fastcmp(p, MENUTYPES_LIST[i])) {
+				lua_pushinteger(L, 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)) {
 		char *caps;
 		// Try to get a Lua action first.
@@ -8224,6 +9855,9 @@ static inline int lib_getenum(lua_State *L)
 	} else if (fastcmp(word,"maptol")) {
 		lua_pushinteger(L, maptol);
 		return 1;
+	} else if (fastcmp(word,"ultimatemode")) {
+		lua_pushboolean(L, ultimatemode != 0);
+		return 1;
 	} else if (fastcmp(word,"mariomode")) {
 		lua_pushboolean(L, mariomode != 0);
 		return 1;
@@ -8290,15 +9924,19 @@ static inline int lib_getenum(lua_State *L)
 	} else if (fastcmp(word,"mapmusflags")) {
 		lua_pushinteger(L, mapmusflags);
 		return 1;
+	} else if (fastcmp(word,"mapmusposition")) {
+		lua_pushinteger(L, mapmusposition);
+		return 1;
 	} else if (fastcmp(word,"server")) {
 		if ((!multiplayer || !netgame) && !playeringame[serverplayer])
 			return 0;
 		LUA_PushUserdata(L, &players[serverplayer], META_PLAYER);
 		return 1;
-	} else if (fastcmp(word,"admin")) {
-		if (!playeringame[adminplayer] || adminplayer == serverplayer)
+	} else if (fastcmp(word,"admin")) { // BACKWARDS COMPATIBILITY HACK: This was replaced with IsPlayerAdmin(), but some 2.1 Lua scripts still use the admin variable. It now points to the first admin player in the array.
+		LUA_Deprecated(L, "admin", "IsPlayerAdmin(player)");
+		if (!playeringame[adminplayers[0]] || IsPlayerAdmin(serverplayer))
 			return 0;
-		LUA_PushUserdata(L, &players[adminplayer], META_PLAYER);
+		LUA_PushUserdata(L, &players[adminplayers[0]], META_PLAYER);
 		return 1;
 	} else if (fastcmp(word,"emeralds")) {
 		lua_pushinteger(L, emeralds);
@@ -8313,7 +9951,6 @@ static inline int lib_getenum(lua_State *L)
 		lua_pushinteger(L, token);
 		return 1;
 	}
-
 	return 0;
 }
 
@@ -8350,17 +9987,17 @@ static int lib_getActionName(lua_State *L)
 	{
 		lua_settop(L, 1); // set top of stack to 1 (removing any extra args, which there shouldn't be)
 		// get the name for this action, if possible.
-		lua_getfield(gL, LUA_REGISTRYINDEX, LREG_ACTIONS);
-		lua_pushnil(gL);
+		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
+		lua_pushnil(L);
 		// Lua stack at this point:
 		//  1   ...       -2              -1
 		// arg  ...   LREG_ACTIONS        nil
-		while (lua_next(gL, -2))
+		while (lua_next(L, -2))
 		{
 			// Lua stack at this point:
 			//  1   ...       -3              -2           -1
 			// arg  ...   LREG_ACTIONS    "A_ACTION"    function
-			if (lua_rawequal(gL, -1, 1)) // is this the same as the arg?
+			if (lua_rawequal(L, -1, 1)) // is this the same as the arg?
 			{
 				// make sure the key (i.e. "A_ACTION") is a string first
 				// (note: we don't use lua_isstring because it also returns true for numbers)
@@ -8369,12 +10006,12 @@ static int lib_getActionName(lua_State *L)
 					lua_pushvalue(L, -2); // push "A_ACTION" string to top of stack
 					return 1;
 				}
-				lua_pop(gL, 2); // pop the name and function
+				lua_pop(L, 2); // pop the name and function
 				break; // probably should have succeeded but we didn't, so end the loop
 			}
-			lua_pop(gL, 1);
+			lua_pop(L, 1);
 		}
-		lua_pop(gL, 1); // pop LREG_ACTIONS
+		lua_pop(L, 1); // pop LREG_ACTIONS
 		return 0; // return nothing (don't error)
 	}
 
diff --git a/src/dehacked.h b/src/dehacked.h
index dfce996a2c5f2fe0ca38221ee52983a8943fa4a1..31f7f220dceb37654af225bf8113928922ac78f7 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,7 +28,7 @@ typedef enum
 } undotype_f;
 
 void DEH_LoadDehackedLump(lumpnum_t lumpnum);
-void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump);
+void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile);
 
 void DEH_Check(void);
 
diff --git a/src/djgppdos/i_cdmus.c b/src/djgppdos/i_cdmus.c
index f707add5ef097681f9d2e211b549f4588289501a..2a629ca1738a1ad7e5d36ae6c83a16fdcd2c22e8 100644
--- a/src/djgppdos/i_cdmus.c
+++ b/src/djgppdos/i_cdmus.c
@@ -50,7 +50,7 @@ static boolean wasPlaying;
 static int     cdVolume=0;          // current cd volume (0-31)
 
 // 0-31 like Music & Sfx, though CD hardware volume is 0-255.
-consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // allow Update for next/loop track
 // some crap cd drivers take up to
diff --git a/src/djgppdos/i_sound.c b/src/djgppdos/i_sound.c
index 88fc807f408cb4f285086fdabd854658b83063ad..847853a89a5af711e3f58e2230bedacf416b8912 100644
--- a/src/djgppdos/i_sound.c
+++ b/src/djgppdos/i_sound.c
@@ -134,21 +134,12 @@ FUNCINLINE static ATTRINLINE int Volset(int vol)
 
 void I_SetSfxVolume(INT32 volume)
 {
-	if (nosound)
+	if (sound_disabled)
 		return;
 
 	set_volume (Volset(volume),-1);
 }
 
-void I_SetMIDIMusicVolume(INT32 volume)
-{
-	if (nomidimusic)
-		return;
-
-	// Now set volume on output device.
-	set_volume (-1, Volset(volume));
-}
-
 //
 // Starting a sound means adding it
 //  to the current list of active sounds
@@ -165,11 +156,13 @@ INT32 I_StartSound ( sfxenum_t     id,
                    INT32         vol,
                    INT32         sep,
                    INT32         pitch,
-                   INT32         priority )
+                   INT32         priority,
+				   INT32         channel)
 {
 	int voice;
+	(void)channel;
 
-	if (nosound)
+	if (sound_disabled)
 	return 0;
 
 	// UNUSED
@@ -190,7 +183,7 @@ void I_StopSound (INT32 handle)
 	//  an setting the channel to zero.
 	int voice=handle & (VIRTUAL_VOICES-1);
 
-	if (nosound)
+	if (sound_disabled)
 		return;
 
 	if (voice_check(voice)==S_sfx[handle>>VOICESSHIFT].data)
@@ -199,7 +192,7 @@ void I_StopSound (INT32 handle)
 
 INT32 I_SoundIsPlaying(INT32 handle)
 {
-	if (nosound)
+	if (sound_disabled)
 		return FALSE;
 
 	if (voice_check(handle & (VIRTUAL_VOICES-1))==S_sfx[handle>>VOICESSHIFT].data)
@@ -229,7 +222,7 @@ void I_UpdateSoundParams( INT32 handle,
 	int voice=handle & (VIRTUAL_VOICES-1);
 	int numsfx=handle>>VOICESSHIFT;
 
-	if (nosound)
+	if (sound_disabled)
 		return;
 
 	if (voice_check(voice)==S_sfx[numsfx].data)
@@ -270,17 +263,17 @@ void I_StartupSound(void)
 	char   err[255];
 #endif
 
-	if (nosound)
+	if (sound_disabled)
 		sfxcard=DIGI_NONE;
 	else
 		sfxcard=DIGI_AUTODETECT;
 
-	if (nomidimusic)
+	if (midi_disabled)
 		midicard=MIDI_NONE;
 	else
 		midicard=MIDI_AUTODETECT; //DetectMusicCard();
 
-	nodigimusic=true; //Alam: No OGG/MP3/IT/MOD support
+	digital_disabled=true; //Alam: No OGG/MP3/IT/MOD support
 
 	// Secure and configure sound device first.
 	CONS_Printf("I_StartupSound: ");
@@ -293,8 +286,8 @@ void I_StartupSound(void)
 	{
 		sprintf (err,"Sound init error : %s\n",allegro_error);
 		CONS_Error (err);
-		nosound=true;
-		nomidimusic=true;
+		sound_disabled=true;
+		midi_disabled=true;
 	}
 	else
 	{
@@ -321,7 +314,11 @@ static MIDI* currsong;   //im assuming only 1 song will be played at once
 static int      islooping=0;
 static int      musicdies=-1;
 UINT8           music_started=0;
+boolean         songpaused=false;
 
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
 
 /* load_midi_mem:
  *  Loads a standard MIDI file from memory, returning a pointer to
@@ -389,53 +386,131 @@ static MIDI *load_midi_mem(char *mempointer,int *e)
 	return midi;
 }
 
-void I_InitMIDIMusic(void)
+void I_InitMusic(void)
 {
-	if (nomidimusic)
+	if (midi_disabled)
 		return;
 
 	I_AddExitFunc(I_ShutdownMusic);
 	music_started = true;
+	songpaused = false;
 }
 
-void I_ShutdownMIDIMusic(void)
+void I_ShutdownMusic(void)
 {
 	if ( !music_started )
 		return;
 
-	I_StopSong(1);
+	I_StopSong();
 
 	music_started=false;
 }
 
-void I_InitDigMusic(void)
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
+
+musictype_t I_SongType(void)
 {
-//	CONS_Printf("Digital music not yet supported under DOS.\n");
+	if (currsong)
+		return MU_MID;
+	else
+		return MU_NONE;
 }
 
-void I_ShutdownDigMusic(void)
+boolean I_SongPlaying()
 {
-//	CONS_Printf("Digital music not yet supported under DOS.\n");
+	return (boolean)currsong;
 }
 
-void I_InitMusic(void)
+boolean I_SongPaused()
 {
-	if (!nodigimusic)
-		I_InitDigMusic();
-	if (!nomidimusic)
-		I_InitMIDIMusic();
+	return songpaused;
 }
 
-void I_ShutdownMusic(void)
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
+
+boolean I_SetSongSpeed(float speed)
 {
-	I_ShutdownMIDIMusic();
-	I_ShutdownDigMusic();
+	(void)speed;
+	return false;
+}
+
+/// ------------------------
+// MUSIC SEEKING
+/// ------------------------
+
+UINT32 I_GetSongLength(void)
+{
+	return 0;
 }
 
-boolean I_PlaySong(INT32 handle, INT32 looping)
+boolean I_SetSongLoopPoint(UINT32 looppoint)
+{
+        (void)looppoint;
+        return false;
+}
+
+UINT32 I_GetSongLoopPoint(void)
+{
+	return 0;
+}
+
+boolean I_SetSongPosition(UINT32 position)
+{
+    (void)position;
+    return false;
+}
+
+UINT32 I_GetSongPosition(void)
+{
+    return 0;
+}
+
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
+
+boolean I_LoadSong(char *data, size_t len)
+{
+	int e = len; //Alam: For error
+	if (midi_disabled)
+		return 0;
+
+	if (memcmp(data,"MThd",4)==0) // support mid file in WAD !!!
+	{
+		currsong=load_midi_mem(data,&e);
+	}
+	else
+	{
+		CONS_Printf("Music Lump is not a MIDI lump\n");
+		return 0;
+	}
+
+	if (currsong==NULL)
+	{
+		CONS_Printf("Not a valid mid file : %d\n",e);
+		return 0;
+	}
+
+	return 1;
+}
+
+void I_UnloadSong(void)
 {
 	handle = 0;
-	if (nomidimusic)
+	if (midi_disabled)
+		return;
+
+	//destroy_midi(currsong);
+}
+
+boolean I_PlaySong(boolean looping)
+{
+	handle = 0;
+	if (midi_disabled)
 		return false;
 
 	islooping = looping;
@@ -445,40 +520,57 @@ boolean I_PlaySong(INT32 handle, INT32 looping)
 	return false;
 }
 
-void I_PauseSong (INT32 handle)
+void I_StopSong(void)
 {
 	handle = 0;
-	if (nomidimusic)
+	if (midi_disabled)
 		return;
 
+	islooping = 0;
+	musicdies = 0;
+	stop_midi();
+	songpaused = false;
+}
+
+void I_PauseSong (INT32 handle)
+{
+	handle = 0;
+	if (midi_disabled)
+		return;
 	midi_pause();
+	songpaused = true;
 }
 
 void I_ResumeSong (INT32 handle)
 {
 	handle = 0;
-	if (nomidimusic)
+	if (midi_disabled)
 		return;
-
 	midi_resume();
+	songpaused = false;
 }
 
-void I_StopSong(INT32 handle)
+
+void I_SetMusicVolume(INT32 volume)
 {
-	handle = 0;
-	if (nomidimusic)
+	if (midi_disabled)
 		return;
 
-	islooping = 0;
-	musicdies = 0;
-	stop_midi();
+	// Now set volume on output device.
+	set_volume (-1, Volset(volume));
+}
+
+boolean I_SetSongTrack(INT32 track)
+{
+	(void)track;
+	return false;
 }
 
 // Is the song playing?
 #if 0
 int I_QrySongPlaying(int handle)
 {
-	if (nomidimusic)
+	if (midi_disabled)
 		return 0;
 
 	//return islooping || musicdies > gametic;
@@ -486,66 +578,43 @@ int I_QrySongPlaying(int handle)
 }
 #endif
 
-void I_UnRegisterSong(INT32 handle)
-{
-	handle = 0;
-	if (nomidimusic)
-		return;
+/// ------------------------
+// MUSIC FADING
+/// ------------------------
 
-	//destroy_midi(currsong);
+void I_SetInternalMusicVolume(UINT8 volume)
+{
+	(void)volume;
 }
 
-INT32 I_RegisterSong(void *data, size_t len)
+void I_StopFadingSong(void)
 {
-	int e = len; //Alam: For error
-	if (nomidimusic)
-		return 0;
-
-	if (memcmp(data,"MThd",4)==0) // support mid file in WAD !!!
-	{
-		currsong=load_midi_mem(data,&e);
-	}
-	else
-	{
-		CONS_Printf("Music Lump is not a MIDI lump\n");
-		return 0;
-	}
-
-	if (currsong==NULL)
-	{
-		CONS_Printf("Not a valid mid file : %d\n",e);
-		return 0;
-	}
-
-	return 1;
 }
 
-/// \todo Add OGG/MP3 support for dos
-boolean I_StartDigSong(const char *musicname, INT32 looping)
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void));
 {
-	musicname = NULL;
-	looping = 0;
-	//CONS_Printf("I_StartDigSong: Not yet supported under DOS.\n");
+	(void)target_volume;
+	(void)source_volume;
+	(void)ms;
 	return false;
 }
 
-void I_StopDigSong(void)
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
 {
-//	CONS_Printf("I_StopDigSong: Not yet supported under DOS.\n");
+	(void)target_volume;
+	(void)ms;
+	return false;
 }
 
-void I_SetDigMusicVolume(INT32 volume)
+boolean I_FadeOutStopSong(UINT32 ms)
 {
-	volume = 0;
-	if (nodigimusic)
-		return;
-
-	// Now set volume on output device.
-//	CONS_Printf("Digital music not yet supported under DOS.\n");
+	(void)ms;
+	return false;
 }
 
-boolean I_SetSongSpeed(float speed)
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
 {
-	(void)speed;
-	return false;
+        (void)ms;
+        (void)looping;
+        return false;
 }
diff --git a/src/djgppdos/rdb-s.h b/src/djgppdos/rdb-s.h
index 6202dacfad9b5151af1a0eab5f98b9ac25f45666..7a6d8be3c65595609ebe14f9e9e58fa60fcad1d3 100644
--- a/src/djgppdos/rdb-s.h
+++ b/src/djgppdos/rdb-s.h
@@ -1,7 +1,7 @@
 // Emacs style mode select   -*- C++ -*-
 //-----------------------------------------------------------------------------
 //
-// Copyright (C) 2005 by Sonic Team Jr.
+// Copyright (C) 2005-2018 by Sonic Team Jr.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/doomdata.h b/src/doomdata.h
index c0586fd65620f44d8b0e0f49e56d629d210772ae..38727b85c7ad196994658a75f1fb9d0a0938b223 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -46,6 +46,9 @@ enum
 	ML_BLOCKMAP,  // LUT, motion clipping, walls/grid element
 };
 
+// Extra flag for objects.
+#define MTF_EXTRA 1
+
 // Reverse gravity flag for objects.
 #define MTF_OBJECTFLIP 2
 
@@ -127,11 +130,9 @@ typedef struct
 #define ML_EFFECT4             512
 #define ML_EFFECT5            1024
 
-// New ones to disable lines for characters
-#define ML_NOSONIC           2048
-#define ML_NOTAILS           4096
-#define ML_NOKNUX            8192
-#define ML_NETONLY          14336 // all of the above
+#define ML_NETONLY           2048 // Apply effect only in netgames
+#define ML_NONET             4096 // Apply  effect only in single player games
+#define ML_EFFECT6           8192
 
 // Bounce off walls!
 #define ML_BOUNCY           16384
diff --git a/src/doomdef.h b/src/doomdef.h
index 04628c14caa245eb282480bce0222b65c65c9ed4..c948d780562961f0593cb3f4f4e672cb718b2a1f 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -148,7 +148,10 @@ extern FILE *logstream;
 
 // Does this version require an added patch file?
 // Comment or uncomment this as necessary.
-#define USE_PATCH_DTA
+//#define USE_PATCH_DTA
+
+// Use .kart extension addons
+//#define USE_KART
 
 // Modification options
 // If you want to take advantage of the Master Server's ability to force clients to update
@@ -203,7 +206,21 @@ extern FILE *logstream;
 // 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.1.0 is not version "1".
-#define MODVERSION 25
+#define MODVERSION 28
+
+// To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
+// Increment MINOREXECVERSION whenever a config change is needed that does not correspond
+// to an increment in MODVERSION. This might never happen in practice.
+// If MODVERSION increases, set MINOREXECVERSION to 0.
+#define MAJOREXECVERSION MODVERSION
+#define MINOREXECVERSION 0
+// (It would have been nice to use VERSION and SUBVERSION but those are zero'd out for DEVELOP builds)
+
+// Macros
+#define GETMAJOREXECVERSION(v) (v & 0xFFFF)
+#define GETMINOREXECVERSION(v) (v >> 16)
+#define GETEXECVERSION(major,minor) (major + (minor << 16))
+#define EXECVERSION GETEXECVERSION(MAJOREXECVERSION, MINOREXECVERSION)
 
 // =========================================================================
 
@@ -356,17 +373,27 @@ typedef enum
 #define NEWTICRATERATIO 1 // try 4 for 140 fps :)
 #define NEWTICRATE (TICRATE*NEWTICRATERATIO)
 
+#define MUSICRATE 1000 // sound timing is calculated by milliseconds
+
 #define RING_DIST 512*FRACUNIT // how close you need to be to a ring to attract it
 
 #define PUSHACCEL (2*FRACUNIT) // Acceleration for MF2_SLIDEPUSH items.
 
 // Special linedef executor tag numbers!
 enum {
-	LE_PINCHPHASE      = -2, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
-	LE_ALLBOSSESDEAD   = -3, // All bosses in the map are dead (Egg capsule raise)
-	LE_BOSSDEAD        = -4, // A boss in the map died (Chaos mode boss tally)
-	LE_BOSS4DROP       = -5,  // CEZ boss dropped its cage
-	LE_BRAKVILEATACK   = -6  // Brak's doing his LOS attack, oh noes
+	LE_PINCHPHASE      =    -2, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
+	LE_ALLBOSSESDEAD   =    -3, // All bosses in the map are dead (Egg capsule raise)
+	LE_BOSSDEAD        =    -4, // A boss in the map died (Chaos mode boss tally)
+	LE_BOSS4DROP       =    -5, // CEZ boss dropped its cage (also subtract the number of hitpoints it's lost)
+	LE_BRAKVILEATACK   =    -6, // Brak's doing his LOS attack, oh noes
+	LE_TURRET          = 32000, // THZ turret
+	LE_BRAKPLATFORM    =  4200, // v2.0 Black Eggman destroys platform
+	LE_CAPSULE2        =   682, // Egg Capsule
+	LE_CAPSULE1        =   681, // Egg Capsule
+	LE_CAPSULE0        =   680, // Egg Capsule
+	LE_KOOPA           =   650, // Distant cousin to Gay Bowser
+	LE_AXE             =   649, // MKB Axe object
+	LE_PARAMWIDTH      =  -100  // If an object that calls LinedefExecute has a nonzero parameter value, this times the parameter will be subtracted. (Mostly for the purpose of coexisting bosses...)
 };
 
 // Name of local directory for config files and savegames
@@ -446,7 +473,7 @@ extern INT32 cv_debug;
 
 #define DBG_BASIC       0x0001
 #define DBG_DETAILED    0x0002
-#define DBG_RANDOMIZER  0x0004
+#define DBG_PLAYER      0x0004
 #define DBG_RENDER      0x0008
 #define DBG_NIGHTSBASIC 0x0010
 #define DBG_NIGHTS      0x0020
@@ -456,6 +483,7 @@ extern INT32 cv_debug;
 #define DBG_MEMORY      0x0200
 #define DBG_SETUP       0x0400
 #define DBG_LUA         0x0800
+#define DBG_RANDOMIZER  0x1000
 
 // =======================
 // Misc stuff for later...
@@ -463,6 +491,7 @@ extern INT32 cv_debug;
 
 // Modifier key variables, accessible anywhere
 extern UINT8 shiftdown, ctrldown, altdown;
+extern boolean capslock;
 
 // if we ever make our alloc stuff...
 #define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL)
@@ -477,6 +506,15 @@ INT32 I_GetKey(void);
 #define max(x, y) (((x) > (y)) ? (x) : (y))
 #endif
 
+// Floating point comparison epsilons from float.h
+#ifndef FLT_EPSILON
+#define FLT_EPSILON 1.1920928955078125e-7f
+#endif
+
+#ifndef DBL_EPSILON
+#define DBL_EPSILON 2.2204460492503131e-16
+#endif
+
 // An assert-type mechanism.
 #ifdef PARANOIA
 #define I_Assert(e) ((e) ? (void)0 : I_Error("assert failed: %s, file %s, line %d", #e, __FILE__, __LINE__))
@@ -560,9 +598,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls.
 //#define PAPER_COLLISIONCORRECTION
 
-/// Hudname padding.
-#define SKINNAMEPADDING
-
 /// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up
 /// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down)
 /// on the bright side it fixes some weird issues with translucent walls
@@ -572,6 +607,13 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 
 /// Handle touching sector specials in P_PlayerAfterThink instead of P_PlayerThink.
 /// \note   Required for proper collision with moving sloped surfaces that have sector specials on them.
-//#define SECTORSPECIALSAFTERTHINK
+#define SECTORSPECIALSAFTERTHINK
+
+/// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up
+/// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down)
+/// on the bright side it fixes some weird issues with translucent walls
+/// \note	SRB2CB port.
+///      	SRB2CB itself ported this from PrBoom+
+#define NEWCLIP
 
 #endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index de260ef065678933e4b6975ff6003fc8489e3963..b6c376d1ce46ebea5bf83b86010110f98aaba201 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -33,8 +33,10 @@
 extern INT16 gamemap;
 extern char mapmusname[7];
 extern UINT16 mapmusflags;
+extern UINT32 mapmusposition;
 #define MUSIC_TRACKMASK   0x0FFF // ----************
 #define MUSIC_RELOADRESET 0x8000 // *---------------
+#define MUSIC_FORCERESET  0x4000 // -*--------------
 // Use other bits if necessary.
 
 extern INT16 maptol;
@@ -90,10 +92,7 @@ extern boolean fromlevelselect;
 // Internal parameters for sound rendering.
 // ========================================
 
-extern boolean nomidimusic; // defined in d_main.c
-extern boolean nosound;
-extern boolean nodigimusic;
-extern boolean music_disabled;
+extern boolean midi_disabled;
 extern boolean sound_disabled;
 extern boolean digital_disabled;
 
@@ -126,21 +125,28 @@ extern INT32 secondarydisplayplayer; // for splitscreen
 
 // Maps of special importance
 extern INT16 spstage_start;
-extern INT16 sstage_start;
-extern INT16 sstage_end;
+extern INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
 
 extern INT16 titlemap;
 extern boolean hidetitlepics;
 extern INT16 bootmap; //bootmap for loading a map on startup
 
+extern INT16 tutorialmap; // map to load for tutorial
+extern boolean tutorialmode; // are we in a tutorial right now?
+extern INT32 tutorialgcs; // which control scheme is loaded?
+extern INT32 tutorialusemouse; // store cv_usemouse user value
+extern INT32 tutorialfreelook; // store cv_alwaysfreelook user value
+extern INT32 tutorialmousemove; // store cv_mousemove user value
+extern INT32 tutorialanalog; // store cv_analog user value
+
 extern boolean looptitle;
-extern boolean useNightsSS;
 
 // CTF colors.
 extern UINT8 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering;
 
 extern tic_t countdowntimer;
 extern boolean countdowntimeup;
+extern boolean exitfadestarted;
 
 typedef struct
 {
@@ -157,6 +163,7 @@ typedef struct
 
 	char   musswitch[7];
 	UINT16 musswitchflags;
+	UINT32 musswitchposition;
 
 	UINT8 fadecolor; // Color number for fade, 0 means don't do the first fade
 	UINT8 fadeinid;  // ID of the first fade, to a color -- ignored if fadecolor is 0
@@ -171,11 +178,65 @@ typedef struct
 
 extern cutscene_t *cutscenes[128];
 
+// Reserve prompt space for tutorials
+#define TUTORIAL_PROMPT 201 // one-based
+#define TUTORIAL_AREAS 6
+#define TUTORIAL_AREA_PROMPTS 5
+#define MAX_PROMPTS (TUTORIAL_PROMPT+TUTORIAL_AREAS*TUTORIAL_AREA_PROMPTS*3) // 3 control modes
+#define MAX_PAGES 128
+
+#define PROMPT_PIC_PERSIST 0
+#define PROMPT_PIC_LOOP 1
+#define PROMPT_PIC_DESTROY 2
+#define MAX_PROMPT_PICS 8
+typedef struct
+{
+	UINT8 numpics;
+	UINT8 picmode; // sequence mode after displaying last pic, 0 = persist, 1 = loop, 2 = destroy
+	UINT8 pictoloop; // if picmode == loop, which pic to loop to?
+	UINT8 pictostart; // initial pic number to show
+	char picname[MAX_PROMPT_PICS][8];
+	UINT8 pichires[MAX_PROMPT_PICS];
+	UINT16 xcoord[MAX_PROMPT_PICS]; // gfx
+	UINT16 ycoord[MAX_PROMPT_PICS]; // gfx
+	UINT16 picduration[MAX_PROMPT_PICS];
+
+	char   musswitch[7];
+	UINT16 musswitchflags;
+	UINT8 musicloop;
+
+	char tag[33]; // page tag
+	char name[34]; // narrator name, extra char for color
+	char iconname[8]; // narrator icon lump
+	boolean rightside; // narrator side, false = left, true = right
+	boolean iconflip; // narrator flip icon horizontally
+	UINT8 hidehud; // hide hud, 0 = show all, 1 = hide depending on prompt position (top/bottom), 2 = hide all
+	UINT8 lines; // # of lines to show. If name is specified, name takes one of the lines. If 0, defaults to 4.
+	INT32 backcolor; // see CON_SetupBackColormap: 0-11, INT32_MAX for user-defined (CONS_BACKCOLOR)
+	UINT8 align; // text alignment, 0 = left, 1 = right, 2 = center
+	UINT8 verticalalign; // vertical text alignment, 0 = top, 1 = bottom, 2 = middle
+	UINT8 textspeed; // text speed, delay in tics between characters.
+	sfxenum_t textsfx; // sfx_ id for printing text
+	UINT8 nextprompt; // next prompt to jump to, one-based. 0 = current prompt
+	UINT8 nextpage; // next page to jump to, one-based. 0 = next page within prompt->numpages
+	char nexttag[33]; // next tag to jump to. If set, this overrides nextprompt and nextpage.
+	INT32 timetonext; // time in tics to jump to next page automatically. 0 = don't jump automatically
+	char *text;
+} textpage_t;
+
+typedef struct
+{
+	textpage_t page[MAX_PAGES];
+	INT32 numpages; // Number of pages in this prompt
+} textprompt_t;
+
+extern textprompt_t *textprompts[MAX_PROMPTS];
+
 // For the Custom Exit linedef.
 extern INT16 nextmapoverride;
-extern boolean skipstats;
+extern UINT8 skipstats;
 
-extern UINT32 totalrings; //  Total # of rings in a level
+extern UINT32 ssspheres; //  Total # of spheres in a level
 
 // Fun extra stuff
 extern INT16 lastmap; // Last level you were at (returning from special stages).
@@ -227,6 +288,7 @@ typedef struct
 	INT16 nextlevel;       ///< Map number of next level, or 1100-1102 to end.
 	char musname[7];       ///< Music track to play. "" for no music.
 	UINT16 mustrack;       ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
+	UINT32 muspos;    ///< Music position to jump to.
 	char forcecharacter[17];  ///< (SKINNAMESIZE+1) Skin to switch to or "" to disable.
 	UINT8 weather;         ///< 0 = sunny day, 1 = storm, 2 = snow, 3 = rain, 4 = blank, 5 = thunder w/o rain, 6 = rain w/o lightning, 7 = heat wave.
 	INT16 skynum;          ///< Sky number to use.
@@ -246,11 +308,13 @@ typedef struct
 	SINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no.
 	UINT8 levelselect;    ///< Is this map available in the level select? If so, which map list is it available in?
 	SINT8 bonustype;      ///< What type of bonus does this level have? (-1 for null.)
+	SINT8 maxbonuslives;  ///< How many bonus lives to award at Intermission? (-1 for unlimited.)
 
-	UINT8 levelflags;     ///< LF_flags:  merged eight booleans into one UINT8 for space, see below
+	UINT8 levelflags;     ///< LF_flags:  merged booleans into one UINT8 for space, see below
 	UINT8 menuflags;      ///< LF2_flags: options that affect record attack / nights mode menus
 
 	char selectheading[22]; ///< Level select heading. Allows for controllable grouping.
+	UINT16 startrings;      ///< Number of rings players start with.
 
 	// Freed animals stuff.
 	UINT8 numFlickies;     ///< Internal. For freed flicky support.
@@ -260,6 +324,15 @@ typedef struct
 	UINT8 numGradedMares;   ///< Internal. For grade support.
 	nightsgrades_t *grades; ///< NiGHTS grades. Allocated dynamically for space reasons. Be careful.
 
+	// Music stuff.
+	UINT32 musinterfadeout;  ///< Fade out level music on intermission screen in milliseconds
+	char musintername[7];    ///< Intermission screen music.
+	
+	char muspostbossname[7];    ///< Post-bossdeath music.
+	UINT16 muspostbosstrack;    ///< Post-bossdeath track.
+	UINT32 muspostbosspos;      ///< Post-bossdeath position
+	UINT32 muspostbossfadein;   ///< Post-bossdeath fade-in milliseconds.
+
 	// Lua stuff.
 	// (This is not ifdeffed so the map header structure can stay identical, just in case.)
 	UINT8 numCustomOptions;     ///< Internal. For Lua custom value support.
@@ -273,6 +346,7 @@ typedef struct
 #define LF_NORELOAD       8 ///< Don't reload level on death
 #define LF_NOZONE        16 ///< Don't include "ZONE" on level title
 #define LF_SAVEGAME      32 ///< Save the game upon loading this level
+#define LF_MIXNIGHTSCOUNTDOWN 64 ///< Play sfx_timeup instead of music change for NiGHTS countdown
 
 #define LF2_HIDEINMENU     1 ///< Hide in the multiplayer menu
 #define LF2_HIDEINSTATS    2 ///< Hide in the statistics screen
@@ -415,6 +489,7 @@ extern UINT16 tailsflytics;
 extern UINT16 underwatertics;
 extern UINT16 spacetimetics;
 extern UINT16 extralifetics;
+extern UINT16 nightslinktics;
 
 extern UINT8 introtoplay;
 extern UINT8 creditscutscene;
@@ -487,6 +562,8 @@ extern boolean precache;
 // wipegamestate can be set to -1
 //  to force a wipe on the next draw
 extern gamestate_t wipegamestate;
+extern INT16 wipetypepre;
+extern INT16 wipetypepost;
 
 // debug flag to cancel adaptiveness
 extern boolean singletics;
@@ -498,11 +575,13 @@ extern boolean singletics;
 #include "d_clisrv.h"
 
 extern consvar_t cv_timetic; // display high resolution timer
+extern consvar_t cv_powerupdisplay; // display powerups
 extern consvar_t cv_showinputjoy; // display joystick in time attack
 extern consvar_t cv_forceskin; // force clients to use the server's skin
 extern consvar_t cv_downloading; // allow clients to downloading WADs.
 extern ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
-extern INT32 adminplayer, serverplayer;
+extern INT32 serverplayer;
+extern INT32 adminplayers[MAXPLAYERS];
 
 /// \note put these in d_clisrv outright?
 
diff --git a/src/doomtype.h b/src/doomtype.h
index b0623de9ac410dae011b3ca97ad72f8eb60a8c3a..7acdde966835b97f6977214e8925729bdbe14108 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -25,6 +25,12 @@
 
 /* 7.18.1.1  Exact-width integer types */
 #ifdef _MSC_VER
+// libopenmpt.h will include stdint.h later;
+// include it now so that INT8_MAX etc. don't get redefined
+#ifdef HAVE_OPENMPT
+#include <stdint.h>
+#endif
+
 #define UINT8 unsigned __int8
 #define SINT8 signed __int8
 
@@ -40,12 +46,13 @@
 typedef long ssize_t;
 
 /* Older Visual C++ headers don't have the Win64-compatible typedefs... */
-#if ((_MSC_VER <= 1200) && (!defined(DWORD_PTR)))
-#define DWORD_PTR DWORD
-#endif
-
-#if ((_MSC_VER <= 1200) && (!defined(PDWORD_PTR)))
-#define PDWORD_PTR PDWORD
+#if (_MSC_VER <= 1200)
+	#ifndef DWORD_PTR
+		#define DWORD_PTR DWORD
+	#endif
+	#ifndef PDWORD_PTR
+		#define PDWORD_PTR PDWORD
+	#endif
 #endif
 #elif defined (__DJGPP__)
 #define UINT8 unsigned char
@@ -80,11 +87,13 @@ typedef long ssize_t;
 #define NOIPX
 #endif
 
+/* Strings and some misc platform specific stuff */
+
 #ifdef _MSC_VER
 	// Microsoft VisualC++
 #if (_MSC_VER <= 1800) // MSVC 2013 and back
 	#define snprintf                _snprintf
-#if (_MSC_VER <= 1200) // MSVC 2012 and back
+#if (_MSC_VER <= 1200) // MSVC 6.0 and back
 	#define vsnprintf               _vsnprintf
 #endif
 #endif
@@ -148,6 +157,8 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 // not the number of bytes in the buffer.
 #define STRBUFCPY(dst,src) strlcpy(dst, src, sizeof dst)
 
+/* 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__
@@ -155,7 +166,7 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 
 	//faB: clean that up !!
 	#if defined( _MSC_VER)  && (_MSC_VER >= 1800) // MSVC 2013 and forward
-	#include "stdbool.h"
+		#include "stdbool.h"
 	#elif defined (_WIN32)
 		#define false   FALSE           // use windows types
 		#define true    TRUE
@@ -166,6 +177,7 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 #endif // __BYTEBOOL__
 
 /* 7.18.2.1  Limits of exact-width integer types */
+
 #ifndef INT8_MIN
 #define INT8_MIN (-128)
 #endif
@@ -205,89 +217,65 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 #define UINT64_MAX 0xffffffffffffffffULL /* 18446744073709551615ULL */
 #endif
 
-union FColorRGBA
-{
-	UINT32 rgba;
-	struct
-	{
-		UINT8 red;
-		UINT8 green;
-		UINT8 blue;
-		UINT8 alpha;
-	} s;
-} ATTRPACK;
-typedef union FColorRGBA RGBA_t;
+/* Compiler-specific attributes and other macros */
 
-typedef enum
-{
-	postimg_none,
-	postimg_water,
-	postimg_motion,
-	postimg_flip,
-	postimg_heat
-} postimg_t;
+#ifdef __GNUC__ // __attribute__ ((X))
+	#define FUNCNORETURN __attribute__ ((noreturn))
+
+	#if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) && defined (__MINGW32__) // MinGW, >= GCC 4.1
+		#include "inttypes.h"
+		#if 0 //defined  (__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO > 0
+			#define FUNCPRINTF __attribute__ ((format(gnu_printf, 1, 2)))
+			#define FUNCDEBUG  __attribute__ ((format(gnu_printf, 2, 3)))
+			#define FUNCIERROR __attribute__ ((format(gnu_printf, 1, 2),noreturn))
+		#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) // >= GCC 4.4
+			#define FUNCPRINTF __attribute__ ((format(ms_printf, 1, 2)))
+			#define FUNCDEBUG  __attribute__ ((format(ms_printf, 2, 3)))
+			#define FUNCIERROR __attribute__ ((format(ms_printf, 1, 2),noreturn))
+		#else
+			#define FUNCPRINTF __attribute__ ((format(printf, 1, 2)))
+			#define FUNCDEBUG  __attribute__ ((format(printf, 2, 3)))
+			#define FUNCIERROR __attribute__ ((format(printf, 1, 2),noreturn))
+		#endif
+	#else
+		#define FUNCPRINTF __attribute__ ((format(printf, 1, 2)))
+		#define FUNCDEBUG  __attribute__ ((format(printf, 2, 3)))
+		#define FUNCIERROR __attribute__ ((format(printf, 1, 2),noreturn))
+	#endif
 
-typedef UINT32 lumpnum_t; // 16 : 16 unsigned long (wad num: lump num)
-#define LUMPERROR UINT32_MAX
+	#ifndef FUNCIERROR
+		#define FUNCIERROR __attribute__ ((noreturn))
+	#endif
 
-typedef UINT32 tic_t;
-#define INFTICS UINT32_MAX
+	#define FUNCMATH __attribute__((const))
 
-#ifdef _BIG_ENDIAN
-#define UINT2RGBA(a) a
-#else
-#define UINT2RGBA(a) (UINT32)((a&0xff)<<24)|((a&0xff00)<<8)|((a&0xff0000)>>8)|(((UINT32)a&0xff000000)>>24)
-#endif
+	#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) // >= GCC 3.1
+		#define FUNCDEAD __attribute__ ((deprecated))
+		#define FUNCINLINE __attribute__((always_inline))
+		#define FUNCNONNULL __attribute__((nonnull))
+	#endif
 
-#ifdef __GNUC__ // __attribute__ ((X))
-#define FUNCNORETURN __attribute__ ((noreturn))
-#if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) && defined (__MINGW32__)
-#include "inttypes.h"
-#if 0 //defined  (__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO > 0
-#define FUNCPRINTF __attribute__ ((format(gnu_printf, 1, 2)))
-#define FUNCDEBUG  __attribute__ ((format(gnu_printf, 2, 3)))
-#define FUNCIERROR __attribute__ ((format(gnu_printf, 1, 2),noreturn))
-#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)
-#define FUNCPRINTF __attribute__ ((format(ms_printf, 1, 2)))
-#define FUNCDEBUG  __attribute__ ((format(ms_printf, 2, 3)))
-#define FUNCIERROR __attribute__ ((format(ms_printf, 1, 2),noreturn))
-#else
-#define FUNCPRINTF __attribute__ ((format(printf, 1, 2)))
-#define FUNCDEBUG  __attribute__ ((format(printf, 2, 3)))
-#define FUNCIERROR __attribute__ ((format(printf, 1, 2),noreturn))
-#endif
-#else
-#define FUNCPRINTF __attribute__ ((format(printf, 1, 2)))
-#define FUNCDEBUG  __attribute__ ((format(printf, 2, 3)))
-#define FUNCIERROR __attribute__ ((format(printf, 1, 2),noreturn))
-#endif
-#ifndef FUNCIERROR
-#define FUNCIERROR __attribute__ ((noreturn))
-#endif
-#define FUNCMATH __attribute__((const))
-#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
-#define FUNCDEAD __attribute__ ((deprecated))
-#define FUNCINLINE __attribute__((always_inline))
-#define FUNCNONNULL __attribute__((nonnull))
-#endif
-#define FUNCNOINLINE __attribute__((noinline))
-#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)
-#ifdef __i386__ // i386 only
-#define FUNCTARGET(X)  __attribute__ ((__target__ (X)))
-#endif
-#endif
-#if defined (__MINGW32__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
-#define ATTRPACK __attribute__((packed, gcc_struct))
-#else
-#define ATTRPACK __attribute__((packed))
-#endif
-#define ATTRUNUSED __attribute__((unused))
+	#define FUNCNOINLINE __attribute__((noinline))
+
+	#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) // >= GCC 4.4
+		#ifdef __i386__ // i386 only
+			#define FUNCTARGET(X)  __attribute__ ((__target__ (X)))
+		#endif
+	#endif
+
+	#if defined (__MINGW32__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) // MinGW, >= GCC 3.4
+		#define ATTRPACK __attribute__((packed, gcc_struct))
+	#else
+		#define ATTRPACK __attribute__((packed))
+	#endif
+
+	#define ATTRUNUSED __attribute__((unused))
 #elif defined (_MSC_VER)
-#define ATTRNORETURN __declspec(noreturn)
-#define ATTRINLINE __forceinline
-#if _MSC_VER > 1200
-#define ATTRNOINLINE __declspec(noinline)
-#endif
+	#define ATTRNORETURN __declspec(noreturn)
+	#define ATTRINLINE __forceinline
+	#if _MSC_VER > 1200 // >= MSVC 6.0
+		#define ATTRNOINLINE __declspec(noinline)
+	#endif
 #endif
 
 #ifndef FUNCPRINTF
@@ -335,4 +323,43 @@ typedef UINT32 tic_t;
 #ifndef ATTRNOINLINE
 #define ATTRNOINLINE
 #endif
+
+/* Miscellaneous types that don't fit anywhere else (Can this be changed?) */
+
+union FColorRGBA
+{
+	UINT32 rgba;
+	struct
+	{
+		UINT8 red;
+		UINT8 green;
+		UINT8 blue;
+		UINT8 alpha;
+	} s;
+} ATTRPACK;
+typedef union FColorRGBA RGBA_t;
+
+typedef enum
+{
+	postimg_none,
+	postimg_water,
+	postimg_motion,
+	postimg_flip,
+	postimg_heat
+} postimg_t;
+
+typedef UINT32 lumpnum_t; // 16 : 16 unsigned long (wad num: lump num)
+#define LUMPERROR UINT32_MAX
+
+typedef UINT32 tic_t;
+#define INFTICS UINT32_MAX
+
+#include "endian.h" // This is needed to make sure the below macro acts correctly in big endian builds
+
+#ifdef SRB2_BIG_ENDIAN
+#define UINT2RGBA(a) a
+#else
+#define UINT2RGBA(a) (UINT32)((a&0xff)<<24)|((a&0xff00)<<8)|((a&0xff0000)>>8)|(((UINT32)a&0xff000000)>>24)
+#endif
+
 #endif //__DOOMTYPE__
diff --git a/src/dummy/i_sound.c b/src/dummy/i_sound.c
index 51dbb610d13657ab567bfd8314ea53dc6ba40350..f09158e013639c8a506058a0eaa7b1259d1285c6 100644
--- a/src/dummy/i_sound.c
+++ b/src/dummy/i_sound.c
@@ -23,13 +23,14 @@ void I_UpdateSound(void){};
 //  SFX I/O
 //
 
-INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority)
+INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
 {
 	(void)id;
 	(void)vol;
 	(void)sep;
 	(void)pitch;
 	(void)priority;
+	(void)channel;
 	return -1;
 }
 
@@ -57,91 +58,160 @@ void I_SetSfxVolume(UINT8 volume)
 	(void)volume;
 }
 
-//
-//  MUSIC I/O
-//
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
 
 void I_InitMusic(void){}
 
 void I_ShutdownMusic(void){}
 
-void I_PauseSong(INT32 handle)
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
+
+musictype_t I_SongType(void)
 {
-	(void)handle;
+	return MU_NONE;
 }
 
-void I_ResumeSong(INT32 handle)
+boolean I_SongPlaying(void)
 {
-	(void)handle;
+	return false;
 }
 
-//
-//  MIDI I/O
-//
+boolean I_SongPaused(void)
+{
+	return false;
+}
 
-void I_InitMIDIMusic(void){}
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
 
-void I_ShutdownMIDIMusic(void){}
+boolean I_SetSongSpeed(float speed)
+{
+	(void)speed;
+	return false;
+}
 
-void I_SetMIDIMusicVolume(UINT8 volume)
+/// ------------------------
+//  MUSIC SEEKING
+/// ------------------------
+
+UINT32 I_GetSongLength(void)
 {
-	(void)volume;
+	return 0;
+}
+
+boolean I_SetSongLoopPoint(UINT32 looppoint)
+{
+        (void)looppoint;
+        return false;
+}
+
+UINT32 I_GetSongLoopPoint(void)
+{
+	return 0;
 }
 
-INT32 I_RegisterSong(void *data, size_t len)
+boolean I_SetSongPosition(UINT32 position)
+{
+    (void)position;
+    return false;
+}
+
+UINT32 I_GetSongPosition(void)
+{
+    return 0;
+}
+
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
+
+boolean I_LoadSong(char *data, size_t len)
 {
 	(void)data;
 	(void)len;
 	return -1;
 }
 
-boolean I_PlaySong(INT32 handle, boolean looping)
+void I_UnloadSong(void)
+{
+	(void)handle;
+}
+
+boolean I_PlaySong(boolean looping)
 {
 	(void)handle;
 	(void)looping;
 	return false;
 }
 
-void I_StopSong(INT32 handle)
+void I_StopSong(void)
 {
 	(void)handle;
 }
 
-void I_UnRegisterSong(INT32 handle)
+void I_PauseSong(void)
 {
 	(void)handle;
 }
 
-//
-//  DIGMUSIC I/O
-//
-
-void I_InitDigMusic(void){}
+void I_ResumeSong(void)
+{
+	(void)handle;
+}
 
-void I_ShutdownDigMusic(void){}
+void I_SetMusicVolume(UINT8 volume)
+{
+	(void)volume;
+}
 
-boolean I_StartDigSong(const char *musicname, boolean looping)
+boolean I_SetSongTrack(int track)
 {
-	(void)musicname;
-	(void)looping;
+	(void)track;
 	return false;
 }
 
-void I_StopDigSong(void){}
+/// ------------------------
+//  MUSIC FADING
+/// ------------------------
 
-void I_SetDigMusicVolume(UINT8 volume)
+void I_SetInternalMusicVolume(UINT8 volume)
 {
 	(void)volume;
 }
 
-boolean I_SetSongSpeed(float speed)
+void I_StopFadingSong(void)
 {
-	(void)speed;
+}
+
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void));
+{
+	(void)target_volume;
+	(void)source_volume;
+	(void)ms;
 	return false;
 }
 
-boolean I_SetSongTrack(int track)
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
 {
-	(void)track;
+	(void)target_volume;
+	(void)ms;
 	return false;
 }
+
+boolean I_FadeOutStopSong(UINT32 ms)
+{
+	(void)ms;
+	return false;
+}
+
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
+{
+        (void)ms;
+        (void)looping;
+        return false;
+}
diff --git a/src/endian.h b/src/endian.h
index 2b876e7cb32c5ebc04fdd06e719ca3fbffae3298..d3c1cb18b4e6923cf101a64059266ed78326175a 100644
--- a/src/endian.h
+++ b/src/endian.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2014-2016 by Sonic Team Junior.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/f_finale.c b/src/f_finale.c
index 05c3d5fc0cf6edbca1a4768ecd09e40d0c47219b..f3ab235b833f37dad2e729b99894faa868c20bb0 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -14,6 +14,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "d_main.h"
+#include "d_netcmd.h"
 #include "f_finale.h"
 #include "g_game.h"
 #include "hu_stuff.h"
@@ -29,10 +30,14 @@
 #include "g_input.h"
 #include "console.h"
 #include "m_random.h"
+#include "m_misc.h" // moviemode functionality
 #include "y_inter.h"
 #include "m_cond.h"
 #include "p_local.h"
 #include "p_setup.h"
+#include "st_stuff.h" // hud hiding
+#include "fastcmp.h"
+#include "console.h"
 
 #ifdef HAVE_BLUA
 #include "lua_hud.h"
@@ -41,14 +46,15 @@
 // Stage of animation:
 // 0 = text, 1 = art screen
 static INT32 finalecount;
-INT32 titlescrollspeed = 80;
+INT32 titlescrollxspeed = 80;
+INT32 titlescrollyspeed = 0;
 UINT8 titlemapinaction = TITLEMAP_OFF;
 
 static INT32 timetonext; // Delay between screen changes
 static INT32 continuetime; // Short delay when continuing
 
 static tic_t animtimer; // Used for some animation timings
-static INT32 roidtics; // Asteroid spinning
+static INT16 skullAnimCounter; // Prompts: Chevron animation
 
 static INT32 deplete;
 static tic_t stoptimer;
@@ -56,6 +62,20 @@ static tic_t stoptimer;
 static boolean keypressed = false;
 
 // (no longer) De-Demo'd Title Screen
+static tic_t xscrolltimer;
+static tic_t yscrolltimer;
+static INT32 menuanimtimer; // Title screen: background animation timing
+mobj_t *titlemapcameraref = NULL;
+
+// menu presentation state
+char curbgname[9];
+SINT8 curfadevalue;
+boolean curhidepics;
+INT32 curbgcolor;
+INT32 curbgxspeed;
+INT32 curbgyspeed;
+boolean curbghide;
+
 static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
 static UINT32 demoIdleLeft;
@@ -76,7 +96,29 @@ static patch_t *ttspop5;
 static patch_t *ttspop6;
 static patch_t *ttspop7;
 
-static void F_SkyScroll(INT32 scrollspeed);
+static boolean goodending;
+static patch_t *endbrdr[2]; // border - blue, white, pink - where have i seen those colours before?
+static patch_t *endbgsp[3]; // nebula, sun, planet
+static patch_t *endegrk[2]; // eggrock - replaced midway through good ending
+static patch_t *endfwrk[3]; // firework - replaced with skin when good ending
+static patch_t *endspkl[3]; // sparkle
+static patch_t *endglow[2]; // glow aura - replaced with black rock's midway through good ending
+static patch_t *endxpld[4]; // mini explosion
+static patch_t *endescp[5]; // escape pod + flame
+static INT32 sparkloffs[3][2]; // eggrock explosions/blackrock sparkles
+static INT32 sparklloop;
+
+//
+// PROMPT STATE
+//
+boolean promptactive = false;
+static mobj_t *promptmo;
+static INT16 promptpostexectag;
+static boolean promptblockcontrols;
+static char *promptpagetext = NULL;
+static INT32 callpromptnum = INT32_MAX;
+static INT32 callpagenum = INT32_MAX;
+static INT32 callplayer = INT32_MAX;
 
 //
 // CUTSCENE TEXT WRITING
@@ -166,93 +208,6 @@ static void F_NewCutscene(const char *basetext)
 	cutscene_textcount = TICRATE/2;
 }
 
-//
-// F_DrawPatchCol
-//
-static void F_DrawPatchCol(INT32 x, patch_t *patch, INT32 col)
-{
-	const column_t *column;
-	const UINT8 *source;
-	UINT8 *desttop, *dest = NULL;
-	const UINT8 *deststop, *destbottom;
-	size_t count;
-
-	desttop = screens[0] + x*vid.dupx;
-	deststop = screens[0] + vid.rowbytes * vid.height;
-	destbottom = desttop + vid.height*vid.width;
-
-	do {
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col]));
-
-		// step through the posts in a column
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop + topdelta*vid.width;
-			count = column->length;
-
-			while (count--)
-			{
-				INT32 dupycount = vid.dupy;
-
-				while (dupycount-- && dest < destbottom)
-				{
-					INT32 dupxcount = vid.dupx;
-					while (dupxcount-- && dest <= deststop)
-						*dest++ = *source;
-
-					dest += (vid.width - vid.dupx);
-				}
-				source++;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-
-		desttop += SHORT(patch->height)*vid.dupy*vid.width;
-	} while(dest < destbottom);
-}
-
-//
-// F_SkyScroll
-//
-static void F_SkyScroll(INT32 scrollspeed)
-{
-	INT32 scrolled, x, mx, fakedwidth;
-	patch_t *pat;
-	INT16 patwidth;
-
-	pat = W_CachePatchName("TITLESKY", PU_CACHE);
-
-	patwidth = SHORT(pat->width);
-	animtimer = ((finalecount*scrollspeed)/16 + patwidth) % patwidth;
-
-	fakedwidth = vid.width / vid.dupx;
-
-	if (rendermode == render_soft)
-	{ // if only hardware rendering could be this elegant and complete
-		scrolled = (patwidth - animtimer) - 1;
-		for (x = 0, mx = scrolled; x < fakedwidth; x++, mx = (mx+1)%patwidth)
-			F_DrawPatchCol(x, pat, mx);
-	}
-#ifdef HWRENDER
-	else if (rendermode != render_none)
-	{ // if only software rendering could be this simple and retarded
-		scrolled = animtimer;
-		if (scrolled > 0)
-			V_DrawScaledPatch(scrolled - patwidth, 0, 0, pat);
-		for (x = 0; x < fakedwidth; x += patwidth)
-			V_DrawScaledPatch(x + scrolled, 0, 0, pat);
-	}
-#endif
-
-	W_UnlockCachedPatch(pat);
-}
-
 // =============
 //  INTRO SCENE
 // =============
@@ -288,6 +243,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 void F_StartIntro(void)
 {
 	S_StopMusic();
+	S_StopSounds();
 
 	if (introtoplay)
 	{
@@ -308,7 +264,7 @@ void F_StartIntro(void)
 	introtext[2] = M_GetText(
 	"As it was about to drain the rings\n"
 	"away from the planet, Sonic burst into\n"
-	"the Satellite and for what he thought\n"
+	"the control room and for what he thought\n"
 	"would be the last time,\xB4 defeated\n"
 	"Dr. Eggman.\n#");
 
@@ -318,11 +274,11 @@ void F_StartIntro(void)
 	"return,\xB8 bringing an all new threat.\n#");
 
 	introtext[4] = M_GetText(
-	"\xA8""About once every year, a strange asteroid\n"
+	"\xA8""About every five years, a strange asteroid\n"
 	"hovers around the planet.\xBF It suddenly\n"
 	"appears from nowhere, circles around, and\n"
 	"\xB6- just as mysteriously as it arrives -\xB6\n"
-	"vanishes after about two months.\xBF\n"
+	"vanishes after only one week.\xBF\n"
 	"No one knows why it appears, or how.\n#");
 
 	introtext[5] = M_GetText(
@@ -334,11 +290,11 @@ void F_StartIntro(void)
 	"the screen, and just shrugged it off.\n#");
 
 	introtext[6] = M_GetText(
-	"It was only later\n"
+	"It was hours later\n"
 	"that he had an\n"
 	"idea. \xBF\xA7\"The Black\n"
-	"Rock usually has a\n"
-	"lot of energy\n"
+	"Rock has a large\n"
+	"amount of energy\n"
 	"within it\xAC...\xA7\xBF\n"
 	"If I can somehow\n"
 	"harness this,\xB8 I\n"
@@ -356,37 +312,37 @@ void F_StartIntro(void)
 	"a reunion party...\n#");
 
 	introtext[8] = M_GetText(
-	"\xA5\"We're\xB6 ready\xB6 to\xB4 fire\xB6 in\xB6 15\xB6 seconds!\"\xA8\xB8\n"
-	"The robot said, his voice crackling a\n"
-	"little down the com-link. \xBF\xA7\"Good!\"\xA8\xB8\n"
-	"Eggman sat back in his Egg-Mobile and\n"
+	"\xA5\"PRE-""\xB6""PARING-""\xB6""TO-""\xB4""FIRE-\xB6IN-""\xB6""15-""\xB6""SECONDS!\"\xA8\xB8\n"
+	"his targeting system crackled\n"
+	"robotically down the com-link. \xBF\xA7\"Good!\"\xA8\xB8\n"
+	"Eggman sat back in his eggmobile and\n"
 	"began to count down as he saw the\n"
-	"GreenFlower city on the main monitor.\n#");
+	"Greenflower mountain on the monitor.\n#");
 
 	introtext[9] = M_GetText(
 	"\xA5\"10...\xD2""9...\xD2""8...\"\xA8\xD2\n"
 	"Meanwhile, Sonic was tearing across the\n"
 	"zones. Everything became a blur as he\n"
-	"ran around loops, skimmed over water,\n"
+	"ran up slopes, skimmed over water,\n"
 	"and catapulted himself off rocks with\n"
 	"his phenomenal speed.\n#");
 
 	introtext[10] = M_GetText(
 	"\xA5\"6...\xD2""5...\xD2""4...\"\xA8\xD2\n"
 	"Sonic knew he was getting closer to the\n"
-	"City, and pushed himself harder.\xB4 Finally,\n"
-	"the city appeared in the horizon.\xD2\xD2\n"
+	"zone, and pushed himself harder.\xB4 Finally,\n"
+	"the mountain appeared on the horizon.\xD2\xD2\n"
 	"\xA5\"3...\xD2""2...\xD2""1...\xD2""Zero.\"\n#");
 
 	introtext[11] = M_GetText(
-	"GreenFlower City was gone.\xC4\n"
+	"Greenflower Mountain was no more.\xC4\n"
 	"Sonic arrived just in time to see what\n"
 	"little of the 'ruins' were left.\n"
-	"Everyone and everything in the city\n"
+	"The natural beauty of the zone\n"
 	"had been obliterated.\n#");
 
 	introtext[12] = M_GetText(
-	"\xA7\"You're not quite as dead as we thought,\n"
+	"\xA7\"You're not quite as gone as we thought,\n"
 	"huh?\xBF Are you going to tell us your plan as\n"
 	"usual or will I \xA8\xB4'have to work it out'\xA7 or\n"
 	"something?\"\xD2\xD2\n"
@@ -400,8 +356,8 @@ void F_StartIntro(void)
 	"leaving Sonic\n"
 	"and Tails behind.\xB6\n"
 	"Tails looked at\n"
-	"the ruins of the\n"
-	"Greenflower City\n"
+	"the once-perfect\n"
+	"mountainside\n"
 	"with a grim face\n"
 	"and sighed.\xC6\n"
 	"\xA7\"Now\xB6 what do we\n"
@@ -447,12 +403,10 @@ void F_StartIntro(void)
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
-	CON_ClearHUD();
 	F_NewCutscene(introtext[0]);
 
 	intro_scenenum = 0;
-	finalecount = animtimer = stoptimer = 0;
-	roidtics = BASEVIDWIDTH - 64;
+	finalecount = animtimer = skullAnimCounter = stoptimer = 0;
 	timetonext = introscenetime[intro_scenenum];
 }
 
@@ -683,7 +637,7 @@ static void F_IntroDrawScene(void)
 		}
 		else
 		{
-			F_SkyScroll(80*4);
+			F_SkyScroll(80*4, 0, "TITLESKY");
 			if (timetonext == 6)
 			{
 				stoptimer = finalecount;
@@ -735,11 +689,42 @@ static void F_IntroDrawScene(void)
 
 	if (intro_scenenum == 4) // The asteroid SPINS!
 	{
-		if (roidtics >= 0)
+		if (intro_curtime > 1)
 		{
-			V_DrawScaledPatch(roidtics, 24, 0,
-				(patch = W_CachePatchName(va("ROID00%.2d", intro_curtime%35), PU_CACHE)));
-			W_UnlockCachedPatch(patch);
+			INT32 worktics = intro_curtime - 1;
+			INT32 scale = FRACUNIT;
+			patch_t *rockpat;
+			UINT8 *colormap = NULL;
+			patch_t *glow;
+			INT32 trans = 0;
+
+			INT32 x = ((BASEVIDWIDTH - 64)<<FRACBITS) - ((intro_curtime*FRACUNIT)/3);
+			INT32 y = 24<<FRACBITS;
+
+			if (worktics < 5)
+			{
+				scale = (worktics<<(FRACBITS-2));
+				x += (30*(FRACUNIT-scale));
+				y += (30*(FRACUNIT-scale));
+			}
+
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_LEVEL);
+
+			if (worktics >= 5)
+				trans = (worktics-5)>>1;
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
+
+			trans = (15-worktics);
+			if (trans < 0)
+				trans = -trans;
+
+			if (finalecount < 15)
+				colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap);
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
 		}
 	}
 
@@ -761,7 +746,7 @@ static void F_IntroDrawScene(void)
 		W_UnlockCachedPatch(sgrass);
 	}
 
-	V_DrawString(cx, cy, 0, cutscene_disptext);
+	V_DrawString(cx, cy, V_ALLOWLOWERCASE, cutscene_disptext);
 }
 
 //
@@ -783,8 +768,6 @@ void F_IntroDrawer(void)
 
 			S_ChangeMusicInternal("_intro", false);
 		}
-		else if (intro_scenenum == 3)
-			roidtics = BASEVIDWIDTH - 64;
 		else if (intro_scenenum == 10)
 		{
 			// The only fade to white in the entire damn game.
@@ -808,18 +791,27 @@ void F_IntroDrawer(void)
 
 			// Stay on black for a bit. =)
 			{
-				tic_t quittime;
-				quittime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds
-				while (quittime > I_GetTime())
+				tic_t nowtime, quittime, lasttime;
+				nowtime = lasttime = I_GetTime();
+				quittime = nowtime + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds
+				while (quittime > nowtime)
 				{
+					while (!((nowtime = I_GetTime()) - lasttime))
+						I_Sleep();
+					lasttime = nowtime;
+
 					I_OsPolling();
 					I_UpdateNoBlit();
 					M_Drawer(); // menu is drawn even on top of wipes
 					I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
+
+					if (moviemode) // make sure we save frames for the white hold too
+						M_SaveFrame();
 				}
 			}
 
 			D_StartTitle();
+			wipegamestate = GS_INTRO;
 			return;
 		}
 		F_NewCutscene(introtext[++intro_scenenum]);
@@ -899,9 +891,6 @@ void F_IntroTicker(void)
 	// advance animation
 	finalecount++;
 
-	if (finalecount % 3 == 0)
-		roidtics--;
-
 	timetonext--;
 
 	F_WriteText();
@@ -979,37 +968,47 @@ static const char *credits[] = {
 	"\1Programming",
 	"Alam \"GBC\" Arias",
 	"Logan \"GBA\" Arias",
-	"Tim \"RedEnchilada\" Bordelon",
 	"Callum Dickinson",
 	"Scott \"Graue\" Feeney",
 	"Nathan \"Jazz\" Giroux",
+	"Vivian \"toaster\" Grannell",
+	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
 	"John \"JTE\" Muniz",
 	"Ehab \"Wolfy\" Saeed",
+	"\"Kaito Sinclaire\"",
 	"\"SSNTails\"",
-	"Matthew \"Inuyasha\" Walsh",
+	"Marco \"mazmazz\" Zafra",
 	"",
 	"\1Programming",
 	"\1Assistance",
-	"\"chi.miru\"", // Red's secret weapon, the REAL reason slopes exist (also helped port drawing code from ZDoom)
+	"\"chi.miru\"", // helped port slope drawing code from ZDoom
 	"Andrew \"orospakr\" Clunis",
+	"Sally \"TehRealSalt\" Cochenour",
 	"Gregor \"Oogaland\" Dick",
 	"Louis-Antoine \"LJSonic\" de Moulins", // for fixing 2.1's netcode (de Rochefort doesn't quite fit on the screen sorry lol)
-	"Vivian \"toaster\" Grannell",
+	"Victor \"Steel Titanium\" Fuentes",
 	"Julio \"Chaos Zero 64\" Guir",
+	"\"Jimita\"",
 	"\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog
+	"\"Lat'\"", // SRB2-CHAT, the chat window from Kart
 	"Matthew \"Shuffle\" Marsalko",
 	"Steven \"StroggOnMeth\" McGranahan",
 	"\"Morph\"", // For SRB2Morphed stuff
 	"Colin \"Sonict\" Pfaff",
 	"Sean \"Sryder13\" Ryder",
+	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
 	"Ben \"Cue\" Woodford",
+	// Git contributors with 5+ approved merges of substantive quality,
+	// or contributors with at least one groundbreaking merge, may be named.
+	// Everyone else is acknowledged under "Special Thanks > SRB2 Community Contributors".
 	"",
 	"\1Sprite Artists",
-	"Odi \"Iceman404\" Atunzu",
+	"\"Iceman404\"",
 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
+	"Sally \"TehRealSalt\" Cochenour",
 	"Jim \"MotorRoach\" DeMello",
 	"Desmond \"Blade\" DesJardins",
 	"Sherman \"CoatRack\" DesJardins",
@@ -1028,7 +1027,7 @@ static const char *credits[] = {
 	"\1Music and Sound",
 	"\1Production",
 	"Malcolm \"RedXVI\" Brown",
-	"David \"Bulmybag\" Bulmer",
+	"Dave \"DemonTomatoDave\" Bulmer",
 	"Paul \"Boinciel\" Clempson",
 	"Cyan Helkaraxe",
 	"Kepa \"Nev3r\" Iceta",
@@ -1052,18 +1051,19 @@ static const char *credits[] = {
 	"Dan \"Blitzzo\" Hagerstrand",
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
-	"Erik \"Torgo\" Nielsen",
-	"Wessel \"Spherallic\" Smit",
+	"\"Kaito Sinclaire\"",
+	"Wessel \"sphere\" Smit",
 	"\"Spazzo\"",
 	"\"SSNTails\"",
 	"Rob Tisdell",
+	"\"Torgo\"",
 	"Jarrett \"JEV3\" Voight",
 	"Johnny \"Sonikku\" Wallbank",
-	"Matthew \"Inuyasha\" Walsh",
-	"Marco \"Digiku\" Zafra",
+	"Marco \"mazmazz\" Zafra",
 	"",
 	"\1Boss Design",
 	"Ben \"Mystic\" Geyer",
+	"Vivian \"toaster\" Grannell",
 	"Thomas \"Shadow Hog\" Igoe",
 	"John \"JTE\" Muniz",
 	"Samuel \"Prime 2.0\" Peters",
@@ -1081,11 +1081,20 @@ static const char *credits[] = {
 	"Bill \"Tets\" Reed",
 	"",
 	"\1Special Thanks",
-	"Doom Legacy Project",
 	"iD Software",
-	"Alex \"MistaED\" Fuller",
+	"Doom Legacy Project",
 	"FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak
+	"Alex \"MistaED\" Fuller",
+	"Pascal \"CodeImp\" vd Heiden", // Doom Builder developer
 	"Randi Heit (<!>)", // For their MSPaint <!> sprite that we nicked
+	"Simon \"sirjuddington\" Judd", // SLADE developer
+	// Acknowledged here are the following:
+	// Minor merge request authors, see guideline above
+	// - Golden - Expanded thin font
+	// Creators of small quantities of sprite/texture assets
+	// - Arietty - New Green Hill-styled textures
+	// - Scizor300 - the only other contributor to the 2.0 SRB2 Asset Pack
+	"SRB2 Community Contributors",
 	"",
 	"\1Produced By",
 	"Sonic Team Junior",
@@ -1137,8 +1146,8 @@ void F_StartCredits(void)
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
-	CON_ClearHUD();
 	S_StopMusic();
+	S_StopSounds();
 
 	S_ChangeMusicInternal("_creds", false);
 
@@ -1158,6 +1167,9 @@ void F_CreditDrawer(void)
 	for (i = 0; credits_pics[i].patch; i++)
 		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (credits_pics[i].y<<FRACBITS) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_CACHE), FRACUNIT>>1);
 
+	// Dim the background
+	V_DrawFadeScreen(0xFF00, 16);
+
 	// Draw credits text on top
 	for (i = 0; credits[i]; i++)
 	{
@@ -1180,16 +1192,34 @@ void F_CreditDrawer(void)
 		if (FixedMul(y,vid.dupy) > vid.height)
 			break;
 	}
+}
+
+void F_CreditTicker(void)
+{
+	// "Simulate" the drawing of the credits so that dedicated mode doesn't get stuck
+	UINT16 i;
+	fixed_t y = (80<<FRACBITS) - 5*(animtimer<<FRACBITS)/8;
+
+	// Draw credits text on top
+	for (i = 0; credits[i]; i++)
+	{
+		switch(credits[i][0])
+		{
+			case 0: y += 80<<FRACBITS; break;
+			case 1: y += 30<<FRACBITS; break;
+			default: y += 12<<FRACBITS; break;
+		}
+		if (FixedMul(y,vid.dupy) > vid.height)
+			break;
+	}
 
+	// Do this here rather than in the drawer you doofus! (this is why dedicated mode broke at credits)
 	if (!credits[i] && y <= 120<<FRACBITS && !finalecount)
 	{
 		timetonext = 5*TICRATE+1;
 		finalecount = 5*TICRATE;
 	}
-}
 
-void F_CreditTicker(void)
-{
 	if (timetonext)
 		timetonext--;
 	else
@@ -1236,7 +1266,7 @@ boolean F_CreditResponder(event_t *event)
 			break;
 	}
 
-	if (!(timesBeaten) && !(netgame || multiplayer))
+	if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug)
 		return false;
 
 	if (event->type != ev_keydown)
@@ -1255,10 +1285,8 @@ boolean F_CreditResponder(event_t *event)
 // ============
 //  EVALUATION
 // ============
-#define INTERVAL 50
-#define TRANSLEVEL V_80TRANS
-static INT32 eemeralds_start;
-static boolean drawemblem = false, drawchaosemblem = false;
+
+#define SPARKLLOOPTIME 7 // must be odd
 
 void F_StartGameEvaluation(void)
 {
@@ -1280,77 +1308,122 @@ void F_StartGameEvaluation(void)
 	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
 		G_SaveGame((UINT32)cursaveslot);
 
+	goodending = (ALL7EMERALDS(emeralds));
+
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
-	CON_ClearHUD();
 
-	finalecount = 0;
+	finalecount = -1;
+	sparklloop = 0;
 }
 
 void F_GameEvaluationDrawer(void)
 {
 	INT32 x, y, i;
-	const fixed_t radius = 48*FRACUNIT;
 	angle_t fa;
 	INT32 eemeralds_cur;
 	char patchname[7] = "CEMGx0";
+	const char* endingtext = (goodending ? "CONGRATULATIONS!" : "TRY AGAIN...");
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Draw all the good crap here.
-	if (ALL7EMERALDS(emeralds))
-		V_DrawString(114, 16, 0, "GOT THEM ALL!");
-	else
-		V_DrawString(124, 16, 0, "TRY AGAIN!");
-
-	eemeralds_start++;
-	eemeralds_cur = eemeralds_start;
 
-	for (i = 0; i < 7; ++i)
+	if (finalecount > 0)
 	{
-		fa = (FixedAngle(eemeralds_cur*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK;
-		x = 160 + FixedInt(FixedMul(FINECOSINE(fa),radius));
-		y = 100 + FixedInt(FixedMul(FINESINE(fa),radius));
-
-		patchname[4] = 'A'+(char)i;
-		if (emeralds & (1<<i))
-			V_DrawScaledPatch(x, y, 0, W_CachePatchName(patchname, PU_CACHE));
-		else
-			V_DrawTranslucentPatch(x, y, TRANSLEVEL, W_CachePatchName(patchname, PU_CACHE));
+		INT32 scale = FRACUNIT;
+		patch_t *rockpat;
+		UINT8 *colormap[2] = {NULL, NULL};
+		patch_t *glow;
+		INT32 trans = 0;
 
-		eemeralds_cur += INTERVAL;
-	}
-	if (eemeralds_start >= 360)
-		eemeralds_start -= 360;
+		x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
+		y = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
 
-	if (finalecount == 5*TICRATE)
-	{
-		if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
+		if (finalecount < 5)
 		{
-			++timesBeaten;
+			scale = (finalecount<<(FRACBITS-2));
+			x += (30*(FRACUNIT-scale));
+			y += (30*(FRACUNIT-scale));
+		}
 
-			if (ALL7EMERALDS(emeralds))
-				++timesBeatenWithEmeralds;
+		if (goodending)
+		{
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_LEVEL);
+			x -= FRACUNIT;
+		}
+		else
+		{
+			rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_LEVEL);
+		}
 
-			if (ultimatemode)
-				++timesBeatenUltimate;
+		if (finalecount >= 5)
+			trans = (finalecount-5)>>1;
+		if (trans < 10)
+			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
 
-			if (M_UpdateUnlockablesAndExtraEmblems())
-				S_StartSound(NULL, sfx_ncitem);
+		trans = (15-finalecount);
+		if (trans < 0)
+			trans = -trans;
 
-			G_SaveGameData();
+		if (finalecount < 15)
+			colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+		V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
+		if (trans < 10)
+		{
+			colormap[1] = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, colormap[1]);
+		}
+		if (goodending)
+		{
+			INT32 j = (sparklloop & 1) ? 2 : 3;
+			if (j > (finalecount/SPARKLLOOPTIME))
+				j = (finalecount/SPARKLLOOPTIME);
+			while (j)
+			{
+				if (j > 1 || sparklloop >= 2)
+				{
+					// if j == 0 - alternate between 0 and 1
+					//         1 -                   1 and 2
+					//         2 -                   2 and not rendered
+					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_LEVEL), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
+				}
+				j--;
+			}
+		}
+		else
+		{
+			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_LEVEL);
+			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
+			else if (sparklloop)
+				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
+					W_CachePatchName("ENDEGRK0", PU_LEVEL), colormap[1]);
 		}
 	}
 
-	if (finalecount >= 5*TICRATE)
+	eemeralds_cur = (finalecount % 360)<<FRACBITS;
+
+	for (i = 0; i < 7; ++i)
 	{
-		if (drawemblem)
-			V_DrawScaledPatch(120, 192, 0, W_CachePatchName("NWNGA0", PU_CACHE));
+		fa = (FixedAngle(eemeralds_cur)>>ANGLETOFINESHIFT) & FINEMASK;
+		x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINECOSINE(fa));
+		y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + (60*FINESINE(fa));
+		eemeralds_cur += (360<<FRACBITS)/7;
+
+		patchname[4] = 'A'+(char)i;
+		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_LEVEL), NULL);
+	}
 
-		if (drawchaosemblem)
-			V_DrawScaledPatch(200, 192, 0, W_CachePatchName("NWNGA0", PU_CACHE));
+	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
 
+#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)
+	{
 		V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
 
 		if (!(netgame) && (!modifiedgame || savemoddata))
@@ -1373,128 +1446,813 @@ void F_GameEvaluationDrawer(void)
 		else
 			V_DrawString(8, 96, V_YELLOWMAP, "Prizes not\nawarded in\nmodified games!");
 	}
+#endif
 }
 
 void F_GameEvaluationTicker(void)
 {
-	finalecount++;
-
-	if (finalecount > 10*TICRATE)
+	if (++finalecount > 10*TICRATE)
+	{
 		F_StartGameEnd();
-}
+		return;
+	}
 
-// ==========
-//  GAME END
-// ==========
-void F_StartGameEnd(void)
-{
-	G_SetGamestate(GS_GAMEEND);
+	if (!goodending)
+	{
+		if (sparklloop)
+			sparklloop--;
 
-	gameaction = ga_nothing;
-	paused = false;
-	CON_ToggleOff();
-	CON_ClearHUD();
-	S_StopMusic();
+		if (finalecount == (5*TICRATE)/2
+			|| finalecount == (7*TICRATE)/2
+			|| finalecount == ((7*TICRATE)/2)+5)
+		{
+			S_StartSound(NULL, sfx_s3k5c);
+			sparklloop = 10;
+		}
+	}
+	else if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
+	{
+		angle_t workingangle = FixedAngle((M_RandomKey(360))<<FRACBITS)>>ANGLETOFINESHIFT;
+		fixed_t workingradius = M_RandomKey(26);
 
-	// In case menus are still up?!!
-	M_ClearMenus(true);
+		sparkloffs[2][0] = sparkloffs[1][0];
+		sparkloffs[2][1] = sparkloffs[1][1];
+		sparkloffs[1][0] = sparkloffs[0][0];
+		sparkloffs[1][1] = sparkloffs[0][1];
 
-	timetonext = TICRATE;
-}
+		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
+		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
+		sparklloop = 0;
+	}
 
-//
-// F_GameEndDrawer
-//
-void F_GameEndDrawer(void)
-{
-}
+	if (finalecount == 5*TICRATE)
+	{
+		if (netgame || multiplayer) // modify this when we finally allow unlocking stuff in 2P
+		{
+			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
+			HU_SetCEchoDuration(6);
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes only awarded in singleplayer!");
+			S_StartSound(NULL, sfx_s3k68);
+		}
+		else if (!modifiedgame || savemoddata)
+		{
+			++timesBeaten;
 
-//
-// F_GameEndTicker
-//
-void F_GameEndTicker(void)
-{
-	if (timetonext > 0)
-		timetonext--;
-	else
-		D_StartTitle();
+			if (ALL7EMERALDS(emeralds))
+				++timesBeatenWithEmeralds;
+
+			if (ultimatemode)
+				++timesBeatenUltimate;
+
+			if (M_UpdateUnlockablesAndExtraEmblems())
+				S_StartSound(NULL, sfx_s3k68);
+
+			G_SaveGameData();
+		}
+		else
+		{
+			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
+			HU_SetCEchoDuration(6);
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes not awarded in modified games!");
+			S_StartSound(NULL, sfx_s3k68);
+		}
+	}
 }
 
+#undef SPARKLLOOPTIME
 
-// ==============
-//  TITLE SCREEN
-// ==============
-void F_StartTitleScreen(void)
+// ==========
+//   ENDING
+// ==========
+
+#define INFLECTIONPOINT (6*TICRATE)
+#define SPARKLLOOPTIME 15 // must be odd
+
+void F_StartEnding(void)
 {
-	S_ChangeMusicInternal("_title", looptitle);
+	G_SetGamestate(GS_ENDING);
+	wipetypepost = INT16_MAX;
 
-	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
-		finalecount = 0;
-	else
-		wipegamestate = GS_TITLESCREEN;
+	// Just in case they're open ... somehow
+	M_ClearMenus(true);
 
-	if (titlemap)
-	{
-		mapthing_t *startpos;
+	// Save before the credits sequence.
+	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
+		G_SaveGame((UINT32)cursaveslot);
 
-		gamestate_t prevwipegamestate = wipegamestate;
-		titlemapinaction = TITLEMAP_LOADING;
-		gamemap = titlemap;
+	gameaction = ga_nothing;
+	paused = false;
+	CON_ToggleOff();
+	S_StopMusic(); // todo: placeholder
+	S_StopSounds();
 
-		if (!mapheaderinfo[gamemap-1])
-			P_AllocMapHeader(gamemap-1);
+	finalecount = -10; // what? this totally isn't a hack. why are you asking?
 
-		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
-		globalweather = mapheaderinfo[gamemap-1]->weather;
+	memset(sparkloffs, 0, sizeof(INT32)*3*2);
+	sparklloop = 0;
 
-		G_DoLoadLevel(true);
-		if (!titlemap)
-			return;
+	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_LEVEL);
 
-		players[displayplayer].playerstate = PST_DEAD; // Don't spawn the player in dummy (I'm still a filthy cheater)
+	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_LEVEL);
+	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_LEVEL);
 
-		// Set Default Position
-		if (playerstarts[0])
-			startpos = playerstarts[0];
-		else if (deathmatchstarts[0])
-			startpos = deathmatchstarts[0];
-		else
-			startpos = NULL;
+	endglow[0] = W_CachePatchName("ENDGLOW0", PU_LEVEL);
+	endglow[1] = W_CachePatchName("ENDGLOW1", PU_LEVEL);
 
-		if (startpos)
+	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_LEVEL);
+	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_LEVEL);
+	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_LEVEL);
+
+	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_LEVEL);
+	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_LEVEL);
+	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_LEVEL);
+
+	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_LEVEL);
+	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_LEVEL);
+	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_LEVEL);
+	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_LEVEL);
+
+	endescp[0] = W_CachePatchName("ENDESCP0", PU_LEVEL);
+	endescp[1] = W_CachePatchName("ENDESCP1", PU_LEVEL);
+	endescp[2] = W_CachePatchName("ENDESCP2", PU_LEVEL);
+	endescp[3] = W_CachePatchName("ENDESCP3", PU_LEVEL);
+	endescp[4] = W_CachePatchName("ENDESCP4", PU_LEVEL);
+
+	// so we only need to check once
+	if ((goodending = ALL7EMERALDS(emeralds)))
+	{
+		UINT8 skinnum = players[consoleplayer].skin;
+		spritedef_t *sprdef;
+		spriteframe_t *sprframe;
+		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 5)
 		{
-			camera.x = startpos->x << FRACBITS;
-			camera.y = startpos->y << FRACBITS;
-			camera.subsector = R_PointInSubsector(camera.x, camera.y);
-			camera.z = camera.subsector->sector->floorheight + ((startpos->options >> ZSHIFT) << FRACBITS);
-			camera.angle = (startpos->angle % 360)*ANG1;
-			camera.aiming = 0;
+			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+			// character head, skin specific
+			sprframe = &sprdef->spriteframes[2];
+			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			sprframe = &sprdef->spriteframes[3];
+			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			sprframe = &sprdef->spriteframes[4];
+			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
 		}
-		else
+		else // eh, yknow what? too lazy to put MISSINGs here. eggman wins if you don't give your character an ending firework display.
 		{
-			camera.x = camera.y = camera.z = camera.angle = camera.aiming = 0;
-			camera.subsector = NULL; // toast is filthy too
+			endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
+			endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
+			endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
 		}
 
-		camera.chase = true;
-		camera.height = 0;
-
-		wipegamestate = prevwipegamestate;
+		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_LEVEL);
 	}
 	else
 	{
-		titlemapinaction = TITLEMAP_OFF;
-		gamemap = 1; // g_game.c
-		CON_ClearHUD();
+		// eggman, skin nonspecific
+		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
+		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
+		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
+
+		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_LEVEL);
 	}
+}
 
-	G_SetGamestate(GS_TITLESCREEN);
+void F_EndingTicker(void)
+{
+	if (++finalecount > INFLECTIONPOINT*2)
+	{
+		F_StartCredits();
+		wipetypepre = INT16_MAX;
+		return;
+	}
 
-	// IWAD dependent stuff.
+	if (goodending && finalecount == INFLECTIONPOINT) // time to swap some assets
+	{
+		endegrk[0] = W_CachePatchName("ENDEGRK2", PU_LEVEL);
+		endegrk[1] = W_CachePatchName("ENDEGRK3", PU_LEVEL);
 
-	animtimer = 0;
+		endglow[0] = W_CachePatchName("ENDGLOW2", PU_LEVEL);
+		endglow[1] = W_CachePatchName("ENDGLOW3", PU_LEVEL);
 
-	demoDelayLeft = demoDelayTime;
+		endxpld[0] = W_CachePatchName("ENDEGRK4", PU_LEVEL);
+	}
+
+	if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
+	{
+		angle_t workingangle = FixedAngle((M_RandomRange(-170, 80))<<FRACBITS)>>ANGLETOFINESHIFT;
+		fixed_t workingradius = M_RandomKey(26);
+
+		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
+		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
+
+		sparklloop = 0;
+	}
+}
+
+void F_EndingDrawer(void)
+{
+	INT32 x, y, i, j, parallaxticker;
+	patch_t *rockpat;
+
+	if (!goodending || finalecount < INFLECTIONPOINT)
+		rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
+	else
+		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_LEVEL);
+
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
+	parallaxticker = finalecount - INFLECTIONPOINT;
+	x = -((parallaxticker*20)<<FRACBITS)/INFLECTIONPOINT;
+	y = ((parallaxticker*7)<<FRACBITS)/INFLECTIONPOINT;
+	i = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
+	j = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
+
+	if (finalecount <= -10)
+		;
+	else if (finalecount < 0)
+		V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 10+finalecount);
+	else if (finalecount <= 20)
+	{
+		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
+		if (finalecount && finalecount < 20)
+		{
+			INT32 trans = (10-finalecount);
+			if (trans < 0)
+			{
+				trans = -trans;
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
+			}
+			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[1]);
+		}
+		else if (finalecount == 20)
+			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
+	}
+	else if (goodending && (parallaxticker == -2 || !parallaxticker))
+	{
+		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
+		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, endegrk[0],
+			R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
+		//V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
+	}
+	else if (goodending && parallaxticker == -1)
+	{
+		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, rockpat,
+			R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE));
+		V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
+	}
+	else
+	{
+		boolean doexplosions = false;
+		boolean borderstuff = false;
+		INT32 tweakx = 0, tweaky = 0;
+
+		if (parallaxticker < 75) // f background's supposed to be visible
+		{
+			V_DrawFixedPatch(-(x/10), -(y/10), FRACUNIT, 0, endbgsp[0], NULL); // nebula
+			V_DrawFixedPatch(-(x/5),  -(y/5),  FRACUNIT, 0, endbgsp[1], NULL); // sun
+			V_DrawFixedPatch(     0,  -(y/2),  FRACUNIT, 0, endbgsp[2], NULL); // planet
+
+			// player's escape pod
+			V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+				(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+				FRACUNIT, 0, endescp[4], NULL);
+			if (parallaxticker > -19)
+			{
+				INT32 trans = (-parallaxticker)>>1;
+				if (trans < 0)
+					trans = 0;
+				V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+					(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+					FRACUNIT, trans<<V_ALPHASHIFT, endescp[(finalecount/2)&3], NULL);
+			}
+
+			if (goodending && parallaxticker > 0) // gunchedrock
+			{
+				INT32 scale = FRACUNIT + ((parallaxticker-10)<<7);
+				INT32 trans = parallaxticker>>2;
+				UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JET, GTC_CACHE);
+
+				if (parallaxticker < 10)
+				{
+					tweakx = parallaxticker<<FRACBITS;
+					tweaky = ((7*parallaxticker)<<(FRACBITS-2))/5;
+				}
+				else
+				{
+					tweakx = 10<<FRACBITS;
+					tweaky = 7<<(FRACBITS-1);
+				}
+				i += tweakx;
+				j -= tweaky;
+
+				x <<= 1;
+				y <<= 1;
+
+				// center detritrus
+				V_DrawFixedPatch(i-x, j-y, FRACUNIT, 0, endegrk[0], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch(i-x, j-y, FRACUNIT, trans<<V_ALPHASHIFT, endegrk[0], NULL);
+
+				 // ring detritrus
+				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y) - ((7<<FRACBITS)/2), scale, 0, endegrk[1], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y), scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
+
+				scale += ((parallaxticker-10)<<7);
+
+				 // shard detritrus
+				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2) - ((7<<FRACBITS)/2), scale, 0, endxpld[0], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2), scale, trans<<V_ALPHASHIFT, endxpld[0], NULL);
+			}
+		}
+		else if (goodending)
+		{
+			tweakx = 10<<FRACBITS;
+			tweaky = 7<<(FRACBITS-1);
+			i += tweakx;
+			j += tweaky;
+			x <<= 1;
+			y <<= 1;
+		}
+
+		if (goodending && parallaxticker > 0)
+		{
+			i -= (3+(tweakx<<1));
+			j += tweaky<<2;
+		}
+
+		if (parallaxticker <= 70) // eggrock/blackrock
+		{
+			INT32 trans;
+			fixed_t scale = FRACUNIT;
+			UINT8 *colormap[2] = {NULL, NULL};
+
+			x += i;
+			y += j;
+
+			if (parallaxticker > 66)
+			{
+				scale = ((70 - parallaxticker)<<(FRACBITS-2));
+				x += (30*(FRACUNIT-scale));
+				y += (30*(FRACUNIT-scale));
+			}
+			else if ((parallaxticker > 60) || (goodending && parallaxticker > 0))
+				;
+			else
+			{
+				doexplosions = true;
+				if (!sparklloop)
+				{
+					x += ((sparkloffs[0][0] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
+					y += ((sparkloffs[0][1] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
+				}
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+				parallaxticker -= 40;
+
+			if ((-parallaxticker/4) < 5)
+			{
+				trans = (-parallaxticker/4) + 5;
+				if (trans < 0)
+					trans = 0;
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endglow[(finalecount & 1) ? 0 : 1], NULL);
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+			{
+				if (finalecount < INFLECTIONPOINT+10)
+					V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, INFLECTIONPOINT+10-finalecount);
+				parallaxticker -= 30;
+			}
+
+			if ((parallaxticker/2) > -15)
+				colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
+			if ((parallaxticker/2) > -25)
+			{
+				trans = (parallaxticker/2) + 15;
+				if (trans < 0)
+					trans = -trans;
+				if (trans < 10)
+					V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat,
+						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+			{
+				if (finalecount < INFLECTIONPOINT+10)
+					V_DrawFixedPatch(x, y, scale, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, rockpat,
+						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
+			}
+			else
+			{
+				if ((-parallaxticker/2) < -5)
+					colormap[1] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+
+				V_DrawFixedPatch(x, y, scale, 0, endegrk[0], colormap[1]);
+
+				if ((-parallaxticker/2) < 5)
+				{
+					trans = (-parallaxticker/2) + 5;
+					if (trans < 0)
+						trans = -trans;
+					if (trans < 10)
+						V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
+				}
+			}
+		}
+		else // firework
+		{
+			fixed_t scale = FRACUNIT;
+			INT32 frame;
+			UINT8 *colormap = NULL;
+			parallaxticker -= 70;
+			x += ((BASEVIDWIDTH-3)<<(FRACBITS-1)) - tweakx;
+			y += (BASEVIDHEIGHT<<(FRACBITS-1)) + tweaky;
+			borderstuff = true;
+
+			if (parallaxticker < 5)
+			{
+				scale = (parallaxticker<<FRACBITS)/4;
+				V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 31, parallaxticker*2);
+			}
+			else
+				scale += (parallaxticker-4)<<5;
+
+			if (goodending)
+				colormap = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE);
+
+			if ((frame = ((parallaxticker & 1) ? 1 : 0) + (parallaxticker/TICRATE)) < 3)
+				V_DrawFixedPatch(x, y, scale, 0, endfwrk[frame], colormap);
+		}
+
+		// explosions
+		if (sparklloop >= 3 && doexplosions)
+		{
+			INT32 boomtime = parallaxticker - sparklloop;
+
+			x = ((((BASEVIDWIDTH-82)/2)+11)<<FRACBITS) - ((boomtime*20)<<FRACBITS)/INFLECTIONPOINT;
+			y = ((((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS) + ((boomtime*7)<<FRACBITS)/INFLECTIONPOINT;
+
+			V_DrawFixedPatch(x + sparkloffs[0][0], y + sparkloffs[0][1],
+				FRACUNIT, 0, endxpld[sparklloop/4], NULL);
+		}
+
+		// initial fade
+		if (finalecount < 30)
+			V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 30-finalecount);
+
+		// border - only emeralds can exist outside it
+		{
+			INT32 trans = 0;
+			if (borderstuff)
+				trans = (10*parallaxticker)/(3*TICRATE);
+			if (trans < 10)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[0]);
+			if (borderstuff && parallaxticker < 11)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (parallaxticker-1)<<V_ALPHASHIFT, endbrdr[1]);
+			else if (goodending && finalecount > INFLECTIONPOINT && finalecount < INFLECTIONPOINT+10)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, endbrdr[1]);
+		}
+
+		// emeralds and emerald accessories
+		if (goodending && finalecount >= TICRATE && finalecount < INFLECTIONPOINT)
+		{
+			INT32 workingtime = finalecount - TICRATE;
+			fixed_t radius = ((vid.width/vid.dupx)*(INFLECTIONPOINT - TICRATE - workingtime))/(INFLECTIONPOINT - TICRATE);
+			angle_t fa;
+			INT32 eemeralds_cur[4];
+			char patchname[7] = "CEMGx0";
+
+			radius <<= FRACBITS;
+
+			for (i = 0; i < 4; ++i)
+			{
+				if (i == 1)
+					workingtime -= sparklloop;
+				else if (i)
+					workingtime -= SPARKLLOOPTIME;
+				eemeralds_cur[i] = (workingtime % 360)<<FRACBITS;
+			}
+
+			// sparkles
+			for (i = 0; i < 7; ++i)
+			{
+				UINT8* colormap;
+				skincolors_t col = SKINCOLOR_GREEN;
+				switch (i)
+				{
+					case 1:
+						col = SKINCOLOR_MAGENTA;
+						break;
+					case 2:
+						col = SKINCOLOR_BLUE;
+						break;
+					case 3:
+						col = SKINCOLOR_SKY;
+						break;
+					case 4:
+						col = SKINCOLOR_ORANGE;
+						break;
+					case 5:
+						col = SKINCOLOR_RED;
+						break;
+					case 6:
+						col = SKINCOLOR_GREY;
+					default:
+					case 0:
+						break;
+				}
+
+				colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_CACHE);
+
+				j = (sparklloop & 1) ? 2 : 3;
+				while (j)
+				{
+					fa = (FixedAngle(eemeralds_cur[j])>>ANGLETOFINESHIFT) & FINEMASK;
+					x =  (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
+					y = (BASEVIDHEIGHT<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
+					eemeralds_cur[j] += (360<<FRACBITS)/7;
+
+					// if j == 0 - alternate between 0 and 1
+					//         1 -                   1 and 2
+					//         2 -                   2 and not rendered
+					V_DrawFixedPatch(x, y, FRACUNIT, 0, endspkl[(j - ((sparklloop & 1) ? 0 : 1))], colormap);
+
+					j--;
+				}
+			}
+
+			// ...then emeralds themselves
+			for (i = 0; i < 7; ++i)
+			{
+				fa = (FixedAngle(eemeralds_cur[0])>>ANGLETOFINESHIFT) & FINEMASK;
+				x = (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
+				y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
+				eemeralds_cur[0] += (360<<FRACBITS)/7;
+
+				patchname[4] = 'A'+(char)i;
+				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_LEVEL), NULL);
+			}
+		} // if (goodending...
+	} // (finalecount > 20)
+
+	// look, i make an ending for you last-minute, the least you could do is let me have this
+	if (cv_soundtest.value == 413)
+	{
+		INT32 trans = 0;
+		boolean donttouch = false;
+		const char *str;
+		if (goodending)
+			str = va("[S] %s: Engage.", skins[players[consoleplayer].skin].realname);
+		else
+			str = "[S] Eggman: Abscond.";
+
+		if (finalecount < 10)
+			trans = (10-finalecount)/2;
+		else if (finalecount > (2*INFLECTIONPOINT) - 20)
+		{
+			trans = 10 + (finalecount/2) - INFLECTIONPOINT;
+			donttouch = true;
+		}
+
+		if (trans != 10)
+		{
+			//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 == (2*INFLECTIONPOINT)-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= (2*INFLECTIONPOINT)-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
+		}
+
+		if (finalecount > (2*INFLECTIONPOINT)-(20+(2*TICRATE)))
+		{
+			INT32 trans2 = abs((5*FINECOSINE((FixedAngle((finalecount*5)<<FRACBITS)>>ANGLETOFINESHIFT & FINEMASK)))>>FRACBITS)+2;
+			if (!donttouch)
+			{
+				trans = 10 + ((2*INFLECTIONPOINT)-(20+(2*TICRATE))) - finalecount;
+				if (trans > trans2)
+					trans2 = trans;
+			}
+			else
+				trans2 += 2*trans;
+			if (trans2 < 10)
+				V_DrawCharacter(26, BASEVIDHEIGHT-33, '\x1C'|(trans2<<V_ALPHASHIFT), false);
+		}
+	}
+}
+
+#undef SPARKLLOOPTIME
+
+// ==========
+//  GAME END
+// ==========
+void F_StartGameEnd(void)
+{
+	G_SetGamestate(GS_GAMEEND);
+
+	gameaction = ga_nothing;
+	paused = false;
+	CON_ToggleOff();
+	S_StopMusic();
+	S_StopSounds();
+
+	// In case menus are still up?!!
+	M_ClearMenus(true);
+
+	timetonext = TICRATE;
+}
+
+//
+// F_GameEndDrawer
+//
+void F_GameEndDrawer(void)
+{
+	// this function does nothing
+}
+
+//
+// F_GameEndTicker
+//
+void F_GameEndTicker(void)
+{
+	if (timetonext > 0)
+		timetonext--;
+	else
+		D_StartTitle();
+}
+
+
+// ==============
+//  TITLE SCREEN
+// ==============
+
+void F_InitMenuPresValues(void)
+{
+	menuanimtimer = 0;
+	prevMenuId = 0;
+	activeMenuId = MainDef.menuid;
+
+	// Set defaults for presentation values
+	strncpy(curbgname, "TITLESKY", 9);
+	curfadevalue = 16;
+	curhidepics = hidetitlepics;
+	curbgcolor = -1;
+	curbgxspeed = titlescrollxspeed;
+	curbgyspeed = titlescrollyspeed;
+	curbghide = true;
+
+	// Find current presentation values
+	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "SRB2BACK" : "TITLESKY");
+	M_SetMenuCurFadeValue(16);
+	M_SetMenuCurHideTitlePics();
+}
+
+//
+// F_SkyScroll
+//
+void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, 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 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+	INT16 patwidth, patheight;
+	INT32 pw, ph; // scaled by dupz
+	patch_t *pat;
+	INT32 i, j;
+
+	if (rendermode == render_none)
+		return;
+
+	if (!patchname || !patchname[0])
+	{
+		V_DrawFill(0, 0, vid.width, vid.height, 31);
+		return;
+	}
+
+	if (!scrollxspeed && !scrollyspeed)
+	{
+		V_DrawPatchFill(W_CachePatchName(patchname, PU_CACHE));
+		return;
+	}
+
+	pat = W_CachePatchName(patchname, PU_CACHE);
+
+	patwidth = SHORT(pat->width);
+	patheight = SHORT(pat->height);
+	pw = patwidth * dupz;
+	ph = patheight * dupz;
+
+	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;
+
+	xscrolltimer = ((menuanimtimer*scrollxspeed)/16 + patwidth*xneg) % (patwidth);
+	yscrolltimer = ((menuanimtimer*scrollyspeed)/16 + patheight*yneg) % (patheight);
+
+	// coordinate offsets
+	xscrolled = xscrolltimer * dupz;
+	yscrolled = yscrolltimer * dupz;
+
+	for (x = (xispos) ? -pw*(tilex-1)+pw : 0, i = 0;
+		i < tilex;
+		x += pw, i++)
+	{
+		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);
+		}
+	}
+
+	W_UnlockCachedPatch(pat);
+}
+
+void F_StartTitleScreen(void)
+{
+	if (menupres[MN_MAIN].musname[0])
+		S_ChangeMusic(menupres[MN_MAIN].musname, menupres[MN_MAIN].mustrack, menupres[MN_MAIN].muslooping);
+	else
+		S_ChangeMusicInternal("_title", looptitle);
+
+	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
+	{
+		finalecount = 0;
+		wipetypepost = menupres[MN_MAIN].enterwipe;
+	}
+	else
+		wipegamestate = GS_TITLESCREEN;
+
+	if (titlemap)
+	{
+		mapthing_t *startpos;
+
+		gamestate_t prevwipegamestate = wipegamestate;
+		titlemapinaction = TITLEMAP_LOADING;
+		titlemapcameraref = NULL;
+		gamemap = titlemap;
+
+		if (!mapheaderinfo[gamemap-1])
+			P_AllocMapHeader(gamemap-1);
+
+		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+		globalweather = mapheaderinfo[gamemap-1]->weather;
+
+		G_DoLoadLevel(true);
+		if (!titlemap)
+			return;
+
+		players[displayplayer].playerstate = PST_DEAD; // Don't spawn the player in dummy (I'm still a filthy cheater)
+
+		// Set Default Position
+		if (playerstarts[0])
+			startpos = playerstarts[0];
+		else if (deathmatchstarts[0])
+			startpos = deathmatchstarts[0];
+		else
+			startpos = NULL;
+
+		if (startpos)
+		{
+			camera.x = startpos->x << FRACBITS;
+			camera.y = startpos->y << FRACBITS;
+			camera.subsector = R_PointInSubsector(camera.x, camera.y);
+			camera.z = camera.subsector->sector->floorheight + ((startpos->options >> ZSHIFT) << FRACBITS);
+			camera.angle = (startpos->angle % 360)*ANG1;
+			camera.aiming = 0;
+		}
+		else
+		{
+			camera.x = camera.y = camera.z = camera.angle = camera.aiming = 0;
+			camera.subsector = NULL; // toast is filthy too
+		}
+
+		camera.chase = true;
+		camera.height = 0;
+
+		// Run enter linedef exec for MN_MAIN, since this is where we start
+		if (menupres[MN_MAIN].entertag)
+			P_LinedefExecute(menupres[MN_MAIN].entertag, players[displayplayer].mo, NULL);
+
+		wipegamestate = prevwipegamestate;
+	}
+	else
+	{
+		titlemapinaction = TITLEMAP_OFF;
+		gamemap = 1; // g_game.c
+		CON_ClearHUD();
+	}
+
+	G_SetGamestate(GS_TITLESCREEN);
+
+	// IWAD dependent stuff.
+
+	animtimer = skullAnimCounter = 0;
+
+	demoDelayLeft = demoDelayTime;
 	demoIdleLeft = demoIdleTime;
 
 	ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
@@ -1517,19 +2275,24 @@ void F_StartTitleScreen(void)
 // (no longer) De-Demo'd Title Screen
 void F_TitleScreenDrawer(void)
 {
+	boolean hidepics;
+
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
 
 	// Draw that sky!
-	if (!titlemapinaction)
-		F_SkyScroll(titlescrollspeed);
+	if (curbgcolor >= 0)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+	else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS)
+		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
 
-	// Don't draw outside of the title screewn, or if the patch isn't there.
+	// Don't draw outside of the title screen, or if the patch isn't there.
 	if (!ttwing || (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS))
 		return;
 
 	// rei|miru: use title pics?
-	if (hidetitlepics)
+	hidepics = curhidepics;
+	if (hidepics)
 #ifdef HAVE_BLUA
 		goto luahook;
 #else
@@ -1579,6 +2342,14 @@ luahook:
 #endif
 }
 
+// separate animation timer for backgrounds, since we also count
+// during GS_TIMEATTACK
+void F_MenuPresTicker(boolean run)
+{
+	if (run)
+		menuanimtimer++;
+}
+
 // (no longer) De-Demo'd Title Screen
 void F_TitleScreenTicker(boolean run)
 {
@@ -1596,22 +2367,28 @@ void F_TitleScreenTicker(boolean run)
 		mobj_t *mo2;
 		mobj_t *cameraref = NULL;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		// If there's a Line 422 Switch Cut-Away view, don't force us.
+		if (!titlemapcameraref || titlemapcameraref->type != MT_ALTVIEWMAN)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
-				continue;
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+			{
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
 
-			mo2 = (mobj_t *)th;
+				mo2 = (mobj_t *)th;
 
-			 if (!mo2)
-				continue;
+				if (!mo2)
+					continue;
 
-			if (mo2->type != MT_ALTVIEWMAN)
-				continue;
+				if (mo2->type != MT_ALTVIEWMAN)
+					continue;
 
-			cameraref = mo2;
-			break;
+				cameraref = titlemapcameraref = mo2;
+				break;
+			}
 		}
+		else
+			cameraref = titlemapcameraref;
 
 		if (cameraref)
 		{
@@ -1625,7 +2402,7 @@ void F_TitleScreenTicker(boolean run)
 		else
 		{
 			// Default behavior: Do a lil' camera spin if a title map is loaded;
-			camera.angle += titlescrollspeed*ANG1/64;
+			camera.angle += titlescrollxspeed*ANG1/64;
 		}
 	}
 
@@ -1704,7 +2481,6 @@ void F_StartContinue(void)
 	keypressed = false;
 	paused = false;
 	CON_ToggleOff();
-	CON_ClearHUD();
 
 	// In case menus are still up?!!
 	M_ClearMenus(true);
@@ -1793,7 +2569,7 @@ boolean F_ContinueResponder(event_t *event)
 	keypressed = true;
 	imcontinuing = true;
 	continuetime = TICRATE;
-	S_StartSound(0, sfx_itemup);
+	S_StartSound(NULL, sfx_itemup);
 	return true;
 }
 
@@ -1801,7 +2577,7 @@ boolean F_ContinueResponder(event_t *event)
 //  CUSTOM CUTSCENES
 // ==================
 static INT32 scenenum, cutnum;
-static INT32 picxpos, picypos, picnum, pictime;
+static INT32 picxpos, picypos, picnum, pictime, picmode, numpics, pictoloop;
 static INT32 textxpos, textypos;
 static boolean dofadenow = false, cutsceneover = false;
 static boolean runningprecutscene = false, precutresetplayer = false;
@@ -1824,9 +2600,10 @@ static void F_AdvanceToNextScene(void)
 	picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum];
 
 	if (cutscenes[cutnum]->scene[scenenum].musswitch[0])
-		S_ChangeMusic(cutscenes[cutnum]->scene[scenenum].musswitch,
+		S_ChangeMusicEx(cutscenes[cutnum]->scene[scenenum].musswitch,
 			cutscenes[cutnum]->scene[scenenum].musswitchflags,
-			cutscenes[cutnum]->scene[scenenum].musicloop);
+			cutscenes[cutnum]->scene[scenenum].musicloop,
+			cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
 
 	// Fade to the next
 	dofadenow = true;
@@ -1869,14 +2646,15 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 
 	G_SetGamestate(GS_CUTSCENE);
 
+	if (wipegamestate == GS_CUTSCENE)
+		wipegamestate = -1;
+
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
 
 	F_NewCutscene(cutscenes[cutscenenum]->scene[0].text);
 
-	CON_ClearHUD();
-
 	cutsceneover = false;
 	runningprecutscene = precutscene;
 	precutresetplayer = resetplayer;
@@ -1897,11 +2675,13 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 	stoptimer = 0;
 
 	if (cutscenes[cutnum]->scene[0].musswitch[0])
-		S_ChangeMusic(cutscenes[cutnum]->scene[0].musswitch,
+		S_ChangeMusicEx(cutscenes[cutnum]->scene[0].musswitch,
 			cutscenes[cutnum]->scene[0].musswitchflags,
-			cutscenes[cutnum]->scene[0].musicloop);
+			cutscenes[cutnum]->scene[0].musicloop,
+			cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
 	else
 		S_StopMusic();
+	S_StopSounds();
 }
 
 //
@@ -1942,7 +2722,7 @@ void F_CutsceneDrawer(void)
 		F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true);
 	}
 
-	V_DrawString(textxpos, textypos, 0, cutscene_disptext);
+	V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, cutscene_disptext);
 }
 
 void F_CutsceneTicker(void)
@@ -1962,7 +2742,7 @@ void F_CutsceneTicker(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (netgame && i != serverplayer && i != adminplayer)
+		if (netgame && i != serverplayer && !IsPlayerAdmin(i))
 			continue;
 
 		if (players[i].cmd.buttons & BT_USE)
@@ -2008,3 +2788,617 @@ boolean F_CutsceneResponder(event_t *event)
 
 	return false;
 }
+
+// ==================
+//  TEXT PROMPTS
+// ==================
+
+static void F_GetPageTextGeometry(UINT8 *pagelines, boolean *rightside, INT32 *boxh, INT32 *texth, INT32 *texty, INT32 *namey, INT32 *chevrony, INT32 *textx, INT32 *textr)
+{
+	// reuse:
+	// cutnum -> promptnum
+	// scenenum -> pagenum
+	lumpnum_t iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname);
+
+	*pagelines = textprompts[cutnum]->page[scenenum].lines ? textprompts[cutnum]->page[scenenum].lines : 4;
+	*rightside = (iconlump != LUMPERROR && textprompts[cutnum]->page[scenenum].rightside);
+
+	// Vertical calculations
+	*boxh = *pagelines*2;
+	*texth = textprompts[cutnum]->page[scenenum].name[0] ? (*pagelines-1)*2 : *pagelines*2; // name takes up first line if it exists
+	*texty = BASEVIDHEIGHT - ((*texth * 4) + (*texth/2)*4);
+	*namey = BASEVIDHEIGHT - ((*boxh * 4) + (*boxh/2)*4);
+	*chevrony = BASEVIDHEIGHT - (((1*2) * 4) + ((1*2)/2)*4); // force on last line
+
+	// Horizontal calculations
+	// Shift text to the right if we have a character icon on the left side
+	// Add 4 margin against icon
+	*textx = (iconlump != LUMPERROR && !*rightside) ? ((*boxh * 4) + (*boxh/2)*4) + 4 : 4;
+	*textr = *rightside ? BASEVIDWIDTH - (((*boxh * 4) + (*boxh/2)*4) + 4) : BASEVIDWIDTH-4;
+}
+
+static fixed_t F_GetPromptHideHudBound(void)
+{
+	UINT8 pagelines;
+	boolean rightside;
+	INT32 boxh, texth, texty, namey, chevrony;
+	INT32 textx, textr;
+
+	if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages ||
+		!textprompts[cutnum]->page[scenenum].hidehud ||
+		(splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced
+		return 0;
+	else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all
+		return BASEVIDHEIGHT;
+
+	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
+
+	// calc boxheight (see V_DrawPromptBack)
+	boxh *= vid.dupy;
+	boxh = (boxh * 4) + (boxh/2)*5; // 4 lines of space plus gaps between and some leeway
+
+	// return a coordinate to check
+	// if negative: don't show hud elements below this coordinate (visually)
+	// if positive: don't show hud elements above this coordinate (visually)
+	return 0 - boxh; // \todo: if prompt at top of screen (someday), make this return positive
+}
+
+boolean F_GetPromptHideHudAll(void)
+{
+	if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages ||
+		!textprompts[cutnum]->page[scenenum].hidehud ||
+		(splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced
+		return false;
+	else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all
+		return true;
+	else
+		return false;
+}
+
+boolean F_GetPromptHideHud(fixed_t y)
+{
+	INT32 ybound;
+	boolean fromtop;
+	fixed_t ytest;
+
+	if (!promptactive)
+		return false;
+
+	ybound = F_GetPromptHideHudBound();
+	fromtop = (ybound >= 0);
+	ytest = (fromtop ? ybound : BASEVIDHEIGHT + ybound);
+
+	return (fromtop ? y < ytest : y >= ytest); // true means hide
+}
+
+static void F_PreparePageText(char *pagetext)
+{
+	UINT8 pagelines;
+	boolean rightside;
+	INT32 boxh, texth, texty, namey, chevrony;
+	INT32 textx, textr;
+
+	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
+
+	if (promptpagetext)
+		Z_Free(promptpagetext);
+	promptpagetext = (pagetext && pagetext[0]) ? V_WordWrap(textx, textr, 0, pagetext) : Z_StrDup("");
+
+	F_NewCutscene(promptpagetext);
+	cutscene_textspeed = textprompts[cutnum]->page[scenenum].textspeed ? textprompts[cutnum]->page[scenenum].textspeed : TICRATE/5;
+	cutscene_textcount = 0; // no delay in beginning
+	cutscene_boostspeed = 0; // don't print 8 characters to start
+
+	// \todo update control hot strings on re-config
+	// and somehow don't reset cutscene text counters
+}
+
+static void F_AdvanceToNextPage(void)
+{
+	INT32 nextprompt = textprompts[cutnum]->page[scenenum].nextprompt ? textprompts[cutnum]->page[scenenum].nextprompt - 1 : INT32_MAX,
+		nextpage = textprompts[cutnum]->page[scenenum].nextpage ? textprompts[cutnum]->page[scenenum].nextpage - 1 : INT32_MAX,
+		oldcutnum = cutnum;
+
+	if (textprompts[cutnum]->page[scenenum].nexttag[0])
+		F_GetPromptPageByNamedTag(textprompts[cutnum]->page[scenenum].nexttag, &nextprompt, &nextpage);
+
+	// determine next prompt
+	if (nextprompt != INT32_MAX)
+	{
+		if (nextprompt <= MAX_PROMPTS && textprompts[nextprompt])
+			cutnum = nextprompt;
+		else
+			cutnum = INT32_MAX;
+	}
+
+	// determine next page
+	if (nextpage != INT32_MAX)
+	{
+		if (cutnum != INT32_MAX)
+		{
+			scenenum = nextpage;
+			if (scenenum >= MAX_PAGES || scenenum > textprompts[cutnum]->numpages-1)
+				scenenum = INT32_MAX;
+		}
+	}
+	else
+	{
+		if (cutnum != oldcutnum)
+			scenenum = 0;
+		else if (scenenum + 1 < MAX_PAGES && scenenum < textprompts[cutnum]->numpages-1)
+			scenenum++;
+		else
+			scenenum = INT32_MAX;
+	}
+
+	// close the prompt if either num is invalid
+	if (cutnum == INT32_MAX || scenenum == INT32_MAX)
+		F_EndTextPrompt(false, false);
+	else
+	{
+		// on page mode, number of tics before allowing boost
+		// on timer mode, number of tics until page advances
+		timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10;
+		F_PreparePageText(textprompts[cutnum]->page[scenenum].text);
+
+		// gfx
+		picnum = textprompts[cutnum]->page[scenenum].pictostart;
+		numpics = textprompts[cutnum]->page[scenenum].numpics;
+		picmode = textprompts[cutnum]->page[scenenum].picmode;
+		pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0;
+		picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
+		picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
+		animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
+
+		// music change
+		if (textprompts[cutnum]->page[scenenum].musswitch[0])
+			S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
+				textprompts[cutnum]->page[scenenum].musswitchflags,
+				textprompts[cutnum]->page[scenenum].musicloop);
+	}
+}
+
+void F_EndTextPrompt(boolean forceexec, boolean noexec)
+{
+	boolean promptwasactive = promptactive;
+	promptactive = false;
+	callpromptnum = callpagenum = callplayer = INT32_MAX;
+
+	if (promptwasactive)
+	{
+		if (promptmo && promptmo->player && promptblockcontrols)
+			promptmo->reactiontime = TICRATE/4; // prevent jumping right away // \todo account freeze realtime for this)
+		// \todo reset frozen realtime?
+	}
+
+	// \todo net safety, maybe loop all player thinkers?
+	if ((promptwasactive || forceexec) && !noexec && promptpostexectag)
+	{
+		if (tmthing) // edge case where starting an invalid prompt immediately on level load will make P_MapStart fail
+			P_LinedefExecute(promptpostexectag, promptmo, NULL);
+		else
+		{
+			P_MapStart();
+			P_LinedefExecute(promptpostexectag, promptmo, NULL);
+			P_MapEnd();
+		}
+	}
+}
+
+void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime)
+{
+	INT32 i;
+
+	// if splitscreen and we already have a prompt active, ignore.
+	// \todo Proper per-player splitscreen support (individual prompts)
+	if (promptactive && splitscreen && promptnum == callpromptnum && pagenum == callpagenum)
+		return;
+
+	// \todo proper netgame support
+	if (netgame)
+	{
+		F_EndTextPrompt(true, false); // run the post-effects immediately
+		return;
+	}
+
+	// We share vars, so no starting text prompts over cutscenes or title screens!
+	keypressed = false;
+	finalecount = 0;
+	timetonext = 0;
+	animtimer = 0;
+	stoptimer = 0;
+	skullAnimCounter = 0;
+
+	// Set up state
+	promptmo = mo;
+	promptpostexectag = postexectag;
+	promptblockcontrols = blockcontrols;
+	(void)freezerealtime; // \todo freeze player->realtime, maybe this needs to cycle through player thinkers
+
+	// Initialize current prompt and scene
+	callpromptnum = promptnum;
+	callpagenum = pagenum;
+	cutnum = (promptnum < MAX_PROMPTS && textprompts[promptnum]) ? promptnum : INT32_MAX;
+	scenenum = (cutnum != INT32_MAX && pagenum < MAX_PAGES && pagenum <= textprompts[cutnum]->numpages-1) ? pagenum : INT32_MAX;
+	promptactive = (cutnum != INT32_MAX && scenenum != INT32_MAX);
+
+	if (promptactive)
+	{
+		// on page mode, number of tics before allowing boost
+		// on timer mode, number of tics until page advances
+		timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10;
+		F_PreparePageText(textprompts[cutnum]->page[scenenum].text);
+
+		// gfx
+		picnum = textprompts[cutnum]->page[scenenum].pictostart;
+		numpics = textprompts[cutnum]->page[scenenum].numpics;
+		picmode = textprompts[cutnum]->page[scenenum].picmode;
+		pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0;
+		picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
+		picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
+		animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
+
+		// music change
+		if (textprompts[cutnum]->page[scenenum].musswitch[0])
+			S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
+				textprompts[cutnum]->page[scenenum].musswitchflags,
+				textprompts[cutnum]->page[scenenum].musicloop);
+
+		// get the calling player
+		if (promptblockcontrols && mo && mo->player)
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (players[i].mo == mo)
+				{
+					callplayer = i;
+					break;
+				}
+			}
+		}
+	}
+	else
+		F_EndTextPrompt(true, false); // run the post-effects immediately
+}
+
+static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
+{
+	INT32 gcs = gcs_custom;
+	boolean suffixed = true;
+
+	if (!tag || !tag[0] || !tutorialmode)
+		return false;
+
+	if (!strncmp(tag, "TAM", 3)) // Movement
+		gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement);
+	else if (!strncmp(tag, "TAC", 3)) // Camera
+	{
+		// Check for gcl_movement so we can differentiate between FPS and Platform schemes.
+		gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement);
+		if (gcs == gcs_custom) // try again, maybe we'll get a match
+			gcs = G_GetControlScheme(gamecontrol, gcl_camera, num_gcl_camera);
+		if (gcs == gcs_fps && !cv_usemouse.value)
+			gcs = gcs_platform; // Platform (arrow) scheme is stand-in for no mouse
+	}
+	else if (!strncmp(tag, "TAD", 3)) // Movement and Camera
+		gcs = G_GetControlScheme(gamecontrol, gcl_movement_camera, num_gcl_movement_camera);
+	else if (!strncmp(tag, "TAJ", 3)) // Jump
+		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
+	else if (!strncmp(tag, "TAS", 3)) // Spin
+		gcs = G_GetControlScheme(gamecontrol, gcl_use, num_gcl_use);
+	else if (!strncmp(tag, "TAA", 3)) // Char ability
+		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
+	else if (!strncmp(tag, "TAW", 3)) // Shield ability
+		gcs = G_GetControlScheme(gamecontrol, gcl_jump_use, num_gcl_jump_use);
+	else
+		gcs = G_GetControlScheme(gamecontrol, gcl_tutorial_used, num_gcl_tutorial_used);
+
+	switch (gcs)
+	{
+		case gcs_fps:
+			// strncat(tag, "FPS", length);
+			suffixed = false;
+			break;
+
+		case gcs_platform:
+			strncat(tag, "PLATFORM", length);
+			break;
+
+		default:
+			strncat(tag, "CUSTOM", length);
+			break;
+	}
+
+	return suffixed;
+}
+
+void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum)
+{
+	INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX;
+	INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0;
+	boolean suffixed = false, found = false;
+	char suffixedtag[33];
+
+	*promptnum = *pagenum = INT32_MAX;
+
+	if (!tag || !tag[0])
+		return;
+
+	strncpy(suffixedtag, tag, 33);
+	suffixedtag[32] = 0;
+
+	if (tutorialmode)
+		suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33);
+
+	for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++)
+	{
+		if (!textprompts[*promptnum])
+			continue;
+
+		for (*pagenum = 0; *pagenum < textprompts[*promptnum]->numpages && *pagenum < MAX_PAGES; (*pagenum)++)
+		{
+			if (suffixed && fastcmp(suffixedtag, textprompts[*promptnum]->page[*pagenum].tag))
+			{
+				// this goes first because fastcmp ends early if first string is shorter
+				found = true;
+				break;
+			}
+			else if (nosuffixpromptnum == INT32_MAX && nosuffixpagenum == INT32_MAX && fastcmp(tag, textprompts[*promptnum]->page[*pagenum].tag))
+			{
+				if (suffixed)
+				{
+					nosuffixpromptnum = *promptnum;
+					nosuffixpagenum = *pagenum;
+					// continue searching for the suffixed tag
+				}
+				else
+				{
+					found = true;
+					break;
+				}
+			}
+		}
+
+		if (found)
+			break;
+	}
+
+	if (suffixed && !found && nosuffixpromptnum != INT32_MAX && nosuffixpagenum != INT32_MAX)
+	{
+		found = true;
+		*promptnum = nosuffixpromptnum;
+		*pagenum = nosuffixpagenum;
+	}
+
+	if (!found)
+		CONS_Debug(DBG_GAMELOGIC, "Text prompt: Can't find a page with named tag %s or suffixed tag %s\n", tag, suffixedtag);
+}
+
+void F_TextPromptDrawer(void)
+{
+	// reuse:
+	// cutnum -> promptnum
+	// scenenum -> pagenum
+	lumpnum_t iconlump;
+	UINT8 pagelines;
+	boolean rightside;
+	INT32 boxh, texth, texty, namey, chevrony;
+	INT32 textx, textr;
+
+	// Data
+	patch_t *patch;
+
+	if (!promptactive)
+		return;
+
+	iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname);
+	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
+
+	// Draw gfx first
+	if (picnum >= 0 && picnum < numpics && textprompts[cutnum]->page[scenenum].picname[picnum][0] != '\0')
+	{
+		if (textprompts[cutnum]->page[scenenum].pichires[picnum])
+			V_DrawSmallScaledPatch(picxpos, picypos, 0,
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_CACHE));
+		else
+			V_DrawScaledPatch(picxpos,picypos, 0,
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_CACHE));
+	}
+
+	// Draw background
+	V_DrawPromptBack(boxh, textprompts[cutnum]->page[scenenum].backcolor);
+
+	// Draw narrator icon
+	if (iconlump != LUMPERROR)
+	{
+		INT32 iconx, icony, scale, scaledsize;
+		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_CACHE);
+
+		// scale and center
+		if (patch->width > patch->height)
+		{
+			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width);
+			scaledsize = FixedMul(patch->height, scale);
+			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
+			icony = ((namey-4) << FRACBITS) + FixedDiv(BASEVIDHEIGHT - namey + 4 - scaledsize, 2); // account for 4 margin
+		}
+		else if (patch->height > patch->width)
+		{
+			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->height);
+			scaledsize = FixedMul(patch->width, scale);
+			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
+			icony = namey << FRACBITS;
+			iconx += FixedDiv(FixedMul(patch->height, scale) - scaledsize, 2);
+		}
+		else
+		{
+			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width);
+			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
+			icony = namey << FRACBITS;
+		}
+
+		if (textprompts[cutnum]->page[scenenum].iconflip)
+			iconx += FixedMul(patch->width, scale) << FRACBITS;
+
+		V_DrawFixedPatch(iconx, icony, scale, (V_SNAPTOBOTTOM|(textprompts[cutnum]->page[scenenum].iconflip ? V_FLIP : 0)), patch, NULL);
+		W_UnlockCachedPatch(patch);
+	}
+
+	// Draw text
+	V_DrawString(textx, texty, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), cutscene_disptext);
+
+	// Draw name
+	// Don't use V_YELLOWMAP here so that the name color can be changed with control codes
+	if (textprompts[cutnum]->page[scenenum].name[0])
+		V_DrawString(textx, namey, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), textprompts[cutnum]->page[scenenum].name);
+
+	// Draw chevron
+	if (promptblockcontrols && !timetonext)
+		V_DrawString(textr-8, chevrony + (skullAnimCounter/5), (V_SNAPTOBOTTOM|V_YELLOWMAP), "\x1B"); // down arrow
+}
+
+void F_TextPromptTicker(void)
+{
+	INT32 i;
+
+	if (!promptactive || paused || P_AutoPause())
+		return;
+
+	// advance animation
+	finalecount++;
+	cutscene_boostspeed = 0;
+
+	// for the chevron
+	if (--skullAnimCounter <= 0)
+		skullAnimCounter = 8;
+
+	// button handling
+	if (textprompts[cutnum]->page[scenenum].timetonext)
+	{
+		if (promptblockcontrols) // same procedure as below, just without the button handling
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (netgame && i != serverplayer && !IsPlayerAdmin(i))
+					continue;
+				else if (splitscreen) {
+					// Both players' controls are locked,
+					// But only consoleplayer can advance the prompt.
+					// \todo Proper per-player splitscreen support (individual prompts)
+					if (i == consoleplayer || i == secondarydisplayplayer)
+						players[i].powers[pw_nocontrol] = 1;
+				}
+				else if (i == consoleplayer)
+					players[i].powers[pw_nocontrol] = 1;
+
+				if (!splitscreen)
+					break;
+			}
+		}
+
+		if (timetonext >= 1)
+			timetonext--;
+
+		if (!timetonext)
+			F_AdvanceToNextPage();
+
+		F_WriteText();
+	}
+	else
+	{
+		if (promptblockcontrols)
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (netgame && i != serverplayer && !IsPlayerAdmin(i))
+					continue;
+				else if (splitscreen) {
+					// Both players' controls are locked,
+					// But only the triggering player can advance the prompt.
+					if (i == consoleplayer || i == secondarydisplayplayer)
+					{
+						players[i].powers[pw_nocontrol] = 1;
+
+						if (callplayer == consoleplayer || callplayer == secondarydisplayplayer)
+						{
+							if (i != callplayer)
+								continue;
+						}
+						else if (i != consoleplayer)
+							continue;
+					}
+					else
+						continue;
+				}
+				else if (i == consoleplayer)
+					players[i].powers[pw_nocontrol] = 1;
+				else
+					continue;
+
+				if ((players[i].cmd.buttons & BT_USE) || (players[i].cmd.buttons & BT_JUMP))
+				{
+					if (timetonext > 1)
+						timetonext--;
+					else if (cutscene_baseptr) // don't set boost if we just reset the string
+						cutscene_boostspeed = 1; // only after a slight delay
+
+					if (keypressed)
+					{
+						if (!splitscreen)
+							break;
+						else
+							continue;
+					}
+
+					if (!timetonext) // is 0 when finished generating text
+					{
+						F_AdvanceToNextPage();
+						if (promptactive)
+							S_StartSound(NULL, sfx_menu1);
+					}
+					keypressed = true; // prevent repeat events
+				}
+				else if (!(players[i].cmd.buttons & BT_USE) && !(players[i].cmd.buttons & BT_JUMP))
+					keypressed = false;
+
+				if (!splitscreen)
+					break;
+			}
+		}
+
+		// generate letter-by-letter text
+		if (scenenum >= MAX_PAGES ||
+			!textprompts[cutnum]->page[scenenum].text ||
+			!textprompts[cutnum]->page[scenenum].text[0] ||
+			!F_WriteText())
+			timetonext = !promptblockcontrols; // never show the chevron if we can't toggle pages
+	}
+
+	// gfx
+	if (picnum >= 0 && picnum < numpics)
+	{
+		if (animtimer <= 0)
+		{
+			boolean persistanimtimer = false;
+
+			if (picnum < numpics-1 && textprompts[cutnum]->page[scenenum].picname[picnum+1][0] != '\0')
+				picnum++;
+			else if (picmode == PROMPT_PIC_LOOP)
+				picnum = pictoloop;
+			else if (picmode == PROMPT_PIC_DESTROY)
+				picnum = -1;
+			else // if (picmode == PROMPT_PIC_PERSIST)
+				persistanimtimer = true;
+
+			if (!persistanimtimer && picnum >= 0)
+			{
+				picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
+				picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
+				pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
+				animtimer = pictime;
+			}
+		}
+		else
+			animtimer--;
+	}
+}
diff --git a/src/f_finale.h b/src/f_finale.h
index aadc64ad08e8abb9547155c8ada4420b49468146..d640abc8a4896590a406d79a5505050cd20cd29f 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -17,6 +17,7 @@
 
 #include "doomtype.h"
 #include "d_event.h"
+#include "p_mobj.h"
 
 //
 // FINALE
@@ -33,16 +34,21 @@ void F_IntroTicker(void);
 void F_TitleScreenTicker(boolean run);
 void F_CutsceneTicker(void);
 void F_TitleDemoTicker(void);
+void F_TextPromptTicker(void);
 
 // Called by main loop.
-FUNCMATH void F_GameEndDrawer(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_GameEvaluationDrawer(void);
 void F_StartGameEvaluation(void);
 void F_GameEvaluationTicker(void);
 
+void F_EndingTicker(void);
+void F_EndingDrawer(void);
+
 void F_CreditTicker(void);
 void F_CreditDrawer(void);
 
@@ -50,9 +56,17 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 void F_CutsceneDrawer(void);
 void F_EndCutScene(void);
 
+void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime);
+void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum);
+void F_TextPromptDrawer(void);
+void F_EndTextPrompt(boolean forceexec, boolean noexec);
+boolean F_GetPromptHideHudAll(void);
+boolean F_GetPromptHideHud(fixed_t y);
+
 void F_StartGameEnd(void);
 void F_StartIntro(void);
 void F_StartTitleScreen(void);
+void F_StartEnding(void);
 void F_StartCredits(void);
 
 boolean F_ContinueResponder(event_t *event);
@@ -60,7 +74,8 @@ void F_StartContinue(void);
 void F_ContinueTicker(void);
 void F_ContinueDrawer(void);
 
-extern INT32 titlescrollspeed;
+extern INT32 titlescrollxspeed;
+extern INT32 titlescrollyspeed;
 
 typedef enum
 {
@@ -69,17 +84,37 @@ typedef enum
 	TITLEMAP_RUNNING
 } titlemap_enum;
 
-extern UINT8 titlemapinaction;
+// Current menu parameters
+
+extern mobj_t *titlemapcameraref;
+extern char curbgname[9];
+extern SINT8 curfadevalue;
+extern boolean curhidepics;
+extern INT32 curbgcolor;
+extern INT32 curbgxspeed;
+extern INT32 curbgyspeed;
+extern boolean curbghide;
+
+#define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
+
+void F_InitMenuPresValues(void);
+void F_MenuPresTicker(boolean run);
 
 //
 // WIPE
 //
+// HACK for menu fading while titlemapinaction; skips the level check
+#define FORCEWIPE -3
+#define FORCEWIPEOFF -2
+
 extern boolean WipeInAction;
 extern INT32 lastwipetic;
 
 void F_WipeStartScreen(void);
 void F_WipeEndScreen(void);
 void F_RunWipe(UINT8 wipetype, boolean drawMenu);
+tic_t F_GetWipeLength(UINT8 wipetype);
+boolean F_WipeExists(UINT8 wipetype);
 
 enum
 {
@@ -94,6 +129,7 @@ enum
 	wipe_evaluation_toblack,
 	wipe_gameend_toblack,
 	wipe_intro_toblack,
+	wipe_ending_toblack,
 	wipe_cutscene_toblack,
 
 	// custom intermissions
@@ -110,15 +146,16 @@ enum
 	wipe_evaluation_final,
 	wipe_gameend_final,
 	wipe_intro_final,
+	wipe_ending_final,
 	wipe_cutscene_final,
 
 	// custom intermissions
 	wipe_specinter_final,
 	wipe_multinter_final,
 
-	NUMWIPEDEFS
+	NUMWIPEDEFS,
+	WIPEFINALSHIFT = (wipe_level_final-wipe_level_toblack)
 };
-#define WIPEFINALSHIFT 13
 extern UINT8 wipedefs[NUMWIPEDEFS];
 
 #endif
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 49ab9cc01442ae5496a9d7533647fb688207ebd0..05229f844dfd6f11bc59311f351cff335d50b30f 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -54,6 +54,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 	0,  // wipe_evaluation_toblack
 	0,  // wipe_gameend_toblack
 	99, // wipe_intro_toblack (hardcoded)
+	0,  // wipe_ending_toblack
 	99, // wipe_cutscene_toblack (hardcoded)
 
 	0,  // wipe_specinter_toblack
@@ -69,6 +70,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 	0,  // wipe_evaluation_final
 	0,  // wipe_gameend_final
 	99, // wipe_intro_final (hardcoded)
+	0,  // wipe_ending_final
 	99, // wipe_cutscene_final (hardcoded)
 
 	0,  // wipe_specinter_final
@@ -378,3 +380,48 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 	WipeInAction = false;
 #endif
 }
+
+/** Returns tic length of wipe
+  * One lump equals one tic
+  */
+tic_t F_GetWipeLength(UINT8 wipetype)
+{
+#ifdef NOWIPE
+	return 0;
+#else
+	static char lumpname[10] = "FADEmmss";
+	lumpnum_t lumpnum;
+	UINT8 wipeframe;
+
+	if (wipetype > 99)
+		return 0;
+
+	for (wipeframe = 0; wipeframe < 100; wipeframe++)
+	{
+		sprintf(&lumpname[4], "%.2hu%.2hu", (UINT16)wipetype, (UINT16)wipeframe);
+
+		lumpnum = W_CheckNumForName(lumpname);
+		if (lumpnum == LUMPERROR)
+			return --wipeframe;
+	}
+	return --wipeframe;
+#endif
+}
+
+boolean F_WipeExists(UINT8 wipetype)
+{
+#ifdef NOWIPE
+	return false;
+#else
+	static char lumpname[10] = "FADEmm00";
+	lumpnum_t lumpnum;
+
+	if (wipetype > 99)
+		return false;
+
+	sprintf(&lumpname[4], "%.2hu00", (UINT16)wipetype);
+
+	lumpnum = W_CheckNumForName(lumpname);
+	return !(lumpnum == LUMPERROR);
+#endif
+}
diff --git a/src/filesrch.c b/src/filesrch.c
index 2b5493642789272e551c30bb48e7a3ece8fab175..8f157bdd522c86af9cb16c647de8f802cb7f42d6 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -306,9 +306,14 @@ closedir (DIR * dirp)
 }
 #endif
 
-static CV_PossibleValue_t addons_cons_t[] = {{0, "SRB2 Folder"}, /*{1, "HOME"}, {2, "SRB2 Folder"},*/ {3, "CUSTOM"}, {0, NULL}};
-consvar_t cv_addons_option = {"addons_option", "SRB2 Folder", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_addons_folder = {"addons_folder", "./", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"},
+#if 1
+												{1, "HOME"}, {2, "SRB2"},
+#endif
+													{3, "CUSTOM"}, {0, NULL}};
+
+consvar_t cv_addons_option = {"addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_folder = {"addons_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}};
 consvar_t cv_addons_md5 = {"addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -326,10 +331,11 @@ size_t menudepthleft = menudepth;
 
 char menusearch[MAXSTRINGLENGTH+1];
 
-char **dirmenu;
-size_t sizedirmenu;
+char **dirmenu, **coredirmenu; // core only local for this file
+size_t sizedirmenu, sizecoredirmenu; // ditto
 size_t dir_on[menudepth];
 UINT8 refreshdirmenu = 0;
+char *refreshdirname = NULL;
 
 size_t packetsizetally = 0;
 size_t mainwadstally = 0;
@@ -362,9 +368,9 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 		return FS_NOTFOUND;
 	}
 
-	if (searchpath[searchpathindex[depthleft]-2] != '/')
+	if (searchpath[searchpathindex[depthleft]-2] != PATHSEP[0])
 	{
-		searchpath[searchpathindex[depthleft]-1] = '/';
+		searchpath[searchpathindex[depthleft]-1] = PATHSEP[0];
 		searchpath[searchpathindex[depthleft]] = 0;
 	}
 	else
@@ -406,7 +412,7 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 					depthleft++;
 			}
 
-			searchpath[searchpathindex[depthleft]-1]='/';
+			searchpath[searchpathindex[depthleft]-1]=PATHSEP[0];
 			searchpath[searchpathindex[depthleft]]=0;
 		}
 		else if (!strcasecmp(searchname, dent->d_name))
@@ -439,39 +445,173 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 	return retval;
 }
 
-char exttable[NUM_EXT_TABLE][5] = {
-	".txt", ".cfg", // exec
-	".wad", ".pk3", ".soc", ".lua"}; // addfile
+char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) plus 3 (null terminator, stop, and length including previous two)
+	"\5.txt", "\5.cfg", // exec
+	"\5.wad",
+#ifdef USE_KART
+	"\6.kart",
+#endif
+	"\5.pk3", "\5.soc", "\5.lua"}; // addfile
 
 char filenamebuf[MAX_WADFILES][MAX_WADPATH];
 
 
-static boolean filemenusearch(char *haystack, char *needle)
+static boolean filemenucmp(char *haystack, char *needle)
 {
 	static char localhaystack[128];
 	strlcpy(localhaystack, haystack, 128);
 	if (!cv_addons_search_case.value)
 		strupr(localhaystack);
-	return ((cv_addons_search_type.value)
-		? (strstr(localhaystack, needle) != 0)
-		: (!strncmp(localhaystack, needle, menusearch[0])));
+	if (cv_addons_search_type.value)
+		return (strstr(localhaystack, needle) != 0);
+	return (!strncmp(localhaystack, needle, menusearch[0]));
+}
+
+void closefilemenu(boolean validsize)
+{
+	// search
+	if (dirmenu)
+	{
+		if (dirmenu != coredirmenu)
+		{
+			if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS))
+			{
+				Z_Free(dirmenu[0]);
+				dirmenu[0] = NULL;
+			}
+			Z_Free(dirmenu);
+		}
+		dirmenu = NULL;
+		sizedirmenu = 0;
+	}
+
+	if (coredirmenu)
+	{
+		// core
+		if (validsize)
+		{
+			for (; sizecoredirmenu > 0; sizecoredirmenu--)
+			{
+				Z_Free(coredirmenu[sizecoredirmenu-1]);
+				coredirmenu[sizecoredirmenu-1] = NULL;
+			}
+		}
+		else
+			sizecoredirmenu = 0;
+
+		Z_Free(coredirmenu);
+		coredirmenu = NULL;
+	}
+
+	if (refreshdirname)
+		Z_Free(refreshdirname);
+	refreshdirname = NULL;
 }
 
-#define searchdir if (menusearch[0] && !filemenusearch(dent->d_name, localmenusearch))\
-					{\
-						rejected++;\
-						continue;\
-					}\
+void searchfilemenu(char *tempname)
+{
+	size_t i, first;
+	char localmenusearch[MAXSTRINGLENGTH] = "";
+
+	if (dirmenu)
+	{
+		if (dirmenu != coredirmenu)
+		{
+			if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS))
+			{
+				Z_Free(dirmenu[0]);
+				dirmenu[0] = NULL;
+			}
+			//Z_Free(dirmenu); -- Z_Realloc later tho...
+		}
+		else
+			dirmenu = NULL;
+	}
+
+	first = (((UINT8)(coredirmenu[0][DIR_TYPE]) == EXT_UP) ? 1 : 0); // skip UP...
+
+	if (!menusearch[0])
+	{
+		if (dirmenu)
+			Z_Free(dirmenu);
+		dirmenu = coredirmenu;
+		sizedirmenu = sizecoredirmenu;
+
+		if (tempname)
+		{
+			for (i = first; i < sizedirmenu; i++)
+			{
+				if (!strcmp(dirmenu[i]+DIR_STRING, tempname))
+				{
+					dir_on[menudepthleft] = i;
+					break;
+				}
+			}
+
+			if (i == sizedirmenu)
+				dir_on[menudepthleft] = first;
+
+			Z_Free(tempname);
+		}
+
+		return;
+	}
+
+	strcpy(localmenusearch, menusearch+1);
+	if (!cv_addons_search_case.value)
+		strupr(localmenusearch);
+
+	sizedirmenu = 0;
+	for (i = first; i < sizecoredirmenu; i++)
+	{
+		if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch))
+			sizedirmenu++;
+	}
+
+	if (!sizedirmenu) // no results...
+	{
+		if ((!(dirmenu = Z_Realloc(dirmenu, sizeof(char *), PU_STATIC, NULL)))
+			|| !(dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS))))
+				I_Error("searchfilemenu(): could not create \"No results...\".");
+		sizedirmenu = 1;
+		dir_on[menudepthleft] = 0;
+		if (tempname)
+			Z_Free(tempname);
+		return;
+	}
+
+	if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL)))
+		I_Error("searchfilemenu(): could not reallocate dirmenu.");
+
+	sizedirmenu = 0;
+	for (i = first; i < sizecoredirmenu; i++)
+	{
+		if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch))
+		{
+			if (tempname && !strcmp(coredirmenu[i]+DIR_STRING, tempname))
+			{
+				dir_on[menudepthleft] = sizedirmenu;
+				Z_Free(tempname);
+				tempname = NULL;
+			}
+			dirmenu[sizedirmenu++] = coredirmenu[i]; // pointer reuse
+		}
+	}
+
+	if (tempname)
+	{
+		dir_on[menudepthleft] = 0; //first; -- can't be first, causes problems
+		Z_Free(tempname);
+	}
+}
 
 boolean preparefilemenu(boolean samedepth)
 {
 	DIR *dirhandle;
 	struct dirent *dent;
 	struct stat fsstat;
-	size_t pos = 0, folderpos = 0, numfolders = 0, rejected = 0;
+	size_t pos = 0, folderpos = 0, numfolders = 0;
 	char *tempname = NULL;
-	boolean noresults = false;
-	char localmenusearch[MAXSTRINGLENGTH] = "";
 
 	if (samedepth)
 	{
@@ -481,20 +621,16 @@ boolean preparefilemenu(boolean samedepth)
 	else
 		menusearch[0] = menusearch[1] = 0; // clear search
 
-	for (; sizedirmenu > 0; sizedirmenu--) // clear out existing items
-	{
-		Z_Free(dirmenu[sizedirmenu-1]);
-		dirmenu[sizedirmenu-1] = NULL;
-	}
-
 	if (!(dirhandle = opendir(menupath))) // get directory
+	{
+		closefilemenu(true);
 		return false;
+	}
 
-	if (menusearch[0])
+	for (; sizecoredirmenu > 0; sizecoredirmenu--) // clear out existing items
 	{
-		strcpy(localmenusearch, menusearch+1);
-		if (!cv_addons_search_case.value)
-			strupr(localmenusearch);
+		Z_Free(coredirmenu[sizecoredirmenu-1]);
+		coredirmenu[sizecoredirmenu-1] = NULL;
 	}
 
 	while (true)
@@ -523,46 +659,45 @@ boolean preparefilemenu(boolean samedepth)
 					size_t len = strlen(dent->d_name)+1;
 					UINT8 ext;
 					for (ext = 0; ext < NUM_EXT_TABLE; ext++)
-						if (!strcasecmp(exttable[ext], dent->d_name+len-5)) break; // extension comparison
+						if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison
 					if (ext == NUM_EXT_TABLE) continue; // not an addfile-able (or exec-able) file
 				}
-				searchdir;
 			}
 			else // directory
-			{
-				searchdir;
 				numfolders++;
-			}
-			sizedirmenu++;
+
+			sizecoredirmenu++;
 		}
 	}
 
-	if (!rejected && !sizedirmenu)
+	if (!sizecoredirmenu)
 	{
+		closedir(dirhandle);
+		closefilemenu(false);
 		if (tempname)
 			Z_Free(tempname);
-		closedir(dirhandle);
 		return false;
 	}
 
-	if (((noresults = (menusearch[0] && !sizedirmenu)))
-		|| (!menusearch[0] && menudepthleft != menudepth-1)) // Make room for UP... or search entry
+	if (menudepthleft != menudepth-1) // Make room for UP...
 	{
-		sizedirmenu++;
+		sizecoredirmenu++;
 		numfolders++;
 		folderpos++;
 	}
 
-	if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL)))
+	if (dirmenu && dirmenu == coredirmenu)
+		dirmenu = NULL;
+
+	if (!(coredirmenu = Z_Realloc(coredirmenu, sizecoredirmenu*sizeof(char *), PU_STATIC, NULL)))
 	{
 		closedir(dirhandle); // just in case
-		I_Error("Ran out of memory whilst preparing add-ons menu");
+		I_Error("preparefilemenu(): could not reallocate coredirmenu.");
 	}
 
-	rejected = 0;
 	rewinddir(dirhandle);
 
-	while ((pos+folderpos) < sizedirmenu)
+	while ((pos+folderpos) < sizecoredirmenu)
 	{
 		menupath[menupathindex[menudepthleft]] = 0;
 		dent = readdir(dirhandle);
@@ -588,14 +723,12 @@ boolean preparefilemenu(boolean samedepth)
 
 			if (!S_ISDIR(fsstat.st_mode)) // file
 			{
-				if (!((numfolders+pos) < sizedirmenu)) continue; // crash prevention
+				if (!((numfolders+pos) < sizecoredirmenu)) continue; // crash prevention
 				for (; ext < NUM_EXT_TABLE; ext++)
-					if (!strcasecmp(exttable[ext], dent->d_name+len-5)) break; // extension comparison
+					if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison
 				if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file
 				ext += EXT_START; // moving to be appropriate position
 
-				searchdir;
-
 				if (ext >= EXT_LOADSTART)
 				{
 					size_t i;
@@ -628,61 +761,43 @@ boolean preparefilemenu(boolean samedepth)
 				folder = 0;
 			}
 			else // directory
-			{
-				searchdir;
 				len += (folder = 1);
-			}
 
 			if (len > 255)
 				len = 255;
 
 			if (!(temp = Z_Malloc((len+DIR_STRING+folder) * sizeof (char), PU_STATIC, NULL)))
-				I_Error("Ran out of memory whilst preparing add-ons menu");
+				I_Error("preparefilemenu(): could not create file entry.");
 			temp[DIR_TYPE] = ext;
 			temp[DIR_LEN] = (UINT8)(len);
 			strlcpy(temp+DIR_STRING, dent->d_name, len);
 			if (folder)
 			{
-				strcpy(temp+len, "/");
-				dirmenu[folderpos++] = temp;
+				strcpy(temp+len, PATHSEP);
+				coredirmenu[folderpos++] = temp;
 			}
 			else
-				dirmenu[numfolders + pos++] = temp;
+				coredirmenu[numfolders + pos++] = temp;
 		}
 	}
 
 	closedir(dirhandle);
 
-	if (noresults) // no results
-		dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS));
-	else if (!menusearch[0] &&menudepthleft != menudepth-1) // now for UP... entry
-		dirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP));
+	if ((menudepthleft != menudepth-1) // now for UP... entry
+		&& !(coredirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP))))
+			I_Error("preparefilemenu(): could not create \"UP...\".");
 
 	menupath[menupathindex[menudepthleft]] = 0;
-	sizedirmenu = (numfolders+pos); // just in case things shrink between opening and rewind
-
-	if (tempname)
-	{
-		size_t i;
-		for (i = 0; i < sizedirmenu; i++)
-		{
-			if (!strcmp(dirmenu[i]+DIR_STRING, tempname))
-			{
-				dir_on[menudepthleft] = i;
-				break;
-			}
-		}
-		Z_Free(tempname);
-	}
+	sizecoredirmenu = (numfolders+pos); // just in case things shrink between opening and rewind
 
-	if (!sizedirmenu)
+	if (!sizecoredirmenu)
 	{
 		dir_on[menudepthleft] = 0;
-		Z_Free(dirmenu);
+		closefilemenu(false);
 		return false;
 	}
-	else if (dir_on[menudepthleft] >= sizedirmenu)
-		dir_on[menudepthleft] = sizedirmenu-1;
+
+	searchfilemenu(tempname);
 
 	return true;
 }
diff --git a/src/filesrch.h b/src/filesrch.h
index e88242698d49f284e0b3efd59f1ae6be4b774abc..4186271b038d23aac0ad9a1f77a0a3b368914ada 100644
--- a/src/filesrch.h
+++ b/src/filesrch.h
@@ -40,6 +40,7 @@ extern char **dirmenu;
 extern size_t sizedirmenu;
 extern size_t dir_on[menudepth];
 extern UINT8 refreshdirmenu;
+extern char *refreshdirname;
 
 extern size_t packetsizetally;
 extern size_t mainwadstally;
@@ -54,6 +55,9 @@ typedef enum
 	EXT_CFG,
 	EXT_LOADSTART,
 	EXT_WAD = EXT_LOADSTART,
+#ifdef USE_KART
+	EXT_KART,
+#endif
 	EXT_PK3,
 	EXT_SOC,
 	EXT_LUA, // allowed even if not HAVE_BLUA so that we can yell on load attempt
@@ -87,6 +91,8 @@ typedef enum
 	REFRESHDIR_MAX = 32
 } refreshdir_enum;
 
+void closefilemenu(boolean validsize);
+void searchfilemenu(char *tempname);
 boolean preparefilemenu(boolean samedepth);
 
 #endif // __FILESRCH_H__
diff --git a/src/g_game.c b/src/g_game.c
index 9db9c413b3a4c9ab448ff8e78dfa137c7fb871c6..dad873fe751110b5808bde696916bfc1f780f8d0 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,7 @@
 #include "console.h"
 #include "d_main.h"
 #include "d_player.h"
+#include "d_clisrv.h"
 #include "f_finale.h"
 #include "p_setup.h"
 #include "p_saveg.h"
@@ -71,6 +72,7 @@ static void G_DoWorldDone(void);
 
 char   mapmusname[7]; // Music name
 UINT16 mapmusflags; // Track and reset bit
+UINT32 mapmusposition; // Position to jump to
 
 INT16 gamemap = 1;
 INT16 maptol;
@@ -116,33 +118,41 @@ INT32 secondarydisplayplayer; // for splitscreen
 
 tic_t gametic;
 tic_t levelstarttic; // gametic at level start
-UINT32 totalrings; // for intermission
+UINT32 ssspheres; // old special stage
 INT16 lastmap; // last level you were at (returning from special stages)
 tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
 
 INT16 spstage_start;
-INT16 sstage_start;
-INT16 sstage_end;
+INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
 
 INT16 titlemap = 0;
 boolean hidetitlepics = false;
 INT16 bootmap; //bootmap for loading a map on startup
 
+INT16 tutorialmap = 0; // map to load for tutorial
+boolean tutorialmode = false; // are we in a tutorial right now?
+INT32 tutorialgcs = gcs_custom; // which control scheme is loaded?
+INT32 tutorialusemouse = 0; // store cv_usemouse user value
+INT32 tutorialfreelook = 0; // store cv_alwaysfreelook user value
+INT32 tutorialmousemove = 0; // store cv_mousemove user value
+INT32 tutorialanalog = 0; // store cv_analog user value
+
 boolean looptitle = false;
-boolean useNightsSS = false;
 
 UINT8 skincolor_redteam = SKINCOLOR_RED;
 UINT8 skincolor_blueteam = SKINCOLOR_BLUE;
-UINT8 skincolor_redring = SKINCOLOR_RED;
-UINT8 skincolor_bluering = SKINCOLOR_AZURE;
+UINT8 skincolor_redring = SKINCOLOR_SALMON;
+UINT8 skincolor_bluering = SKINCOLOR_CORNFLOWER;
 
 tic_t countdowntimer = 0;
 boolean countdowntimeup = false;
+boolean exitfadestarted = false;
 
 cutscene_t *cutscenes[128];
+textprompt_t *textprompts[MAX_PROMPTS];
 
 INT16 nextmapoverride;
-boolean skipstats;
+UINT8 skipstats;
 
 // Pointers to each CTF flag
 mobj_t *redflag;
@@ -203,6 +213,7 @@ UINT16 tailsflytics = 8*TICRATE;
 UINT16 underwatertics = 30*TICRATE;
 UINT16 spacetimetics = 11*TICRATE + (TICRATE/2);
 UINT16 extralifetics = 4*TICRATE;
+UINT16 nightslinktics = 2*TICRATE;
 
 INT32 gameovertics = 15*TICRATE;
 
@@ -317,22 +328,57 @@ static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"},
 "More Axis Sets"
 #endif
 
+// don't mind me putting these here, I was lazy to figure out where else I could put those without blowing up the compiler.
+
+// it automatically becomes compact with 20+ players, but if you like it, I guess you can turn that on!
+consvar_t cv_compactscoreboard= {"compactscoreboard", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat timer thingy
+static CV_PossibleValue_t chattime_cons_t[] = {{5, "MIN"}, {999, "MAX"}, {0, NULL}};
+consvar_t cv_chattime = {"chattime", "8", CV_SAVE, chattime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chatwidth
+static CV_PossibleValue_t chatwidth_cons_t[] = {{64, "MIN"}, {150, "MAX"}, {0, NULL}};
+consvar_t cv_chatwidth = {"chatwidth", "128", CV_SAVE, chatwidth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chatheight
+static CV_PossibleValue_t chatheight_cons_t[] = {{6, "MIN"}, {22, "MAX"}, {0, NULL}};
+consvar_t cv_chatheight= {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat notifications (do you want to hear beeps? I'd understand if you didn't.)
+consvar_t cv_chatnotifications= {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// chat spam protection (why would you want to disable that???)
+consvar_t cv_chatspamprotection= {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// minichat text background
+consvar_t cv_chatbacktint = {"chatbacktint", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
+static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Window"}, {1, "Console"}, {2, "Window (Hidden)"}, {0, NULL}};
+consvar_t cv_consolechat = {"chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+
 consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_invertmouse = {"invertmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_alwaysfreelook = {"alwaysmlook", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_invertmouse2 = {"invertmouse2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_alwaysfreelook2 = {"alwaysmlook2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousemove = {"mousemove", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousemove2 = {"mousemove2", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_analog = {"analog", "Off", CV_CALL, CV_OnOff, Analog_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_analog2 = {"analog2", "Off", CV_CALL, CV_OnOff, Analog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_useranalog = {"useranalog", "Off", CV_SAVE|CV_CALL, CV_OnOff, UserAnalog_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_useranalog2 = {"useranalog2", "Off", CV_SAVE|CV_CALL, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {0, NULL}};
+consvar_t cv_alwaysfreelook2 = {"alwaysmlook2", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chasefreelook = {"chasemlook", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chasefreelook2 = {"chasemlook2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousemove = {"mousemove", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousemove2 = {"mousemove2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// previously "analog", "analog2", "useranalog", and "useranalog2", invalidating 2.1-era copies of config.cfg
+// changed because it'd be nice to see people try out our actually good controls with gamepads now autobrake exists
+consvar_t cv_analog = {"sessionanalog", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_analog2 = {"sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_useranalog = {"configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_useranalog2 = {"configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // deez New User eXperiences
+static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {0, NULL}};
 consvar_t cv_directionchar = {"directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_directionchar2 = {"directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_autobrake = {"autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -352,23 +398,23 @@ typedef enum
 	AXISFIRENORMAL,
 } axis_input_e;
 
-consvar_t cv_turnaxis = {"joyaxis_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_turnaxis = {"joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_moveaxis = {"joyaxis_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_sideaxis = {"joyaxis_side", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_lookaxis = {"joyaxis_look", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fireaxis = {"joyaxis_fire", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_firenaxis = {"joyaxis_firenormal", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_sideaxis = {"joyaxis_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_lookaxis = {"joyaxis_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_jumpaxis = {"joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_spinaxis = {"joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_fireaxis = {"joyaxis_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_firenaxis = {"joyaxis_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_turnaxis2 = {"joyaxis2_turn", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_turnaxis2 = {"joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_moveaxis2 = {"joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_sideaxis2 = {"joyaxis2_side", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_lookaxis2 = {"joyaxis2_look", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fireaxis2 = {"joyaxis2_fire", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_firenaxis2 = {"joyaxis2_firenormal", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_sideaxis2 = {"joyaxis2_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_lookaxis2 = {"joyaxis2_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_jumpaxis2 = {"joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_spinaxis2 = {"joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_fireaxis2 = {"joyaxis2_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_firenaxis2 = {"joyaxis2_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #if MAXPLAYERS > 32
 #error "please update player_name table using the new value for MAXPLAYERS"
@@ -873,22 +919,25 @@ static fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn
 void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 {
 	boolean forcestrafe = false;
-	INT32 tspeed, forward, side, axis, i;
+	INT32 tspeed, forward, side, axis, altaxis, i;
 	const INT32 speed = 1;
 	// these ones used for multiple conditions
-	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove;
+	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
 	player_t *player = &players[consoleplayer];
 	camera_t *thiscam = &camera;
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
 	static boolean resetdown; // don't cam reset every frame
+	static boolean joyaiming; // check the last frame's value if we need to reset the camera
 
 	G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
 
 	// why build a ticcmd if we're paused?
 	// Or, for that matter, if we're being reborn.
-	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN))
+	// ...OR if we're blindfolded. No looking into the floor.
+	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && (player->playerstate == PST_REBORN || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	&& (leveltime < hidetime * TICRATE) && (player->pflags & PF_TAGIT)))))
 	{
 		cmd->angleturn = (INT16)(localangle >> 16);
 		cmd->aiming = G_ClipAimingPitch(&localaiming);
@@ -903,10 +952,18 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	movefkey = PLAYER1INPUTDOWN(gc_forward);
 	movebkey = PLAYER1INPUTDOWN(gc_backward);
 
-	mouseaiming = (PLAYER1INPUTDOWN(gc_mouseaiming)) ^ cv_alwaysfreelook.value;
+	mouseaiming = (PLAYER1INPUTDOWN(gc_mouseaiming)) ^
+		(cv_chasecam.value ? cv_chasefreelook.value : cv_alwaysfreelook.value);
 	analogjoystickmove = cv_usejoystick.value && !Joystick.bGamepadStyle;
 	gamepadjoystickmove = cv_usejoystick.value && Joystick.bGamepadStyle;
 
+	thisjoyaiming = (cv_chasecam.value) ? cv_chasefreelook.value : cv_alwaysfreelook.value;
+
+	// Reset the vertical look if we're no longer joyaiming
+	if (!thisjoyaiming && joyaiming)
+		localaiming = 0;
+	joyaiming = thisjoyaiming;
+
 	axis = JoyAxis(AXISTURN);
 	if (gamepadjoystickmove && axis != 0)
 	{
@@ -991,9 +1048,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 
 	// forward with key or button
 	axis = JoyAxis(AXISMOVE);
-	if (movefkey || (gamepadjoystickmove && axis < 0))
+	altaxis = JoyAxis(AXISLOOK);
+	if (movefkey || (gamepadjoystickmove && axis < 0)
+		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
+			&& (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && altaxis < 0))))
 		forward = forwardmove[speed];
-	if (movebkey || (gamepadjoystickmove && axis > 0))
+	if (movebkey || (gamepadjoystickmove && axis > 0)
+		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
+			&& (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && altaxis > 0))))
 		forward -= forwardmove[speed];
 
 	if (analogjoystickmove && axis != 0)
@@ -1081,25 +1143,28 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		}
 
 		axis = JoyAxis(AXISLOOK);
-		if (analogjoystickmove && axis != 0 && cv_lookaxis.value != 0)
+		if (analogjoystickmove && joyaiming && axis != 0 && cv_lookaxis.value != 0)
 			localaiming += (axis<<16) * screen_invert;
 
 		// spring back if not using keyboard neither mouselookin'
-		if (!keyboard_look && cv_lookaxis.value == 0 && !mouseaiming)
+		if (!keyboard_look && cv_lookaxis.value == 0 && !joyaiming && !mouseaiming)
 			localaiming = 0;
 
-		if (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
-		{
-			localaiming += KB_LOOKSPEED * screen_invert;
-			keyboard_look = true;
-		}
-		else if (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
-			localaiming -= KB_LOOKSPEED * screen_invert;
-			keyboard_look = true;
+			if (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
+			{
+				localaiming += KB_LOOKSPEED * screen_invert;
+				keyboard_look = true;
+			}
+			else if (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+			{
+				localaiming -= KB_LOOKSPEED * screen_invert;
+				keyboard_look = true;
+			}
+			else if (PLAYER1INPUTDOWN(gc_centerview))
+				localaiming = 0;
 		}
-		else if (PLAYER1INPUTDOWN(gc_centerview))
-			localaiming = 0;
 
 		// accept no mlook for network games
 		if (!cv_allowmlook.value)
@@ -1180,16 +1245,17 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 {
 	boolean forcestrafe = false;
-	INT32 tspeed, forward, side, axis, i;
+	INT32 tspeed, forward, side, axis, altaxis, i;
 	const INT32 speed = 1;
 	// these ones used for multiple conditions
-	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove;
+	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
 	player_t *player = &players[secondarydisplayplayer];
 	camera_t *thiscam = (player->bot == 2 ? &camera : &camera2);
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
 	static boolean resetdown; // don't cam reset every frame
+	static boolean joyaiming; // check the last frame's value if we need to reset the camera
 
 	G_CopyTiccmd(cmd,  I_BaseTiccmd2(), 1); // empty, or external driver
 
@@ -1210,10 +1276,18 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	movefkey = PLAYER2INPUTDOWN(gc_forward);
 	movebkey = PLAYER2INPUTDOWN(gc_backward);
 
-	mouseaiming = (PLAYER2INPUTDOWN(gc_mouseaiming)) ^ cv_alwaysfreelook2.value;
+	mouseaiming = (PLAYER2INPUTDOWN(gc_mouseaiming)) ^
+		(cv_chasecam2.value ? cv_chasefreelook2.value : cv_alwaysfreelook2.value);
 	analogjoystickmove = cv_usejoystick2.value && !Joystick2.bGamepadStyle;
 	gamepadjoystickmove = cv_usejoystick2.value && Joystick2.bGamepadStyle;
 
+	thisjoyaiming = (cv_chasecam2.value) ? cv_chasefreelook2.value : cv_alwaysfreelook2.value;
+
+	// Reset the vertical look if we're no longer joyaiming
+	if (!thisjoyaiming && joyaiming)
+		localaiming2 = 0;
+	joyaiming = thisjoyaiming;
+
 	axis = Joy2Axis(AXISTURN);
 	if (gamepadjoystickmove && axis != 0)
 	{
@@ -1298,9 +1372,14 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 
 	// forward with key or button
 	axis = Joy2Axis(AXISMOVE);
-	if (movefkey || (gamepadjoystickmove && axis < 0))
+	altaxis = Joy2Axis(AXISLOOK);
+	if (movefkey || (gamepadjoystickmove && axis < 0)
+		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
+			&& (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && altaxis < 0))))
 		forward = forwardmove[speed];
-	if (movebkey || (gamepadjoystickmove && axis > 0))
+	if (movebkey || (gamepadjoystickmove && axis > 0)
+		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
+			&& (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && altaxis > 0))))
 		forward -= forwardmove[speed];
 
 	if (analogjoystickmove && axis != 0)
@@ -1385,25 +1464,28 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		}
 
 		axis = Joy2Axis(AXISLOOK);
-		if (analogjoystickmove && axis != 0 && cv_lookaxis2.value != 0)
+		if (analogjoystickmove && joyaiming && axis != 0 && cv_lookaxis2.value != 0)
 			localaiming2 += (axis<<16) * screen_invert;
 
 		// spring back if not using keyboard neither mouselookin'
-		if (!keyboard_look && cv_lookaxis2.value == 0 && !mouseaiming)
+		if (!keyboard_look && cv_lookaxis2.value == 0 && !joyaiming && !mouseaiming)
 			localaiming2 = 0;
 
-		if (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
-		{
-			localaiming2 += KB_LOOKSPEED * screen_invert;
-			keyboard_look = true;
-		}
-		else if (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
-			localaiming2 -= KB_LOOKSPEED * screen_invert;
-			keyboard_look = true;
+			if (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && axis < 0))
+			{
+				localaiming2 += KB_LOOKSPEED * screen_invert;
+				keyboard_look = true;
+			}
+			else if (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && axis > 0))
+			{
+				localaiming2 -= KB_LOOKSPEED * screen_invert;
+				keyboard_look = true;
+			}
+			else if (PLAYER2INPUTDOWN(gc_centerview))
+				localaiming2 = 0;
 		}
-		else if (PLAYER2INPUTDOWN(gc_centerview))
-			localaiming2 = 0;
 
 		// accept no mlook for network games
 		if (!cv_allowmlook.value)
@@ -1521,11 +1603,6 @@ static void Analog_OnChange(void)
 		return;
 	}
 
-	if (cv_analog.value)
-		players[consoleplayer].pflags |= PF_ANALOGMODE;
-	else
-		players[consoleplayer].pflags &= ~PF_ANALOGMODE;
-
 	SendWeaponPref();
 }
 
@@ -1541,51 +1618,26 @@ static void Analog2_OnChange(void)
 		return;
 	}
 
-	if (cv_analog2.value)
-		players[secondarydisplayplayer].pflags |= PF_ANALOGMODE;
-	else
-		players[secondarydisplayplayer].pflags &= ~PF_ANALOGMODE;
-
 	SendWeaponPref2();
 }
 
 static void DirectionChar_OnChange(void)
 {
-	if (cv_directionchar.value)
-		players[consoleplayer].pflags |= PF_DIRECTIONCHAR;
-	else
-		players[consoleplayer].pflags &= ~PF_DIRECTIONCHAR;
-
 	SendWeaponPref();
 }
 
 static void DirectionChar2_OnChange(void)
 {
-	if (cv_directionchar2.value)
-		players[secondarydisplayplayer].pflags |= PF_DIRECTIONCHAR;
-	else
-		players[secondarydisplayplayer].pflags &= ~PF_DIRECTIONCHAR;
-
 	SendWeaponPref2();
 }
 
 static void AutoBrake_OnChange(void)
 {
-	if (cv_autobrake.value)
-		players[consoleplayer].pflags |= PF_AUTOBRAKE;
-	else
-		players[consoleplayer].pflags &= ~PF_AUTOBRAKE;
-
 	SendWeaponPref();
 }
 
 static void AutoBrake2_OnChange(void)
 {
-	if (cv_autobrake2.value)
-		players[secondarydisplayplayer].pflags |= PF_AUTOBRAKE;
-	else
-		players[secondarydisplayplayer].pflags &= ~PF_AUTOBRAKE;
-
 	SendWeaponPref2();
 }
 
@@ -1670,7 +1722,8 @@ void G_DoLoadLevel(boolean resetplayer)
 	CON_ClearHUD();
 }
 
-static INT32 pausedelay = 0;
+INT32 pausedelay = 0;
+boolean pausebreakkey = false;
 static INT32 camtoggledelay, camtoggledelay2 = 0;
 
 //
@@ -1680,7 +1733,8 @@ static INT32 camtoggledelay, camtoggledelay2 = 0;
 boolean G_Responder(event_t *ev)
 {
 	// allow spy mode changes even during the demo
-	if (gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == KEY_F12)
+	if (gamestate == GS_LEVEL && ev->type == ev_keydown
+		&& (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[gc_viewpoint][0] || ev->data1 == gamecontrol[gc_viewpoint][1]))
 	{
 		if (splitscreen || !netgame)
 			displayplayer = consoleplayer;
@@ -1788,14 +1842,16 @@ boolean G_Responder(event_t *ev)
 			return true;
 		}
 	}
-	else if (gamestate == GS_CREDITS)
+	else if (gamestate == GS_CREDITS || gamestate == GS_ENDING) // todo: keep ending here?
 	{
 		if (HU_Responder(ev))
 			return true; // chat ate the event
 
 		if (F_CreditResponder(ev))
 		{
-			F_StartGameEvaluation();
+			// Skip credits for everyone
+			if (!netgame || server || IsPlayerAdmin(consoleplayer))
+				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 			return true;
 		}
 	}
@@ -1818,19 +1874,35 @@ boolean G_Responder(event_t *ev)
 	{
 		case ev_keydown:
 			if (ev->data1 == gamecontrol[gc_pause][0]
-				|| ev->data1 == gamecontrol[gc_pause][1])
+				|| ev->data1 == gamecontrol[gc_pause][1]
+				|| ev->data1 == KEY_PAUSE)
 			{
-				if (!pausedelay)
+				if (modeattacking && !demoplayback && (gamestate == GS_LEVEL))
 				{
-					// don't let busy scripts prevent pausing
-					pausedelay = NEWTICRATE/7;
+					pausebreakkey = (ev->data1 == KEY_PAUSE);
+					if (menuactive || pausedelay < 0 || leveltime < 2)
+						return true;
 
-					// command will handle all the checks for us
-					COM_ImmedExecute("pause");
-					return true;
+					if (pausedelay < 1+(NEWTICRATE/2))
+						pausedelay = 1+(NEWTICRATE/2);
+					else if (++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3))
+					{
+						G_SetRetryFlag();
+						return true;
+					}
+					pausedelay++; // counteract subsequent subtraction this frame
 				}
 				else
-					pausedelay = NEWTICRATE/7;
+				{
+					INT32 oldpausedelay = pausedelay;
+					pausedelay = (NEWTICRATE/7);
+					if (!oldpausedelay)
+					{
+						// command will handle all the checks for us
+						COM_ImmedExecute("pause");
+						return true;
+					}
+				}
 			}
 			if (ev->data1 == gamecontrol[gc_camtoggle][0]
 				|| ev->data1 == gamecontrol[gc_camtoggle][1])
@@ -1890,11 +1962,19 @@ void G_Ticker(boolean run)
 		{
 			G_ClearRetryFlag();
 
-			// Costs a life to retry ... unless the player in question is dead already.
-			if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE)
-				players[consoleplayer].lives -= 1;
+			if (modeattacking)
+			{
+				pausedelay = INT32_MIN;
+				M_ModeAttackRetry(0);
+			}
+			else
+			{
+				// Costs a life to retry ... unless the player in question is dead already.
+				if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES)
+					players[consoleplayer].lives -= 1;
 
-			G_DoReborn(consoleplayer);
+				G_DoReborn(consoleplayer);
+			}
 		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -1932,6 +2012,7 @@ void G_Ticker(boolean run)
 				F_TitleDemoTicker();
 			P_Ticker(run); // tic the game
 			ST_Ticker();
+			F_TextPromptTicker();
 			AM_Ticker();
 			HU_Ticker();
 			break;
@@ -1943,6 +2024,7 @@ void G_Ticker(boolean run)
 			break;
 
 		case GS_TIMEATTACK:
+			F_MenuPresTicker(run);
 			break;
 
 		case GS_INTRO:
@@ -1950,6 +2032,12 @@ void G_Ticker(boolean run)
 				F_IntroTicker();
 			break;
 
+		case GS_ENDING:
+			if (run)
+				F_EndingTicker();
+			HU_Ticker();
+			break;
+
 		case GS_CUTSCENE:
 			if (run)
 				F_CutsceneTicker();
@@ -1982,6 +2070,7 @@ void G_Ticker(boolean run)
 			if (titlemapinaction) P_Ticker(run); // then intentionally fall through
 			/* FALLTHRU */
 		case GS_WAITINGPLAYERS:
+			F_MenuPresTicker(run);
 			F_TitleScreenTicker(run);
 			break;
 
@@ -1992,8 +2081,13 @@ void G_Ticker(boolean run)
 
 	if (run)
 	{
-		if (pausedelay)
-			pausedelay--;
+		if (pausedelay && pausedelay != INT32_MIN)
+		{
+			if (pausedelay > 0)
+				pausedelay--;
+			else
+				pausedelay++;
+		}
 
 		if (camtoggledelay)
 			camtoggledelay--;
@@ -2199,7 +2293,7 @@ void G_PlayerReborn(INT32 player)
 	p->pflags |= PF_JUMPDOWN;
 
 	p->playerstate = PST_LIVE;
-	p->rings = 0; // 0 rings
+	p->rings = p->spheres = 0; // 0 rings
 	p->panim = PA_IDLE; // standing animation
 
 	//if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there
@@ -2211,9 +2305,14 @@ void G_PlayerReborn(INT32 player)
 		{
 			strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
 			mapmusname[6] = 0;
-			mapmusflags = mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK;
+			mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
+			mapmusposition = mapheaderinfo[gamemap-1]->muspos;
 		}
-		S_ChangeMusic(mapmusname, mapmusflags, true);
+
+		// This is in S_Start, but this was not here previously.
+		// if (cv_resetmusic.value)
+		// 	S_StopMusic();
+		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
 	}
 
 	if (gametype == GT_COOP)
@@ -2230,6 +2329,8 @@ void G_PlayerReborn(INT32 player)
 	if (p->mare == 255)
 		p->mare = 0;
 
+	p->marelap = p->marebonuslap = 0;
+
 	// Check to make sure their color didn't change somehow...
 	if (G_GametypeHasTeams())
 	{
@@ -2303,6 +2404,8 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 
 	P_SpawnPlayer(playernum);
 
+	players[playernum].rings = mapheaderinfo[gamemap-1]->startrings;
+
 	if (starpost) //Don't even bother with looking for a place to spawn.
 	{
 		P_MovePlayerToStarpost(playernum);
@@ -2468,9 +2571,9 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
 	I_Assert((oldmo != NULL) && (newmo != NULL));
 
 	// scan all thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -2548,7 +2651,7 @@ void G_DoReborn(INT32 playernum)
 					//nextmapoverride = spstage_start;
 					nextmapoverride = gamemap;
 					countdown2 = TICRATE;
-					skipstats = true;
+					skipstats = 2;
 
 					for (i = 0; i < MAXPLAYERS; i++)
 					{
@@ -2653,7 +2756,7 @@ void G_DoReborn(INT32 playernum)
 		else
 		{
 #ifdef HAVE_BLUA
-			LUAh_MapChange();
+			LUAh_MapChange(gamemap);
 #endif
 			G_DoLoadLevel(true);
 			return;
@@ -2752,6 +2855,14 @@ void G_ExitLevel(void)
 		// Remove CEcho text on round end.
 		HU_ClearCEcho();
 	}
+	else if (gamestate == GS_ENDING)
+	{
+		F_StartCredits();
+	}
+	else if (gamestate == GS_CREDITS)
+	{
+		F_StartGameEvaluation();
+	}
 }
 
 // See also the enum GameType in doomstat.h
@@ -2794,7 +2905,11 @@ INT32 G_GetGametypeByName(const char *gametypestr)
 //
 boolean G_IsSpecialStage(INT32 mapnum)
 {
-	if (gametype == GT_COOP && modeattacking != ATTACKING_RECORD && mapnum >= sstage_start && mapnum <= sstage_end)
+	if (gametype != GT_COOP || modeattacking == ATTACKING_RECORD)
+		return false;
+	if (mapnum >= sstage_start && mapnum <= sstage_end)
+		return true;
+	if (mapnum >= smpstage_start && mapnum <= smpstage_end)
 		return true;
 
 	return false;
@@ -2933,6 +3048,9 @@ static void G_DoCompleted(void)
 
 	tokenlist = 0; // Reset the list
 
+	if (modeattacking && pausedelay)
+		pausedelay = 0;
+
 	gameaction = ga_nothing;
 
 	if (metalplayback)
@@ -3008,7 +3126,7 @@ static void G_DoCompleted(void)
 		nextmap = cm;
 	}
 
-	if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1102-1)
+	if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1)
 		I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
 
 	// wrap around in race
@@ -3019,21 +3137,14 @@ static void G_DoCompleted(void)
 	{
 		token--;
 
-		if (!(emeralds & EMERALD1))
-			nextmap = (INT16)(sstage_start - 1); // Special Stage 1
-		else if (!(emeralds & EMERALD2))
-			nextmap = (INT16)(sstage_start); // Special Stage 2
-		else if (!(emeralds & EMERALD3))
-			nextmap = (INT16)(sstage_start + 1); // Special Stage 3
-		else if (!(emeralds & EMERALD4))
-			nextmap = (INT16)(sstage_start + 2); // Special Stage 4
-		else if (!(emeralds & EMERALD5))
-			nextmap = (INT16)(sstage_start + 3); // Special Stage 5
-		else if (!(emeralds & EMERALD6))
-			nextmap = (INT16)(sstage_start + 4); // Special Stage 6
-		else if (!(emeralds & EMERALD7))
-			nextmap = (INT16)(sstage_start + 5); // Special Stage 7
-		else
+		for (i = 0; i < 7; i++)
+			if (!(emeralds & (1<<i)))
+			{
+				nextmap = ((netgame || multiplayer) ? smpstage_start : sstage_start) + i - 1; // to special stage!
+				break;
+			}
+
+		if (i == 7)
 			gottoken = false;
 	}
 
@@ -3069,7 +3180,7 @@ void G_AfterIntermission(void)
 {
 	HU_ClearCEcho();
 
-	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking) // Start a custom cutscene.
+	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
 	else
 	{
@@ -3181,6 +3292,11 @@ void G_EndGame(void)
 	// Only do evaluation and credits in coop games.
 	if (gametype == GT_COOP)
 	{
+		if (nextmap == 1103-1) // end game with ending
+		{
+			F_StartEnding();
+			return;
+		}
 		if (nextmap == 1102-1) // end game with credits
 		{
 			F_StartCredits();
@@ -3205,9 +3321,9 @@ void G_LoadGameSettings(void)
 {
 	// defaults
 	spstage_start = 1;
-	sstage_start = 50;
-	sstage_end = 57; // 8 special stages in vanilla SRB2
-	useNightsSS = false; //true;
+	sstage_start = smpstage_start = 50;
+	sstage_end = smpstage_end = 56; // 7 special stages in vanilla SRB2
+	sstage_end++; // plus one weirdo
 
 	// initialize free sfx slots for skin sounds
 	S_InitRuntimeSounds();
@@ -3599,7 +3715,7 @@ void G_SaveGame(UINT32 slot)
 	backup = va("%s",savename);
 
 	// save during evaluation or credits? game's over, folks!
-	if (gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
+	if (gamestate == GS_ENDING || gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
 		gamecomplete = true;
 
 	gameaction = ga_nothing;
@@ -3777,7 +3893,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	{
 		// Clear a bunch of variables
 		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
-		countdown = countdown2 = 0;
+		countdown = countdown2 = exitfadestarted = 0;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
@@ -3813,7 +3929,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		unlocktriggers = 0;
 
 		// clear itemfinder, just in case
-		CV_StealthSetValue(&cv_itemfinder, 0);
+		if (!dedicated)	// except in dedicated servers, where it is not registered and can actually I_Error debug builds
+			CV_StealthSetValue(&cv_itemfinder, 0);
 	}
 
 	// internal game map
@@ -4264,7 +4381,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 		ghostext.flags = 0;
 	}
 
-	if (ghost->player && ghost->player->followmobj)
+	if (ghost->player && ghost->player->followmobj) // bloats tails runs but what can ya do
 	{
 		INT16 temp;
 
@@ -4365,16 +4482,15 @@ void G_ConsGhostTic(void)
 				demo_p += sizeof(angle_t); // angle, unnecessary for cons.
 
 				mobj = NULL;
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 					mobj = (mobj_t *)th;
 					if (mobj->type == (mobjtype_t)type && mobj->x == x && mobj->y == y && mobj->z == z)
 						break;
-					mobj = NULL; // wasn't this one, keep searching.
 				}
-				if (mobj && mobj->health != health) // Wasn't damaged?! This is desync! Fix it!
+				if (th != &thlist[THINK_MOBJ] && mobj->health != health) // Wasn't damaged?! This is desync! Fix it!
 				{
 					if (demosynced)
 						CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
@@ -4491,6 +4607,9 @@ void G_GhostTicker(void)
 				switch(g->color)
 				{
 				default:
+				case GHC_RETURNSKIN:
+					g->mo->skin = g->oldmo.skin;
+					// fallthru
 				case GHC_NORMAL: // Go back to skin color
 					g->mo->color = g->oldmo.color;
 					break;
@@ -4501,6 +4620,9 @@ void G_GhostTicker(void)
 				case GHC_FIREFLOWER: // Fireflower
 					g->mo->color = SKINCOLOR_WHITE;
 					break;
+				case GHC_NIGHTSSKIN: // not actually a colour
+					g->mo->skin = &skins[DEFAULTNIGHTSSKIN];
+					break;
 				}
 			}
 			if (xziptic & EZT_FLIP)
@@ -4666,6 +4788,7 @@ void G_GhostTicker(void)
 				p->next = g->next;
 			else
 				ghosts = g->next;
+			Z_Free(g);
 			continue;
 		}
 		p = g;
@@ -5445,7 +5568,7 @@ void G_DoPlayDemo(char *defdemoname)
 	SetPlayerSkin(0, skin);
 
 #ifdef HAVE_BLUA
-	LUAh_MapChange();
+	LUAh_MapChange(gamemap);
 #endif
 	displayplayer = consoleplayer = 0;
 	memset(playeringame,0,sizeof(playeringame));
@@ -5666,29 +5789,28 @@ void G_AddGhost(char *defdemoname)
 	mthing = playerstarts[0];
 	I_Assert(mthing);
 	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
-		fixed_t x,y,z;
-		sector_t *sector;
-		x = mthing->x << FRACBITS;
-		y = mthing->y << FRACBITS;
-		sector = R_PointInSubsector(x, y)->sector;
+		fixed_t z,f,c;
+		gh->mo = P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST);
+		gh->mo->angle = FixedAngle(mthing->angle*FRACUNIT);
+		f = gh->mo->floorz;
+		c = gh->mo->ceilingz - mobjinfo[MT_PLAYER].height;
 		if (!!(mthing->options & MTF_AMBUSH) ^ !!(mthing->options & MTF_OBJECTFLIP))
 		{
-			z = sector->ceilingheight - mobjinfo[MT_PLAYER].height;
+			z = c;
 			if (mthing->options >> ZSHIFT)
 				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
-			if (z < sector->floorheight)
-				z = sector->floorheight;
+			if (z < f)
+				z = f;
 		}
 		else
 		{
-			z = sector->floorheight;
+			z = f;
 			if (mthing->options >> ZSHIFT)
 				z += ((mthing->options >> ZSHIFT) << FRACBITS);
-			if (z > sector->ceilingheight - mobjinfo[MT_PLAYER].height)
-				z = sector->ceilingheight - mobjinfo[MT_PLAYER].height;
+			if (z > c)
+				z = c;
 		}
-		gh->mo = P_SpawnMobj(x, y, z, MT_GHOST);
-		gh->mo->angle = FixedAngle(mthing->angle*FRACUNIT);
+		gh->mo->z = z;
 	}
 
 	gh->oldmo.x = gh->mo->x;
@@ -5761,9 +5883,9 @@ void G_DoPlayMetal(void)
 		metalbuffer = metal_p = W_CacheLumpNum(l, PU_STATIC);
 
 	// find metal sonic
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -5772,7 +5894,7 @@ void G_DoPlayMetal(void)
 
 		break;
 	}
-	if (!mo)
+	if (th == &thlist[THINK_MOBJ])
 	{
 		CONS_Alert(CONS_ERROR, M_GetText("Failed to find bot entity.\n"));
 		Z_Free(metalbuffer);
@@ -5887,8 +6009,14 @@ boolean G_CheckDemoStatus(void)
 {
 	boolean saved;
 
-	if(ghosts) // ... ... ...
-		ghosts = NULL; // :)
+	while (ghosts)
+	{
+		demoghost *next = ghosts->next;
+		Z_Free(ghosts);
+		ghosts = next;
+	}
+	ghosts = NULL;
+
 
 	// DO NOT end metal sonic demos here
 
diff --git a/src/g_game.h b/src/g_game.h
index d6b41830e8c674e52bf0cb3374e526709df3e93b..4b63bc180afcb0908e5ffeefddb907cb07d098a7 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -53,11 +53,17 @@ extern INT16 prevmap, nextmap;
 extern INT32 gameovertics;
 extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
 extern INT16 rw_maximums[NUM_WEAPONS];
+extern INT32 pausedelay;
+extern boolean pausebreakkey;
+
+extern boolean promptactive;
 
 // 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;
 extern consvar_t cv_crosshair, cv_crosshair2;
-extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
-extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_mousemove2;
+extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_chasefreelook, cv_mousemove;
+extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_chasefreelook2, cv_mousemove2;
 extern consvar_t cv_useranalog, cv_useranalog2;
 extern consvar_t cv_analog, cv_analog2;
 extern consvar_t cv_directionchar, cv_directionchar2;
@@ -136,7 +142,9 @@ typedef enum
 	GHC_NORMAL = 0,
 	GHC_SUPER,
 	GHC_FIREFLOWER,
-	GHC_INVINCIBLE
+	GHC_INVINCIBLE,
+	GHC_NIGHTSSKIN, // not actually a colour
+	GHC_RETURNSKIN // ditto
 } ghostcolor_t;
 
 // Record/playback tics
diff --git a/src/g_input.c b/src/g_input.c
index 67aaf41791c33d90a360a38fe78faaec960155e9..45c517e1a634740caf8a25accf5facc93df35227 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -25,10 +25,10 @@ static CV_PossibleValue_t mousesens_cons_t[] = {{1, "MIN"}, {MAXMOUSESENSITIVITY
 static CV_PossibleValue_t onecontrolperkey_cons_t[] = {{1, "One"}, {2, "Several"}, {0, NULL}};
 
 // mouse values are used once
-consvar_t cv_mousesens = {"mousesens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousesens2 = {"mousesens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens = {"mouseysens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens2 = {"mouseysens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens = {"mousesens", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens2 = {"mousesens2", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens = {"mouseysens", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens2 = {"mouseysens2", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_controlperkey = {"controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 INT32 mousex, mousey;
@@ -45,6 +45,48 @@ UINT8 gamekeydown[NUMINPUTS];
 // two key codes (or virtual key) per game control
 INT32 gamecontrol[num_gamecontrols][2];
 INT32 gamecontrolbis[num_gamecontrols][2]; // secondary splitscreen player
+INT32 gamecontroldefault[num_gamecontrolschemes][num_gamecontrols][2]; // default control storage, use 0 (gcs_custom) for memory retention
+INT32 gamecontrolbisdefault[num_gamecontrolschemes][num_gamecontrols][2];
+
+// lists of GC codes for selective operation
+const INT32 gcl_tutorial_check[num_gcl_tutorial_check] = {
+	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
+	gc_turnleft, gc_turnright
+};
+
+const INT32 gcl_tutorial_used[num_gcl_tutorial_used] = {
+	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
+	gc_turnleft, gc_turnright,
+	gc_jump, gc_use
+};
+
+const INT32 gcl_tutorial_full[num_gcl_tutorial_full] = {
+	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
+	gc_lookup, gc_lookdown, gc_turnleft, gc_turnright, gc_centerview,
+	gc_jump, gc_use,
+	gc_fire, gc_firenormal
+};
+
+const INT32 gcl_movement[num_gcl_movement] = {
+	gc_forward, gc_backward, gc_strafeleft, gc_straferight
+};
+
+const INT32 gcl_camera[num_gcl_camera] = {
+	gc_turnleft, gc_turnright
+};
+
+const INT32 gcl_movement_camera[num_gcl_movement_camera] = {
+	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
+	gc_turnleft, gc_turnright
+};
+
+const INT32 gcl_jump[num_gcl_jump] = { gc_jump };
+
+const INT32 gcl_use[num_gcl_use] = { gc_use };
+
+const INT32 gcl_jump_use[num_gcl_jump_use] = {
+	gc_jump, gc_use
+};
 
 typedef struct
 {
@@ -98,6 +140,8 @@ void G_MapEventsToControls(event_t *ev)
 			break;
 
 		case ev_mouse: // buttons are virtual keys
+			if (menuactive || CON_Ready() || chat_on)
+				break;
 			mousex = (INT32)(ev->data2*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f));
 			mousey = (INT32)(ev->data3*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f));
 			mlooky = (INT32)(ev->data3*((cv_mouseysens.value*cv_mousesens.value)/110.0f + 0.1f));
@@ -105,7 +149,7 @@ void G_MapEventsToControls(event_t *ev)
 
 		case ev_joystick: // buttons are virtual keys
 			i = ev->data1;
-			if (i >= JOYAXISSET)
+			if (i >= JOYAXISSET || menuactive || CON_Ready() || chat_on)
 				break;
 			if (ev->data2 != INT32_MAX) joyxmove[i] = ev->data2;
 			if (ev->data3 != INT32_MAX) joyymove[i] = ev->data3;
@@ -113,13 +157,15 @@ void G_MapEventsToControls(event_t *ev)
 
 		case ev_joystick2: // buttons are virtual keys
 			i = ev->data1;
-			if (i >= JOYAXISSET)
+			if (i >= JOYAXISSET || menuactive || CON_Ready() || chat_on)
 				break;
 			if (ev->data2 != INT32_MAX) joy2xmove[i] = ev->data2;
 			if (ev->data3 != INT32_MAX) joy2ymove[i] = ev->data3;
 			break;
 
 		case ev_mouse2: // buttons are virtual keys
+			if (menuactive || CON_Ready() || chat_on)
+				break;
 			mouse2x = (INT32)(ev->data2*((cv_mousesens2.value*cv_mousesens2.value)/110.0f + 0.1f));
 			mouse2y = (INT32)(ev->data3*((cv_mousesens2.value*cv_mousesens2.value)/110.0f + 0.1f));
 			mlook2y = (INT32)(ev->data3*((cv_mouseysens2.value*cv_mousesens2.value)/110.0f + 0.1f));
@@ -550,6 +596,10 @@ static const char *gamecontrolname[num_gamecontrols] =
 	"jump",
 	"console",
 	"pause",
+	"systemmenu",
+	"screenshot",
+	"recordgif",
+	"viewpoint",
 	"custom1",
 	"custom2",
 	"custom3",
@@ -566,6 +616,16 @@ void G_ClearControlKeys(INT32 (*setupcontrols)[2], INT32 control)
 	setupcontrols[control][1] = KEY_NULL;
 }
 
+void G_ClearAllControlKeys(void)
+{
+	INT32 i;
+	for (i = 0; i < num_gamecontrols; i++)
+	{
+		G_ClearControlKeys(gamecontrol, i);
+		G_ClearControlKeys(gamecontrolbis, i);
+	}
+}
+
 //
 // Returns the name of a key (or virtual key for mouse and joy)
 // the input value being an keynum
@@ -611,55 +671,150 @@ INT32 G_KeyStringtoNum(const char *keystr)
 	return 0;
 }
 
-void G_Controldefault(void)
+void G_DefineDefaultControls(void)
 {
-	gamecontrol[gc_forward    ][0] = 'w';
-	gamecontrol[gc_backward   ][0] = 's';
-	gamecontrol[gc_strafeleft ][0] = 'a';
-	gamecontrol[gc_straferight][0] = 'd';
-	gamecontrol[gc_turnleft   ][0] = KEY_LEFTARROW;
-	gamecontrol[gc_turnright  ][0] = KEY_RIGHTARROW;
-	gamecontrol[gc_weaponnext ][0] = 'e';
-	gamecontrol[gc_weaponprev ][0] = 'q';
-	gamecontrol[gc_wepslot1   ][0] = '1';
-	gamecontrol[gc_wepslot2   ][0] = '2';
-	gamecontrol[gc_wepslot3   ][0] = '3';
-	gamecontrol[gc_wepslot4   ][0] = '4';
-	gamecontrol[gc_wepslot5   ][0] = '5';
-	gamecontrol[gc_wepslot6   ][0] = '6';
-	gamecontrol[gc_wepslot7   ][0] = '7';
-	gamecontrol[gc_wepslot8   ][0] = '8';
-	gamecontrol[gc_wepslot9   ][0] = '9';
-	gamecontrol[gc_wepslot10  ][0] = '0';
-	gamecontrol[gc_fire       ][0] = KEY_RCTRL;
-	gamecontrol[gc_fire       ][1] = KEY_MOUSE1+0;
-	gamecontrol[gc_firenormal ][0] = 'c';
-	gamecontrol[gc_tossflag   ][0] = '\'';
-	gamecontrol[gc_use        ][0] = KEY_LSHIFT;
-	gamecontrol[gc_camtoggle  ][0] = 'v';
-	gamecontrol[gc_camreset   ][0] = 'r';
-	gamecontrol[gc_lookup     ][0] = KEY_UPARROW;
-	gamecontrol[gc_lookdown   ][0] = KEY_DOWNARROW;
-	gamecontrol[gc_centerview ][0] = KEY_END;
-	gamecontrol[gc_talkkey    ][0] = 't';
-	gamecontrol[gc_teamkey    ][0] = 'y';
-	gamecontrol[gc_scores     ][0] = KEY_TAB;
-	gamecontrol[gc_jump       ][0] = KEY_SPACE;
-	gamecontrol[gc_console    ][0] = KEY_CONSOLE;
-	gamecontrol[gc_pause      ][0] = KEY_PAUSE;
+	INT32 i;
+
+	// FPS game controls (WASD)
+	gamecontroldefault[gcs_fps][gc_forward    ][0] = 'w';
+	gamecontroldefault[gcs_fps][gc_backward   ][0] = 's';
+	gamecontroldefault[gcs_fps][gc_strafeleft ][0] = 'a';
+	gamecontroldefault[gcs_fps][gc_straferight][0] = 'd';
+	gamecontroldefault[gcs_fps][gc_lookup     ][0] = KEY_UPARROW;
+	gamecontroldefault[gcs_fps][gc_lookdown   ][0] = KEY_DOWNARROW;
+	gamecontroldefault[gcs_fps][gc_turnleft   ][0] = KEY_LEFTARROW;
+	gamecontroldefault[gcs_fps][gc_turnright  ][0] = KEY_RIGHTARROW;
+	gamecontroldefault[gcs_fps][gc_centerview ][0] = KEY_END;
+	gamecontroldefault[gcs_fps][gc_jump       ][0] = KEY_SPACE;
+	gamecontroldefault[gcs_fps][gc_use        ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_fps][gc_fire       ][0] = KEY_RCTRL;
+	gamecontroldefault[gcs_fps][gc_fire       ][1] = KEY_MOUSE1+0;
+	gamecontroldefault[gcs_fps][gc_firenormal ][0] = 'c';
+
+	// Platform game controls (arrow keys)
+	gamecontroldefault[gcs_platform][gc_forward    ][0] = KEY_UPARROW;
+	gamecontroldefault[gcs_platform][gc_backward   ][0] = KEY_DOWNARROW;
+	gamecontroldefault[gcs_platform][gc_strafeleft ][0] = 'a';
+	gamecontroldefault[gcs_platform][gc_straferight][0] = 'd';
+	gamecontroldefault[gcs_platform][gc_lookup     ][0] = KEY_PGUP;
+	gamecontroldefault[gcs_platform][gc_lookdown   ][0] = KEY_PGDN;
+	gamecontroldefault[gcs_platform][gc_turnleft   ][0] = KEY_LEFTARROW;
+	gamecontroldefault[gcs_platform][gc_turnright  ][0] = KEY_RIGHTARROW;
+	gamecontroldefault[gcs_platform][gc_centerview ][0] = KEY_END;
+	gamecontroldefault[gcs_platform][gc_jump       ][0] = KEY_SPACE;
+	gamecontroldefault[gcs_platform][gc_use        ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_platform][gc_fire       ][0] = 's';
+	gamecontroldefault[gcs_platform][gc_fire       ][1] = KEY_MOUSE1+0;
+	gamecontroldefault[gcs_platform][gc_firenormal ][0] = 'w';
+
+	for (i = 1; i < num_gamecontrolschemes; i++) // skip gcs_custom (0)
+	{
+		gamecontroldefault[i][gc_weaponnext ][0] = 'e';
+		gamecontroldefault[i][gc_weaponprev ][0] = 'q';
+		gamecontroldefault[i][gc_wepslot1   ][0] = '1';
+		gamecontroldefault[i][gc_wepslot2   ][0] = '2';
+		gamecontroldefault[i][gc_wepslot3   ][0] = '3';
+		gamecontroldefault[i][gc_wepslot4   ][0] = '4';
+		gamecontroldefault[i][gc_wepslot5   ][0] = '5';
+		gamecontroldefault[i][gc_wepslot6   ][0] = '6';
+		gamecontroldefault[i][gc_wepslot7   ][0] = '7';
+		gamecontroldefault[i][gc_wepslot8   ][0] = '8';
+		gamecontroldefault[i][gc_wepslot9   ][0] = '9';
+		gamecontroldefault[i][gc_wepslot10  ][0] = '0';
+		gamecontroldefault[i][gc_tossflag   ][0] = '\'';
+		gamecontroldefault[i][gc_camtoggle  ][0] = 'v';
+		gamecontroldefault[i][gc_camreset   ][0] = 'r';
+		gamecontroldefault[i][gc_talkkey    ][0] = 't';
+		gamecontroldefault[i][gc_teamkey    ][0] = 'y';
+		gamecontroldefault[i][gc_scores     ][0] = KEY_TAB;
+		gamecontroldefault[i][gc_console    ][0] = KEY_CONSOLE;
+		gamecontroldefault[i][gc_pause      ][0] = 'p';
+		gamecontroldefault[i][gc_screenshot ][0] = KEY_F8;
+		gamecontroldefault[i][gc_recordgif  ][0] = KEY_F9;
+		gamecontroldefault[i][gc_viewpoint  ][0] = KEY_F12;
+
+		// Gamepad controls -- same for both schemes
+		gamecontroldefault[i][gc_weaponnext ][1] = KEY_JOY1+1; // B
+		gamecontroldefault[i][gc_weaponprev ][1] = KEY_JOY1+2; // X
+		gamecontroldefault[i][gc_tossflag   ][1] = KEY_JOY1+0; // A
+		gamecontroldefault[i][gc_use        ][1] = KEY_JOY1+4; // LB
+		gamecontroldefault[i][gc_camtoggle  ][1] = KEY_HAT1+0; // D-Pad Up
+		gamecontroldefault[i][gc_camreset   ][1] = KEY_JOY1+3; // Y
+		gamecontroldefault[i][gc_centerview ][1] = KEY_JOY1+9; // Right Stick
+		gamecontroldefault[i][gc_talkkey    ][1] = KEY_HAT1+2; // D-Pad Left
+		gamecontroldefault[i][gc_scores     ][1] = KEY_HAT1+3; // D-Pad Right
+		gamecontroldefault[i][gc_jump       ][1] = KEY_JOY1+5; // RB
+		gamecontroldefault[i][gc_pause      ][1] = KEY_JOY1+6; // Back
+		gamecontroldefault[i][gc_screenshot ][1] = KEY_HAT1+1; // D-Pad Down
+		gamecontroldefault[i][gc_systemmenu ][0] = KEY_JOY1+7; // Start
+
+		// Second player controls only have joypad defaults
+		gamecontrolbisdefault[i][gc_weaponnext][0] = KEY_2JOY1+1; // B
+		gamecontrolbisdefault[i][gc_weaponprev][0] = KEY_2JOY1+2; // X
+		gamecontrolbisdefault[i][gc_tossflag  ][0] = KEY_2JOY1+0; // A
+		gamecontrolbisdefault[i][gc_use       ][0] = KEY_2JOY1+4; // LB
+		gamecontrolbisdefault[i][gc_camreset  ][0] = KEY_2JOY1+3; // Y
+		gamecontrolbisdefault[i][gc_centerview][0] = KEY_2JOY1+9; // Right Stick
+		gamecontrolbisdefault[i][gc_jump      ][0] = KEY_2JOY1+5; // RB
+		//gamecontrolbisdefault[i][gc_pause     ][0] = KEY_2JOY1+6; // Back
+		//gamecontrolbisdefault[i][gc_systemmenu][0] = KEY_2JOY1+7; // Start
+		gamecontrolbisdefault[i][gc_camtoggle ][0] = KEY_2HAT1+0; // D-Pad Up
+		gamecontrolbisdefault[i][gc_screenshot][0] = KEY_2HAT1+1; // D-Pad Down
+		//gamecontrolbisdefault[i][gc_talkkey   ][0] = KEY_2HAT1+2; // D-Pad Left
+		//gamecontrolbisdefault[i][gc_scores    ][0] = KEY_2HAT1+3; // D-Pad Right
+	}
 }
 
-void G_SaveKeySetting(FILE *f)
+INT32 G_GetControlScheme(INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen)
+{
+	INT32 i, j, gc;
+	boolean skipscheme;
+
+	for (i = 1; i < num_gamecontrolschemes; i++) // skip gcs_custom (0)
+	{
+		skipscheme = false;
+		for (j = 0; j < (gclist && gclen ? gclen : num_gamecontrols); j++)
+		{
+			gc = (gclist && gclen) ? gclist[j] : j;
+			if (((fromcontrols[gc][0] && gamecontroldefault[i][gc][0]) ? fromcontrols[gc][0] != gamecontroldefault[i][gc][0] : true) &&
+				((fromcontrols[gc][0] && gamecontroldefault[i][gc][1]) ? fromcontrols[gc][0] != gamecontroldefault[i][gc][1] : true) &&
+				((fromcontrols[gc][1] && gamecontroldefault[i][gc][0]) ? fromcontrols[gc][1] != gamecontroldefault[i][gc][0] : true) &&
+				((fromcontrols[gc][1] && gamecontroldefault[i][gc][1]) ? fromcontrols[gc][1] != gamecontroldefault[i][gc][1] : true))
+			{
+				skipscheme = true;
+				break;
+			}
+		}
+		if (!skipscheme)
+			return i;
+	}
+
+	return gcs_custom;
+}
+
+void G_CopyControls(INT32 (*setupcontrols)[2], INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen)
+{
+	INT32 i, gc;
+
+	for (i = 0; i < (gclist && gclen ? gclen : num_gamecontrols); i++)
+	{
+		gc = (gclist && gclen) ? gclist[i] : i;
+		setupcontrols[gc][0] = fromcontrols[gc][0];
+		setupcontrols[gc][1] = fromcontrols[gc][1];
+	}
+}
+
+void G_SaveKeySetting(FILE *f, INT32 (*fromcontrols)[2], INT32 (*fromcontrolsbis)[2])
 {
 	INT32 i;
 
 	for (i = 1; i < num_gamecontrols; i++)
 	{
 		fprintf(f, "setcontrol \"%s\" \"%s\"", gamecontrolname[i],
-			G_KeynumToString(gamecontrol[i][0]));
+			G_KeynumToString(fromcontrols[i][0]));
 
-		if (gamecontrol[i][1])
-			fprintf(f, " \"%s\"\n", G_KeynumToString(gamecontrol[i][1]));
+		if (fromcontrols[i][1])
+			fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrols[i][1]));
 		else
 			fprintf(f, "\n");
 	}
@@ -667,39 +822,173 @@ void G_SaveKeySetting(FILE *f)
 	for (i = 1; i < num_gamecontrols; i++)
 	{
 		fprintf(f, "setcontrol2 \"%s\" \"%s\"", gamecontrolname[i],
-			G_KeynumToString(gamecontrolbis[i][0]));
+			G_KeynumToString(fromcontrolsbis[i][0]));
 
-		if (gamecontrolbis[i][1])
-			fprintf(f, " \"%s\"\n", G_KeynumToString(gamecontrolbis[i][1]));
+		if (fromcontrolsbis[i][1])
+			fprintf(f, " \"%s\"\n", G_KeynumToString(fromcontrolsbis[i][1]));
 		else
 			fprintf(f, "\n");
 	}
 }
 
-void G_CheckDoubleUsage(INT32 keynum)
+INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify)
 {
+	INT32 result = gc_null;
 	if (cv_controlperkey.value == 1)
 	{
 		INT32 i;
 		for (i = 0; i < num_gamecontrols; i++)
 		{
 			if (gamecontrol[i][0] == keynum)
-				gamecontrol[i][0] = KEY_NULL;
+			{
+				result = i;
+				if (modify) gamecontrol[i][0] = KEY_NULL;
+			}
 			if (gamecontrol[i][1] == keynum)
-				gamecontrol[i][1] = KEY_NULL;
+			{
+				result = i;
+				if (modify) gamecontrol[i][1] = KEY_NULL;
+			}
 			if (gamecontrolbis[i][0] == keynum)
-				gamecontrolbis[i][0] = KEY_NULL;
+			{
+				result = i;
+				if (modify) gamecontrolbis[i][0] = KEY_NULL;
+			}
 			if (gamecontrolbis[i][1] == keynum)
-				gamecontrolbis[i][1] = KEY_NULL;
+			{
+				result = i;
+				if (modify) gamecontrolbis[i][1] = KEY_NULL;
+			}
+			if (result && !modify)
+				return result;
 		}
 	}
+	return result;
 }
 
-static void setcontrol(INT32 (*gc)[2], INT32 na)
+static INT32 G_FilterKeyByVersion(INT32 numctrl, INT32 keyidx, INT32 player, INT32 *keynum1, INT32 *keynum2, boolean *nestedoverride)
+{
+	// Special case: ignore KEY_PAUSE because it's hardcoded
+	if (keyidx == 0 && *keynum1 == KEY_PAUSE)
+	{
+		if (*keynum2 != KEY_PAUSE)
+		{
+			*keynum1 = *keynum2; // shift down keynum2 and continue
+			*keynum2 = 0;
+		}
+		else
+			return -1; // skip setting control
+	}
+	else if (keyidx == 1 && *keynum2 == KEY_PAUSE)
+		return -1; // skip setting control
+
+	if (GETMAJOREXECVERSION(cv_execversion.value) < 27 && ( // v2.1.22
+		numctrl == gc_weaponnext || numctrl == gc_weaponprev || numctrl == gc_tossflag ||
+		numctrl == gc_use || numctrl == gc_camreset || numctrl == gc_jump ||
+		numctrl == gc_pause || numctrl == gc_systemmenu || numctrl == gc_camtoggle ||
+		numctrl == gc_screenshot || numctrl == gc_talkkey || numctrl == gc_scores ||
+		numctrl == gc_centerview
+	))
+	{
+		INT32 keynum = 0, existingctrl = 0;
+		INT32 defaultkey;
+		boolean defaultoverride = false;
+
+		// get the default gamecontrol
+		if (player == 0 && numctrl == gc_systemmenu)
+			defaultkey = gamecontrol[numctrl][0];
+		else
+			defaultkey = (player == 1 ? gamecontrolbis[numctrl][0] : gamecontrol[numctrl][1]);
+
+		// Assign joypad button defaults if there is an open slot.
+		// At this point, gamecontrol/bis should have the default controls
+		// (unless LOADCONFIG is being run)
+		//
+		// If the player runs SETCONTROL in-game, this block should not be reached
+		// because EXECVERSION is locked onto the latest version.
+		if (keyidx == 0 && !*keynum1)
+		{
+			if (*keynum2) // push keynum2 down; this is an edge case
+			{
+				*keynum1 = *keynum2;
+				*keynum2 = 0;
+				keynum = *keynum1;
+			}
+			else
+			{
+				keynum = defaultkey;
+				defaultoverride = true;
+			}
+		}
+		else if (keyidx == 1 && (!*keynum2 || (!*keynum1 && *keynum2))) // last one is the same edge case as above
+		{
+			keynum = defaultkey;
+			defaultoverride = true;
+		}
+		else // default to the specified keynum
+			keynum = (keyidx == 1 ? *keynum2 : *keynum1);
+
+		// Did our last call override keynum2?
+		if (*nestedoverride)
+		{
+			defaultoverride = true;
+			*nestedoverride = false;
+		}
+
+		// Fill keynum2 with the default control
+		if (keyidx == 0 && !*keynum2)
+		{
+			*keynum2 = defaultkey;
+			// Tell the next call that this is an override
+			*nestedoverride = true;
+
+			// if keynum2 already matches keynum1, we probably recursed
+			// so unset it
+			if (*keynum1 == *keynum2)
+			{
+				*keynum2 = 0;
+				*nestedoverride = false;
+		}
+		}
+
+		// check if the key is being used somewhere else before passing it
+		// pass it through if it's the same numctrl. This is an edge case -- when using
+		// LOADCONFIG, gamecontrol is not reset with default.
+		//
+		// Also, only check if we're actually overriding, to preserve behavior where
+		// config'd keys overwrite default keys.
+		if (defaultoverride)
+			existingctrl = G_CheckDoubleUsage(keynum, false);
+
+		if (keynum && (!existingctrl || existingctrl == numctrl))
+			return keynum;
+		else if (keyidx == 0 && *keynum2)
+		{
+			// try it again and push down keynum2
+			*keynum1 = *keynum2;
+			*keynum2 = 0;
+			return G_FilterKeyByVersion(numctrl, keyidx, player, keynum1, keynum2, nestedoverride);
+			// recursion *should* be safe because we only assign keynum2 to a joy default
+			// and then clear it if we find that keynum1 already has the joy default.
+		}
+		else
+			return 0;
+	}
+
+	// All's good, so pass the keynum as-is
+	if (keyidx == 1)
+		return *keynum2;
+	else //if (keyidx == 0)
+		return *keynum1;
+}
+
+static void setcontrol(INT32 (*gc)[2])
 {
 	INT32 numctrl;
 	const char *namectrl;
-	INT32 keynum;
+	INT32 keynum, keynum1, keynum2;
+	INT32 player = ((void*)gc == (void*)&gamecontrolbis ? 1 : 0);
+	boolean nestedoverride = false;
 
 	namectrl = COM_Argv(1);
 	for (numctrl = 0; numctrl < num_gamecontrols && stricmp(namectrl, gamecontrolname[numctrl]);
@@ -710,12 +999,39 @@ static void setcontrol(INT32 (*gc)[2], INT32 na)
 		CONS_Printf(M_GetText("Control '%s' unknown\n"), namectrl);
 		return;
 	}
-	keynum = G_KeyStringtoNum(COM_Argv(2));
-	G_CheckDoubleUsage(keynum);
-	gc[numctrl][0] = keynum;
+	keynum1 = G_KeyStringtoNum(COM_Argv(2));
+	keynum2 = G_KeyStringtoNum(COM_Argv(3));
+	keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride);
 
-	if (na == 4)
-		gc[numctrl][1] = G_KeyStringtoNum(COM_Argv(3));
+	if (keynum >= 0)
+	{
+		(void)G_CheckDoubleUsage(keynum, true);
+
+		// if keynum was rejected, try it again with keynum2
+		if (!keynum && keynum2)
+		{
+			keynum1 = keynum2; // push down keynum2
+			keynum2 = 0;
+			keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride);
+			if (keynum >= 0)
+				(void)G_CheckDoubleUsage(keynum, true);
+		}
+	}
+
+	if (keynum >= 0)
+		gc[numctrl][0] = keynum;
+
+	if (keynum2)
+	{
+		keynum = G_FilterKeyByVersion(numctrl, 1, player, &keynum1, &keynum2, &nestedoverride);
+		if (keynum >= 0)
+		{
+			if (keynum != gc[numctrl][0])
+				gc[numctrl][1] = keynum;
+			else
+				gc[numctrl][1] = 0;
+		}
+	}
 	else
 		gc[numctrl][1] = 0;
 }
@@ -732,7 +1048,7 @@ void Command_Setcontrol_f(void)
 		return;
 	}
 
-	setcontrol(gamecontrol, na);
+	setcontrol(gamecontrol);
 }
 
 void Command_Setcontrol2_f(void)
@@ -747,5 +1063,5 @@ void Command_Setcontrol2_f(void)
 		return;
 	}
 
-	setcontrol(gamecontrolbis, na);
+	setcontrol(gamecontrolbis);
 }
diff --git a/src/g_input.h b/src/g_input.h
index 2a447c683d35bcf083c81222185e45be8cc2c28f..d3c6937817b91c050fd3f83d3539147b37362962 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -93,12 +93,24 @@ typedef enum
 	gc_jump,
 	gc_console,
 	gc_pause,
+	gc_systemmenu,
+	gc_screenshot,
+	gc_recordgif,
+	gc_viewpoint,
 	gc_custom1, // Lua scriptable
 	gc_custom2, // Lua scriptable
 	gc_custom3, // Lua scriptable
 	num_gamecontrols
 } gamecontrols_e;
 
+typedef enum
+{
+	gcs_custom,
+	gcs_fps,
+	gcs_platform,
+	num_gamecontrolschemes
+} gamecontrolschemes_e;
+
 // mouse values are used once
 extern consvar_t cv_mousesens, cv_mouseysens;
 extern consvar_t cv_mousesens2, cv_mouseysens2;
@@ -116,9 +128,31 @@ extern UINT8 gamekeydown[NUMINPUTS];
 // two key codes (or virtual key) per game control
 extern INT32 gamecontrol[num_gamecontrols][2];
 extern INT32 gamecontrolbis[num_gamecontrols][2]; // secondary splitscreen player
+extern INT32 gamecontroldefault[num_gamecontrolschemes][num_gamecontrols][2]; // default control storage, use 0 (gcs_custom) for memory retention
+extern INT32 gamecontrolbisdefault[num_gamecontrolschemes][num_gamecontrols][2];
 #define PLAYER1INPUTDOWN(gc) (gamekeydown[gamecontrol[gc][0]] || gamekeydown[gamecontrol[gc][1]])
 #define PLAYER2INPUTDOWN(gc) (gamekeydown[gamecontrolbis[gc][0]] || gamekeydown[gamecontrolbis[gc][1]])
 
+#define num_gcl_tutorial_check 6
+#define num_gcl_tutorial_used 8
+#define num_gcl_tutorial_full 13
+#define num_gcl_movement 4
+#define num_gcl_camera 2
+#define num_gcl_movement_camera 6
+#define num_gcl_jump 1
+#define num_gcl_use 1
+#define num_gcl_jump_use 2
+
+extern const INT32 gcl_tutorial_check[num_gcl_tutorial_check];
+extern const INT32 gcl_tutorial_used[num_gcl_tutorial_used];
+extern const INT32 gcl_tutorial_full[num_gcl_tutorial_full];
+extern const INT32 gcl_movement[num_gcl_movement];
+extern const INT32 gcl_camera[num_gcl_camera];
+extern const INT32 gcl_movement_camera[num_gcl_movement_camera];
+extern const INT32 gcl_jump[num_gcl_jump];
+extern const INT32 gcl_use[num_gcl_use];
+extern const INT32 gcl_jump_use[num_gcl_jump_use];
+
 // peace to my little coder fingers!
 // check a gamecontrol being active or not
 
@@ -131,10 +165,13 @@ INT32 G_KeyStringtoNum(const char *keystr);
 
 // detach any keys associated to the given game control
 void G_ClearControlKeys(INT32 (*setupcontrols)[2], INT32 control);
+void G_ClearAllControlKeys(void);
 void Command_Setcontrol_f(void);
 void Command_Setcontrol2_f(void);
-void G_Controldefault(void);
-void G_SaveKeySetting(FILE *f);
-void G_CheckDoubleUsage(INT32 keynum);
+void G_DefineDefaultControls(void);
+INT32 G_GetControlScheme(INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen);
+void G_CopyControls(INT32 (*setupcontrols)[2], INT32 (*fromcontrols)[2], const INT32 *gclist, INT32 gclen);
+void G_SaveKeySetting(FILE *f, INT32 (*fromcontrols)[2], INT32 (*fromcontrolsbis)[2]);
+INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify);
 
 #endif
diff --git a/src/g_state.h b/src/g_state.h
index 81548b7cec7857c71a40c71ea71a98a3df4a390c..9d34da6a97706ba99fb0d6aac9a25407622b581e 100644
--- a/src/g_state.h
+++ b/src/g_state.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,12 +27,14 @@ typedef enum
 
 	GS_TITLESCREEN,     // title screen
 	GS_TIMEATTACK,      // time attack menu
+
 	GS_CREDITS,         // credit sequence
 	GS_EVALUATION,      // Evaluation at the end of a game.
-	GS_GAMEEND,         // game end sequence
+	GS_GAMEEND,         // game end sequence - "did you get all those chaos emeralds?"
 
 	// Hardcoded fades or other fading methods
 	GS_INTRO,           // introduction
+	GS_ENDING,          // currently shared between bad and good endings
 	GS_CUTSCENE,        // custom cutscene
 
 	// Not fadable
@@ -50,6 +52,7 @@ typedef enum
 } gameaction_t;
 
 extern gamestate_t gamestate;
+extern UINT8 titlemapinaction;
 extern UINT8 ultimatemode; // was sk_insane
 extern gameaction_t gameaction;
 
diff --git a/src/hardware/hw3sound.c b/src/hardware/hw3sound.c
index c68430921fa4053b5e3fc96fe23f94804291a0ea..f7c6e1da025e1db33471bd8775bc6a8ff4c826a3 100644
--- a/src/hardware/hw3sound.c
+++ b/src/hardware/hw3sound.c
@@ -361,7 +361,7 @@ INT32 HW3S_I_StartSound(const void *origin_p, source3D_data_t *source_parm, chan
 
 	if (splitscreen) listenmobj2 = players[secondarydisplayplayer].mo;
 
-	if (nosound)
+	if (sound_disabled)
 		return -1;
 
 	sfx = &S_sfx[sfx_id];
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index fa5bce308eff82bf52021c98bc19e1797ceb87ee..48393249214ff7dc7dd13051d88f5b50c2478a44 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -29,6 +29,7 @@
 #include "../m_argv.h"
 #include "../i_video.h"
 #include "../w_wad.h"
+#include "../p_setup.h" // levelfadecol
 
 // --------------------------------------------------------------------------
 // This is global data for planes rendering
@@ -193,14 +194,14 @@ static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1,
 	v2dy = bsp->dy;
 
 	den = v2dy*v1dx - v2dx*v1dy;
-	if (den == 0)
+	if (fabsf((float)den) < 1.0E-36f) // avoid checking exactly for 0.0
 		return NULL;       // parallel
 
 	// first check the frac along the polygon segment,
 	// (do not accept hit with the extensions)
 	num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx;
 	frac = num / den;
-	if (frac < 0 || frac > 1)
+	if (frac < 0.0 || frac > 1.0)
 		return NULL;
 
 	// now get the frac along the BSP line
@@ -217,29 +218,6 @@ static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1,
 	return &pt;
 }
 
-#if 0
-//Hurdler: it's not used anymore
-static boolean NearVertice (polyvertex_t *p1, polyvertex_t *p2)
-{
-#if 1
-	float diff;
-	diff = p2->x - p1->x;
-	if (diff < -1.5f || diff > 1.5f)
-		return false;
-	diff = p2->y - p1->y;
-	if (diff < -1.5f || diff > 1.5f)
-		return false;
-#else
-	if (p1->x != p2->x)
-		return false;
-	if (p1->y != p2->y)
-		return false;
-#endif
-	// p1 and p2 are considered the same vertex
-	return true;
-}
-#endif
-
 // if two vertice coords have a x and/or y difference
 // of less or equal than 1 FRACUNIT, they are considered the same
 // point. Note: hardcoded value, 1.0f could be anything else.
@@ -253,11 +231,23 @@ static boolean SameVertice (polyvertex_t *p1, polyvertex_t *p2)
 	diff = p2->y - p1->y;
 	if (diff < -1.5f || diff > 1.5f)
 		return false;
-#else
+#elif 0
 	if (p1->x != p2->x)
 		return false;
 	if (p1->y != p2->y)
 		return false;
+#elif 0
+	if (fabsf( p2->x - p1->x ) > 1.0E-36f )
+		return false;
+	if (fabsf( p2->y - p1->y ) > 1.0E-36f )
+		return false;
+#else
+#define  DIVLINE_VERTEX_DIFF   0.45f
+	float ep = DIVLINE_VERTEX_DIFF;
+	if (fabsf( p2->x - p1->x ) > ep )
+		return false;
+	if (fabsf( p2->y - p1->y ) > ep )
+		return false;
 #endif
 	// p1 and p2 are considered the same vertex
 	return true;
@@ -294,57 +284,57 @@ static void SplitPoly (fdivline_t *bsp,         //splitting parametric line
 		// start & end points
 		pv = fracdivline(bsp, &poly->pts[i], &poly->pts[j]);
 
-		if (pv)
+		if (pv == NULL)
+			continue;
+
+		if (ps < 0)
 		{
-			if (ps < 0)
+			// first point
+			ps = i;
+			vs = *pv;
+			fracs = bspfrac;
+		}
+		else
+		{
+			//the partition line traverse a junction between two segments
+			// or the two points are so close, they can be considered as one
+			// thus, don't accept, since split 2 must be another vertex
+			if (SameVertice(pv, &lastpv))
 			{
-				// first point
-				ps = i;
-				vs = *pv;
-				fracs = bspfrac;
+				if (pe < 0)
+				{
+					ps = i;
+					psonline = 1;
+				}
+				else
+				{
+					pe = i;
+					peonline = 1;
+				}
 			}
 			else
 			{
-				//the partition line traverse a junction between two segments
-				// or the two points are so close, they can be considered as one
-				// thus, don't accept, since split 2 must be another vertex
-				if (SameVertice(pv, &lastpv))
+				if (pe < 0)
 				{
-					if (pe < 0)
-					{
-						ps = i;
-						psonline = 1;
-					}
-					else
-					{
-						pe = i;
-						peonline = 1;
-					}
+					pe = i;
+					ve = *pv;
+					frace = bspfrac;
 				}
 				else
 				{
-					if (pe < 0)
-					{
-						pe = i;
-						ve = *pv;
-						frace = bspfrac;
-					}
-					else
-					{
-					// a frac, not same vertice as last one
-					// we already got pt2 so pt 2 is not on the line,
-					// so we probably got back to the start point
-					// which is on the line
-						if (SameVertice(pv, &vs))
-							psonline = 1;
-						break;
-					}
+				// a frac, not same vertice as last one
+				// we already got pt2 so pt 2 is not on the line,
+				// so we probably got back to the start point
+				// which is on the line
+					if (SameVertice(pv, &vs))
+						psonline = 1;
+					break;
 				}
 			}
-
-			// remember last point intercept to detect identical points
-			lastpv = *pv;
 		}
+
+		// remember last point intercept to detect identical points
+		lastpv = *pv;
 	}
 
 	// no split: the partition line is either parallel and
@@ -368,7 +358,7 @@ static void SplitPoly (fdivline_t *bsp,         //splitting parametric line
 		return;
 	}
 
-	if (ps >= 0 && pe < 0)
+	if (pe < 0)
 	{
 		//I_Error("SplitPoly: only one point for split line (%d %d)", ps, pe);
 		*frontpoly = poly;
@@ -387,7 +377,7 @@ static void SplitPoly (fdivline_t *bsp,         //splitting parametric line
 		*backpoly = HWR_AllocPoly(2 + nptback);
 	else
 		*backpoly = NULL;
-	if (nptfront)
+	if (nptfront > 0)
 		*frontpoly = HWR_AllocPoly(2 + nptfront);
 	else
 		*frontpoly = NULL;
@@ -482,42 +472,42 @@ static poly_t *CutOutSubsecPoly(seg_t *lseg, INT32 count, poly_t *poly)
 
 			pv = fracdivline(&cutseg, &poly->pts[i], &poly->pts[j]);
 
-			if (pv)
+			if (pv == NULL)
+				continue;
+
+			if (ps < 0)
 			{
-				if (ps < 0)
+				ps = i;
+				vs = *pv;
+				fracs = bspfrac;
+			}
+			else
+			{
+				//frac 1 on previous segment,
+				//     0 on the next,
+				//the split line goes through one of the convex poly
+				// vertices, happens quite often since the convex
+				// poly is already adjacent to the subsector segs
+				// on most borders
+				if (SameVertice(pv, &vs))
+					continue;
+
+				if (fracs <= bspfrac)
 				{
+					nump = 2 + poly->numpts - (i-ps);
+					pe = ps;
 					ps = i;
-					vs = *pv;
-					fracs = bspfrac;
+					ve = *pv;
 				}
 				else
 				{
-					//frac 1 on previous segment,
-					//     0 on the next,
-					//the split line goes through one of the convex poly
-					// vertices, happens quite often since the convex
-					// poly is already adjacent to the subsector segs
-					// on most borders
-					if (SameVertice(pv, &vs))
-						continue;
-
-					if (fracs <= bspfrac)
-					{
-						nump = 2 + poly->numpts - (i-ps);
-						pe = ps;
-						ps = i;
-						ve = *pv;
-					}
-					else
-					{
-						nump = 2 + (i-ps);
-						pe = i;
-						ve = vs;
-						vs = *pv;
-					}
-					//found 2nd point
-					break;
+					nump = 2 + (i-ps);
+					pe = i;
+					ve = vs;
+					vs = *pv;
 				}
+				//found 2nd point
+				break;
 			}
 		}
 
@@ -581,18 +571,42 @@ static inline void HWR_SubsecPoly(INT32 num, poly_t *poly)
 // search for the segs source of this divline
 static inline void SearchDivline(node_t *bsp, fdivline_t *divline)
 {
-#if 0 // MAR - If you don't use the same partition line that the BSP uses, the front/back polys won't match the subsectors in the BSP!
-#endif
 	divline->x = FIXED_TO_FLOAT(bsp->x);
 	divline->y = FIXED_TO_FLOAT(bsp->y);
 	divline->dx = FIXED_TO_FLOAT(bsp->dx);
 	divline->dy = FIXED_TO_FLOAT(bsp->dy);
 }
 
+#ifdef HWR_LOADING_SCREEN
 //Hurdler: implement a loading status
 static size_t ls_count = 0;
 static UINT8 ls_percent = 0;
 
+static void loading_status(void)
+{
+	char s[16];
+	int x, y;
+
+	I_OsPolling();
+	CON_Drawer();
+	sprintf(s, "%d%%", (++ls_percent)<<1);
+	x = BASEVIDWIDTH/2;
+	y = BASEVIDHEIGHT/2;
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // Black background to match fade in effect
+	//V_DrawPatchFill(W_CachePatchName("SRB2BACK",PU_CACHE)); // SRB2 background, ehhh too bright.
+	M_DrawTextBox(x-58, y-8, 13, 1);
+	V_DrawString(x-50, y, V_YELLOWMAP, "Loading...");
+	V_DrawRightAlignedString(x+50, y, V_YELLOWMAP, s);
+
+	// Is this really necessary at this point..?
+	V_DrawCenteredString(BASEVIDWIDTH/2, 40, V_YELLOWMAP, "OPENGL MODE IS INCOMPLETE AND MAY");
+	V_DrawCenteredString(BASEVIDWIDTH/2, 50, V_YELLOWMAP, "NOT DISPLAY SOME SURFACES.");
+	V_DrawCenteredString(BASEVIDWIDTH/2, 70, V_YELLOWMAP, "USE AT SONIC'S RISK.");
+
+	I_UpdateNoVsync();
+}
+#endif
+
 // poly : the convex polygon that encloses all child subsectors
 static void WalkBSPNode(INT32 bspnum, poly_t *poly, UINT16 *leafnode, fixed_t *bbox)
 {
@@ -630,36 +644,19 @@ static void WalkBSPNode(INT32 bspnum, poly_t *poly, UINT16 *leafnode, fixed_t *b
 		}
 		else
 		{
-			HWR_SubsecPoly(bspnum&(~NF_SUBSECTOR), poly);
-			//Hurdler: implement a loading status
+			HWR_SubsecPoly(bspnum & ~NF_SUBSECTOR, poly);
 
+			//Hurdler: implement a loading status
+#ifdef HWR_LOADING_SCREEN
 			if (ls_count-- <= 0)
 			{
-				char s[16];
-				int x, y;
-
-				I_OsPolling();
 				ls_count = numsubsectors/50;
-				CON_Drawer();
-				sprintf(s, "%d%%", (++ls_percent)<<1);
-				x = BASEVIDWIDTH/2;
-				y = BASEVIDHEIGHT/2;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // Black background to match fade in effect
-				//V_DrawPatchFill(W_CachePatchName("SRB2BACK",PU_CACHE)); // SRB2 background, ehhh too bright.
-				M_DrawTextBox(x-58, y-8, 13, 1);
-				V_DrawString(x-50, y, V_YELLOWMAP, "Loading...");
-				V_DrawRightAlignedString(x+50, y, V_YELLOWMAP, s);
-
-				// Is this really necessary at this point..?
-				V_DrawCenteredString(BASEVIDWIDTH/2, 40, V_YELLOWMAP, "OPENGL MODE IS INCOMPLETE AND MAY");
-				V_DrawCenteredString(BASEVIDWIDTH/2, 50, V_YELLOWMAP, "NOT DISPLAY SOME SURFACES.");
-				V_DrawCenteredString(BASEVIDWIDTH/2, 70, V_YELLOWMAP, "USE AT SONIC'S RISK.");
-
-				I_UpdateNoVsync();
+				loading_status();
 			}
+#endif
 		}
 		M_ClearBox(bbox);
-		poly = extrasubsectors[bspnum&~NF_SUBSECTOR].planepoly;
+		poly = extrasubsectors[bspnum & ~NF_SUBSECTOR].planepoly;
 
 		for (i = 0, pt = poly->pts; i < poly->numpts; i++,pt++)
 			M_AddToBox(bbox, FLOAT_TO_FIXED(pt->x), FLOAT_TO_FIXED(pt->y));
@@ -691,14 +688,13 @@ static void WalkBSPNode(INT32 bspnum, poly_t *poly, UINT16 *leafnode, fixed_t *b
 	if (backpoly)
 	{
 		// Correct back bbox to include floor/ceiling convex polygon
-		WalkBSPNode(bsp->children[1], backpoly, &bsp->children[1],
-			bsp->bbox[1]);
+		WalkBSPNode(bsp->children[1], backpoly, &bsp->children[1], bsp->bbox[1]);
 
-		// enlarge bbox with seconde child
+		// enlarge bbox with second child
 		M_AddToBox(bbox, bsp->bbox[1][BOXLEFT  ],
-			bsp->bbox[1][BOXTOP   ]);
+		                 bsp->bbox[1][BOXTOP   ]);
 		M_AddToBox(bbox, bsp->bbox[1][BOXRIGHT ],
-			bsp->bbox[1][BOXBOTTOM]);
+		                 bsp->bbox[1][BOXBOTTOM]);
 	}
 }
 
@@ -778,9 +774,9 @@ static void SearchSegInBSP(INT32 bspnum,polyvertex_t *p,poly_t *poly)
 
 	if (bspnum & NF_SUBSECTOR)
 	{
-		if (bspnum!=-1)
+		if (bspnum != -1)
 		{
-			bspnum&=~NF_SUBSECTOR;
+			bspnum &= ~NF_SUBSECTOR;
 			q = extrasubsectors[bspnum].planepoly;
 			if (poly == q || !q)
 				return;
@@ -966,7 +962,9 @@ void HWR_CreatePlanePolygons(INT32 bspnum)
 	fixed_t rootbbox[4];
 
 	CONS_Debug(DBG_RENDER, "Creating polygons, please wait...\n");
+#ifdef HWR_LOADING_SCREEN
 	ls_count = ls_percent = 0; // reset the loading status
+#endif
 	CON_Drawer(); //let the user know what we are doing
 	I_FinishUpdate(); // page flip or blit buffer
 
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 78fc31afccceb1397b08bc40bc3a17e247ff0d89..6bc2c712e3f5c593d24aed10dd643c7134d76d50 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -41,50 +41,340 @@ static INT32 blocksize, blockwidth, blockheight;
 INT32 patchformat = GR_TEXFMT_AP_88; // use alpha for holes
 INT32 textureformat = GR_TEXFMT_P_8; // use chromakey for hole
 
-// sprite, use alpha and chroma key for hole
-static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
-	INT32 pblockwidth, INT32 pblockheight, INT32 blockmodulo,
-	INT32 ptexturewidth, INT32 ptextureheight,
-	INT32 originx, INT32 originy, // where to draw patch in surface block
-	const patch_t *realpatch, INT32 bpp)
+static const INT32 format2bpp[16] =
 {
-	INT32 x, x1, x2;
-	INT32 col, ncols;
-	fixed_t xfrac, xfracstep;
-	fixed_t yfrac, yfracstep, position, count;
-	fixed_t scale_y;
+	0, //0
+	0, //1
+	1, //2  GR_TEXFMT_ALPHA_8
+	1, //3  GR_TEXFMT_INTENSITY_8
+	1, //4  GR_TEXFMT_ALPHA_INTENSITY_44
+	1, //5  GR_TEXFMT_P_8
+	4, //6  GR_RGBA
+	0, //7
+	0, //8
+	0, //9
+	2, //10 GR_TEXFMT_RGB_565
+	2, //11 GR_TEXFMT_ARGB_1555
+	2, //12 GR_TEXFMT_ARGB_4444
+	2, //13 GR_TEXFMT_ALPHA_INTENSITY_88
+	2, //14 GR_TEXFMT_AP_88
+};
+
+
+// This code was originally placed directly in HWR_DrawPatchInCache.
+// It is now split from it for my sanity! (and the sanity of others)
+// -- Monster Iestyn (13/02/19)
+static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipmap_t *mipmap,
+								INT32 pblockheight, INT32 blockmodulo,
+								fixed_t yfracstep, fixed_t scale_y,
+								texpatch_t *originPatch, INT32 patchheight,
+								INT32 bpp)
+{
+	fixed_t yfrac, position, count;
+	UINT8 *dest;
+	const UINT8 *source;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = 0;
+
+	// for writing a pixel to dest
 	RGBA_t colortemp;
+	UINT8 alpha;
+	UINT8 texel;
+	UINT16 texelu16;
+
+	(void)patchheight; // This parameter is unused
+
+	if (originPatch) // originPatch can be NULL here, unlike in the software version
+		originy = originPatch->originy;
+
+	while (patchcol->topdelta != 0xff)
+	{
+		topdelta = patchcol->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		source = (const UINT8 *)patchcol + 3;
+		count  = ((patchcol->length * scale_y) + (FRACUNIT/2)) >> FRACBITS;
+		position = originy + topdelta;
+
+		yfrac = 0;
+		//yfracstep = (patchcol->length << FRACBITS) / count;
+		if (position < 0)
+		{
+			yfrac = -position<<FRACBITS;
+			count += (((position * scale_y) + (FRACUNIT/2)) >> FRACBITS);
+			position = 0;
+		}
+
+		position = ((position * scale_y) + (FRACUNIT/2)) >> FRACBITS;
+
+		if (position < 0)
+			position = 0;
+
+		if (position + count >= pblockheight)
+			count = pblockheight - position;
+
+		dest = block + (position*blockmodulo);
+		while (count > 0)
+		{
+			count--;
+
+			texel = source[yfrac>>FRACBITS];
+
+			if (firetranslucent && (transtables[(texel<<8)+0x40000]!=texel))
+				alpha = 0x80;
+			else
+				alpha = 0xff;
+
+			//Hurdler: not perfect, but better than holes
+			if (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX && (mipmap->flags & TF_CHROMAKEYED))
+				texel = HWR_CHROMAKEY_EQUIVALENTCOLORINDEX;
+			//Hurdler: 25/04/2000: now support colormap in hardware mode
+			else if (mipmap->colormap)
+				texel = mipmap->colormap[texel];
+
+			// hope compiler will get this switch out of the loops (dreams...)
+			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
+			// Alam: SRB2 uses Mingw, HUGS
+			switch (bpp)
+			{
+				case 2 : texelu16 = (UINT16)((alpha<<8) | texel);
+						 memcpy(dest, &texelu16, sizeof(UINT16));
+						 break;
+				case 3 : colortemp = V_GetColor(texel);
+						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
+						 break;
+				case 4 : colortemp = V_GetColor(texel);
+						 colortemp.s.alpha = alpha;
+						 memcpy(dest, &colortemp, sizeof(RGBA_t));
+						 break;
+				// default is 1
+				default: *dest = texel;
+						 break;
+			}
+
+			dest += blockmodulo;
+			yfrac += yfracstep;
+		}
+		patchcol = (const column_t *)((const UINT8 *)patchcol + patchcol->length + 4);
+	}
+}
+
+static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block, GLMipmap_t *mipmap,
+								INT32 pblockheight, INT32 blockmodulo,
+								fixed_t yfracstep, fixed_t scale_y,
+								texpatch_t *originPatch, INT32 patchheight,
+								INT32 bpp)
+{
+	fixed_t yfrac, position, count;
 	UINT8 *dest;
 	const UINT8 *source;
-	const column_t *patchcol;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = 0;
+
+	// for writing a pixel to dest
+	RGBA_t colortemp;
 	UINT8 alpha;
-	UINT8 *block = mipmap->grInfo.data;
 	UINT8 texel;
 	UINT16 texelu16;
 
-	x1 = originx;
-	x2 = x1 + SHORT(realpatch->width);
+	if (originPatch) // originPatch can be NULL here, unlike in the software version
+		originy = originPatch->originy;
+
+	while (patchcol->topdelta != 0xff)
+	{
+		topdelta = patchcol->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		topdelta = patchheight-patchcol->length-topdelta;
+		source = (const UINT8 *)patchcol + 3;
+		count  = ((patchcol->length * scale_y) + (FRACUNIT/2)) >> FRACBITS;
+		position = originy + topdelta;
+
+		yfrac = (patchcol->length-1) << FRACBITS;
+
+		if (position < 0)
+		{
+			yfrac += position<<FRACBITS;
+			count += (((position * scale_y) + (FRACUNIT/2)) >> FRACBITS);
+			position = 0;
+		}
+
+		position = ((position * scale_y) + (FRACUNIT/2)) >> FRACBITS;
+
+		if (position < 0)
+			position = 0;
+
+		if (position + count >= pblockheight)
+			count = pblockheight - position;
+
+		dest = block + (position*blockmodulo);
+		while (count > 0)
+		{
+			count--;
+
+			texel = source[yfrac>>FRACBITS];
+
+			if (firetranslucent && (transtables[(texel<<8)+0x40000]!=texel))
+				alpha = 0x80;
+			else
+				alpha = 0xff;
+
+			//Hurdler: not perfect, but better than holes
+			if (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX && (mipmap->flags & TF_CHROMAKEYED))
+				texel = HWR_CHROMAKEY_EQUIVALENTCOLORINDEX;
+			//Hurdler: 25/04/2000: now support colormap in hardware mode
+			else if (mipmap->colormap)
+				texel = mipmap->colormap[texel];
+
+			// hope compiler will get this switch out of the loops (dreams...)
+			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
+			// Alam: SRB2 uses Mingw, HUGS
+			switch (bpp)
+			{
+				case 2 : texelu16 = (UINT16)((alpha<<8) | texel);
+						 memcpy(dest, &texelu16, sizeof(UINT16));
+						 break;
+				case 3 : colortemp = V_GetColor(texel);
+						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
+						 break;
+				case 4 : colortemp = V_GetColor(texel);
+						 colortemp.s.alpha = alpha;
+						 memcpy(dest, &colortemp, sizeof(RGBA_t));
+						 break;
+				// default is 1
+				default: *dest = texel;
+						 break;
+			}
+
+			dest += blockmodulo;
+			yfrac -= yfracstep;
+		}
+		patchcol = (const column_t *)((const UINT8 *)patchcol + patchcol->length + 4);
+	}
+}
+
+
+// Simplified patch caching function
+// for use by sprites and other patches that are not part of a wall texture
+// no alpha or flipping should be present since we do not want non-texture graphics to have them
+// no offsets are used either
+// -- Monster Iestyn (13/02/19)
+static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
+	INT32 pblockwidth, INT32 pblockheight,
+	INT32 pwidth, INT32 pheight,
+	const patch_t *realpatch)
+{
+	INT32 ncols;
+	fixed_t xfrac, xfracstep;
+	fixed_t yfracstep, scale_y;
+	const column_t *patchcol;
+	UINT8 *block = mipmap->grInfo.data;
+	INT32 bpp;
+	INT32 blockmodulo;
+
+	if (pwidth <= 0 || pheight <= 0)
+		return;
+
+	ncols = (pwidth * pblockwidth) / pwidth;
+
+	// source advance
+	xfrac = 0;
+	xfracstep = (pwidth        << FRACBITS) / pblockwidth;
+	yfracstep = (pheight       << FRACBITS) / pblockheight;
+	scale_y   = (pblockheight  << FRACBITS) / pheight;
+
+	bpp = format2bpp[mipmap->grInfo.format];
+
+	if (bpp < 1 || bpp > 4)
+		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
+
+	// NOTE: should this actually be pblockwidth*bpp?
+	blockmodulo = blockwidth*bpp;
+
+	// Draw each column to the block cache
+	for (; ncols--; block += bpp, xfrac += xfracstep)
+	{
+		patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
+
+		HWR_DrawColumnInCache(patchcol, block, mipmap,
+								pblockheight, blockmodulo,
+								yfracstep, scale_y,
+								NULL, pheight, // not that pheight is going to get used anyway...
+								bpp);
+	}
+}
+
+// This function we use for caching patches that belong to textures
+static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
+	INT32 pblockwidth, INT32 pblockheight,
+	texture_t *texture, texpatch_t *patch,
+	const patch_t *realpatch)
+{
+	INT32 x, x1, x2;
+	INT32 col, ncols;
+	fixed_t xfrac, xfracstep;
+	fixed_t yfracstep, scale_y;
+	const column_t *patchcol;
+	UINT8 *block = mipmap->grInfo.data;
+	INT32 bpp;
+	INT32 blockmodulo;
+	INT32 width, height;
+	// Column drawing function pointer.
+	static void (*ColumnDrawerPointer)(const column_t *patchcol, UINT8 *block, GLMipmap_t *mipmap,
+								INT32 pblockheight, INT32 blockmodulo,
+								fixed_t yfracstep, fixed_t scale_y,
+								texpatch_t *originPatch, INT32 patchheight,
+								INT32 bpp);
+
+	if (texture->width <= 0 || texture->height <= 0)
+		return;
+
+	/*if ((patch->style == AST_TRANSLUCENT) && (patch->alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
+	{
+		if (patch->alpha < 255/11) // Is the patch way too translucent? Don't render then.
+			continue;
+		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawTransFlippedColumnInCache : HWR_DrawTransColumnInCache;
+	}
+	else*/
+	{
+		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
+	}
+
+	x1 = patch->originx;
+	width = SHORT(realpatch->width);
+	height = SHORT(realpatch->height);
+	x2 = x1 + width;
+
+	if (x1 > texture->width || x2 < 0)
+		return; // patch not located within texture's x bounds, ignore
+
+	if (patch->originy > texture->height || (patch->originy + height) < 0)
+		return; // patch not located within texture's y bounds, ignore
+
+	// patch is actually inside the texture!
+	// now check if texture is partly off-screen and adjust accordingly
 
+	// left edge
 	if (x1 < 0)
 		x = 0;
 	else
 		x = x1;
 
-	if (x2 > ptexturewidth)
-		x2 = ptexturewidth;
+	// right edge
+	if (x2 > texture->width)
+		x2 = texture->width;
 
-	if (!ptexturewidth)
-		return;
 
-	col = x * pblockwidth / ptexturewidth;
-	ncols = ((x2 - x) * pblockwidth) / ptexturewidth;
+	col = x * pblockwidth / texture->width;
+	ncols = ((x2 - x) * pblockwidth) / texture->width;
 
 /*
-	CONS_Debug(DBG_RENDER, "patch %dx%d texture %dx%d block %dx%d\n", SHORT(realpatch->width),
-															SHORT(realpatch->height),
-															ptexturewidth,
-															textureheight,
-															pblockwidth,pblockheight);
+	CONS_Debug(DBG_RENDER, "patch %dx%d texture %dx%d block %dx%d\n",
+															width, height,
+															texture->width,          texture->height,
+															pblockwidth,             pblockheight);
 	CONS_Debug(DBG_RENDER, "      col %d ncols %d x %d\n", col, ncols, x);
 */
 
@@ -93,90 +383,31 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	if (x1 < 0)
 		xfrac = -x1<<FRACBITS;
 
-	xfracstep = (ptexturewidth << FRACBITS) / pblockwidth;
-	yfracstep = (ptextureheight<< FRACBITS) / pblockheight;
+	xfracstep = (texture->width << FRACBITS) / pblockwidth;
+	yfracstep = (texture->height<< FRACBITS) / pblockheight;
+	scale_y   = (pblockheight  << FRACBITS) / texture->height;
+
+	bpp = format2bpp[mipmap->grInfo.format];
+
 	if (bpp < 1 || bpp > 4)
 		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
 
+	// NOTE: should this actually be pblockwidth*bpp?
+	blockmodulo = blockwidth*bpp;
+
+	// Draw each column to the block cache
 	for (block += col*bpp; ncols--; block += bpp, xfrac += xfracstep)
 	{
-		INT32 topdelta, prevdelta = -1;
-		patchcol = (const column_t *)((const UINT8 *)realpatch
-		 + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
-
-		scale_y = (pblockheight << FRACBITS) / ptextureheight;
-
-		while (patchcol->topdelta != 0xff)
-		{
-			topdelta = patchcol->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)patchcol + 3;
-			count  = ((patchcol->length * scale_y) + (FRACUNIT/2)) >> FRACBITS;
-			position = originy + topdelta;
-
-			yfrac = 0;
-			//yfracstep = (patchcol->length << FRACBITS) / count;
-			if (position < 0)
-			{
-				yfrac = -position<<FRACBITS;
-				count += (((position * scale_y) + (FRACUNIT/2)) >> FRACBITS);
-				position = 0;
-			}
-
-			position = ((position * scale_y) + (FRACUNIT/2)) >> FRACBITS;
-
-			if (position < 0)
-				position = 0;
-
-			if (position + count >= pblockheight)
-				count = pblockheight - position;
+		if (patch->flip & 1)
+			patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[(width-1)-(xfrac>>FRACBITS)]));
+		else
+			patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
 
-			dest = block + (position*blockmodulo);
-			while (count > 0)
-			{
-				count--;
-
-				texel = source[yfrac>>FRACBITS];
-
-				if (firetranslucent && (transtables[(texel<<8)+0x40000]!=texel))
-					alpha = 0x80;
-				else
-					alpha = 0xff;
-
-				//Hurdler: not perfect, but better than holes
-				if (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX && (mipmap->flags & TF_CHROMAKEYED))
-					texel = HWR_CHROMAKEY_EQUIVALENTCOLORINDEX;
-				//Hurdler: 25/04/2000: now support colormap in hardware mode
-				else if (mipmap->colormap)
-					texel = mipmap->colormap[texel];
-
-				// hope compiler will get this switch out of the loops (dreams...)
-				// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
-				// Alam: SRB2 uses Mingw, HUGS
-				switch (bpp)
-				{
-					case 2 : texelu16 = (UINT16)((alpha<<8) | texel);
-					         memcpy(dest, &texelu16, sizeof(UINT16));
-					         break;
-					case 3 : colortemp = V_GetColor(texel);
-					         memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
-					         break;
-					case 4 : colortemp = V_GetColor(texel);
-					         colortemp.s.alpha = alpha;
-					         memcpy(dest, &colortemp, sizeof(RGBA_t));
-					         break;
-					// default is 1
-					default: *dest = texel;
-					         break;
-				}
-
-				dest += blockmodulo;
-				yfrac += yfracstep;
-			}
-			patchcol = (const column_t *)((const UINT8 *)patchcol + patchcol->length + 4);
-		}
+		ColumnDrawerPointer(patchcol, block, mipmap,
+								pblockheight, blockmodulo,
+								yfracstep, scale_y,
+								patch, height,
+								bpp);
 	}
 }
 
@@ -337,26 +568,6 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 	//CONS_Debug(DBG_RENDER, "Width is %d, Height is %d\n", blockwidth, blockheight);
 }
 
-
-static const INT32 format2bpp[16] =
-{
-	0, //0
-	0, //1
-	1, //2  GR_TEXFMT_ALPHA_8
-	1, //3  GR_TEXFMT_INTENSITY_8
-	1, //4  GR_TEXFMT_ALPHA_INTENSITY_44
-	1, //5  GR_TEXFMT_P_8
-	4, //6  GR_RGBA
-	0, //7
-	0, //8
-	0, //9
-	2, //10 GR_TEXFMT_RGB_565
-	2, //11 GR_TEXFMT_ARGB_1555
-	2, //12 GR_TEXFMT_ARGB_4444
-	2, //13 GR_TEXFMT_ALPHA_INTENSITY_88
-	2, //14 GR_TEXFMT_AP_88
-};
-
 static UINT8 *MakeBlock(GLMipmap_t *grMipmap)
 {
 	UINT8 *block;
@@ -440,13 +651,10 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
 	{
 		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
-		HWR_DrawPatchInCache(&grtex->mipmap,
+		HWR_DrawTexturePatchInCache(&grtex->mipmap,
 		                     blockwidth, blockheight,
-		                     blockwidth*format2bpp[grtex->mipmap.grInfo.format],
-		                     texture->width, texture->height,
-		                     patch->originx, patch->originy,
-		                     realpatch,
-		                     format2bpp[grtex->mipmap.grInfo.format]);
+		                     texture, patch,
+		                     realpatch);
 		Z_Unlock(realpatch);
 	}
 	//Hurdler: not efficient at all but I don't remember exactly how HWR_DrawPatchInCache works :(
@@ -533,11 +741,8 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 
 		HWR_DrawPatchInCache(grMipmap,
 			newwidth, newheight,
-			blockwidth*format2bpp[grMipmap->grInfo.format],
 			grPatch->width, grPatch->height,
-			0, 0,
-			patch,
-			format2bpp[grMipmap->grInfo.format]);
+			patch);
 	}
 
 	grPatch->max_s = (float)newwidth / (float)blockwidth;
@@ -581,8 +786,8 @@ void HWR_FreeTextureCache(void)
 
 	// free all hardware-converted graphics cached in the heap
 	// our gool is only the textures since user of the texture is the texture cache
-	Z_FreeTags(PU_HWRCACHE, PU_HWRCACHE);
-	Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRCACHE_UNLOCKED);
+	Z_FreeTag(PU_HWRCACHE);
+	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
 
 	// Alam: free the Z_Blocks before freeing it's users
 
@@ -629,8 +834,8 @@ void HWR_SetPalette(RGBA_t *palette)
 	// now flush data texture cache so 32 bit texture are recomputed
 	if (patchformat == GR_RGBA || textureformat == GR_RGBA)
 	{
-		Z_FreeTags(PU_HWRCACHE, PU_HWRCACHE);
-		Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRCACHE_UNLOCKED);
+		Z_FreeTag(PU_HWRCACHE);
+		Z_FreeTag(PU_HWRCACHE_UNLOCKED);
 	}
 }
 
diff --git a/src/hardware/hw_clip.c b/src/hardware/hw_clip.c
index fd6ba13f331b47ee14d547c71c4b5b54cbb2b561..6d120efe72c4ca251b61dfa5c68be1b206719d31 100644
--- a/src/hardware/hw_clip.c
+++ b/src/hardware/hw_clip.c
@@ -338,7 +338,7 @@ angle_t gld_FrustumAngle(void)
 	}
 
 	// If the pitch is larger than this you can look all around at a FOV of 90
-	if (aimingangle > (ANGLE_45+ANG1) && (ANGLE_315-ANG1) > aimingangle)
+	if (abs((signed)aimingangle) > 46 * ANG1)
 		return 0xffffffff;
 
 	// ok, this is a gross hack that barely works...
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index f6bbf9455a2415184f2d7d69abe5b56f079d4c5e..44929dd67ba00267173afde6f4fa55a50440c107 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -26,10 +26,6 @@
 #include <windows.h>
 #endif
 
-#if defined (VID_X11) && !defined (HAVE_SDL)
-#include <GL/glx.h>
-#endif
-
 #include "../doomdef.h"
 //THIS MUST DISAPPEAR!!!
 #include "hw_glide.h"
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 47c7c02a0b76a6e7f229f20a2036e243338eaffc..5dcead77cb9c59ed4d192feadf64ecde878802ad 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -20,8 +20,8 @@
 #define _HWR_DEFS_
 #include "../doomtype.h"
 
-#define ZCLIP_PLANE 4.0f
-#define NZCLIP_PLANE 0.9f
+#define ZCLIP_PLANE 4.0f // Used for the actual game drawing
+#define NZCLIP_PLANE 0.9f // Seems to be only used for the HUD and screen textures
 
 // ==========================================================================
 //                                                               SIMPLE TYPES
@@ -127,12 +127,13 @@ enum EPolyFlags
 
 	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pels are discarded (holes in texture)
 	PF_Translucent      = 0x00000002,   // Poly is transparent, alpha = level of transparency
-	PF_Additive         = 0x00000024,   // Poly is added to the frame buffer
+	PF_Additive         = 0x00000004,   // Poly is added to the frame buffer
 	PF_Environment      = 0x00000008,   // Poly should be drawn environment mapped.
 	                                    // Hurdler: used for text drawing
 	PF_Substractive     = 0x00000010,   // for splat
 	PF_NoAlphaTest      = 0x00000020,   // hiden param
-	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive)&~PF_NoAlphaTest,
+	PF_Fog              = 0x00000040,   // Fog blocks
+	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive|PF_Fog)&~PF_NoAlphaTest,
 
 		// other flag bits
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index dc97f70146a636b300c3b773b7a534b5be163143..d9e688c0aaa5d8745754c662f50472b621ae47ba 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -33,6 +33,8 @@
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../st_stuff.h"
+#include "../p_local.h" // stplyr
+#include "../g_game.h" // players
 
 #include <fcntl.h>
 #include "../i_video.h"  // for rendermode != render_glide
@@ -112,10 +114,10 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	if (option & V_NOSCALESTART)
 		sdupx = sdupy = 2.0f;
 
-	v[0].x = v[3].x = (x*sdupx-gpatch->leftoffset*pdupx)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdupx+(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdupy-gpatch->topoffset*pdupy)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
+	v[0].x = v[3].x = (x*sdupx-SHORT(gpatch->leftoffset)*pdupx)/vid.width - 1;
+	v[2].x = v[1].x = (x*sdupx+(SHORT(gpatch->width)-SHORT(gpatch->leftoffset))*pdupx)/vid.width - 1;
+	v[0].y = v[1].y = 1-(y*sdupy-SHORT(gpatch->topoffset)*pdupy)/vid.height;
+	v[2].y = v[3].y = 1-(y*sdupy+(SHORT(gpatch->height)-SHORT(gpatch->topoffset))*pdupy)/vid.height;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
@@ -147,14 +149,11 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 //  | /|
 //  |/ |
 //  0--1
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+	float dupx, dupy, fscalew, fscaleh, fwidth, fheight;
 
-	if (alphalevel == 12)
-		alphalevel = 0;
-	else if (alphalevel >= 10 && alphalevel < 13)
+	UINT8 perplayershuffle = 0;
+
+	if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
@@ -163,40 +162,193 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	else
 		HWR_GetMappedPatch(gpatch, colormap);
 
+	dupx = (float)vid.dupx;
+	dupy = (float)vid.dupy;
+
 	switch (option & V_SCALEPATCHMASK)
 	{
 	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
+		dupx = dupy = 1.0f;
 		break;
 	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
+		dupx = (float)vid.smalldupx;
+		dupy = (float)vid.smalldupy;
 		break;
 	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
+		dupx = (float)vid.meddupx;
+		dupy = (float)vid.meddupy;
 		break;
 	}
 
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fscalew = fscaleh = FIXED_TO_FLOAT(pscale);
+
+	// See my comments in v_video.c's V_DrawFixedPatch
+	// -- Monster Iestyn 29/10/18
+	{
+		float offsetx = 0.0f, offsety = 0.0f;
+
+		// left offset
+		if (option & V_FLIP)
+			offsetx = (float)(SHORT(gpatch->width) - SHORT(gpatch->leftoffset)) * fscalew;
+		else
+			offsetx = (float)SHORT(gpatch->leftoffset) * fscalew;
 
-	if (option & V_SPLITSCREEN)
-		sdupy /= 2.0f;
+		// top offset
+		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
+		offsety = (float)SHORT(gpatch->topoffset) * fscaleh;
 
-	if (option & V_FLIP) // Need to flip both this and sow
+		if ((option & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
+		{
+			offsetx *= dupx;
+			offsety *= dupy;
+		}
+
+		cx -= offsetx;
+		cy -= offsety;
+	}
+
+	if (splitscreen && (option & V_PERPLAYER))
 	{
-		v[0].x = v[3].x = (cx*sdupx-(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
-		v[2].x = v[1].x = (cx*sdupx+gpatch->leftoffset*pdupx)/vid.width - 1;
+		float adjusty = ((option & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		fscaleh /= 2;
+		cy /= 2;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			float adjustx = ((option & V_NOSCALESTART) ? vid.width : BASEVIDWIDTH)/2.0f;
+			fscalew /= 2;
+			cx /= 2;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				option &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(option & V_NOSCALESTART))
+	{
+		cx = cx * dupx;
+		cy = cy * dupy;
+
+		if (!(option & V_SCALEPATCHMASK))
+		{
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
+			// cx and cy are possibly *slightly* off from float maths
+			// This is done before here compared to software because we directly alter cx and cy to centre
+			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			{
+				// Need to temporarily cache the real patch to get the colour of the top left pixel
+				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				if (!column->topdelta)
+				{
+					const UINT8 *source = (const UINT8 *)(column) + 3;
+					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				}
+				Z_Free(realpatch);
+			}
+			// centre screen
+			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+			{
+				if (option & V_SNAPTORIGHT)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+				else if (!(option & V_SNAPTOLEFT))
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2;
+				if (perplayershuffle & 4)
+					cx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+				else if (perplayershuffle & 8)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+			}
+			if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+			{
+				if (option & V_SNAPTOBOTTOM)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+				else if (!(option & V_SNAPTOTOP))
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2;
+				if (perplayershuffle & 1)
+					cy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+				else if (perplayershuffle & 2)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+			}
+		}
+	}
+
+	if (pscale != FRACUNIT || (splitscreen && option & V_PERPLAYER))
+	{
+		fwidth = (float)SHORT(gpatch->width) * fscalew * dupx;
+		fheight = (float)SHORT(gpatch->height) * fscaleh * dupy;
 	}
 	else
 	{
-		v[0].x = v[3].x = (cx*sdupx-gpatch->leftoffset*pdupx)/vid.width - 1;
-		v[2].x = v[1].x = (cx*sdupx+(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
+		fwidth = (float)SHORT(gpatch->width) * dupx;
+		fheight = (float)SHORT(gpatch->height) * dupy;
 	}
 
-	v[0].y = v[1].y = 1-(cy*sdupy-gpatch->topoffset*pdupy)/vid.height;
-	v[2].y = v[3].y = 1-(cy*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
+	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
+	cx = -1 + (cx / (vid.width/2));
+	cy = 1 - (cy / (vid.height/2));
+
+	// fwidth and fheight are similar
+	fwidth /= vid.width / 2;
+	fheight /= vid.height / 2;
+
+	// set the polygon vertices to the right positions
+	v[0].x = v[3].x = cx;
+	v[2].x = v[1].x = cx + fwidth;
+
+	v[0].y = v[1].y = cy;
+	v[2].y = v[3].y = cy - fheight;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
@@ -249,48 +401,128 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 //  | /|
 //  |/ |
 //  0--1
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+	float dupx, dupy, fscale, fwidth, fheight;
 
-	if (alphalevel == 12)
-		alphalevel = 0;
-	else if (alphalevel >= 10 && alphalevel < 13)
+	if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
 
+	dupx = (float)vid.dupx;
+	dupy = (float)vid.dupy;
+
 	switch (option & V_SCALEPATCHMASK)
 	{
 	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
+		dupx = dupy = 1.0f;
 		break;
 	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
+		dupx = (float)vid.smalldupx;
+		dupy = (float)vid.smalldupy;
 		break;
 	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
+		dupx = (float)vid.meddupx;
+		dupy = (float)vid.meddupy;
 		break;
 	}
 
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fscale = FIXED_TO_FLOAT(pscale);
+
+	// fuck it, no GL support for croppedpatch v_perplayer right now. it's not like it's accessible to Lua or anything, and we only use it for menus...
+
+	cy -= (float)SHORT(gpatch->topoffset) * fscale;
+	cx -= (float)SHORT(gpatch->leftoffset) * fscale;
+
+	if (!(option & V_NOSCALESTART))
+	{
+		cx = cx * dupx;
+		cy = cy * dupy;
+
+		if (!(option & V_SCALEPATCHMASK))
+		{
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
+			// cx and cy are possibly *slightly* off from float maths
+			// This is done before here compared to software because we directly alter cx and cy to centre
+			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			{
+				// Need to temporarily cache the real patch to get the colour of the top left pixel
+				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				if (!column->topdelta)
+				{
+					const UINT8 *source = (const UINT8 *)(column) + 3;
+					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				}
+				Z_Free(realpatch);
+			}
+			// centre screen
+			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+			{
+				if (option & V_SNAPTORIGHT)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+				else if (!(option & V_SNAPTOLEFT))
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2;
+			}
+			if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+			{
+				if (option & V_SNAPTOBOTTOM)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+				else if (!(option & V_SNAPTOTOP))
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2;
+			}
+		}
+	}
+
+	fwidth = w;
+	fheight = h;
+
+	if (fwidth > SHORT(gpatch->width))
+		fwidth = SHORT(gpatch->width);
+
+	if (fheight > SHORT(gpatch->height))
+		fheight = SHORT(gpatch->height);
+
+	if (pscale != FRACUNIT)
+	{
+		fwidth *=  fscale * dupx;
+		fheight *=  fscale * dupy;
+	}
+	else
+	{
+		fwidth *= dupx;
+		fheight *= dupy;
+	}
+
+	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
+	cx = -1 + (cx / (vid.width/2));
+	cy = 1 - (cy / (vid.height/2));
+
+	// fwidth and fheight are similar
+	fwidth /= vid.width / 2;
+	fheight /= vid.height / 2;
 
-	v[0].x = v[3].x =     (cx*sdupx -           gpatch->leftoffset  * pdupx) / vid.width - 1;
-	v[2].x = v[1].x =     (cx*sdupx + ((w) - gpatch->leftoffset) * pdupx) / vid.width - 1;
-	v[0].y = v[1].y = 1 - (cy*sdupy -           gpatch->topoffset   * pdupy) / vid.height;
-	v[2].y = v[3].y = 1 - (cy*sdupy + ((h) - gpatch->topoffset)  * pdupy) / vid.height;
+	// set the polygon vertices to the right positions
+	v[0].x = v[3].x = cx;
+	v[2].x = v[1].x = cx + fwidth;
+
+	v[0].y = v[1].y = cy;
+	v[2].y = v[3].y = cy - fheight;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].sow = v[3].sow = ((sx  )/(float)gpatch->width )*gpatch->max_s;
-	v[2].sow = v[1].sow = ((sx+w)/(float)gpatch->width )*gpatch->max_s;
-	v[0].tow = v[1].tow = ((sy  )/(float)gpatch->height)*gpatch->max_t;
-	v[2].tow = v[3].tow = ((sy+h)/(float)gpatch->height)*gpatch->max_t;
+	v[0].sow = v[3].sow     = ((sx  )/(float)SHORT(gpatch->width) )*gpatch->max_s;
+	if (sx + w > SHORT(gpatch->width))
+		v[2].sow = v[1].sow = gpatch->max_s;
+	else
+		v[2].sow = v[1].sow = ((sx+w)/(float)SHORT(gpatch->width) )*gpatch->max_s;
+
+	v[0].tow = v[1].tow     = ((sy  )/(float)SHORT(gpatch->height))*gpatch->max_t;
+	if (sy + h > SHORT(gpatch->height))
+		v[2].tow = v[3].tow = gpatch->max_t;
+	else
+		v[2].tow = v[3].tow = ((sy+h)/(float)SHORT(gpatch->height))*gpatch->max_t;
 
 	flags = BLENDMODE|PF_Clip|PF_NoZClip|PF_NoDepthTest;
 
@@ -434,18 +666,14 @@ void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum
 //  | /|
 //  |/ |
 //  0--1
-void HWR_FadeScreenMenuBack(UINT32 color, INT32 height)
+void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength)
 {
 	FOutVector  v[4];
 	FSurfaceInfo Surf;
 
-	// setup some neat-o translucency effect
-	if (!height) //cool hack 0 height is full height
-		height = vid.height;
-
 	v[0].x = v[3].x = -1.0f;
 	v[2].x = v[1].x =  1.0f;
-	v[0].y = v[1].y =  1.0f-((height<<1)/(float)vid.height);
+	v[0].y = v[1].y = -1.0f;
 	v[2].y = v[3].y =  1.0f;
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
@@ -454,8 +682,191 @@ void HWR_FadeScreenMenuBack(UINT32 color, INT32 height)
 	v[0].tow = v[1].tow = 1.0f;
 	v[2].tow = v[3].tow = 0.0f;
 
-	Surf.FlatColor.rgba = UINT2RGBA(color);
-	Surf.FlatColor.s.alpha = (UINT8)((0xff/2) * ((float)height / vid.height)); //calum: varies console alpha
+	if (color & 0xFF00) // Do COLORMAP fade.
+	{
+		Surf.FlatColor.rgba = UINT2RGBA(0x01010160);
+		Surf.FlatColor.s.alpha = (strength*8);
+	}
+	else // Do TRANSMAP** fade.
+	{
+		Surf.FlatColor.rgba = V_GetColor(color).rgba;
+		Surf.FlatColor.s.alpha = softwaretranstogl[strength];
+	}
+	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+}
+
+// -----------------+
+// HWR_DrawFadeFill : draw flat coloured rectangle, with transparency
+// -----------------+
+void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength)
+{
+	FOutVector v[4];
+	FSurfaceInfo Surf;
+	float fx, fy, fw, fh;
+
+	UINT8 perplayershuffle = 0;
+
+//  3--2
+//  | /|
+//  |/ |
+//  0--1
+
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(color & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
+
+		if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+		{
+			if (color & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(color & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+		{
+			// same thing here
+			if (color & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(color & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
+
+	//Hurdler: do we still use this argb color? if not, we should remove it
+	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
+	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
+
+	v[0].sow = v[3].sow = 0.0f;
+	v[2].sow = v[1].sow = 1.0f;
+	v[0].tow = v[1].tow = 0.0f;
+	v[2].tow = v[3].tow = 1.0f;
+
+	if (actualcolor & 0xFF00) // Do COLORMAP fade.
+	{
+		Surf.FlatColor.rgba = UINT2RGBA(0x01010160);
+		Surf.FlatColor.s.alpha = (strength*8);
+	}
+	else // Do TRANSMAP** fade.
+	{
+		Surf.FlatColor.rgba = V_GetColor(actualcolor).rgba;
+		Surf.FlatColor.s.alpha = softwaretranstogl[strength];
+	}
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
 }
 
@@ -486,6 +897,32 @@ void HWR_DrawConsoleBack(UINT32 color, INT32 height)
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
 }
 
+// Very similar to HWR_DrawConsoleBack, except we draw from the middle(-ish) of the screen to the bottom.
+void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight)
+{
+	FOutVector  v[4];
+	FSurfaceInfo Surf;
+	INT32 height = (boxheight * 4) + (boxheight/2)*5; // 4 lines of space plus gaps between and some leeway
+
+	// setup some neat-o translucency effect
+
+	v[0].x = v[3].x = -1.0f;
+	v[2].x = v[1].x =  1.0f;
+	v[0].y = v[1].y =  -1.0f;
+	v[2].y = v[3].y =  -1.0f+((height<<1)/(float)vid.height);
+	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
+
+	v[0].sow = v[3].sow = 0.0f;
+	v[2].sow = v[1].sow = 1.0f;
+	v[0].tow = v[1].tow = 1.0f;
+	v[2].tow = v[3].tow = 0.0f;
+
+	Surf.FlatColor.rgba = UINT2RGBA(color);
+	Surf.FlatColor.s.alpha = (color == 0 ? 0xC0 : 0x80); // make black darker, like software
+
+	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+}
+
 
 // ==========================================================================
 //                                                             R_DRAW.C STUFF
@@ -620,18 +1057,6 @@ void HWR_DrawViewBorder(INT32 clearlines)
 //                                                     AM_MAP.C DRAWING STUFF
 // ==========================================================================
 
-// Clear the automap part of the screen
-void HWR_clearAutomap(void)
-{
-	FRGBAFloat fColor = {0, 0, 0, 1};
-
-	// minx,miny,maxx,maxy
-	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
-	HWD.pfnClearBuffer(true, true, &fColor);
-	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
-}
-
-
 // -----------------+
 // HWR_drawAMline   : draw a line of the automap (the clipping is already done in automap code)
 // Arg              : color is a RGB 888 value
@@ -652,6 +1077,173 @@ void HWR_drawAMline(const fline_t *fl, INT32 color)
 	HWD.pfnDraw2DLine(&v1, &v2, color_rgba);
 }
 
+// -------------------+
+// HWR_DrawConsoleFill     : draw flat coloured transparent rectangle because that's cool, and hw sucks less than sw for that.
+// -------------------+
+void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 actualcolor)
+{
+	FOutVector v[4];
+	FSurfaceInfo Surf;
+	float fx, fy, fw, fh;
+
+	UINT8 perplayershuffle = 0;
+
+//  3--2
+//  | /|
+//  |/ |
+//  0--1
+
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(color & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
+
+		if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+		{
+			if (color & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(color & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+		{
+			// same thing here
+			if (color & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(color & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
+
+	//Hurdler: do we still use this argb color? if not, we should remove it
+	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
+	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
+
+	v[0].sow = v[3].sow = 0.0f;
+	v[2].sow = v[1].sow = 1.0f;
+	v[0].tow = v[1].tow = 0.0f;
+	v[2].tow = v[3].tow = 1.0f;
+
+	Surf.FlatColor.rgba = UINT2RGBA(actualcolor);
+	Surf.FlatColor.s.alpha = 0x80;
+
+	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+}
 
 // -----------------+
 // HWR_DrawFill     : draw flat coloured rectangle, with no texture
@@ -660,25 +1252,163 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 {
 	FOutVector v[4];
 	FSurfaceInfo Surf;
-	float sdupx, sdupy;
+	float fx, fy, fw, fh;
 
-	if (w < 0 || h < 0)
-		return; // consistency w/ software
+	UINT8 perplayershuffle = 0;
 
 //  3--2
 //  | /|
 //  |/ |
 //  0--1
-	sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
 
-	if (color & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(color & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
+		{
+			RGBA_t rgbaColour = V_GetColor(color);
+			FRGBAFloat clearColour;
+			clearColour.red = (float)rgbaColour.s.red / 255;
+			clearColour.green = (float)rgbaColour.s.green / 255;
+			clearColour.blue = (float)rgbaColour.s.blue / 255;
+			clearColour.alpha = 1;
+			HWD.pfnClearBuffer(true, false, &clearColour);
+			return;
+		}
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
+
+		if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+		{
+			if (color & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(color & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+		{
+			// same thing here
+			if (color & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(color & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
 
-	v[0].x = v[3].x = (x*sdupx)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdupx + w*sdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdupy)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdupy + h*sdupy)/vid.height;
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
 
 	//Hurdler: do we still use this argb color? if not, we should remove it
 	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
@@ -777,21 +1507,24 @@ UINT8 *HWR_GetScreenshot(void)
 	return buf;
 }
 
-boolean HWR_Screenshot(const char *lbmname)
+boolean HWR_Screenshot(const char *pathname)
 {
 	boolean ret;
 	UINT8 *buf = malloc(vid.width * vid.height * 3 * sizeof (*buf));
 
 	if (!buf)
+	{
+		CONS_Debug(DBG_RENDER, "HWR_Screenshot: Failed to allocate memory\n");
 		return false;
+	}
 
 	// returns 24bit 888 RGB
 	HWD.pfnReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
 
 #ifdef USE_PNG
-	ret = M_SavePNG(lbmname, buf, vid.width, vid.height, false);
+	ret = M_SavePNG(pathname, buf, vid.width, vid.height, NULL);
 #else
-	ret = saveTGA(lbmname, buf, vid.width, vid.height);
+	ret = saveTGA(pathname, buf, vid.width, vid.height);
 #endif
 	free(buf);
 	return ret;
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 7672f47c26a71febd941e0ec2157dafd5446e24d..e2fa90eb035de94d0b5ad7d81de624ed0fbe4341 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -32,10 +32,6 @@
 //                                                       STANDARD DLL EXPORTS
 // ==========================================================================
 
-#ifdef HAVE_SDL
-#undef VID_X11
-#endif
-
 EXPORT boolean HWRAPI(Init) (I_Error_t ErrorFunction);
 #ifndef HAVE_SDL
 EXPORT void HWRAPI(Shutdown) (void);
@@ -43,9 +39,6 @@ EXPORT void HWRAPI(Shutdown) (void);
 #ifdef _WINDOWS
 EXPORT void HWRAPI(GetModeList) (vmode_t **pvidmodes, INT32 *numvidmodes);
 #endif
-#ifdef VID_X11
-EXPORT Window HWRAPI(HookXwin) (Display *, INT32, INT32, boolean);
-#endif
 #if defined (PURESDL) || defined (macintosh)
 EXPORT void HWRAPI(SetPalette) (INT32 *, RGBA_t *gamma);
 #else
@@ -71,14 +64,11 @@ EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
 EXPORT INT32 HWRAPI(GetRenderVersion) (void);
 
-#ifdef VID_X11 // ifdef to be removed as soon as windoze supports that as well
-// metzgermeister: added for Voodoo detection
-EXPORT char *HWRAPI(GetRenderer) (void);
-#endif
 #ifdef SHUFFLE
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 #endif
+EXPORT void HWRAPI(FlushScreenTextures) (void);
 EXPORT void HWRAPI(StartScreenWipe) (void);
 EXPORT void HWRAPI(EndScreenWipe) (void);
 EXPORT void HWRAPI(DoScreenWipe) (float alpha);
@@ -114,16 +104,13 @@ struct hwdriver_s
 #ifdef _WINDOWS
 	GetModeList         pfnGetModeList;
 #endif
-#ifdef VID_X11
-	HookXwin            pfnHookXwin;
-	GetRenderer         pfnGetRenderer;
-#endif
 #ifndef HAVE_SDL
 	Shutdown            pfnShutdown;
 #endif
 #ifdef SHUFFLE
 	PostImgRedraw       pfnPostImgRedraw;
 #endif
+	FlushScreenTextures pfnFlushScreenTextures;
 	StartScreenWipe     pfnStartScreenWipe;
 	EndScreenWipe       pfnEndScreenWipe;
 	DoScreenWipe        pfnDoScreenWipe;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 5d1a81d4f4318826e3ce9c3b8d176a3b2baa00f5..9656e54e9b1d44df9bc921cbb544a03e8101a26d 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -27,6 +27,9 @@
 // the original aspect ratio of Doom graphics isn't square
 #define ORIGINAL_ASPECT (320.0f/200.0f)
 
+// Uncomment this to enable the OpenGL loading screen
+//#define HWR_LOADING_SCREEN
+
 // -----------
 // structures
 // -----------
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 267666749baf363ec02652fdb65f342a96f0d8f0..a52d72869ee87e9c40fa6f6348d2d489da67f73c 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -62,7 +62,7 @@ static dynlights_t *dynlights = &view_dynlights[0];
 light_t lspr[NUMLIGHTS] =
 {
 	// type       offset x,   y  coronas color, c_size,light color,l_radius, sqr radius computed at init
-	// UNDEFINED: 0
+	// NOLIGHT: 0
 	{ UNDEFINED_SPR,  0.0f,   0.0f, 0x00000000,  24.0f, 0x00000000,   0.0f, 0.0f},
 	// weapons
 	// RINGSPARK_L
@@ -151,10 +151,9 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_POSS
 	&lspr[NOLIGHT],     // SPR_SPOS
 	&lspr[NOLIGHT],     // SPR_FISH
-	&lspr[NOLIGHT],     // SPR_BUZZ Graue 03-10-2004
-	&lspr[NOLIGHT],     // SPR_RBUZ Graue 03-10-2004
+	&lspr[NOLIGHT],     // SPR_BUZZ
+	&lspr[NOLIGHT],     // SPR_RBUZ
 	&lspr[NOLIGHT],     // SPR_JETB
-	&lspr[NOLIGHT],     // SPR_JETW
 	&lspr[NOLIGHT],     // SPR_JETG
 	&lspr[NOLIGHT],     // SPR_CCOM
 	&lspr[NOLIGHT],     // SPR_DETN
@@ -162,19 +161,25 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_TRET
 	&lspr[NOLIGHT],     // SPR_TURR
 	&lspr[NOLIGHT],     // SPR_SHRP
+	&lspr[NOLIGHT],     // SPR_CRAB
 	&lspr[NOLIGHT],     // SPR_JJAW
 	&lspr[NOLIGHT],     // SPR_SNLR
 	&lspr[NOLIGHT],     // SPR_VLTR
 	&lspr[NOLIGHT],     // SPR_PNTY
 	&lspr[NOLIGHT],     // SPR_ARCH
 	&lspr[NOLIGHT],     // SPR_CBFS
+	&lspr[JETLIGHT_L],  // SPR_STAB
 	&lspr[NOLIGHT],     // SPR_SPSH
 	&lspr[NOLIGHT],     // SPR_ESHI
 	&lspr[NOLIGHT],     // SPR_GSNP
+	&lspr[NOLIGHT],     // SPR_GSNL
+	&lspr[NOLIGHT],     // SPR_GSNH
 	&lspr[NOLIGHT],     // SPR_MNUS
+	&lspr[NOLIGHT],     // SPR_MNUD
 	&lspr[NOLIGHT],     // SPR_SSHL
 	&lspr[NOLIGHT],     // SPR_UNID
-	&lspr[NOLIGHT],     // SPR_BBUZ
+	&lspr[NOLIGHT],     // SPR_CANA
+	&lspr[NOLIGHT],     // SPR_CANG
 
 	// Generic Boos Items
 	&lspr[JETLIGHT_L],     // SPR_JETF // Boss jet fumes
@@ -197,9 +202,14 @@ light_t *t_lspr[NUMSPRITES] =
 	// Boss 4 (Castle Eggman)
 	&lspr[NOLIGHT],     // SPR_EGGP
 	&lspr[REDBALL_L],   // SPR_EFIR
+	&lspr[NOLIGHT],     // SPR_EGR1
 
 	// Boss 5 (Arid Canyon)
-	&lspr[NOLIGHT],     // SPR_EGGQ
+	&lspr[NOLIGHT],     //SPR_FANG // replaces EGGQ
+	&lspr[NOLIGHT],     //SPR_FBOM
+	&lspr[NOLIGHT],     //SPR_FSGN
+	&lspr[REDBALL_L],   //SPR_BARX // bomb explosion (also used by barrel)
+	&lspr[NOLIGHT],     //SPR_BARD // bomb dust (also used by barrel)
 
 	// Boss 6 (Red Volcano)
 	&lspr[NOLIGHT],     // SPR_EEGR
@@ -227,18 +237,18 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_RING
 	&lspr[NOLIGHT],     // SPR_TRNG
 	&lspr[NOLIGHT],     // SPR_TOKE
-	&lspr[REDBALL_L],     // SPR_RFLG
-	&lspr[BLUEBALL_L],     // SPR_BFLG
-	&lspr[NOLIGHT],     // SPR_NWNG
+	&lspr[REDBALL_L],   // SPR_RFLG
+	&lspr[BLUEBALL_L],  // SPR_BFLG
+	&lspr[NOLIGHT],     // SPR_SPHR
+	&lspr[NOLIGHT],     // SPR_NCHP
+	&lspr[NOLIGHT],     // SPR_NSTR
 	&lspr[NOLIGHT],     // SPR_EMBM
 	&lspr[NOLIGHT],     // SPR_CEMG
-	&lspr[NOLIGHT],     // SPR_EMER
+	&lspr[NOLIGHT],     // SPR_SHRD
 
 	// Interactive Objects
-	&lspr[NOLIGHT],     // SPR_FANS
 	&lspr[NOLIGHT],     // SPR_BBLS
 	&lspr[NOLIGHT],     // SPR_SIGN
-	&lspr[NOLIGHT],     // SPR_STEM
 	&lspr[NOLIGHT],     // SPR_SPIK
 	&lspr[NOLIGHT],     // SPR_SFLM
 	&lspr[NOLIGHT],     // SPR_USPK
@@ -294,17 +304,19 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_FWR4
 	&lspr[NOLIGHT],     // SPR_BUS1
 	&lspr[NOLIGHT],     // SPR_BUS2
+	&lspr[NOLIGHT],     // SPR_BUS3
 	// Trees (both GFZ and misc)
 	&lspr[NOLIGHT],     // SPR_TRE1
 	&lspr[NOLIGHT],     // SPR_TRE2
 	&lspr[NOLIGHT],     // SPR_TRE3
 	&lspr[NOLIGHT],     // SPR_TRE4
 	&lspr[NOLIGHT],     // SPR_TRE5
+	&lspr[NOLIGHT],     // SPR_TRE6
 
 	// Techno Hill Scenery
 	&lspr[NOLIGHT],     // SPR_THZP
 	&lspr[NOLIGHT],     // SPR_FWR5
-	&lspr[REDBALL_L],     // SPR_ALRM
+	&lspr[REDBALL_L],   // SPR_ALRM
 
 	// Deep Sea Scenery
 	&lspr[NOLIGHT],     // SPR_GARG
@@ -327,11 +339,36 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_RSPB
 	&lspr[REDBALL_L],   // SPR_SFBR
 	&lspr[REDBALL_L],   // SPR_BFBR
+	&lspr[NOLIGHT],     // SPR_BANR
+	&lspr[NOLIGHT],     // SPR_PINE
+	&lspr[NOLIGHT],     // SPR_CEZB
+	&lspr[REDBALL_L],   // SPR_CNDL
+	&lspr[NOLIGHT],     // SPR_FLMH
+	&lspr[REDBALL_L],   // SPR_CTRC
+	&lspr[NOLIGHT],     // SPR_CFLG
+	&lspr[NOLIGHT],     // SPR_CSTA
+	&lspr[NOLIGHT],     // SPR_CBBS
 
 	// Arid Canyon Scenery
 	&lspr[NOLIGHT],     // SPR_BTBL
 	&lspr[NOLIGHT],     // SPR_STBL
 	&lspr[NOLIGHT],     // SPR_CACT
+	&lspr[NOLIGHT],     // SPR_WWSG
+	&lspr[NOLIGHT],     // SPR_WWS2
+	&lspr[NOLIGHT],     // SPR_WWS3
+	&lspr[NOLIGHT],     // SPR_OILL
+	&lspr[NOLIGHT],     // SPR_OILF
+	&lspr[NOLIGHT],     // SPR_BARR
+	&lspr[NOLIGHT],     // SPR_REMT
+	&lspr[NOLIGHT],     // SPR_TAZD
+	&lspr[NOLIGHT],     // SPR_ADST
+	&lspr[NOLIGHT],     // SPR_MCRT
+	&lspr[NOLIGHT],     // SPR_MCSP
+	&lspr[NOLIGHT],     // SPR_NON2
+	&lspr[NOLIGHT],     // SPR_SALD
+	&lspr[NOLIGHT],     // SPR_TRAE
+	&lspr[NOLIGHT],     // SPR_TRAI
+	&lspr[NOLIGHT],     // SPR_STEA
 
 	// Red Volcano Scenery
 	&lspr[REDBALL_L],   // SPR_FLME
@@ -347,12 +384,25 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_XMS3
 	&lspr[NOLIGHT],     // SPR_XMS4
 	&lspr[NOLIGHT],     // SPR_XMS5
+	&lspr[NOLIGHT],     // SPR_FHZI
+
+	// Halloween Scenery
+	&lspr[RINGLIGHT_L], // SPR_PUMK
+	&lspr[NOLIGHT],     // SPR_HHPL
+	&lspr[NOLIGHT],     // SPR_SHRM
+	&lspr[NOLIGHT],     // SPR_HHZM
 
 	// Botanic Serenity Scenery
 	&lspr[NOLIGHT],     // SPR_BSZ1
 	&lspr[NOLIGHT],     // SPR_BSZ2
 	&lspr[NOLIGHT],     // SPR_BSZ3
-	&lspr[NOLIGHT],     // SPR_BSZ4
+	//&lspr[NOLIGHT],     -- SPR_BSZ4
+	&lspr[NOLIGHT],     // SPR_BST1
+	&lspr[NOLIGHT],     // SPR_BST2
+	&lspr[NOLIGHT],     // SPR_BST3
+	&lspr[NOLIGHT],     // SPR_BST4
+	&lspr[NOLIGHT],     // SPR_BST5
+	&lspr[NOLIGHT],     // SPR_BST6
 	&lspr[NOLIGHT],     // SPR_BSZ5
 	&lspr[NOLIGHT],     // SPR_BSZ6
 	&lspr[NOLIGHT],     // SPR_BSZ7
@@ -375,8 +425,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_FIRS
 	&lspr[NOLIGHT],     // SPR_BUBS
 	&lspr[NOLIGHT],     // SPR_ZAPS
-	&lspr[INVINCIBLE_L],     // SPR_IVSP
-	&lspr[SUPERSPARK_L],     // SPR_SSPK
+	&lspr[INVINCIBLE_L], // SPR_IVSP
+	&lspr[SUPERSPARK_L], // SPR_SSPK
 
 	&lspr[NOLIGHT],     // SPR_GOAL
 
@@ -398,13 +448,20 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_FL14
 	&lspr[NOLIGHT],     // SPR_FL15
 	&lspr[NOLIGHT],     // SPR_FL16
+	&lspr[NOLIGHT],     // SPR_FS01
+	&lspr[NOLIGHT],     // SPR_FS02
 
 	// Springs
+	&lspr[NOLIGHT],     // SPR_FANS
+	&lspr[NOLIGHT],     // SPR_STEM
+	&lspr[NOLIGHT],     // SPR_BUMP
+	&lspr[NOLIGHT],     // SPR_BLON
 	&lspr[NOLIGHT],     // SPR_SPRY
 	&lspr[NOLIGHT],     // SPR_SPRR
-	&lspr[NOLIGHT],     // SPR_SPRB Graue
+	&lspr[NOLIGHT],     // SPR_SPRB
 	&lspr[NOLIGHT],     // SPR_YSPR
 	&lspr[NOLIGHT],     // SPR_RSPR
+	&lspr[NOLIGHT],     // SPR_BSPR
 	&lspr[NOLIGHT],     // SPR_SSWY
 	&lspr[NOLIGHT],     // SPR_SSWR
 	&lspr[NOLIGHT],     // SPR_SSWB
@@ -420,7 +477,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_DUST
 	&lspr[NOLIGHT],     // SPR_FPRT
 	&lspr[SUPERSPARK_L], // SPR_TFOG
-	&lspr[NIGHTSLIGHT_L],     // SPR_SEED // Sonic CD flower seed
+	&lspr[NIGHTSLIGHT_L], // SPR_SEED
 	&lspr[NOLIGHT],     // SPR_PRTL
 
 	// Game Indicators
@@ -431,6 +488,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_GFLG
 
 	&lspr[NOLIGHT],     // SPR_CORK
+	&lspr[NOLIGHT],     // SPR_LHRT
 
 	// Ring Weapons
 	&lspr[RINGLIGHT_L],     // SPR_RRNG
@@ -459,25 +517,43 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_GOOM
 	&lspr[NOLIGHT],     // SPR_BGOM
 	&lspr[REDBALL_L],     // SPR_FFWR
-	&lspr[SMALLREDBALL_L],     // SPR_FBLL
+	&lspr[SMALLREDBALL_L], // SPR_FBLL
 	&lspr[NOLIGHT],     // SPR_SHLL
-	&lspr[REDBALL_L],     // SPR_PUMA
+	&lspr[REDBALL_L],   // SPR_PUMA
 	&lspr[NOLIGHT],     // SPR_HAMM
 	&lspr[NOLIGHT],     // SPR_KOOP
-	&lspr[REDBALL_L],     // SPR_BFLM
+	&lspr[REDBALL_L],   // SPR_BFLM
 	&lspr[NOLIGHT],     // SPR_MAXE
 	&lspr[NOLIGHT],     // SPR_MUS1
 	&lspr[NOLIGHT],     // SPR_MUS2
 	&lspr[NOLIGHT],     // SPR_TOAD
 
 	// NiGHTS Stuff
-	&lspr[SUPERSONIC_L],     // SPR_NDRN // NiGHTS drone
+	&lspr[SUPERSONIC_L], // SPR_NDRN // NiGHTS drone
 	&lspr[NOLIGHT],     // SPR_NSPK
 	&lspr[NOLIGHT],     // SPR_NBMP
 	&lspr[NOLIGHT],     // SPR_HOOP
 	&lspr[NOLIGHT],     // SPR_HSCR
 	&lspr[NOLIGHT],     // SPR_NPRU
 	&lspr[NOLIGHT],     // SPR_CAPS
+	&lspr[INVINCIBLE_L], // SPR_IDYA
+	&lspr[NOLIGHT],     // SPR_NTPN
+	&lspr[NOLIGHT],     // SPR_SHLP
+
+	// Secret badniks and hazards, shhhh
+	&lspr[NOLIGHT],     // SPR_PENG
+	&lspr[NOLIGHT],     // SPR_POPH,
+	&lspr[NOLIGHT],     // SPR_HIVE
+	&lspr[NOLIGHT],     // SPR_BUMB,
+	&lspr[NOLIGHT],     // SPR_BBUZ
+	&lspr[NOLIGHT],     // SPR_FMCE,
+	&lspr[NOLIGHT],     // SPR_HMCE,
+	&lspr[NOLIGHT],     // SPR_CACO,
+	&lspr[BLUEBALL_L],  // SPR_BAL2,
+	&lspr[NOLIGHT],     // SPR_SBOB,
+	&lspr[BLUEBALL_L],  // SPR_SBFL,
+	&lspr[BLUEBALL_L],  // SPR_SBSK,
+	&lspr[NOLIGHT],     // SPR_BATT,
 
 	// Debris
 	&lspr[RINGSPARK_L],  // SPR_SPRK
@@ -485,6 +561,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[SUPERSPARK_L], // SPR_BOM2
 	&lspr[SUPERSPARK_L], // SPR_BOM3
 	&lspr[NOLIGHT],      // SPR_BOM4
+	&lspr[REDBALL_L],    // SPR_BMNB
+	&lspr[NOLIGHT],      // SPR_WDDB
 
 	// Crumbly rocks
 	&lspr[NOLIGHT],     // SPR_ROIA
@@ -504,8 +582,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_ROIO
 	&lspr[NOLIGHT],     // SPR_ROIP
 
-	// Blue Spheres
-	&lspr[NOLIGHT],     // SPR_BBAL
+	// Bricks
+	&lspr[NOLIGHT], // SPR_BRIC
 
 	// Gravity Well Objects
 	&lspr[NOLIGHT],     // SPR_GWLG
@@ -726,6 +804,14 @@ void HWR_WallLighting(FOutVector *wlVerts)
 		FSurfaceInfo    Surf;
 		float           dist_p2d, d[4], s;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		// check bounding box first
 		if (SphereTouchBBox3D(&wlVerts[2], &wlVerts[0], &LIGHT_POS(j), DL_RADIUS(j))==false)
 			continue;
@@ -776,8 +862,6 @@ void HWR_WallLighting(FOutVector *wlVerts)
 #ifdef DL_HIGH_QUALITY
 		Surf.FlatColor.s.alpha = (UINT8)((1-dist_p2d/DL_SQRRADIUS(j))*Surf.FlatColor.s.alpha);
 #endif
-		if (!dynlights->mo[j]->state)
-			return;
 		// next state is null so fade out with alpha
 		if (dynlights->mo[j]->state->nextstate == S_NULL)
 			Surf.FlatColor.s.alpha = (UINT8)(((float)dynlights->mo[j]->tics/(float)dynlights->mo[j]->state->tics)*Surf.FlatColor.s.alpha);
@@ -808,6 +892,14 @@ void HWR_PlaneLighting(FOutVector *clVerts, int nrClipVerts)
 		FSurfaceInfo    Surf;
 		float           dist_p2d, s;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		// BP: The kickass Optimization: check if light touch bounding box
 		if (SphereTouchBBox3D(&p1, &p2, &dynlights->position[j], DL_RADIUS(j))==false)
 			continue;
@@ -839,8 +931,6 @@ void HWR_PlaneLighting(FOutVector *clVerts, int nrClipVerts)
 #ifdef DL_HIGH_QUALITY
 		Surf.FlatColor.s.alpha = (unsigned char)((1 - dist_p2d/DL_SQRRADIUS(j))*Surf.FlatColor.s.alpha);
 #endif
-		if (!dynlights->mo[j]->state)
-			return;
 		// next state is null so fade out with alpha
 		if ((dynlights->mo[j]->state->nextstate == S_NULL))
 			Surf.FlatColor.s.alpha = (unsigned char)(((float)dynlights->mo[j]->tics/(float)dynlights->mo[j]->state->tics)*Surf.FlatColor.s.alpha);
@@ -971,6 +1061,14 @@ void HWR_DrawCoronas(void)
 		if (!(p_lspr->type & CORONA_SPR))
 			continue;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		transform(&cx,&cy,&cz);
 
 		// more realistique corona !
@@ -1032,7 +1130,8 @@ void HWR_DrawCoronas(void)
 // --------------------------------------------------------------------------
 void HWR_ResetLights(void)
 {
-	dynlights->nb = 0;
+	while (dynlights->nb)
+		P_SetTarget(&dynlights->mo[--dynlights->nb], NULL);
 }
 
 // --------------------------------------------------------------------------
@@ -1063,24 +1162,25 @@ void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
 		return;
 #endif
 
+	if (dynlights->nb >= DL_MAX_LIGHT)
+		return;
+
 	// check if sprite contain dynamic light
 	p_lspr = t_lspr[spr->mobj->sprite];
-	if ((p_lspr->type&DYNLIGHT_SPR)
-	  && ((p_lspr->type != LIGHT_SPR) || cv_grstaticlighting.value)
-	  && (dynlights->nb < DL_MAX_LIGHT)
+	if (!(p_lspr->type & DYNLIGHT_SPR))
+		return;
+	if ((p_lspr->type != LIGHT_SPR) || cv_grstaticlighting.value)
+		return;
 
-	  && spr->mobj->state)
-	{
-		LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(spr->mobj->x);
-		LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(spr->mobj->z)+FIXED_TO_FLOAT(spr->mobj->height>>1)+p_lspr->light_yoffset;
-		LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(spr->mobj->y);
+	LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(spr->mobj->x);
+	LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(spr->mobj->z)+FIXED_TO_FLOAT(spr->mobj->height>>1)+p_lspr->light_yoffset;
+	LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(spr->mobj->y);
 
-		P_SetTarget(&dynlights->mo[dynlights->nb], spr->mobj);
+	P_SetTarget(&dynlights->mo[dynlights->nb], spr->mobj);
 
-		dynlights->p_lspr[dynlights->nb] = p_lspr;
+	dynlights->p_lspr[dynlights->nb] = p_lspr;
 
-		dynlights->nb++;
-	}
+	dynlights->nb++;
 }
 
 static GLPatch_t lightmappatch;
@@ -1234,6 +1334,14 @@ static void HWR_CheckSubsector(size_t num, fixed_t *bbox)
 //			if (CircleTouchBBox(&p1, &p2, &LIGHT_POS(lightnum), DL_RADIUS(lightnum))==false)
 //				continue;
 
+			if (!dynlights->mo[lightnum])
+				continue;
+			if (P_MobjWasRemoved(dynlights->mo[lightnum]))
+			{
+				P_SetTarget(&dynlights->mo[lightnum], NULL);
+				continue;
+			}
+
 			count = sub->numlines;          // how many linedefs
 			line = &segs[sub->firstline];   // first line seg
 			while (count--)
@@ -1251,18 +1359,20 @@ static void HWR_CheckSubsector(size_t num, fixed_t *bbox)
 // --------------------------------------------------------------------------
 static void HWR_AddMobjLights(mobj_t *thing)
 {
-	if (t_lspr[thing->sprite]->type & CORONA_SPR)
-	{
-		LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(thing->x);
-		LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(thing->z) + t_lspr[thing->sprite]->light_yoffset;
-		LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(thing->y);
+	if (dynlights->nb >= DL_MAX_LIGHT)
+		return;
+	if (!(t_lspr[thing->sprite]->type & CORONA_SPR))
+		return;
 
-		dynlights->p_lspr[dynlights->nb] = t_lspr[thing->sprite];
+	LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(thing->x);
+	LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(thing->z) + t_lspr[thing->sprite]->light_yoffset;
+	LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(thing->y);
 
-		dynlights->nb++;
-		if (dynlights->nb > DL_MAX_LIGHT)
-			dynlights->nb = DL_MAX_LIGHT;
-	}
+	P_SetTarget(&dynlights->mo[dynlights->nb], thing);
+
+	dynlights->p_lspr[dynlights->nb] = t_lspr[thing->sprite];
+
+	dynlights->nb++;
 }
 
 //Hurdler: The goal of this function is to walk through all the bsp starting
@@ -1288,12 +1398,9 @@ static void HWR_SearchLightsInMobjs(void)
 	//mobj_t *            mobj;
 
 	// search in the list of thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-	{
-		// a mobj ?
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			HWR_AddMobjLights((mobj_t *)th);
-	}
 }
 #endif
 
@@ -1305,7 +1412,7 @@ void HWR_CreateStaticLightmaps(int bspnum)
 #ifdef STATICLIGHT
 	CONS_Debug(DBG_RENDER, "HWR_CreateStaticLightmaps\n");
 
-	dynlights->nb = 0;
+	HWR_ResetLights();
 
 	// First: Searching for lights
 	// BP: if i was you, I will make it in create mobj since mobj can be create
@@ -1316,8 +1423,6 @@ void HWR_CreateStaticLightmaps(int bspnum)
 	// Second: Build all lightmap for walls covered by lights
 	validcount++; // to be sure
 	HWR_ComputeLightMapsInBSPNode(bspnum, NULL);
-
-	dynlights->nb = 0;
 #else
 	(void)bspnum;
 #endif
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 864cd04b66eec0bc710dcd54ac392e4e2623bb23..c600800fd9ac9e0710a0eecdf44d7e7bf8a68753 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -122,7 +122,7 @@ consvar_t cv_grfiltermode = {"gr_filtermode", "Nearest", CV_CALL, grfiltermode_c
 consvar_t cv_granisotropicmode = {"gr_anisotropicmode", "1", CV_CALL, granisotropicmode_cons_t,
                              CV_anisotropic_ONChange, 0, NULL, NULL, 0, 0, NULL};
 //static consvar_t cv_grzbuffer = {"gr_zbuffer", "On", 0, CV_OnOff};
-consvar_t cv_grcorrecttricks = {"gr_correcttricks", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grcorrecttricks = {"gr_correcttricks", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grsolvetjoin = {"gr_solvetjoin", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static void CV_FogDensity_ONChange(void)
@@ -491,10 +491,10 @@ UINT32 HWR_Lighting(INT32 light, UINT32 color, UINT32 fadecolor, boolean fogbloc
 }
 
 
-static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color, UINT32 fadecolor) // Let's see if this can work
+static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color) // Let's see if this can work
 {
-	RGBA_t realcolor, fogcolor, surfcolor;
-	INT32 alpha, fogalpha;
+	RGBA_t realcolor, surfcolor;
+	INT32 alpha;
 
 	// Don't go out of bounds
 	if (light < 0)
@@ -503,13 +503,11 @@ static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color, UINT32 fadecolor) // L
 		light = 255;
 
 	realcolor.rgba = color;
-	fogcolor.rgba = fadecolor;
 
 	alpha = (realcolor.s.alpha*255)/25;
-	fogalpha = (fogcolor.s.alpha*255)/25;
 
-	// Fog blocks seem to get slightly more opaque with more opaque colourmap opacity, and much more opaque with darker brightness
-	surfcolor.s.alpha = (UINT8)(CALCLIGHT(light, ((0xFF-light)+alpha)/2)+CALCLIGHT(0xFF-light, ((light)+fogalpha)/2));
+	// at 255 brightness, alpha is between 0 and 127, at 0 brightness alpha will always be 255
+	surfcolor.s.alpha = (alpha*light)/(2*256)+255-light;
 
 	return surfcolor.s.alpha;
 }
@@ -582,7 +580,7 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	if (nrPlaneVerts < 3)   //not even a triangle ?
 		return;
 
-	if (nrPlaneVerts > UINT16_MAX) // FIXME: exceeds plVerts size
+	if (nrPlaneVerts > (INT32)UINT16_MAX) // FIXME: exceeds plVerts size
 	{
 		CONS_Debug(DBG_RENDER, "polygon size of %d exceeds max value of %d vertices\n", nrPlaneVerts, UINT16_MAX);
 		return;
@@ -769,10 +767,10 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, true);
 	}
 
-	if (PolyFlags & PF_Translucent)
+	if (PolyFlags & (PF_Translucent|PF_Fog))
 	{
 		Surf.FlatColor.s.alpha = (UINT8)alpha;
-		PolyFlags |= PF_Modulated|PF_Occlude|PF_Clip;
+		PolyFlags |= PF_Modulated|PF_Clip;
 	}
 	else
 		PolyFlags |= PF_Masked|PF_Modulated|PF_Clip;
@@ -1065,12 +1063,11 @@ static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 //
 // HWR_SplitWall
 //
-static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum, FSurfaceInfo* Surf, UINT32 cutflag)
+static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum, FSurfaceInfo* Surf, INT32 cutflag, ffloor_t *pfloor)
 {
 	/* SoM: split up and light walls according to the
 	 lightlist. This may also include leaving out parts
 	 of the wall that can't be seen */
-	GLTexture_t * glTex;
 
 	float realtop, realbot, top, bot;
 	float pegt, pegb, pegmul;
@@ -1093,8 +1090,8 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	INT32   solid, i;
 	lightlist_t *  list = sector->lightlist;
 	const UINT8 alpha = Surf->FlatColor.s.alpha;
-	FUINT lightnum;
-	extracolormap_t *colormap;
+	FUINT lightnum = sector->lightlevel;
+	extracolormap_t *colormap = NULL;
 
 	realtop = top = wallVerts[3].y;
 	realbot = bot = wallVerts[0].y;
@@ -1110,7 +1107,7 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	endpegmul = (endpegb - endpegt) / (endtop - endbot);
 #endif
 
-	for (i = 1; i < sector->numlights; i++)
+	for (i = 0; i < sector->numlights; i++)
 	{
 #ifdef ESLOPE
         if (endtop < endrealbot)
@@ -1118,35 +1115,38 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 		if (top < realbot)
 			return;
 
-	//Hurdler: fix a crashing bug, but is it correct?
-//		if (!list[i].caster)
-//			continue;
+	// There's a compiler warning here if this comment isn't here because of indentation
+		if (!(list[i].flags & FF_NOSHADE))
+		{
+			if (pfloor && (pfloor->flags & FF_FOG))
+			{
+				lightnum = pfloor->master->frontsector->lightlevel;
+				colormap = pfloor->master->frontsector->extra_colormap;
+			}
+			else
+			{
+				lightnum = *list[i].lightlevel;
+				colormap = *list[i].extra_colormap;
+			}
+		}
 
 		solid = false;
 
-		if (list[i].caster)
+		if ((sector->lightlist[i].flags & FF_CUTSOLIDS) && !(cutflag & FF_EXTRA))
+			solid = true;
+		else if ((sector->lightlist[i].flags & FF_CUTEXTRA) && (cutflag & FF_EXTRA))
 		{
-			if (sector->lightlist[i].caster->flags & FF_CUTSOLIDS && !(cutflag & FF_EXTRA))
-				solid = true;
-			else if (sector->lightlist[i].caster->flags & FF_CUTEXTRA && cutflag & FF_EXTRA)
+			if (sector->lightlist[i].flags & FF_EXTRA)
 			{
-				if (sector->lightlist[i].caster->flags & FF_EXTRA)
-				{
-					if (sector->lightlist[i].caster->flags == cutflag) // Only merge with your own types
-						solid = true;
-				}
-				else
+				if ((sector->lightlist[i].flags & (FF_FOG|FF_SWIMMABLE)) == (cutflag & (FF_FOG|FF_SWIMMABLE))) // Only merge with your own types
 					solid = true;
 			}
 			else
-				solid = false;
+				solid = true;
 		}
 		else
 			solid = false;
 
-		if (cutflag == FF_CUTSOLIDS) // These are regular walls sent in from StoreWallRange, they shouldn't be cut from this
-			solid = false;
-
 #ifdef ESLOPE
 		if (list[i].slope)
 		{
@@ -1186,34 +1186,55 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 			if (solid && endtop > endbheight)
 				endtop = endbheight;
 #endif
-			continue;
 		}
 
+#ifdef ESLOPE
+		if (i + 1 < sector->numlights)
+		{
+			if (list[i+1].slope)
+			{
+				temp = P_GetZAt(list[i+1].slope, v1x, v1y);
+				bheight = FIXED_TO_FLOAT(temp);
+				temp = P_GetZAt(list[i+1].slope, v2x, v2y);
+				endbheight = FIXED_TO_FLOAT(temp);
+			}
+			else
+				bheight = endbheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+			endbheight = endrealbot;
+		}
+#else
+		if (i + 1 < sector->numlights)
+		{
+			bheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+		}
+#endif
+
+#ifdef ESLOPE
+		if (endbheight >= endtop)
+#endif
+		if (bheight >= top)
+			continue;
+
 		//Found a break;
-		bot = height;
+		bot = bheight;
 
 		if (bot < realbot)
 			bot = realbot;
 
 #ifdef ESLOPE
-		endbot = endheight;
+		endbot = endbheight;
 
 		if (endbot < endrealbot)
 			endbot = endrealbot;
 #endif
-
-		// colormap test
-		if (list[i-1].caster)
-		{
-			lightnum = *list[i-1].lightlevel;
-			colormap = list[i-1].extra_colormap;
-		}
-		else
-		{
-			lightnum = sector->lightlevel;
-			colormap = sector->extra_colormap;
-		}
-
 		Surf->FlatColor.s.alpha = alpha;
 
 #ifdef ESLOPE
@@ -1236,23 +1257,16 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 		wallVerts[0].y = wallVerts[1].y = bot;
 #endif
 
-		glTex = HWR_GetTexture(texnum);
-		if (cutflag & FF_TRANSLUCENT)
+		if (cutflag & FF_FOG)
+			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+		else if (cutflag & FF_TRANSLUCENT)
 			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
-		else if (glTex->mipmap.flags & TF_TRANSPARENT)
-			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Environment, false, lightnum, colormap);
 		else
 			HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
 
-		if (solid)
-			top = bheight;
-		else
-			top = height;
+		top = bot;
 #ifdef ESLOPE
-		if (solid)
-			endtop = endbheight;
-		else
-			endtop = endheight;
+		endtop = endbot;
 #endif
 	}
 
@@ -1264,17 +1278,7 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	if (top <= realbot)
 		return;
 
-	if (list[i-1].caster)
-	{
-		lightnum = *list[i-1].lightlevel;
-		colormap = list[i-1].extra_colormap;
-	}
-	else
-	{
-		lightnum = sector->lightlevel;
-		colormap = sector->extra_colormap;
-	}
-		Surf->FlatColor.s.alpha = alpha;
+	Surf->FlatColor.s.alpha = alpha;
 
 #ifdef ESLOPE
 	wallVerts[3].t = pegt + ((realtop - top) * pegmul);
@@ -1296,122 +1300,17 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
     wallVerts[0].y = wallVerts[1].y = bot;
 #endif
 
-	glTex = HWR_GetTexture(texnum);
-	if (cutflag & FF_TRANSLUCENT)
+	if (cutflag & FF_FOG)
+		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+	else if (cutflag & FF_TRANSLUCENT)
 		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
-	else if (glTex->mipmap.flags & TF_TRANSPARENT)
-		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Environment, false, lightnum, colormap);
 	else
 		HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
 }
 
-//
-// HWR_SplitFog
-// Exclusively for fog
-//
-static void HWR_SplitFog(sector_t *sector, wallVert3D *wallVerts, FSurfaceInfo* Surf, UINT32 cutflag, FUINT lightnum, extracolormap_t *colormap)
-{
-	/* SoM: split up and light walls according to the
-	 lightlist. This may also include leaving out parts
-	 of the wall that can't be seen */
-	float realtop, realbot, top, bot;
-	float pegt, pegb, pegmul;
-	float height = 0.0f, bheight = 0.0f;
-	INT32   solid, i;
-	lightlist_t *  list = sector->lightlist;
-	const UINT8 alpha = Surf->FlatColor.s.alpha;
-
-	realtop = top = wallVerts[2].y;
-	realbot = bot = wallVerts[0].y;
-	pegt = wallVerts[2].t;
-	pegb = wallVerts[0].t;
-	pegmul = (pegb - pegt) / (top - bot);
-
-	for (i = 1; i < sector->numlights; i++)
-	{
-		if (top < realbot)
-			return;
-
-	//Hurdler: fix a crashing bug, but is it correct?
-//		if (!list[i].caster)
-//			continue;
-
-		solid = false;
-
-		if (list[i].caster)
-		{
-			if (sector->lightlist[i].caster->flags & FF_FOG && cutflag & FF_FOG) // Only fog cuts fog
-			{
-				if (sector->lightlist[i].caster->flags & FF_EXTRA)
-				{
-					if (sector->lightlist[i].caster->flags == cutflag) // only cut by the same
-						solid = true;
-				}
-				else
-					solid = true;
-			}
-		}
-
-		height = FIXED_TO_FLOAT(list[i].height);
-
-		if (solid)
-			bheight = FIXED_TO_FLOAT(*list[i].caster->bottomheight);
-
-		if (height >= top)
-		{
-			if (solid && top > bheight)
-				top = bheight;
-			continue;
-		}
-
-		//Found a break;
-		bot = height;
-
-		if (bot < realbot)
-			bot = realbot;
-
-		{
-
-
-
-			Surf->FlatColor.s.alpha = alpha;
-		}
-
-		wallVerts[3].t = wallVerts[2].t = pegt + ((realtop - top) * pegmul);
-		wallVerts[0].t = wallVerts[1].t = pegt + ((realtop - bot) * pegmul);
-
-		// set top/bottom coords
-		wallVerts[2].y = wallVerts[3].y = top;
-		wallVerts[0].y = wallVerts[1].y = bot;
-
-		if (!solid) // Don't draw it if there's more fog behind it
-			HWR_AddTransparentWall(wallVerts, Surf, 0, PF_Translucent|PF_NoTexture, true, lightnum, colormap);
-
-		top = height;
-	}
-
-	bot = realbot;
-	if (top <= realbot)
-		return;
-
-	{
-
-		Surf->FlatColor.s.alpha = alpha;
-	}
-
-	wallVerts[3].t = wallVerts[2].t = pegt + ((realtop - top) * pegmul);
-	wallVerts[0].t = wallVerts[1].t = pegt + ((realtop - bot) * pegmul);
-
-	// set top/bottom coords
-	wallVerts[2].y = wallVerts[3].y = top;
-	wallVerts[0].y = wallVerts[1].y = bot;
-
-	HWR_AddTransparentWall(wallVerts, Surf, 0, PF_Translucent|PF_NoTexture, true, lightnum, colormap);
-}
-
-// HWR_DrawSkyWalls
+// HWR_DrawSkyWall
 // Draw walls into the depth buffer so that anything behind is culled properly
-static void HWR_DrawSkyWall(wallVert3D *wallVerts, FSurfaceInfo *Surf, fixed_t bottom, fixed_t top)
+static void HWR_DrawSkyWall(wallVert3D *wallVerts, FSurfaceInfo *Surf)
 {
 	HWD.pfnSetTexture(NULL);
 	// no texture
@@ -1419,9 +1318,7 @@ static void HWR_DrawSkyWall(wallVert3D *wallVerts, FSurfaceInfo *Surf, fixed_t b
 	wallVerts[0].t = wallVerts[1].t = 0;
 	wallVerts[0].s = wallVerts[3].s = 0;
 	wallVerts[2].s = wallVerts[1].s = 0;
-	// set top/bottom coords
-	wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(top); // No real way to find the correct height of this
-	wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(bottom); // worldlow/bottom because it needs to cover up the lower thok barrier wall
+	// this no longer sets top/bottom coords, this should be done before caling the function
 	HWR_ProjectWall(wallVerts, Surf, PF_Invisible|PF_Clip|PF_NoTexture, 255, NULL);
 	// PF_Invisible so it's not drawn into the colour buffer
 	// PF_NoTexture for no texture
@@ -1542,8 +1439,10 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 
 	if (gr_backsector)
 	{
-		INT32 gr_toptexture, gr_bottomtexture;
+		INT32 gr_toptexture = 0, gr_bottomtexture = 0;
 		// two sided line
+		boolean bothceilingssky = false; // turned on if both back and front ceilings are sky
+		boolean bothfloorssky = false; // likewise, but for floors
 
 #ifdef ESLOPE
 		SLOPEPARAMS(gr_backsector->c_slope, worldhigh, worldhighslope, gr_backsector->ceilingheight)
@@ -1556,17 +1455,23 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 
 		// hack to allow height changes in outdoor areas
 		// This is what gets rid of the upper textures if there should be sky
-		if (gr_frontsector->ceilingpic == skyflatnum &&
-			gr_backsector->ceilingpic  == skyflatnum)
+		if (gr_frontsector->ceilingpic == skyflatnum
+			&& gr_backsector->ceilingpic  == skyflatnum)
 		{
-			worldtop = worldhigh;
-#ifdef ESLOPE
-			worldtopslope = worldhighslope;
-#endif
+			bothceilingssky = true;
 		}
 
-		gr_toptexture = R_GetTextureNum(gr_sidedef->toptexture);
-		gr_bottomtexture = R_GetTextureNum(gr_sidedef->bottomtexture);
+		// likewise, but for floors and upper textures
+		if (gr_frontsector->floorpic == skyflatnum
+			&& gr_backsector->floorpic == skyflatnum)
+		{
+			bothfloorssky = true;
+		}
+
+		if (!bothceilingssky)
+			gr_toptexture = R_GetTextureNum(gr_sidedef->toptexture);
+		if (!bothfloorssky)
+			gr_bottomtexture = R_GetTextureNum(gr_sidedef->bottomtexture);
 
 		// check TOP TEXTURE
 		if ((
@@ -1642,7 +1547,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_toptexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1724,7 +1629,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_bottomtexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1985,15 +1890,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 			}
 #endif
 
-			if (grTex->mipmap.flags & TF_TRANSPARENT)
-				blendmode = PF_Translucent;
-
 			if (gr_frontsector->numlights)
 			{
 				if (!(blendmode & PF_Masked))
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_TRANSLUCENT);
+					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_TRANSLUCENT, NULL);
 				else
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTSOLIDS);
+				{
+					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
+				}
 			}
 			else if (!(blendmode & PF_Masked))
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_midtexture, blendmode, false, lightnum, colormap);
@@ -2008,84 +1912,42 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 			}*/
 		}
 
-		// Isn't this just the most lovely mess
+#if 1
+		// Sky culling
+		// No longer so much a mess as before!
 		if (!gr_curline->polyseg) // Don't do it for polyobjects
 		{
-			if (gr_frontsector->ceilingpic == skyflatnum || gr_backsector->ceilingpic == skyflatnum)
+			if (gr_frontsector->ceilingpic == skyflatnum)
 			{
-				fixed_t depthwallheight;
-
-				if (!gr_sidedef->toptexture || (gr_frontsector->ceilingpic == skyflatnum && gr_backsector->ceilingpic == skyflatnum)) // when both sectors are sky, the top texture isn't drawn
-					depthwallheight = gr_frontsector->ceilingheight < gr_backsector->ceilingheight ? gr_frontsector->ceilingheight : gr_backsector->ceilingheight;
-				else
-					depthwallheight = gr_frontsector->ceilingheight > gr_backsector->ceilingheight ? gr_frontsector->ceilingheight : gr_backsector->ceilingheight;
-
-				if (gr_frontsector->ceilingheight-gr_frontsector->floorheight <= 0) // current sector is a thok barrier
-				{
-					if (gr_backsector->ceilingheight-gr_backsector->floorheight <= 0) // behind sector is also a thok barrier
-					{
-						if (!gr_sidedef->bottomtexture) // Only extend further down if there's no texture
-							HWR_DrawSkyWall(wallVerts, &Surf, worldbottom < worldlow ? worldbottom : worldlow, INT32_MAX);
-						else
-							HWR_DrawSkyWall(wallVerts, &Surf, worldbottom > worldlow ? worldbottom : worldlow, INT32_MAX);
-					}
-					// behind sector is not a thok barrier
-					else if (gr_backsector->ceilingheight <= gr_frontsector->ceilingheight) // behind sector ceiling is lower or equal to current sector
-						HWR_DrawSkyWall(wallVerts, &Surf, depthwallheight, INT32_MAX);
-						// gr_front/backsector heights need to be used here because of the worldtop being set to worldhigh earlier on
-				}
-				else if (gr_backsector->ceilingheight-gr_backsector->floorheight <= 0) // behind sector is a thok barrier, current sector is not
-				{
-					if (gr_backsector->ceilingheight >= gr_frontsector->ceilingheight // thok barrier ceiling height is equal to or greater than current sector ceiling height
-						|| gr_backsector->floorheight <= gr_frontsector->floorheight // thok barrier ceiling height is equal to or less than current sector floor height
-						|| gr_backsector->ceilingpic != skyflatnum) // thok barrier is not a sky
-						HWR_DrawSkyWall(wallVerts, &Surf, depthwallheight, INT32_MAX);
-				}
-				else // neither sectors are thok barriers
+				if (gr_backsector->ceilingpic != skyflatnum) // don't cull if back sector is also sky
 				{
-					if ((gr_backsector->ceilingheight < gr_frontsector->ceilingheight && !gr_sidedef->toptexture) // no top texture and sector behind is lower
-						|| gr_backsector->ceilingpic != skyflatnum) // behind sector is not a sky
-						HWR_DrawSkyWall(wallVerts, &Surf, depthwallheight, INT32_MAX);
+					wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(INT32_MAX); // draw to top of map space
+#ifdef ESLOPE
+					wallVerts[0].y = FIXED_TO_FLOAT(worldtop);
+					wallVerts[1].y = FIXED_TO_FLOAT(worldtopslope);
+#else
+					wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(worldtop);
+#endif
+					HWR_DrawSkyWall(wallVerts, &Surf);
 				}
 			}
-			// And now for sky floors!
-			if (gr_frontsector->floorpic == skyflatnum || gr_backsector->floorpic == skyflatnum)
-			{
-				fixed_t depthwallheight;
-
-				if (!gr_sidedef->bottomtexture)
-					depthwallheight = worldbottom > worldlow ? worldbottom : worldlow;
-				else
-					depthwallheight = worldbottom < worldlow ? worldbottom : worldlow;
 
-				if (gr_frontsector->ceilingheight-gr_frontsector->floorheight <= 0) // current sector is a thok barrier
-				{
-					if (gr_backsector->ceilingheight-gr_backsector->floorheight <= 0) // behind sector is also a thok barrier
-					{
-						if (!gr_sidedef->toptexture) // Only extend up if there's no texture
-							HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, worldtop > worldhigh ? worldtop : worldhigh);
-						else
-							HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, worldtop < worldhigh ? worldtop : worldhigh);
-					}
-					// behind sector is not a thok barrier
-					else if (gr_backsector->floorheight >= gr_frontsector->floorheight) // behind sector floor is greater or equal to current sector
-						HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, depthwallheight);
-				}
-				else if (gr_backsector->ceilingheight-gr_backsector->floorheight <= 0) // behind sector is a thok barrier, current sector is not
-				{
-					if (gr_backsector->floorheight <= gr_frontsector->floorheight // thok barrier floor height is equal to or less than current sector floor height
-						|| gr_backsector->ceilingheight >= gr_frontsector->ceilingheight // thok barrier floor height is equal to or greater than current sector ceiling height
-						|| gr_backsector->floorpic != skyflatnum) // thok barrier is not a sky
-						HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, depthwallheight);
-				}
-				else // neither sectors are thok barriers
+			if (gr_frontsector->floorpic == skyflatnum)
+			{
+				if (gr_backsector->floorpic != skyflatnum) // don't cull if back sector is also sky
 				{
-					if ((gr_backsector->floorheight > gr_frontsector->floorheight && !gr_sidedef->bottomtexture) // no bottom texture and sector behind is higher
-						|| gr_backsector->floorpic != skyflatnum) // behind sector is not a sky
-						HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, depthwallheight);
+#ifdef ESLOPE
+					wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
+					wallVerts[2].y = FIXED_TO_FLOAT(worldbottomslope);
+#else
+					wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
+#endif
+					wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(INT32_MIN); // draw to bottom of map space
+					HWR_DrawSkyWall(wallVerts, &Surf);
 				}
 			}
 		}
+#endif
 	}
 	else
 	{
@@ -2143,7 +2005,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 			// I don't think that solid walls can use translucent linedef types...
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
 			else
 			{
 				if (grTex->mipmap.flags & TF_TRANSPARENT)
@@ -2156,9 +2018,27 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 		if (!gr_curline->polyseg)
 		{
 			if (gr_frontsector->ceilingpic == skyflatnum) // It's a single-sided line with sky for its sector
-				HWR_DrawSkyWall(wallVerts, &Surf, worldtop, INT32_MAX);
+			{
+				wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(INT32_MAX); // draw to top of map space
+#ifdef ESLOPE
+				wallVerts[0].y = FIXED_TO_FLOAT(worldtop);
+				wallVerts[1].y = FIXED_TO_FLOAT(worldtopslope);
+#else
+				wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(worldtop);
+#endif
+				HWR_DrawSkyWall(wallVerts, &Surf);
+			}
 			if (gr_frontsector->floorpic == skyflatnum)
-				HWR_DrawSkyWall(wallVerts, &Surf, INT32_MIN, worldbottom);
+			{
+#ifdef ESLOPE
+				wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
+				wallVerts[2].y = FIXED_TO_FLOAT(worldbottomslope);
+#else
+				wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
+#endif
+				wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(INT32_MIN); // draw to bottom of map space
+				HWR_DrawSkyWall(wallVerts, &Surf);
+			}
 		}
 	}
 
@@ -2235,27 +2115,64 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				}
 				else
 				{
-#ifdef ESLOPE // P.S. this is better-organized than the old version
-					fixed_t offs = sides[(newline ? newline : rover->master)->sidenum[0]].rowoffset;
-					grTex = HWR_GetTexture(texnum);
+					fixed_t texturevpeg;
+					boolean attachtobottom = false;
+#ifdef ESLOPE
+					boolean slopeskew = false; // skew FOF walls with slopes?
+#endif
+
+					// Wow, how was this missing from OpenGL for so long?
+					// ...Oh well, anyway, Lower Unpegged now changes pegging of FOFs like in software
+					// -- Monster Iestyn 26/06/18
+					if (newline)
+					{
+						texturevpeg = sides[newline->sidenum[0]].rowoffset;
+						attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM);
+#ifdef ESLOPE
+						slopeskew = !!(newline->flags & ML_DONTPEGTOP);
+#endif
+					}
+					else
+					{
+						texturevpeg = sides[rover->master->sidenum[0]].rowoffset;
+						attachtobottom = !!(gr_linedef->flags & ML_DONTPEGBOTTOM);
+#ifdef ESLOPE
+						slopeskew = !!(rover->master->flags & ML_DONTPEGTOP);
+#endif
+					}
 
-					wallVerts[3].t = (*rover->topheight - h + offs) * grTex->scaleY;
-					wallVerts[2].t = (*rover->topheight - hS + offs) * grTex->scaleY;
-					wallVerts[0].t = (*rover->topheight - l + offs) * grTex->scaleY;
-					wallVerts[1].t = (*rover->topheight - lS + offs) * grTex->scaleY;
-#else
 					grTex = HWR_GetTexture(texnum);
 
-					if (newline)
+#ifdef ESLOPE
+					if (!slopeskew) // no skewing
 					{
-						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;
+						if (attachtobottom)
+							texturevpeg -= *rover->topheight - *rover->bottomheight;
+						wallVerts[3].t = (*rover->topheight - h + texturevpeg) * grTex->scaleY;
+						wallVerts[2].t = (*rover->topheight - hS + texturevpeg) * grTex->scaleY;
+						wallVerts[0].t = (*rover->topheight - l + texturevpeg) * grTex->scaleY;
+						wallVerts[1].t = (*rover->topheight - lS + texturevpeg) * 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;
+						if (!attachtobottom) // skew by top
+						{
+							wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
+							wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY;
+							wallVerts[1].t = (hS - lS + texturevpeg) * grTex->scaleY;
+						}
+						else // skew by bottom
+						{
+							wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY;
+							wallVerts[3].t = wallVerts[0].t - (h - l) * grTex->scaleY;
+							wallVerts[2].t = wallVerts[1].t - (hS - lS) * grTex->scaleY;
+						}
 					}
+#else
+					if (attachtobottom)
+						texturevpeg -= *rover->topheight - *rover->bottomheight;
+					wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + texturevpeg) * grTex->scaleY;
+					wallVerts[0].t = wallVerts[1].t = (*rover->topheight - l + texturevpeg) * grTex->scaleY;
 #endif
 
 					wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
@@ -2265,7 +2182,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode;
 
-					blendmode = PF_Translucent|PF_NoTexture;
+					blendmode = PF_Fog|PF_NoTexture;
 
 					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
@@ -2273,15 +2190,15 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 					if (rover->master->frontsector->extra_colormap)
 					{
 
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba,rover->master->frontsector->extra_colormap->fadergba);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba);
 					}
 					else
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG,FADEFOG);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG);
 					}
 
 					if (gr_frontsector->numlights)
-						HWR_SplitFog(gr_frontsector, wallVerts, &Surf, rover->flags, lightnum, colormap);
+						HWR_SplitWall(gr_frontsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -2289,18 +2206,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT)
+					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 					{
 						blendmode = PF_Translucent;
 						Surf.FlatColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
-					else if (grTex->mipmap.flags & TF_TRANSPARENT)
-					{
-						blendmode = PF_Environment;
-					}
 
 					if (gr_frontsector->numlights)
-						HWR_SplitWall(gr_frontsector, wallVerts, texnum, &Surf, rover->flags);
+						HWR_SplitWall(gr_frontsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2389,22 +2302,22 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode;
 
-					blendmode = PF_Translucent|PF_NoTexture;
+					blendmode = PF_Fog|PF_NoTexture;
 
 					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
 
 					if (rover->master->frontsector->extra_colormap)
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba,rover->master->frontsector->extra_colormap->fadergba);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba);
 					}
 					else
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG,FADEFOG);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG);
 					}
 
 					if (gr_backsector->numlights)
-						HWR_SplitFog(gr_backsector, wallVerts, &Surf, rover->flags, lightnum, colormap);
+						HWR_SplitWall(gr_backsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -2412,18 +2325,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT)
+					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 					{
 						blendmode = PF_Translucent;
 						Surf.FlatColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
-					else if (grTex->mipmap.flags & TF_TRANSPARENT)
-					{
-						blendmode = PF_Environment;
-					}
 
 					if (gr_backsector->numlights)
-						HWR_SplitWall(gr_backsector, wallVerts, texnum, &Surf, rover->flags);
+						HWR_SplitWall(gr_backsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2451,6 +2360,12 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 {
 	fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends
 	fixed_t backf1, backf2, backc1, backc2; // back floor ceiling ends
+	boolean bothceilingssky = false, bothfloorssky = false;
+
+	if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
+		bothceilingssky = true;
+	if (abacksector->floorpic == skyflatnum && afrontsector->floorpic == skyflatnum)
+		bothfloorssky = true;
 
 	// GZDoom method of sloped line clipping
 
@@ -2483,62 +2398,45 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 		backf1 = backf2 = abacksector->floorheight;
 		backc1 = backc2 = abacksector->ceilingheight;
 	}
-
-	// now check for closed sectors!
-	if (backc1 <= frontf1 && backc2 <= frontf2)
-	{
-		checkforemptylines = false;
-		if (!seg->sidedef->toptexture)
-			return false;
-
-		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
-			return false;
-
-		return true;
-	}
-
-	if (backf1 >= frontc1 && backf2 >= frontc2)
-	{
-		checkforemptylines = false;
-		if (!seg->sidedef->bottomtexture)
-			return false;
-
-		// properly render skies (consider door "open" if both floors are sky):
-		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
-			return false;
-
-		return true;
-	}
-
-	if (backc1 <= backf1 && backc2 <= backf2)
+	// properly render skies (consider door "open" if both ceilings are sky)
+	// same for floors
+	if (!bothceilingssky && !bothfloorssky)
 	{
-		checkforemptylines = false;
-		// preserve a kind of transparent door/lift special effect:
-		if (backc1 < frontc1 || backc2 < frontc2)
+		// now check for closed sectors!
+		if ((backc1 <= frontf1 && backc2 <= frontf2)
+			|| (backf1 >= frontc1 && backf2 >= frontc2))
 		{
-			if (!seg->sidedef->toptexture)
-				return false;
+			checkforemptylines = false;
+			return true;
 		}
-		if (backf1 > frontf1 || backf2 > frontf2)
+
+		if (backc1 <= backf1 && backc2 <= backf2)
 		{
-			if (!seg->sidedef->bottomtexture)
-				return false;
+			// preserve a kind of transparent door/lift special effect:
+			if (((backc1 >= frontc1 && backc2 >= frontc2) || seg->sidedef->toptexture)
+			&& ((backf1 <= frontf1 && backf2 <= frontf2) || seg->sidedef->bottomtexture))
+			{
+				checkforemptylines = false;
+				return true;
+			}
 		}
-		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
-			return false;
+	}
 
-		if (abacksector->floorpic == skyflatnum && afrontsector->floorpic == skyflatnum)
+	if (!bothceilingssky) {
+		if (backc1 != frontc1 || backc2 != frontc2)
+		{
+			checkforemptylines = false;
 			return false;
-
-		return true;
+		}
 	}
 
-	if (backc1 != frontc1 || backc2 != frontc2
-		|| backf1 != frontf1 || backf2 != frontf2)
+	if (!bothfloorssky) {
+		if (backf1 != frontf1 || backf2 != frontf2)
 		{
 			checkforemptylines = false;
 			return false;
 		}
+	}
 
 	return false;
 }
@@ -2841,10 +2739,11 @@ static void HWR_AddLine(seg_t * line)
 #ifndef NEWCLIP
 	INT32 x1, x2;
 	angle_t span, tspan;
+	boolean bothceilingssky = false, bothfloorssky = false;
 #endif
 
 	// SoM: Backsector needs to be run through R_FakeFlat
-	sector_t tempsec;
+	static sector_t tempsec;
 
 	fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
 #ifdef POLYOBJECTS
@@ -2966,8 +2865,31 @@ static void HWR_AddLine(seg_t * line)
     }
     else
     {
+		boolean bothceilingssky = false, bothfloorssky = false;
+
 		gr_backsector = R_FakeFlat(gr_backsector, &tempsec, NULL, NULL, true);
-		if (CheckClip(line, gr_frontsector, gr_backsector))
+
+		if (gr_backsector->ceilingpic == skyflatnum && gr_frontsector->ceilingpic == skyflatnum)
+			bothceilingssky = true;
+		if (gr_backsector->floorpic == skyflatnum && gr_frontsector->floorpic == skyflatnum)
+			bothfloorssky = true;
+
+		if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
+		{
+			if (
+#ifdef POLYOBJECTS
+			!line->polyseg &&
+#endif
+			!line->sidedef->midtexture
+			&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
+			|| (gr_frontsector->tag == gr_backsector->tag)))
+				return; // line is empty, don't even bother
+			// treat like wide open window instead
+			HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
+			return;
+		}
+
+		if (CheckClip(line, gr_frontsector, gr_backsector))
 		{
 			gld_clipper_SafeAddClipRange(angle2, angle1);
 			checkforemptylines = false;
@@ -2989,6 +2911,25 @@ static void HWR_AddLine(seg_t * line)
 
 	gr_backsector = R_FakeFlat(gr_backsector, &tempsec, NULL, NULL, true);
 
+	if (gr_backsector->ceilingpic == skyflatnum && gr_frontsector->ceilingpic == skyflatnum)
+		bothceilingssky = true;
+	if (gr_backsector->floorpic == skyflatnum && gr_frontsector->floorpic == skyflatnum)
+		bothfloorssky = true;
+
+	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
+	{
+		if (
+#ifdef POLYOBJECTS
+		!line->polyseg &&
+#endif
+		!line->sidedef->midtexture
+		&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
+		|| (gr_frontsector->tag == gr_backsector->tag)))
+			return; // line is empty, don't even bother
+
+		goto clippass; // treat like wide open window instead
+	}
+
 #ifdef ESLOPE
 	if (gr_frontsector->f_slope || gr_frontsector->c_slope || gr_backsector->f_slope || gr_backsector->c_slope)
 	{
@@ -3007,47 +2948,58 @@ static void HWR_AddLine(seg_t * line)
 		SLOPEPARAMS( gr_backsector->f_slope, backf1,  backf2,  gr_backsector->floorheight)
 		SLOPEPARAMS( gr_backsector->c_slope, backc1,  backc2,  gr_backsector->ceilingheight)
 #undef SLOPEPARAMS
-
-		// Closed door.
-		if ((backc1 <= frontf1 && backc2 <= frontf2)
-			|| (backf1 >= frontc1 && backf2 >= frontc2))
+		// if both ceilings are skies, consider it always "open"
+		// same for floors
+		if (!bothceilingssky && !bothfloorssky)
 		{
-			goto clipsolid;
-		}
+			// Closed door.
+			if ((backc1 <= frontf1 && backc2 <= frontf2)
+				|| (backf1 >= frontc1 && backf2 >= frontc2))
+			{
+				goto clipsolid;
+			}
 
-		// Check for automap fix.
-		if (backc1 <= backf1 && backc2 <= backf2
-		&& ((backc1 >= frontc1 && backc2 >= frontc2) || gr_curline->sidedef->toptexture)
-		&& ((backf1 <= frontf1 && backf2 >= frontf2) || gr_curline->sidedef->bottomtexture)
-		&& (gr_backsector->ceilingpic != skyflatnum || gr_frontsector->ceilingpic != skyflatnum))
-			goto clipsolid;
+			// Check for automap fix.
+			if (backc1 <= backf1 && backc2 <= backf2
+			&& ((backc1 >= frontc1 && backc2 >= frontc2) || gr_curline->sidedef->toptexture)
+			&& ((backf1 <= frontf1 && backf2 >= frontf2) || gr_curline->sidedef->bottomtexture))
+				goto clipsolid;
+		}
 
 		// Window.
-		if (backc1 != frontc1 || backc2 != frontc2
-			|| backf1 != frontf1 || backf2 != frontf2)
-		{
-			goto clippass;
-		}
+		if (!bothceilingssky) // ceilings are always the "same" when sky
+			if (backc1 != frontc1 || backc2 != frontc2)
+				goto clippass;
+		if (!bothfloorssky)	// floors are always the "same" when sky
+			if (backf1 != frontf1 || backf2 != frontf2)
+				goto clippass;
 	}
 	else
 #endif
 	{
-		// Closed door.
-		if (gr_backsector->ceilingheight <= gr_frontsector->floorheight ||
-			gr_backsector->floorheight >= gr_frontsector->ceilingheight)
-			goto clipsolid;
-
-		// Check for automap fix.
-		if (gr_backsector->ceilingheight <= gr_backsector->floorheight
-		&& ((gr_backsector->ceilingheight >= gr_frontsector->ceilingheight) || gr_curline->sidedef->toptexture)
-		&& ((gr_backsector->floorheight <= gr_backsector->floorheight) || gr_curline->sidedef->bottomtexture)
-		&& (gr_backsector->ceilingpic != skyflatnum || gr_frontsector->ceilingpic != skyflatnum))
-			goto clipsolid;
+		// if both ceilings are skies, consider it always "open"
+		// same for floors
+		if (!bothceilingssky && !bothfloorssky)
+		{
+			// Closed door.
+			if (gr_backsector->ceilingheight <= gr_frontsector->floorheight ||
+				gr_backsector->floorheight >= gr_frontsector->ceilingheight)
+				goto clipsolid;
+
+			// Check for automap fix.
+			if (gr_backsector->ceilingheight <= gr_backsector->floorheight
+			&& ((gr_backsector->ceilingheight >= gr_frontsector->ceilingheight) || gr_curline->sidedef->toptexture)
+			&& ((gr_backsector->floorheight <= gr_backsector->floorheight) || gr_curline->sidedef->bottomtexture))
+				goto clipsolid;
+		}
 
 		// Window.
-		if (gr_backsector->ceilingheight != gr_frontsector->ceilingheight ||
-			gr_backsector->floorheight != gr_frontsector->floorheight)
-			goto clippass;
+		if (!bothceilingssky) // ceilings are always the "same" when sky
+			if (gr_backsector->ceilingheight != gr_frontsector->ceilingheight)
+				goto clippass;
+		if (!bothfloorssky)	// floors are always the "same" when sky
+			if (gr_backsector->floorheight != gr_frontsector->floorheight)
+				goto clippass;
 	}
 
 	// Reject empty lines used for triggers and special events.
@@ -3117,8 +3069,8 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	return gld_clipper_SafeCheckRange(angle2, angle1);
 #else
 	// check clip list for an open space
-	angle1 = R_PointToAngle(px1, py1) - dup_viewangle;
-	angle2 = R_PointToAngle(px2, py2) - dup_viewangle;
+	angle1 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px1>>1, py1>>1) - dup_viewangle;
+	angle2 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px2>>1, py2>>1) - dup_viewangle;
 
 	span = angle1 - angle2;
 
@@ -3238,7 +3190,7 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	if (nrPlaneVerts < 3)   //not even a triangle ?
 		return;
 
-	if (nrPlaneVerts > UINT16_MAX) // FIXME: exceeds plVerts size
+	if (nrPlaneVerts > (size_t)UINT16_MAX) // FIXME: exceeds plVerts size
 	{
 		CONS_Debug(DBG_RENDER, "polygon size of %s exceeds max value of %d vertices\n", sizeu1(nrPlaneVerts), UINT16_MAX);
 		return;
@@ -3452,7 +3404,7 @@ static void HWR_Subsector(size_t num)
 	INT16 count;
 	seg_t *line;
 	subsector_t *sub;
-	sector_t tempsec; //SoM: 4/7/2000
+	static sector_t tempsec; //SoM: 4/7/2000
 	INT32 floorlightlevel;
 	INT32 ceilinglightlevel;
 	INT32 locFloorHeight, locCeilingHeight;
@@ -3564,12 +3516,12 @@ static void HWR_Subsector(size_t num)
 		light = R_GetPlaneLight(gr_frontsector, locFloorHeight, false);
 		if (gr_frontsector->floorlightsec == -1)
 			floorlightlevel = *gr_frontsector->lightlist[light].lightlevel;
-		floorcolormap = gr_frontsector->lightlist[light].extra_colormap;
+		floorcolormap = *gr_frontsector->lightlist[light].extra_colormap;
 
 		light = R_GetPlaneLight(gr_frontsector, locCeilingHeight, false);
 		if (gr_frontsector->ceilinglightsec == -1)
 			ceilinglightlevel = *gr_frontsector->lightlist[light].lightlevel;
-		ceilingcolormap = gr_frontsector->lightlist[light].extra_colormap;
+		ceilingcolormap = *gr_frontsector->lightlist[light].extra_colormap;
 	}
 
 	sub->sector->extra_colormap = gr_frontsector->extra_colormap;
@@ -3624,9 +3576,7 @@ static void HWR_Subsector(size_t num)
 #ifndef POLYSKY
 	// Moved here because before, when above the ceiling and the floor does not have the sky flat, it doesn't draw the sky
 	if (gr_frontsector->ceilingpic == skyflatnum || gr_frontsector->floorpic == skyflatnum)
-	{
 		drawsky = true;
-	}
 #endif
 
 #ifdef R_FAKEFLOORS
@@ -3634,8 +3584,6 @@ static void HWR_Subsector(size_t num)
 	{
 		/// \todo fix light, xoffs, yoffs, extracolormap ?
 		ffloor_t * rover;
-
-		R_Prep3DFloors(gr_frontsector);
 		for (rover = gr_frontsector->ffloors;
 			rover; rover = rover->next)
 		{
@@ -3669,19 +3617,19 @@ static void HWR_Subsector(size_t num)
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					if (rover->master->frontsector->extra_colormap)
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba, rover->master->frontsector->extra_colormap->fadergba);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba);
 					else
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG, FADEFOG);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
 					                       *gr_frontsector->lightlist[light].lightlevel,
-					                       alpha, rover->master->frontsector, PF_Translucent|PF_NoTexture,
+					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT) // SoM: Flags are more efficient
+				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256) // SoM: Flags are more efficient
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
@@ -3697,7 +3645,7 @@ static void HWR_Subsector(size_t num)
 					                       *rover->bottomheight,
 					                       *gr_frontsector->lightlist[light].lightlevel,
 					                       rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, PF_Translucent,
-					                       false, gr_frontsector->lightlist[light].extra_colormap);
+					                       false, *gr_frontsector->lightlist[light].extra_colormap);
 #endif
 				}
 				else
@@ -3705,7 +3653,7 @@ static void HWR_Subsector(size_t num)
 					HWR_GetFlat(levelflats[*rover->bottompic].lumpnum);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->bottompic].lumpnum,
-					                rover->master->frontsector, 255, false, gr_frontsector->lightlist[light].extra_colormap);
+					                rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 
@@ -3732,19 +3680,19 @@ static void HWR_Subsector(size_t num)
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					if (rover->master->frontsector->extra_colormap)
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba, rover->master->frontsector->extra_colormap->fadergba);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba);
 					else
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG, FADEFOG);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   true,
 					                       *rover->topheight,
 					                       *gr_frontsector->lightlist[light].lightlevel,
-					                       alpha, rover->master->frontsector, PF_Translucent|PF_NoTexture,
+					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT)
+				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
@@ -3760,7 +3708,7 @@ static void HWR_Subsector(size_t num)
 					                        *rover->topheight,
 					                        *gr_frontsector->lightlist[light].lightlevel,
 					                        rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, PF_Translucent,
-					                        false, gr_frontsector->lightlist[light].extra_colormap);
+					                        false, *gr_frontsector->lightlist[light].extra_colormap);
 #endif
 
 				}
@@ -3769,7 +3717,7 @@ static void HWR_Subsector(size_t num)
 					HWR_GetFlat(levelflats[*rover->toppic].lumpnum);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->toppic].lumpnum,
-					                  rover->master->frontsector, 255, false, gr_frontsector->lightlist[light].extra_colormap);
+					                  rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 		}
@@ -4146,12 +4094,10 @@ static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float v
 
 static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float this_scale)
 {
-	UINT8 i;
-	float tr_x, tr_y;
-	FOutVector *wv;
 	FOutVector swallVerts[4];
 	FSurfaceInfo sSurf;
 	fixed_t floorheight, mobjfloor;
+	float offset = 0;
 
 	mobjfloor = HWR_OpaqueFloorAtPos(
 		spr->mobj->x, spr->mobj->y,
@@ -4161,7 +4107,7 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 		angle_t shadowdir;
 
 		// Set direction
-		if (splitscreen && stplyr != &players[displayplayer])
+		if (splitscreen && stplyr == &players[secondarydisplayplayer])
 			shadowdir = localangle2 + FixedAngle(cv_cam2_rotate.value);
 		else
 			shadowdir = localangle + FixedAngle(cv_cam_rotate.value);
@@ -4190,6 +4136,8 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 		}
 
 		floorheight = FixedInt(spr->mobj->z - floorheight);
+
+		offset = floorheight;
 	}
 	else
 		floorheight = FixedInt(spr->mobj->z - mobjfloor);
@@ -4202,47 +4150,42 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 	//  0--1
 
 	// x1/x2 were already scaled in HWR_ProjectSprite
+	// First match the normal sprite
 	swallVerts[0].x = swallVerts[3].x = spr->x1;
 	swallVerts[2].x = swallVerts[1].x = spr->x2;
+	swallVerts[0].z = swallVerts[3].z = spr->z1;
+	swallVerts[2].z = swallVerts[1].z = spr->z2;
 
-	if (spr->mobj && this_scale != 1.0f)
+	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
 	{
 		// Always a pixel above the floor, perfectly flat.
 		swallVerts[0].y = swallVerts[1].y = swallVerts[2].y = swallVerts[3].y = spr->ty - gpatch->topoffset * this_scale - (floorheight+3);
 
-		swallVerts[0].z = swallVerts[1].z = spr->tz - (gpatch->height-gpatch->topoffset) * this_scale;
-		swallVerts[2].z = swallVerts[3].z = spr->tz + gpatch->topoffset * this_scale;
+		// Now transform the TOP vertices along the floor in the direction of the camera
+		swallVerts[3].x = spr->x1 + ((gpatch->height * this_scale) + offset) * gr_viewcos;
+		swallVerts[2].x = spr->x2 + ((gpatch->height * this_scale) + offset) * gr_viewcos;
+		swallVerts[3].z = spr->z1 + ((gpatch->height * this_scale) + offset) * gr_viewsin;
+		swallVerts[2].z = spr->z2 + ((gpatch->height * this_scale) + offset) * gr_viewsin;
 	}
 	else
 	{
 		// Always a pixel above the floor, perfectly flat.
 		swallVerts[0].y = swallVerts[1].y = swallVerts[2].y = swallVerts[3].y = spr->ty - gpatch->topoffset - (floorheight+3);
 
-		// Spread out top away from the camera. (Fixme: Make it always move out in the same direction!... somehow.)
-		swallVerts[0].z = swallVerts[1].z = spr->tz - (gpatch->height-gpatch->topoffset);
-		swallVerts[2].z = swallVerts[3].z = spr->tz + gpatch->topoffset;
+		// Now transform the TOP vertices along the floor in the direction of the camera
+		swallVerts[3].x = spr->x1 + (gpatch->height + offset) * gr_viewcos;
+		swallVerts[2].x = spr->x2 + (gpatch->height + offset) * gr_viewcos;
+		swallVerts[3].z = spr->z1 + (gpatch->height + offset) * gr_viewsin;
+		swallVerts[2].z = spr->z2 + (gpatch->height + offset) * gr_viewsin;
 	}
 
-	// transform
-	wv = swallVerts;
-
-	for (i = 0; i < 4; i++,wv++)
+	// We also need to move the bottom ones away when shadowoffs is on
+	if (cv_shadowoffs.value)
 	{
-		// Offset away from the camera based on height from floor.
-		if (cv_shadowoffs.value)
-			wv->z += floorheight;
-		wv->z += 3;
-
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
-
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
+		swallVerts[0].x = spr->x1 + offset * gr_viewcos;
+		swallVerts[1].x = spr->x2 + offset * gr_viewcos;
+		swallVerts[0].z = spr->z1 + offset * gr_viewsin;
+		swallVerts[1].z = spr->z2 + offset * gr_viewsin;
 	}
 
 	if (spr->flip)
@@ -4285,8 +4228,8 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
 				lightlevel = *sector->lightlist[light].lightlevel;
 
-			if (sector->lightlist[light].extra_colormap)
-				colormap = sector->lightlist[light].extra_colormap;
+			if (*sector->lightlist[light].extra_colormap)
+				colormap = *sector->lightlist[light].extra_colormap;
 		}
 		else
 		{
@@ -4322,6 +4265,393 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 	}
 }
 
+// This is expecting a pointer to an array containing 4 wallVerts for a sprite
+static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts)
+{
+	if (cv_grspritebillboarding.value
+		&& spr && spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE)
+		&& wallVerts)
+	{
+		float basey = FIXED_TO_FLOAT(spr->mobj->z);
+		float lowy = wallVerts[0].y;
+		if (P_MobjFlip(spr->mobj) == -1)
+		{
+			basey = FIXED_TO_FLOAT(spr->mobj->z + spr->mobj->height);
+		}
+		// Rotate sprites to fully billboard with the camera
+		// X, Y, AND Z need to be manipulated for the polys to rotate around the
+		// origin, because of how the origin setting works I believe that should
+		// be mobj->z or mobj->z + mobj->height
+		wallVerts[2].y = wallVerts[3].y = (spr->ty - basey) * gr_viewludsin + basey;
+		wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gr_viewludsin + basey;
+		// translate back to be around 0 before translating back
+		wallVerts[3].x += ((spr->ty - basey) * gr_viewludcos) * gr_viewcos;
+		wallVerts[2].x += ((spr->ty - basey) * gr_viewludcos) * gr_viewcos;
+
+		wallVerts[0].x += ((lowy - basey) * gr_viewludcos) * gr_viewcos;
+		wallVerts[1].x += ((lowy - basey) * gr_viewludcos) * gr_viewcos;
+
+		wallVerts[3].z += ((spr->ty - basey) * gr_viewludcos) * gr_viewsin;
+		wallVerts[2].z += ((spr->ty - basey) * gr_viewludcos) * gr_viewsin;
+
+		wallVerts[0].z += ((lowy - basey) * gr_viewludcos) * gr_viewsin;
+		wallVerts[1].z += ((lowy - basey) * gr_viewludcos) * gr_viewsin;
+	}
+}
+
+static void HWR_SplitSprite(gr_vissprite_t *spr)
+{
+	float this_scale = 1.0f;
+	FOutVector wallVerts[4];
+	FOutVector baseWallVerts[4]; // This is what the verts should end up as
+	GLPatch_t *gpatch;
+	FSurfaceInfo Surf;
+	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
+	extracolormap_t *colormap;
+	FUINT lightlevel;
+	FBITFIELD blend = 0;
+	UINT8 alpha;
+
+	INT32 i;
+	float realtop, realbot, top, bot;
+	float towtop, towbot, towmult;
+	float bheight;
+	float realheight, heightmult;
+	const sector_t *sector = spr->mobj->subsector->sector;
+	const lightlist_t *list = sector->lightlist;
+#ifdef ESLOPE
+	float endrealtop, endrealbot, endtop, endbot;
+	float endbheight;
+	float endrealheight;
+	fixed_t temp;
+	fixed_t v1x, v1y, v2x, v2y;
+#endif
+
+	this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
+
+	if (hires)
+		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
+
+	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+
+	// cache the patch in the graphics card memory
+	//12/12/99: Hurdler: same comment as above (for md2)
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
+	HWR_GetMappedPatch(gpatch, spr->colormap);
+
+	// Draw shadow BEFORE sprite
+	if (cv_shadow.value // Shadows enabled
+		&& (spr->mobj->flags & (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY)) != (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY) // Ceiling scenery have no shadow.
+		&& !(spr->mobj->flags2 & MF2_DEBRIS) // Debris have no corona or shadow.
+#ifdef ALAM_LIGHTING
+		&& !(t_lspr[spr->mobj->sprite]->type // Things with dynamic lights have no shadow.
+		&& (!spr->mobj->player || spr->mobj->player->powers[pw_super])) // Except for non-super players.
+#endif
+		&& (spr->mobj->z >= spr->mobj->floorz)) // Without this, your shadow shows on the floor, even after you die and fall through the ground.
+	{
+		////////////////////
+		// SHADOW SPRITE! //
+		////////////////////
+		HWR_DrawSpriteShadow(spr, gpatch, this_scale);
+	}
+
+	baseWallVerts[0].x = baseWallVerts[3].x = spr->x1;
+	baseWallVerts[2].x = baseWallVerts[1].x = spr->x2;
+	baseWallVerts[0].z = baseWallVerts[3].z = spr->z1;
+	baseWallVerts[1].z = baseWallVerts[2].z = spr->z2;
+
+	baseWallVerts[2].y = baseWallVerts[3].y = spr->ty;
+	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
+		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height * this_scale;
+	else
+		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height;
+
+	v1x = FLOAT_TO_FIXED(spr->x1);
+	v1y = FLOAT_TO_FIXED(spr->z1);
+	v2x = FLOAT_TO_FIXED(spr->x2);
+	v2y = FLOAT_TO_FIXED(spr->z2);
+
+	if (spr->flip)
+	{
+		baseWallVerts[0].sow = baseWallVerts[3].sow = gpatch->max_s;
+		baseWallVerts[2].sow = baseWallVerts[1].sow = 0;
+	}
+	else
+	{
+		baseWallVerts[0].sow = baseWallVerts[3].sow = 0;
+		baseWallVerts[2].sow = baseWallVerts[1].sow = gpatch->max_s;
+	}
+
+	// flip the texture coords (look familiar?)
+	if (spr->vflip)
+	{
+		baseWallVerts[3].tow = baseWallVerts[2].tow = gpatch->max_t;
+		baseWallVerts[0].tow = baseWallVerts[1].tow = 0;
+	}
+	else
+	{
+		baseWallVerts[3].tow = baseWallVerts[2].tow = 0;
+		baseWallVerts[0].tow = baseWallVerts[1].tow = gpatch->max_t;
+	}
+
+	// if it has a dispoffset, push it a little towards the camera
+	if (spr->dispoffset) {
+		float co = -gr_viewcos*(0.05f*spr->dispoffset);
+		float si = -gr_viewsin*(0.05f*spr->dispoffset);
+		baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
+		baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
+		baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
+		baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
+	}
+
+	// Let dispoffset work first since this adjust each vertex
+	HWR_RotateSpritePolyToAim(spr, baseWallVerts);
+
+	realtop = top = baseWallVerts[3].y;
+	realbot = bot = baseWallVerts[0].y;
+	towtop = baseWallVerts[3].tow;
+	towbot = baseWallVerts[0].tow;
+	towmult = (towbot - towtop) / (top - bot);
+
+#ifdef ESLOPE
+	endrealtop = endtop = baseWallVerts[2].y;
+	endrealbot = endbot = baseWallVerts[1].y;
+#endif
+
+	// copy the contents of baseWallVerts into the drawn wallVerts array
+	// baseWallVerts is used to know the final shape to easily get the vertex
+	// co-ordinates
+	memcpy(wallVerts, baseWallVerts, sizeof(baseWallVerts));
+
+	if (!cv_translucency.value) // translucency disabled
+	{
+		Surf.FlatColor.s.alpha = 0xFF;
+		blend = PF_Translucent|PF_Occlude;
+	}
+	else if (spr->mobj->flags2 & MF2_SHADOW)
+	{
+		Surf.FlatColor.s.alpha = 0x40;
+		blend = PF_Translucent;
+	}
+	else if (spr->mobj->frame & FF_TRANSMASK)
+		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+	else
+	{
+		// BP: i agree that is little better in environement but it don't
+		//     work properly under glide nor with fogcolor to ffffff :(
+		// Hurdler: PF_Environement would be cool, but we need to fix
+		//          the issue with the fog before
+		Surf.FlatColor.s.alpha = 0xFF;
+		blend = PF_Translucent|PF_Occlude;
+	}
+
+	alpha = Surf.FlatColor.s.alpha;
+
+	// Start with the lightlevel and colormap from the top of the sprite
+	lightlevel = *list[sector->numlights - 1].lightlevel;
+	colormap = *list[sector->numlights - 1].extra_colormap;
+	i = 0;
+	temp = FLOAT_TO_FIXED(realtop);
+
+	if (spr->mobj->frame & FF_FULLBRIGHT)
+		lightlevel = 255;
+
+#ifdef ESLOPE
+	for (i = 1; i < sector->numlights; i++)
+	{
+		fixed_t h = sector->lightlist[i].slope ? P_GetZAt(sector->lightlist[i].slope, spr->mobj->x, spr->mobj->y)
+					: sector->lightlist[i].height;
+		if (h <= temp)
+		{
+			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+				lightlevel = *list[i-1].lightlevel;
+			colormap = *list[i-1].extra_colormap;
+			break;
+		}
+	}
+#else
+	i = R_GetPlaneLight(sector, temp, false);
+	if (!(spr->mobj->frame & FF_FULLBRIGHT))
+		lightlevel = *list[i].lightlevel;
+	colormap = *list[i].extra_colormap;
+#endif
+
+	for (i = 0; i < sector->numlights; i++)
+	{
+#ifdef ESLOPE
+		if (endtop < endrealbot)
+#endif
+		if (top < realbot)
+			return;
+
+		// even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite
+		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
+		{
+			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+				lightlevel = *list[i].lightlevel;
+			colormap = *list[i].extra_colormap;
+		}
+
+#ifdef ESLOPE
+		if (i + 1 < sector->numlights)
+		{
+			if (list[i+1].slope)
+			{
+				temp = P_GetZAt(list[i+1].slope, v1x, v1y);
+				bheight = FIXED_TO_FLOAT(temp);
+				temp = P_GetZAt(list[i+1].slope, v2x, v2y);
+				endbheight = FIXED_TO_FLOAT(temp);
+			}
+			else
+				bheight = endbheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+			endbheight = endrealbot;
+		}
+#else
+		if (i + 1 < sector->numlights)
+		{
+			bheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+		}
+#endif
+
+#ifdef ESLOPE
+		if (endbheight >= endtop)
+#endif
+		if (bheight >= top)
+			continue;
+
+		bot = bheight;
+
+		if (bot < realbot)
+			bot = realbot;
+
+#ifdef ESLOPE
+		endbot = endbheight;
+
+		if (endbot < endrealbot)
+			endbot = endrealbot;
+#endif
+
+#ifdef ESLOPE
+		wallVerts[3].tow = towtop + ((realtop - top) * towmult);
+		wallVerts[2].tow = towtop + ((endrealtop - endtop) * towmult);
+		wallVerts[0].tow = towtop + ((realtop - bot) * towmult);
+		wallVerts[1].tow = towtop + ((endrealtop - endbot) * towmult);
+
+		wallVerts[3].y = top;
+		wallVerts[2].y = endtop;
+		wallVerts[0].y = bot;
+		wallVerts[1].y = endbot;
+
+		// The x and y only need to be adjusted in the case that it's not a papersprite
+		if (cv_grspritebillboarding.value
+			&& spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE))
+		{
+			// Get the x and z of the vertices so billboarding draws correctly
+			realheight = realbot - realtop;
+			endrealheight = endrealbot - endrealtop;
+			heightmult = (realtop - top) / realheight;
+			wallVerts[3].x = baseWallVerts[3].x + (baseWallVerts[3].x - baseWallVerts[0].x) * heightmult;
+			wallVerts[3].z = baseWallVerts[3].z + (baseWallVerts[3].z - baseWallVerts[0].z) * heightmult;
+
+			heightmult = (endrealtop - endtop) / endrealheight;
+			wallVerts[2].x = baseWallVerts[2].x + (baseWallVerts[2].x - baseWallVerts[1].x) * heightmult;
+			wallVerts[2].z = baseWallVerts[2].z + (baseWallVerts[2].z - baseWallVerts[1].z) * heightmult;
+
+			heightmult = (realtop - bot) / realheight;
+			wallVerts[0].x = baseWallVerts[3].x + (baseWallVerts[3].x - baseWallVerts[0].x) * heightmult;
+			wallVerts[0].z = baseWallVerts[3].z + (baseWallVerts[3].z - baseWallVerts[0].z) * heightmult;
+
+			heightmult = (endrealtop - endbot) / endrealheight;
+			wallVerts[1].x = baseWallVerts[2].x + (baseWallVerts[2].x - baseWallVerts[1].x) * heightmult;
+			wallVerts[1].z = baseWallVerts[2].z + (baseWallVerts[2].z - baseWallVerts[1].z) * heightmult;
+		}
+#else
+		wallVerts[3].tow = wallVerts[2].tow = towtop + ((realtop - top) * towmult);
+		wallVerts[0].tow = wallVerts[1].tow = towtop + ((realtop - bot) * towmult);
+
+		wallVerts[2].y = wallVerts[3].y = top;
+		wallVerts[0].y = wallVerts[1].y = bot;
+
+		// The x and y only need to be adjusted in the case that it's not a papersprite
+		if (cv_grspritebillboarding.value
+			&& spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE))
+		{
+			// Get the x and z of the vertices so billboarding draws correctly
+			realheight = realbot - realtop;
+			heightmult = (realtop - top) / realheight;
+			wallVerts[3].x = baseWallVerts[3].x + (baseWallVerts[3].x - baseWallVerts[0].x) * heightmult;
+			wallVerts[3].z = baseWallVerts[3].z + (baseWallVerts[3].z - baseWallVerts[0].z) * heightmult;
+			wallVerts[2].x = baseWallVerts[2].x + (baseWallVerts[2].x - baseWallVerts[1].x) * heightmult;
+			wallVerts[2].z = baseWallVerts[2].z + (baseWallVerts[2].z - baseWallVerts[1].z) * heightmult;
+
+			heightmult = (realtop - bot) / realheight;
+			wallVerts[0].x = baseWallVerts[3].x + (baseWallVerts[3].x - baseWallVerts[0].x) * heightmult;
+			wallVerts[0].z = baseWallVerts[3].z + (baseWallVerts[3].z - baseWallVerts[0].z) * heightmult;
+			wallVerts[1].x = baseWallVerts[2].x + (baseWallVerts[2].x - baseWallVerts[1].x) * heightmult;
+			wallVerts[1].z = baseWallVerts[2].z + (baseWallVerts[2].z - baseWallVerts[1].z) * heightmult;
+		}
+#endif
+
+		if (colormap)
+			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
+		else
+			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
+
+		Surf.FlatColor.s.alpha = alpha;
+
+		HWD.pfnDrawPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip);
+
+		top = bot;
+#ifdef ESLOPE
+		endtop = endbot;
+#endif
+	}
+
+	bot = realbot;
+#ifdef ESLOPE
+	endbot = endrealbot;
+	if (endtop <= endrealbot)
+#endif
+	if (top <= realbot)
+		return;
+
+	// If we're ever down here, somehow the above loop hasn't draw all the light levels of sprite
+#ifdef ESLOPE
+	wallVerts[3].tow = towtop + ((realtop - top) * towmult);
+	wallVerts[2].tow = towtop + ((endrealtop - endtop) * towmult);
+	wallVerts[0].tow = towtop + ((realtop - bot) * towmult);
+	wallVerts[1].tow = towtop + ((endrealtop - endbot) * towmult);
+
+	wallVerts[3].y = top;
+	wallVerts[2].y = endtop;
+	wallVerts[0].y = bot;
+	wallVerts[1].y = endbot;
+#else
+	wallVerts[3].tow = wallVerts[2].tow = towtop + ((realtop - top) * towmult);
+	wallVerts[0].tow = wallVerts[1].tow = towtop + ((realtop - bot) * towmult);
+
+	wallVerts[2].y = wallVerts[3].y = top;
+	wallVerts[0].y = wallVerts[1].y = bot;
+#endif
+
+	if (colormap)
+		Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
+	else
+		Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
+
+	Surf.FlatColor.s.alpha = alpha;
+
+	HWD.pfnDrawPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip);
+}
+
 // -----------------+
 // HWR_DrawSprite   : Draw flat sprites
 //                  : (monsters, bonuses, weapons, lights, ...)
@@ -4329,10 +4659,8 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 // -----------------+
 static void HWR_DrawSprite(gr_vissprite_t *spr)
 {
-	UINT8 i;
-	float tr_x, tr_y, this_scale = 1.0f;
+	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
-	FOutVector *wv;
 	GLPatch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
@@ -4348,6 +4676,12 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	if (!spr->mobj->subsector)
 		return;
 
+	if (spr->mobj->subsector->sector->numlights)
+	{
+		HWR_SplitSprite(spr);
+		return;
+	}
+
 	// cache sprite graphics
 	//12/12/99: Hurdler:
 	//          OK, I don't change anything for MD2 support because I want to be
@@ -4373,7 +4707,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	wallVerts[0].x = wallVerts[3].x = spr->x1;
 	wallVerts[2].x = wallVerts[1].x = spr->x2;
 	wallVerts[2].y = wallVerts[3].y = spr->ty;
-	if (spr->mobj && this_scale != 1.0f)
+	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
 		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height * this_scale;
 	else
 		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
@@ -4381,24 +4715,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
 	wallVerts[0].z = wallVerts[3].z = spr->z1;
-	wallVerts[2].z = wallVerts[1].z = spr->z2;
-
-	// transform
-	wv = wallVerts;
-
-	for (i = 0; i < 4; i++,wv++)
-	{
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
-
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
-	}
+	wallVerts[1].z = wallVerts[2].z = spr->z2;
 
 	if (spr->flip)
 	{
@@ -4440,6 +4757,19 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 		HWR_DrawSpriteShadow(spr, gpatch, this_scale);
 	}
 
+	// if it has a dispoffset, push it a little towards the camera
+	if (spr->dispoffset) {
+		float co = -gr_viewcos*(0.05f*spr->dispoffset);
+		float si = -gr_viewsin*(0.05f*spr->dispoffset);
+		wallVerts[0].z = wallVerts[3].z = wallVerts[0].z+si;
+		wallVerts[1].z = wallVerts[2].z = wallVerts[1].z+si;
+		wallVerts[0].x = wallVerts[3].x = wallVerts[0].x+co;
+		wallVerts[1].x = wallVerts[2].x = wallVerts[1].x+co;
+	}
+
+	// Let dispoffset work first since this adjust each vertex
+	HWR_RotateSpritePolyToAim(spr, wallVerts);
+
 	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
 	// sprite lighting by modulating the RGB components
 	/// \todo coloured
@@ -4450,26 +4780,8 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 		UINT8 lightlevel = 255;
 		extracolormap_t *colormap = sector->extra_colormap;
 
-		if (sector->numlights)
-		{
-			INT32 light;
-
-			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
-
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *sector->lightlist[light].lightlevel;
-
-			if (sector->lightlist[light].extra_colormap)
-				colormap = sector->lightlist[light].extra_colormap;
-		}
-		else
-		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = sector->lightlevel;
-
-			if (sector->extra_colormap)
-				colormap = sector->extra_colormap;
-		}
+		if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			lightlevel = sector->lightlevel;
 
 		if (colormap)
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
@@ -4509,11 +4821,8 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 // Sprite drawer for precipitation
 static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 {
-	UINT8 i;
 	FBITFIELD blend = 0;
-	float tr_x, tr_y;
 	FOutVector wallVerts[4];
-	FOutVector *wv;
 	GLPatch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 
@@ -4539,24 +4848,11 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[1].z = wallVerts[2].z = wallVerts[3].z = spr->tz;
-
-	// transform
-	wv = wallVerts;
-
-	for (i = 0; i < 4; i++, wv++)
-	{
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
+	wallVerts[0].z = wallVerts[3].z = spr->z1;
+	wallVerts[1].z = wallVerts[2].z = spr->z2;
 
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
-	}
+	// Let dispoffset work first since this adjust each vertex
+	HWR_RotateSpritePolyToAim(spr, wallVerts);
 
 	wallVerts[0].sow = wallVerts[3].sow = 0;
 	wallVerts[2].sow = wallVerts[1].sow = gpatch->max_s;
@@ -4584,8 +4880,8 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
 				lightlevel = *sector->lightlist[light].lightlevel;
 
-			if (sector->lightlist[light].extra_colormap)
-				colormap = sector->lightlist[light].extra_colormap;
+			if (*sector->lightlist[light].extra_colormap)
+				colormap = *sector->lightlist[light].extra_colormap;
 		}
 		else
 		{
@@ -4673,7 +4969,7 @@ static void HWR_SortVisSprites(void)
 				best = ds;
 			}
 			// order visprites of same scale by dispoffset, smallest first
-			else if (ds->tz == bestdist && ds->dispoffset < bestdispoffset)
+			else if (fabsf(ds->tz - bestdist) < 1.0E-36f && ds->dispoffset < bestdispoffset)
 			{
 				bestdispoffset = ds->dispoffset;
 				best = ds;
@@ -4689,6 +4985,33 @@ static void HWR_SortVisSprites(void)
 		gr_vsprsortedhead.prev->next = best;
 		gr_vsprsortedhead.prev = best;
 	}
+
+	// Sryder:	Oh boy, while it's nice having ALL the sprites sorted properly, it fails when we bring MD2's into the
+	//			mix and they want to be translucent. So let's place all the translucent sprites and MD2's AFTER
+	//			everything else, but still ordered of course, the depth buffer can handle the opaque ones plenty fine.
+	//			We just need to move all translucent ones to the end in order
+	// TODO:	Fully sort all sprites and MD2s with walls and floors, this part will be unnecessary after that
+	best = gr_vsprsortedhead.next;
+	for (i = 0; i < gr_visspritecount; i++)
+	{
+		if ((best->mobj->flags2 & MF2_SHADOW) || (best->mobj->frame & FF_TRANSMASK))
+		{
+			if (best == gr_vsprsortedhead.next)
+			{
+				gr_vsprsortedhead.next = best->next;
+			}
+			best->prev->next = best->next;
+			best->next->prev = best->prev;
+			best->prev = gr_vsprsortedhead.prev;
+			gr_vsprsortedhead.prev->next = best;
+			gr_vsprsortedhead.prev = best;
+			ds = best;
+			best = best->next;
+			ds->next = &gr_vsprsortedhead;
+		}
+		else
+			best = best->next;
+	}
 }
 
 // A drawnode is something that points to a 3D floor, 3D side, or masked
@@ -5020,6 +5343,7 @@ static void HWR_CreateDrawNodes(void)
 //  Draw all vissprites
 // --------------------------------------------------------------------------
 #ifdef SORTING
+// added the stransform so they can be switched as drawing happenes so MD2s and sprites are sorted correctly with each other
 static void HWR_DrawSprites(void)
 {
 	if (gr_visspritecount > 0)
@@ -5040,44 +5364,20 @@ static void HWR_DrawSprites(void)
 				{
 					if (!cv_grmd2.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
 						HWR_DrawSprite(spr);
+					else
+						HWR_DrawMD2(spr);
 				}
-				else if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
-					HWR_DrawSprite(spr);
-		}
-	}
-}
-#endif
-// --------------------------------------------------------------------------
-//  Draw all MD2
-// --------------------------------------------------------------------------
-static void HWR_DrawMD2S(void)
-{
-	if (gr_visspritecount > 0)
-	{
-		gr_vissprite_t *spr;
-
-		// draw all MD2 back to front
-		for (spr = gr_vsprsortedhead.next;
-			spr != &gr_vsprsortedhead;
-			spr = spr->next)
-		{
-#ifdef HWPRECIP
-			if (!spr->precip)
-			{
-#endif
-				if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+				else
 				{
-					if (md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound == false && md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale > 0.0f)
+					if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
+						HWR_DrawSprite(spr);
+					else
 						HWR_DrawMD2(spr);
 				}
-				else if (md2_models[spr->mobj->sprite].notfound == false && md2_models[spr->mobj->sprite].scale > 0.0f)
-					HWR_DrawMD2(spr);
-#ifdef HWPRECIP
-			}
-#endif
 		}
 	}
 }
+#endif
 
 // --------------------------------------------------------------------------
 // HWR_AddSprites
@@ -5116,8 +5416,10 @@ static void HWR_AddSprites(sector_t *sec)
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
 
-			if (approx_dist <= limit_dist)
-				HWR_ProjectSprite(thing);
+			if (approx_dist > limit_dist)
+				continue;
+
+			HWR_ProjectSprite(thing);
 		}
 	}
 	else
@@ -5129,7 +5431,7 @@ static void HWR_AddSprites(sector_t *sec)
 	}
 
 #ifdef HWPRECIP
-	// Someone seriously wants infinite draw distance for precipitation?
+	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
 	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
@@ -5139,17 +5441,12 @@ static void HWR_AddSprites(sector_t *sec)
 
 			approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
 
-			if (approx_dist <= limit_dist)
-				HWR_ProjectPrecipitationSprite(precipthing);
+			if (approx_dist > limit_dist)
+				continue;
+
+			HWR_ProjectPrecipitationSprite(precipthing);
 		}
 	}
-	else
-	{
-		// Draw everything in sector, no checks
-		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
-			if (!(precipthing->precipflags & PCF_INVISIBLE))
-				HWR_ProjectPrecipitationSprite(precipthing);
-	}
 #endif
 }
 
@@ -5162,8 +5459,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 {
 	gr_vissprite_t *vis;
 	float tr_x, tr_y;
-	float tx, tz;
+	float tz;
 	float x1, x2;
+	float rightsin, rightcos;
 	float this_scale;
 	float gz, gzt;
 	spritedef_t *sprdef;
@@ -5176,8 +5474,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	angle_t ang;
 	INT32 heightsec, phs;
 	const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
-	float offset;
-	float ang_scale = 1.0f, ang_scalez = 0.0f;
+	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
 	if (!thing)
@@ -5196,7 +5493,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
 		return;
 
-	tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
+	// The above can stay as it works for cutting sprites that are too close
+	tr_x = FIXED_TO_FLOAT(thing->x);
+	tr_y = FIXED_TO_FLOAT(thing->y);
 
 	// decide which patch to use for sprite relative to player
 #ifdef RANGECHECK
@@ -5231,26 +5530,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		I_Error("sprframes NULL for sprite %d\n", thing->sprite);
 #endif
 
-	if (papersprite)
-	{
-		// Use the actual view angle, rather than the angle formed
-		// between the view point and the thing
-		// this makes sure paper sprites always appear at the right angle!
-		// Note: DO NOT do this in software mode version, it actually
-		// makes papersprites look WORSE there (I know, I've tried)
-		// Monster Iestyn - 13/05/17
-		ang = dup_viewangle - (thing->player ? thing->player->drawangle : thing->angle);
-		ang_scale = FIXED_TO_FLOAT(FINESINE(ang>>ANGLETOFINESHIFT));
-		ang_scalez = FIXED_TO_FLOAT(FINECOSINE(ang>>ANGLETOFINESHIFT));
-
-		if (ang_scale < 0)
-		{
-			ang_scale = -ang_scale;
-			ang_scalez = -ang_scalez;
-		}
-	}
-	else if (sprframe->rotate != SRF_SINGLE)
-		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
+	ang = R_PointToAngle (thing->x, thing->y) - mobjangle;
 
 	if (sprframe->rotate == SRF_SINGLE)
 	{
@@ -5258,6 +5538,14 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		rot = 0;                        //Fab: for vis->patch below
 		lumpoff = sprframe->lumpid[0];     //Fab: see note above
 		flip = sprframe->flip; // Will only be 0x00 or 0xFF
+
+		if (papersprite && ang < ANGLE_180)
+		{
+			if (flip)
+				flip = 0;
+			else
+				flip = 255;
+		}
 	}
 	else
 	{
@@ -5272,38 +5560,57 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		//Fab: lumpid is the index for spritewidth,spriteoffset... tables
 		lumpoff = sprframe->lumpid[rot];
 		flip = sprframe->flip & (1<<rot);
+
+		if (papersprite && ang < ANGLE_180)
+		{
+			if (flip)
+				flip = 0;
+			else
+				flip = 1<<rot;
+		}
 	}
 
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
 
-	// calculate edges of the shape
-	if (flip)
-		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
+	if (papersprite)
+	{
+		rightsin = FIXED_TO_FLOAT(FINESINE((mobjangle)>>ANGLETOFINESHIFT));
+		rightcos = FIXED_TO_FLOAT(FINECOSINE((mobjangle)>>ANGLETOFINESHIFT));
+	}
 	else
-		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
-
-	z1 = tz - (offset * ang_scalez);
-	tx -= offset * ang_scale;
-
-	// project x
-	x1 = gr_windowcenterx + (tx * gr_centerx / tz);
-
-	//faB : tr_x doesnt matter
-	// hurdler: it's used in cliptosolidsegs
-	tr_x = x1;
-
-	x1 = tx;
+	{
+		rightsin = FIXED_TO_FLOAT(FINESINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+		rightcos = FIXED_TO_FLOAT(FINECOSINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	}
 
-	offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+	if (flip)
+	{
+		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
+	}
+	else
+	{
+		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
+	}
 
-	z2 = z1 + (offset * ang_scalez);
-	tx += offset * ang_scale;
+	// test if too close
+/*
+	if (papersprite)
+	{
+		z1 = tz - x1 * angle_scalez;
+		z2 = tz + x2 * angle_scalez;
 
-	if (papersprite && max(z1, z2) < ZCLIP_PLANE)
-		return;
+		if (max(z1, z2) < ZCLIP_PLANE)
+			return;
+	}
+*/
 
-	x2 = gr_windowcenterx + (tx * gr_centerx / tz);
+	z1 = tr_y + x1 * rightsin;
+	z2 = tr_y - x2 * rightsin;
+	x1 = tr_x + x1 * rightcos;
+	x2 = tr_x - x2 * rightcos;
 
 	if (vflip)
 	{
@@ -5323,7 +5630,10 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	}
 
 	heightsec = thing->subsector->sector->heightsec;
-	phs = players[displayplayer].mo->subsector->sector->heightsec;
+	if (viewplayer->mo && viewplayer->mo->subsector)
+		phs = viewplayer->mo->subsector->sector->heightsec;
+	else
+		phs = -1;
 
 	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
 	{
@@ -5340,13 +5650,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	// store information in a vissprite
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
-#if 0
 	vis->x2 = x2;
-#else
-	(void)x2;
-#endif
-	vis->x2 = tx;
-	vis->tz = tz;
+	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
 	vis->dispoffset = thing->info->dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
 	vis->patchlumpnum = sprframe->lumppat[rot];
 	vis->flip = flip;
@@ -5355,9 +5660,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
-	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
+	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
 	{
-		if (vis->mobj->type == MT_CYBRAKDEMON)
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
 			vis->colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
 		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
 			vis->colormap = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
@@ -5367,7 +5672,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	else if (thing->color)
 	{
 		// New colormap stuff for skins Tails 06-07-2002
-		if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player!
+		if (thing->colorized)
+			vis->colormap = R_GetTranslationColormap(TC_RAINBOW, thing->color, GTC_CACHE);
+		else if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)thing->skin-skins;
 			vis->colormap = R_GetTranslationColormap((INT32)skinnum, thing->color, GTC_CACHE);
@@ -5379,7 +5686,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = gzt - gr_viewz;
+	vis->ty = gzt;
 
 	//CONS_Debug(DBG_RENDER, "------------------\nH: sprite  : %d\nH: frame   : %x\nH: type    : %d\nH: sname   : %s\n\n",
 	//            thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);
@@ -5395,8 +5702,10 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 {
 	gr_vissprite_t *vis;
 	float tr_x, tr_y;
-	float tx, tz;
+	float tz;
 	float x1, x2;
+	float z1, z2;
+	float rightsin, rightcos;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
 	size_t lumpoff;
@@ -5414,7 +5723,8 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	if (tz < ZCLIP_PLANE)
 		return;
 
-	tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
+	tr_x = FIXED_TO_FLOAT(thing->x);
+	tr_y = FIXED_TO_FLOAT(thing->y);
 
 	// decide which patch to use for sprite relative to player
 	if ((unsigned)thing->sprite >= numsprites)
@@ -5441,32 +5751,32 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	lumpoff = sprframe->lumpid[0];
 	flip = sprframe->flip; // Will only be 0x00 or 0xFF
 
-	// calculate edges of the shape
-	tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
-
-	// project x
-	x1 = gr_windowcenterx + (tx * gr_centerx / tz);
-
-	//faB : tr_x doesnt matter
-	// hurdler: it's used in cliptosolidsegs
-	tr_x = x1;
-
-	x1 = tx;
+	rightsin = FIXED_TO_FLOAT(FINESINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	rightcos = FIXED_TO_FLOAT(FINECOSINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	if (flip)
+	{
+		x1 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset);
+		x2 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
+	}
+	else
+	{
+		x1 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
+		x2 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset);
+	}
 
-	tx += FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width);
-	x2 = gr_windowcenterx + (tx * gr_centerx / tz);
+	z1 = tr_y + x1 * rightsin;
+	z2 = tr_y - x2 * rightsin;
+	x1 = tr_x + x1 * rightcos;
+	x2 = tr_x - x2 * rightcos;
 
 	//
 	// store information in a vissprite
 	//
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
-#if 0
 	vis->x2 = x2;
-#else
-	(void)x2;
-#endif
-	vis->x2 = tx;
+	vis->z1 = z1;
+	vis->z2 = z2;
 	vis->tz = tz;
 	vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
 	vis->patchlumpnum = sprframe->lumppat[rot];
@@ -5476,16 +5786,26 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset) - gr_viewz;
+	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
 
 	vis->precip = true;
+
+	// okay... this is a hack, but weather isn't networked, so it should be ok
+	if (!(thing->precipflags & PCF_THUNK))
+	{
+		if (thing->precipflags & PCF_RAIN)
+			P_RainThinker(thing);
+		else
+			P_SnowThinker(thing);
+		thing->precipflags |= PCF_THUNK;
+	}
 }
 #endif
 
 // ==========================================================================
 //
 // ==========================================================================
-static void HWR_DrawSkyBackground(player_t *player)
+static void HWR_DrawSkyBackground(void)
 {
 	FOutVector v[4];
 	angle_t angle;
@@ -5493,23 +5813,24 @@ static void HWR_DrawSkyBackground(player_t *player)
 	float aspectratio;
 	float angleturn;
 
-//  3--2
-//  | /|
-//  |/ |
-//  0--1
-
-	(void)player;
 	HWR_GetTexture(texturetranslation[skytexture]);
+	aspectratio = (float)vid.width/(float)vid.height;
 
 	//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
 	//         because it's called just after clearing the screen
 	//         and thus, the near clipping plane is set to 3.99
-	v[0].x = v[3].x = -4.0f;
-	v[1].x = v[2].x =  4.0f;
-	v[0].y = v[1].y = -4.0f;
-	v[2].y = v[3].y =  4.0f;
+	// Sryder: Just use the near clipping plane value then
 
-	v[0].z = v[1].z = v[2].z = v[3].z = 4.0f;
+	//  3--2
+	//  | /|
+	//  |/ |
+	//  0--1
+	v[0].x = v[3].x = -ZCLIP_PLANE-1;
+	v[1].x = v[2].x =  ZCLIP_PLANE+1;
+	v[0].y = v[1].y = -ZCLIP_PLANE-1;
+	v[2].y = v[3].y =  ZCLIP_PLANE+1;
+
+	v[0].z = v[1].z = v[2].z = v[3].z = ZCLIP_PLANE+1;
 
 	// X
 
@@ -5521,30 +5842,36 @@ static void HWR_DrawSkyBackground(player_t *player)
 
 	dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
 
-	v[0].sow = v[3].sow = ((float) angle / ((ANGLE_90-1)*dimensionmultiply));
-	v[2].sow = v[1].sow = (-1.0f/dimensionmultiply)+((float) angle / ((ANGLE_90-1)*dimensionmultiply));
+	v[0].sow = v[3].sow = (-1.0f * angle) / ((ANGLE_90-1)*dimensionmultiply); // left
+	v[2].sow = v[1].sow = v[0].sow + (1.0f/dimensionmultiply); // right (or left + 1.0f)
+	// use +angle and -1.0f above instead if you wanted old backwards behavior
 
 	// Y
 	angle = aimingangle;
-
-	aspectratio = (float)vid.width/(float)vid.height;
 	dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->height/(128.0f*aspectratio));
-	angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
+
+	if (splitscreen)
+	{
+		dimensionmultiply *= 2;
+		angle *= 2;
+	}
 
 	// Middle of the sky should always be at angle 0
 	// need to keep correct aspect ratio with X
 	if (atransform.flip)
 	{
 		// During vertical flip the sky should be flipped and it's y movement should also be flipped obviously
-		v[3].tow = v[2].tow = -(0.5f-(0.5f/dimensionmultiply));
-		v[0].tow = v[1].tow = (-1.0f/dimensionmultiply)-(0.5f-(0.5f/dimensionmultiply));
+		v[3].tow = v[2].tow = -(0.5f-(0.5f/dimensionmultiply)); // top
+		v[0].tow = v[1].tow = v[3].tow - (1.0f/dimensionmultiply); // bottom (or top - 1.0f)
 	}
 	else
 	{
-		v[3].tow = v[2].tow = (-1.0f/dimensionmultiply)-(0.5f-(0.5f/dimensionmultiply));
-		v[0].tow = v[1].tow = -(0.5f-(0.5f/dimensionmultiply));
+		v[0].tow = v[1].tow = -(0.5f-(0.5f/dimensionmultiply)); // bottom
+		v[3].tow = v[2].tow = v[0].tow - (1.0f/dimensionmultiply); // top (or bottom - 1.0f)
 	}
 
+	angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
+
 	if (angle > ANGLE_180) // Do this because we don't want the sky to suddenly teleport when crossing over 0 to 360 and vice versa
 	{
 		angle = InvAngle(angle);
@@ -5577,7 +5904,7 @@ static inline void HWR_ClearView(void)
 	                 (INT32)gr_viewwindowy,
 	                 (INT32)(gr_viewwindowx + gr_viewwidth),
 	                 (INT32)(gr_viewwindowy + gr_viewheight),
-	                 3.99f);
+	                 ZCLIP_PLANE);
 	HWD.pfnClearBuffer(false, true, 0);
 
 	//disable clip window - set to full size
@@ -5603,7 +5930,7 @@ void HWR_SetViewSize(void)
 
 	gr_viewwindowx = (vid.width - gr_viewwidth) / 2;
 	gr_windowcenterx = (float)(vid.width / 2);
-	if (gr_viewwidth == vid.width)
+	if (fabsf(gr_viewwidth - vid.width) < 1.0E-36f)
 	{
 		gr_baseviewwindowy = 0;
 		gr_basewindowcentery = gr_viewheight / 2;               // window top left corner at 0,0
@@ -5616,6 +5943,8 @@ void HWR_SetViewSize(void)
 
 	gr_pspritexscale = gr_viewwidth / BASEVIDWIDTH;
 	gr_pspriteyscale = ((vid.height*gr_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
+
+	HWD.pfnFlushScreenTextures();
 }
 
 // ==========================================================================
@@ -5624,7 +5953,6 @@ void HWR_SetViewSize(void)
 void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 {
 	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
-	FTransform stransform;
 	postimg_t *type;
 
 	if (splitscreen && player == &players[secondarydisplayplayer])
@@ -5688,31 +6016,12 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
 	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
 	atransform.scalex = 1;
-	atransform.scaley = ORIGINAL_ASPECT;
+	atransform.scaley = (float)vid.width/vid.height;
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
 	atransform.splitscreen = splitscreen;
 
-	// Transform for sprites
-	stransform.anglex = 0.0f;
-	stransform.angley = -270.0f;
-
-	if (*type == postimg_flip)
-		stransform.flip = true;
-	else
-		stransform.flip = false;
-
-	stransform.x      = 0.0f;
-	stransform.y      = 0.0f;
-	stransform.z      = 0.0f;
-	stransform.scalex = 1;
-	stransform.scaley = 1;
-	stransform.scalez = 1;
-	stransform.fovxangle = 90.0f;
-	stransform.fovyangle = 90.0f;
-	stransform.splitscreen = splitscreen;
-
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
@@ -5727,7 +6036,7 @@ if (0)
 }
 
 	if (drawsky)
-		HWR_DrawSkyBackground(player);
+		HWR_DrawSkyBackground();
 
 	//Hurdler: it doesn't work in splitscreen mode
 	drawsky = splitscreen;
@@ -5801,10 +6110,7 @@ if (0)
 #ifdef SORTING
 	HWR_SortVisSprites();
 #endif
-	HWR_DrawMD2S();
 
-	// Draw the sprites with trivial transform
-	HWD.pfnSetTransform(&stransform);
 #ifdef SORTING
 	HWR_DrawSprites();
 #endif
@@ -5849,7 +6155,6 @@ if (0)
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 {
 	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
-	FTransform stransform;
 	postimg_t *type;
 
 	const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
@@ -5884,7 +6189,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 
 	// note: sets viewangle, viewx, viewy, viewz
-	R_SetupFrame(player, false); // This can stay false because it is only used to set viewsky in r_main.c, which isn't used here
+	R_SetupFrame(player);
 
 	// copy view cam position for local use
 	dup_viewx = viewx;
@@ -5928,31 +6233,12 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
 	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
 	atransform.scalex = 1;
-	atransform.scaley = ORIGINAL_ASPECT;
+	atransform.scaley = (float)vid.width/vid.height;
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
 	atransform.splitscreen = splitscreen;
 
-	// Transform for sprites
-	stransform.anglex = 0.0f;
-	stransform.angley = -270.0f;
-
-	if (*type == postimg_flip)
-		stransform.flip = true;
-	else
-		stransform.flip = false;
-
-	stransform.x      = 0.0f;
-	stransform.y      = 0.0f;
-	stransform.z      = 0.0f;
-	stransform.scalex = 1;
-	stransform.scaley = 1;
-	stransform.scalez = 1;
-	stransform.fovxangle = 90.0f;
-	stransform.fovyangle = 90.0f;
-	stransform.splitscreen = splitscreen;
-
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
@@ -5967,7 +6253,7 @@ if (0)
 }
 
 	if (!skybox && drawsky) // Don't draw the regular sky if there's a skybox
-		HWR_DrawSkyBackground(player);
+		HWR_DrawSkyBackground();
 
 	//Hurdler: it doesn't work in splitscreen mode
 	drawsky = splitscreen;
@@ -6041,10 +6327,7 @@ if (0)
 #ifdef SORTING
 	HWR_SortVisSprites();
 #endif
-	HWR_DrawMD2S();
 
-	// Draw the sprites with trivial transform
-	HWD.pfnSetTransform(&stransform);
 #ifdef SORTING
 	HWR_DrawSprites();
 #endif
@@ -6232,6 +6515,7 @@ void HWR_Shutdown(void)
 	HWR_FreeExtraSubsectors();
 	HWR_FreePolyPool();
 	HWR_FreeTextureCache();
+	HWD.pfnFlushScreenTextures();
 }
 
 void transform(float *cx, float *cy, float *cz)
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index cb49f817ce1cf1f3e294755eebde0c19e9d08b74..f8524990f9d6f3096150ae76e505400c5708cb8e 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -31,16 +31,14 @@
 void HWR_Startup(void);
 void HWR_Shutdown(void);
 
-void HWR_clearAutomap(void);
 void HWR_drawAMline(const fline_t *fl, INT32 color);
-void HWR_FadeScreenMenuBack(UINT32 color, INT32 height);
+void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength);
 void HWR_DrawConsoleBack(UINT32 color, INT32 height);
+void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight);
 void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player);
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player);
 void HWR_DrawViewBorder(INT32 clearlines);
 void HWR_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum);
-UINT8 *HWR_GetScreenshot(void);
-boolean HWR_Screenshot(const char *lbmname);
 void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
 void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
@@ -51,8 +49,13 @@ void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
 void HWR_PrepLevelCache(size_t pnumtextures);
 void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color);
+void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength);
+void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 actualcolor);	// Lat: separate flags from color since color needs to be an uint to work right.
 void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum);
 
+UINT8 *HWR_GetScreenshot(void);
+boolean HWR_Screenshot(const char *pathname);
+
 void HWR_AddCommands(void);
 void HWR_CorrectSWTricks(void);
 void transform(float *cx, float *cy, float *cz);
@@ -94,6 +97,7 @@ extern consvar_t cv_grcorrecttricks;
 extern consvar_t cv_voodoocompatibility;
 extern consvar_t cv_grfovchange;
 extern consvar_t cv_grsolvetjoin;
+extern consvar_t cv_grspritebillboarding;
 
 extern float gr_viewwidth, gr_viewheight, gr_baseviewwindowy;
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index bfb2638eeeea7a5c78078c64b72ed37bbc202fe1..d4728315ad88f97e5f1f16ac16ed037a8f7376ce 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -40,6 +40,8 @@
 #include "../w_wad.h"
 #include "../z_zone.h"
 #include "../r_things.h"
+#include "../r_draw.h"
+#include "../p_tick.h"
 
 #include "hw_main.h"
 #include "../v_video.h"
@@ -978,8 +980,18 @@ spritemd2found:
 	fclose(f);
 }
 
-static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, skincolors_t color)
+// Define for getting accurate color brightness readings according to how the human eye sees them.
+// https://en.wikipedia.org/wiki/Relative_luminance
+// 0.2126 to red
+// 0.7152 to green
+// 0.0722 to blue
+// (See this same define in k_kart.c!)
+#define SETBRIGHTNESS(brightness,r,g,b) \
+	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
+
+static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, INT32 skinnum, skincolors_t color)
 {
+	UINT8 i;
 	UINT16 w = gpatch->width, h = gpatch->height;
 	UINT32 size = w*h;
 	RGBA_t *image, *blendimage, *cur, blendcolor;
@@ -1005,50 +1017,112 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	image = gpatch->mipmap.grInfo.data;
 	blendimage = blendgpatch->mipmap.grInfo.data;
 
+	// Average all of the translation's colors
 	if (color == SKINCOLOR_NONE || color >= MAXTRANSLATIONS)
 		blendcolor = V_GetColor(0xff);
 	else
-		blendcolor = V_GetColor(Color_Index[color-1][4]);
-
-	while (size--)
 	{
-		if (blendimage->s.alpha == 0)
+		const UINT8 div = 6;
+		const UINT8 start = 4;
+		UINT32 r, g, b;
+
+		blendcolor = V_GetColor(Color_Index[color-1][start]);
+		r = (UINT32)(blendcolor.s.red*blendcolor.s.red);
+		g = (UINT32)(blendcolor.s.green*blendcolor.s.green);
+		b = (UINT32)(blendcolor.s.blue*blendcolor.s.blue);
+
+		for (i = 1; i < div; i++)
 		{
-			// Don't bother with blending the pixel if the alpha of the blend pixel is 0
-			cur->rgba = image->rgba;
+			RGBA_t nextcolor = V_GetColor(Color_Index[color-1][start+i]);
+			r += (UINT32)(nextcolor.s.red*nextcolor.s.red);
+			g += (UINT32)(nextcolor.s.green*nextcolor.s.green);
+			b += (UINT32)(nextcolor.s.blue*nextcolor.s.blue);
 		}
-		else
+
+		blendcolor.s.red = (UINT8)(FixedSqrt((r/div)<<FRACBITS)>>FRACBITS);
+		blendcolor.s.green = (UINT8)(FixedSqrt((g/div)<<FRACBITS)>>FRACBITS);
+		blendcolor.s.blue = (UINT8)(FixedSqrt((b/div)<<FRACBITS)>>FRACBITS);
+	}
+
+	// rainbow support, could theoretically support boss ones too
+	if (skinnum == TC_RAINBOW)
+	{
+		while (size--)
 		{
-			INT32 tempcolor;
-			INT16 tempmult, tempalpha;
-			tempalpha = -(abs(blendimage->s.red-127)-127)*2;
-			if (tempalpha > 255)
-				tempalpha = 255;
-			else if (tempalpha < 0)
-				tempalpha = 0;
-
-			tempmult = (blendimage->s.red-127)*2;
-			if (tempmult > 255)
-				tempmult = 255;
-			else if (tempmult < 0)
-				tempmult = 0;
-
-			tempcolor = (image->s.red*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.red)/255)) * blendimage->s.alpha)/255;
-			cur->s.red = (UINT8)tempcolor;
-			tempcolor = (image->s.green*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.green)/255)) * blendimage->s.alpha)/255;
-			cur->s.green = (UINT8)tempcolor;
-			tempcolor = (image->s.blue*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.blue)/255)) * blendimage->s.alpha)/255;
-			cur->s.blue = (UINT8)tempcolor;
-			cur->s.alpha = image->s.alpha;
+			if (image->s.alpha == 0 && blendimage->s.alpha == 0)
+			{
+				// Don't bother with blending the pixel if the alpha of the blend pixel is 0
+				cur->rgba = image->rgba;
+			}
+			else
+			{
+				UINT32 tempcolor;
+				UINT16 imagebright, blendbright, finalbright, colorbright;
+				SETBRIGHTNESS(imagebright,image->s.red,image->s.green,image->s.blue);
+				SETBRIGHTNESS(blendbright,blendimage->s.red,blendimage->s.green,blendimage->s.blue);
+				// slightly dumb average between the blend image color and base image colour, usually one or the other will be fully opaque anyway
+				finalbright = (imagebright*(255-blendimage->s.alpha))/255 + (blendbright*blendimage->s.alpha)/255;
+				SETBRIGHTNESS(colorbright,blendcolor.s.red,blendcolor.s.green,blendcolor.s.blue);
+
+				tempcolor = (finalbright*blendcolor.s.red)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.red = (UINT8)tempcolor;
+				tempcolor = (finalbright*blendcolor.s.green)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.green = (UINT8)tempcolor;
+				tempcolor = (finalbright*blendcolor.s.blue)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.blue = (UINT8)tempcolor;
+				cur->s.alpha = image->s.alpha;
+			}
+
+			cur++; image++; blendimage++;
 		}
+	}
+	else
+	{
+		while (size--)
+		{
+			if (blendimage->s.alpha == 0)
+			{
+				// Don't bother with blending the pixel if the alpha of the blend pixel is 0
+				cur->rgba = image->rgba;
+			}
+			else
+			{
+				INT32 tempcolor;
+				INT16 tempmult, tempalpha;
+				tempalpha = -(abs(blendimage->s.red-127)-127)*2;
+				if (tempalpha > 255)
+					tempalpha = 255;
+				else if (tempalpha < 0)
+					tempalpha = 0;
+
+				tempmult = (blendimage->s.red-127)*2;
+				if (tempmult > 255)
+					tempmult = 255;
+				else if (tempmult < 0)
+					tempmult = 0;
+
+				tempcolor = (image->s.red*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.red)/255)) * blendimage->s.alpha)/255;
+				cur->s.red = (UINT8)tempcolor;
+				tempcolor = (image->s.green*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.green)/255)) * blendimage->s.alpha)/255;
+				cur->s.green = (UINT8)tempcolor;
+				tempcolor = (image->s.blue*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.blue)/255)) * blendimage->s.alpha)/255;
+				cur->s.blue = (UINT8)tempcolor;
+				cur->s.alpha = image->s.alpha;
+			}
 
-		cur++; image++; blendimage++;
+			cur++; image++; blendimage++;
+		}
 	}
 
 	return;
 }
 
-static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, const UINT8 *colormap, skincolors_t color)
+#undef SETBRIGHTNESS
+
+static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT32 skinnum, const UINT8 *colormap, skincolors_t color)
 {
 	// mostly copied from HWR_GetMappedPatch, hence the similarities and comment
 	GLMipmap_t *grmip, *newmip;
@@ -1089,13 +1163,14 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, con
 	grmip->nextcolormap = newmip;
 	newmip->colormap = colormap;
 
-	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, color);
+	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, skinnum, color);
 
 	HWD.pfnSetTexture(newmip);
 	Z_ChangeTag(newmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 
+
 // -----------------+
 // HWR_DrawMD2      : Draw MD2
 //                  : (monsters, bonuses, weapons, lights, ...)
@@ -1123,6 +1198,9 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 	if (!md2 || !skin)
 		return 0;
 
+	if ((unsigned)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
+		return 0;
+
 	while (!(md2->model->spr2frames[spr2*2 + 1])
 		&& spr2 != SPR2_STND
 		&& ++i != 32) // recursion limiter
@@ -1136,7 +1214,6 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 
 		switch(spr2)
 		{
-
 		// Normal special cases.
 		case SPR2_JUMP:
 			spr2 = ((player
@@ -1145,9 +1222,11 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
 		case SPR2_TIRE:
-			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			spr2 = ((player
+					? player->charability
+					: skin->ability)
+					== CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 			break;
-
 		// Use the handy list, that's what it's there for!
 		default:
 			spr2 = spr2defaults[spr2];
@@ -1157,6 +1236,9 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 		spr2 |= super;
 	}
 
+	if (i >= 32) // probably an infinite loop...
+		return 0;
+
 	return spr2;
 }
 
@@ -1194,8 +1276,8 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
 				lightlevel = *sector->lightlist[light].lightlevel;
 
-			if (sector->lightlist[light].extra_colormap)
-				colormap = sector->lightlist[light].extra_colormap;
+			if (*sector->lightlist[light].extra_colormap)
+				colormap = *sector->lightlist[light].extra_colormap;
 		}
 		else
 		{
@@ -1285,7 +1367,30 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format
 				&& gpatch->width == ((GLPatch_t *)md2->blendgrpatch)->width && gpatch->height == ((GLPatch_t *)md2->blendgrpatch)->height)
 			{
-				HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, spr->colormap, (skincolors_t)spr->mobj->color);
+				INT32 skinnum = TC_DEFAULT;
+				if ((spr->mobj->flags & (MF_ENEMY|MF_BOSS)) && (spr->mobj->flags2 & MF2_FRET) && !(spr->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
+				{
+					if (spr->mobj->type == MT_CYBRAKDEMON || spr->mobj->colorized)
+						skinnum = TC_ALLWHITE;
+					else if (spr->mobj->type == MT_METALSONIC_BATTLE)
+						skinnum = TC_METALSONIC;
+					else
+						skinnum = TC_BOSS;
+				}
+				else if (spr->mobj->color)
+				{
+					if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+					{
+						if (spr->mobj->colorized)
+							skinnum = TC_RAINBOW;
+						else
+						{
+							skinnum = (INT32)((skin_t*)spr->mobj->skin-skins);
+						}
+					}
+					else skinnum = TC_DEFAULT;
+				}
+				HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, skinnum, spr->colormap, (skincolors_t)spr->mobj->color);
 			}
 			else
 			{
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index c7cda35af6f308c1f8fa28e0fb8358fd55542996..24a5639330c12fe59ed1aae43ace60d54139c0f3 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -29,6 +29,11 @@
 // model version
 #define MD2_VERSION                     8
 
+// magic number "IDP2" or 844121161
+#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
+// model version
+#define MD2_VERSION                     8
+
 #define MD2_MAX_TRIANGLES               8192
 #define MD2_MAX_VERTICES                4096
 #define MD2_MAX_TEXCOORDS               4096
diff --git a/src/hardware/r_opengl/r_opengl-vc10.vcxproj b/src/hardware/r_opengl/r_opengl-vc10.vcxproj
index f04ae320b36e43cf15f0bf89cf53834b6ef8efcb..d1f856e968d376bda1aeb5a14a8cc407b2e86f9d 100644
--- a/src/hardware/r_opengl/r_opengl-vc10.vcxproj
+++ b/src/hardware/r_opengl/r_opengl-vc10.vcxproj
@@ -1,6 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
@@ -9,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -22,7 +38,7 @@
     <ProjectName>r_opengl</ProjectName>
     <ProjectGuid>{51137D5C-4E81-4955-AACF-EA3092006051}</ProjectGuid>
     <RootNamespace>r_opengl</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
@@ -30,21 +46,45 @@
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -52,37 +92,69 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\r_opengl\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <Midl>
@@ -123,6 +195,49 @@
       <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
       <TargetMachine>MachineX86</TargetMachine>
       <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)r_opengl.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\r_opengl\r_opengl.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;__WIN32__;__MSC__;USE_WGL_SWAP;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)r_opengl.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level4</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <OutputFile>$(OutDir)r_opengl.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)r_opengl.pdb</ProgramDatabaseFile>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
     <Bscmake>
       <SuppressStartupBanner>true</SuppressStartupBanner>
@@ -168,6 +283,49 @@
       <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
       <TargetMachine>MachineX64</TargetMachine>
       <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)r_opengl.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\r_opengl\r_opengl.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;__WIN32__;__MSC__;USE_WGL_SWAP;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)r_opengl.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level4</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <OutputFile>$(OutDir)r_opengl.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)r_opengl.pdb</ProgramDatabaseFile>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
     <Bscmake>
       <SuppressStartupBanner>true</SuppressStartupBanner>
@@ -215,6 +373,52 @@
       <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
       <TargetMachine>MachineX86</TargetMachine>
       <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)r_opengl.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <Midl>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\r_opengl\r_opengl.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;__WIN32__;__MSC__;USE_WGL_SWAP;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)r_opengl.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <OutputFile>$(OutDir)r_opengl.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)r_opengl.pdb</ProgramDatabaseFile>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
     <Bscmake>
       <SuppressStartupBanner>true</SuppressStartupBanner>
@@ -262,6 +466,52 @@
       <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
       <TargetMachine>MachineX64</TargetMachine>
       <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)r_opengl.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <Midl>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\r_opengl\r_opengl.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;__WIN32__;__MSC__;USE_WGL_SWAP;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)r_opengl.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <OutputFile>$(OutDir)r_opengl.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)r_opengl.pdb</ProgramDatabaseFile>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)r_opengl.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
     <Bscmake>
       <SuppressStartupBanner>true</SuppressStartupBanner>
@@ -271,15 +521,23 @@
   <ItemGroup>
     <ClCompile Include="ogl_win.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <ClCompile Include="r_opengl.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 15ab53013c7c20a80b23972f9e815422f1fcb871..dfee19857cca3881adbd9dc123fd56b48aef7416 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1,7 +1,7 @@
 // Emacs style mode select   -*- C++ -*-
 //-----------------------------------------------------------------------------
 //
-// Copyright (C) 1998-2006 by Sonic Team Junior.
+// Copyright (C) 1998-2018 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -57,7 +57,7 @@ typedef struct GLRGBAFloat GLRGBAFloat;
 #define      N_PI_DEMI               (M_PIl/2.0f) //(1.5707963268f)
 
 #define      ASPECT_RATIO            (1.0f)  //(320.0f/200.0f)
-#define      FAR_CLIPPING_PLANE      150000.0f // Draw further! Tails 01-21-2001
+#define      FAR_CLIPPING_PLANE      32768.0f // Draw further! Tails 01-21-2001
 static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 
 // **************************************************************************
@@ -101,10 +101,19 @@ static GLint       viewport[4];
 #endif
 
 // Yay for arbitrary  numbers! NextTexAvail is buggy for some reason.
-static GLuint screentexture = 60000;
-static GLuint startScreenWipe = 60001;
-static GLuint endScreenWipe = 60002;
-static GLuint finalScreenTexture = 60003;
+// Sryder:	NextTexAvail is broken for these because palette changes or changes to the texture filter or antialiasing
+//			flush all of the stored textures, leaving them unavailable at times such as between levels
+//			These need to start at 0 and be set to their number, and be reset to 0 when deleted so that intel GPUs
+//			can know when the textures aren't there, as textures are always considered resident in their virtual memory
+// TODO:	Store them in a more normal way
+#define SCRTEX_SCREENTEXTURE 65535
+#define SCRTEX_STARTSCREENWIPE 65534
+#define SCRTEX_ENDSCREENWIPE 65533
+#define SCRTEX_FINALSCREENTEXTURE 65532
+static GLuint screentexture = 0;
+static GLuint startScreenWipe = 0;
+static GLuint endScreenWipe = 0;
+static GLuint finalScreenTexture = 0;
 #if 0
 GLuint screentexture = FIRST_TEX_AVAIL;
 #endif
@@ -245,6 +254,7 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglBindTexture glBindTexture
 /* texture mapping */ //GL_EXT_copy_texture
 #define pglCopyTexImage2D glCopyTexImage2D
+#define pglCopyTexSubImage2D glCopyTexSubImage2D
 
 #else //!STATIC_OPENGL
 
@@ -361,6 +371,8 @@ static PFNglBindTexture pglBindTexture;
 /* texture mapping */ //GL_EXT_copy_texture
 typedef void (APIENTRY * PFNglCopyTexImage2D) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
 static PFNglCopyTexImage2D pglCopyTexImage2D;
+typedef void (APIENTRY * PFNglCopyTexSubImage2D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+static PFNglCopyTexSubImage2D pglCopyTexSubImage2D;
 #endif
 /* GLU functions */
 typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
@@ -460,6 +472,7 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglBindTexture , glBindTexture)
 
 	GETOPENGLFUNC(pglCopyTexImage2D , glCopyTexImage2D)
+	GETOPENGLFUNC(pglCopyTexSubImage2D , glCopyTexSubImage2D)
 
 #undef GETOPENGLFUNC
 
@@ -538,7 +551,8 @@ static void GLPerspective(GLdouble fovy, GLdouble aspect)
 	const GLdouble deltaZ = zFar - zNear;
 	GLdouble cotangent;
 
-	if ((deltaZ == 0.0f) || (sine == 0.0f) || (aspect == 0.0f)) {
+	if ((fabsf((float)deltaZ) < 1.0E-36f) || fpclassify(sine) == FP_ZERO || fpclassify(aspect) == FP_ZERO)
+	{
 		return;
 	}
 	cotangent = cos(radians) / sine;
@@ -572,7 +586,7 @@ static void GLProject(GLdouble objX, GLdouble objY, GLdouble objZ,
 			out[2] * projMatrix[2*4+i] +
 			out[3] * projMatrix[3*4+i];
 	}
-	if (in[3] == 0.0f) return;
+	if (fpclassify(in[3]) == FP_ZERO) return;
 	in[0] /= in[3];
 	in[1] /= in[3];
 	in[2] /= in[3];
@@ -597,6 +611,10 @@ void SetModelView(GLint w, GLint h)
 {
 //	DBG_Printf("SetModelView(): %dx%d\n", (int)w, (int)h);
 
+	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
+	if (screen_width != w || screen_height != h)
+		FlushScreenTextures();
+
 	screen_width = w;
 	screen_height = h;
 
@@ -734,6 +752,7 @@ void Flush(void)
 		screentexture = FIRST_TEX_AVAIL;
 	}
 #endif
+
 	tex_downloaded = 0;
 }
 
@@ -948,26 +967,38 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 			switch (PolyFlags & PF_Blending) {
 				case PF_Translucent & PF_Blending:
 					pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Masked & PF_Blending:
 					// Hurdler: does that mean lighting is only made by alpha src?
 					// it sounds ok, but not for polygonsmooth
 					pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
+					pglAlphaFunc(GL_GREATER, 0.5f);
 					break;
 				case PF_Additive & PF_Blending:
 					pglBlendFunc(GL_SRC_ALPHA, GL_ONE);                 // src * alpha + dest
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Environment & PF_Blending:
 					pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Substractive & PF_Blending:
 					// good for shadow
 					// not realy but what else ?
 					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+					break;
+				case PF_Fog & PF_Fog:
+					// Sryder: Fog
+					// multiplies input colour by input alpha, and destination colour by input colour, then adds them
+					pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				default : // must be 0, otherwise it's an error
 					// No blending
 					pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
+					pglAlphaFunc(GL_GREATER, 0.5f);
 					break;
 			}
 		}
@@ -1120,6 +1151,7 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 						tex[w*j+i].s.green = 0;
 						tex[w*j+i].s.blue  = 0;
 						tex[w*j+i].s.alpha = 0;
+						pTexInfo->flags |= TF_TRANSPARENT; // there is a hole in it
 					}
 					else
 					{
@@ -1189,8 +1221,17 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 		tex_downloaded = pTexInfo->downloaded;
 		pglBindTexture(GL_TEXTURE_2D, pTexInfo->downloaded);
 
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+		// disable texture filtering on any texture that has holes so there's no dumb borders or blending issues
+		if (pTexInfo->flags & TF_TRANSPARENT)
+		{
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		}
+		else
+		{
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+		}
 
 #ifdef USE_PALETTED_TEXTURE
 			//Hurdler: not really supported and not tested recently
@@ -1559,12 +1600,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 			ambient[1] = 0.75f;
 		if (ambient[2] > 0.75f)
 			ambient[2] = 0.75f;
-
-		if (color[3] < 255)
-		{
-			pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-			pglDepthMask(GL_FALSE);
-		}
 	}
 
 	pglEnable(GL_CULL_FACE);
@@ -1589,10 +1624,12 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 		pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
 		pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
 #endif
+		if (color[3] < 255)
+			SetBlend(PF_Translucent|PF_Modulated|PF_Clip);
+		else
+			SetBlend(PF_Masked|PF_Modulated|PF_Occlude|PF_Clip);
 	}
 
-	DrawPolygon(NULL, NULL, 0, PF_Masked|PF_Modulated|PF_Occlude|PF_Clip);
-
 	pglPushMatrix(); // should be the same as glLoadIdentity
 	//Hurdler: now it seems to work
 	pglTranslatef(pos->x, pos->z, pos->y);
@@ -1600,14 +1637,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 		scaley = -scaley;
 	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
 	pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
-	//pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-
-	// Remove depth mask when the model is transparent so it doesn't cut thorugh sprites // SRB2CBTODO: For all stuff too?!
-	if (color && color[3] < 255)
-	{
-		pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-		pglDepthMask(GL_FALSE);
-	}
 
 	val = *gl_cmd_buffer++;
 
@@ -1632,7 +1661,7 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 
 			pglTexCoord2f(s, t);
 
-			if (!nextframe || pol == 0.0f)
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
 				pglNormal3f(frame->vertices[pindex].normal[0],
 				            frame->vertices[pindex].normal[1],
@@ -1675,7 +1704,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	if (color)
 		pglDisable(GL_LIGHTING);
 	pglShadeModel(GL_FLAT);
-	pglDepthMask(GL_TRUE);
 	pglDisable(GL_CULL_FACE);
 }
 
@@ -1702,6 +1730,7 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 	pglLoadIdentity();
 	if (stransform)
 	{
+		boolean fovx90;
 		// keep a trace of the transformation for md2
 		memcpy(&md2_transform, stransform, sizeof (md2_transform));
 
@@ -1716,7 +1745,8 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 
 		pglMatrixMode(GL_PROJECTION);
 		pglLoadIdentity();
-		special_splitscreen = (stransform->splitscreen && stransform->fovxangle == 90.0f);
+		fovx90 = stransform->fovxangle > 0.0f && fabsf(stransform->fovxangle - 90.0f) < 0.5f;
+		special_splitscreen = (stransform->splitscreen && fovx90);
 		if (special_splitscreen)
 			GLPerspective(53.13l, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
@@ -1822,10 +1852,25 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 }
 #endif //SHUFFLE
 
+// Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
+//			a new size
+EXPORT void HWRAPI(FlushScreenTextures) (void)
+{
+	pglDeleteTextures(1, &screentexture);
+	pglDeleteTextures(1, &startScreenWipe);
+	pglDeleteTextures(1, &endScreenWipe);
+	pglDeleteTextures(1, &finalScreenTexture);
+	screentexture = 0;
+	startScreenWipe = 0;
+	endScreenWipe = 0;
+	finalScreenTexture = 0;
+}
+
 // Create Screen to fade from
 EXPORT void HWRAPI(StartScreenWipe) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (startScreenWipe == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -1834,20 +1879,29 @@ EXPORT void HWRAPI(StartScreenWipe) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		startScreenWipe = SCRTEX_STARTSCREENWIPE;
 	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = startScreenWipe;
 }
 
 // Create Screen to fade to
 EXPORT void HWRAPI(EndScreenWipe)(void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (endScreenWipe == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -1856,14 +1910,22 @@ EXPORT void HWRAPI(EndScreenWipe)(void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		endScreenWipe = SCRTEX_ENDSCREENWIPE;
 	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = endScreenWipe;
 }
 
 
@@ -1905,7 +1967,7 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 
 	pglEnd();
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	tex_downloaded = screentexture;
 }
 
 // Do screen fades!
@@ -1993,6 +2055,7 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 
 		pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
 		pglActiveTexture(GL_TEXTURE0);
+		tex_downloaded = endScreenWipe;
 	}
 	else
 	{
@@ -2017,9 +2080,8 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 			pglTexCoord2f(xfix, 0.0f);
 			pglVertex3f(1.0f, -1.0f, 1.0f);
 		pglEnd();
+		tex_downloaded = endScreenWipe;
 	}
-
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
 }
 
 
@@ -2027,6 +2089,7 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 EXPORT void HWRAPI(MakeScreenTexture) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (screentexture == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -2035,19 +2098,28 @@ EXPORT void HWRAPI(MakeScreenTexture) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		screentexture = SCRTEX_SCREENTEXTURE;
 	pglBindTexture(GL_TEXTURE_2D, screentexture);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = screentexture;
 }
 
 EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (finalScreenTexture == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -2056,20 +2128,31 @@ EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		finalScreenTexture = SCRTEX_FINALSCREENTEXTURE;
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = finalScreenTexture;
 
 }
 
 EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 {
 	float xfix, yfix;
+	float origaspect, newaspect;
+	float xoff = 1, yoff = 1; // xoffset and yoffset for the polygon to have black bars around the screen
+	FRGBAFloat clearColour;
 	INT32 texsize = 2048;
 
 	if(screen_width <= 1024)
@@ -2080,35 +2163,47 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
-	//pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+	origaspect = (float)screen_width / screen_height;
+	newaspect = (float)width / height;
+	if (origaspect < newaspect)
+	{
+		xoff = origaspect / newaspect;
+		yoff = 1;
+	}
+	else if (origaspect > newaspect)
+	{
+		xoff = 1;
+		yoff = newaspect / origaspect;
+	}
+
 	pglViewport(0, 0, width, height);
 
+	clearColour.red = clearColour.green = clearColour.blue = 0;
+	clearColour.alpha = 1;
+	ClearBuffer(true, false, &clearColour);
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
 	pglBegin(GL_QUADS);
 
 		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
 		// Bottom left
 		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1, -1, 1.0f);
+		pglVertex3f(-xoff, -yoff, 1.0f);
 
 		// Top left
 		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1, 1, 1.0f);
+		pglVertex3f(-xoff, yoff, 1.0f);
 
 		// Top right
 		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1, 1, 1.0f);
+		pglVertex3f(xoff, yoff, 1.0f);
 
 		// Bottom right
 		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1, -1, 1.0f);
+		pglVertex3f(xoff, -yoff, 1.0f);
 
 	pglEnd();
 
-	SetModelView(screen_width, screen_height);
-	SetStates();
-
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	tex_downloaded = finalScreenTexture;
 }
 
 #endif //HWRENDER
diff --git a/src/hardware/s_openal/s_openal-vc10.vcxproj b/src/hardware/s_openal/s_openal-vc10.vcxproj
index 8b4f6cbbe43a77fff7e49cee6e83a8470da8f04d..5039cd006d6c65881a7b354ccd5c340c8f12f050 100644
--- a/src/hardware/s_openal/s_openal-vc10.vcxproj
+++ b/src/hardware/s_openal/s_openal-vc10.vcxproj
@@ -1,6 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
@@ -9,6 +17,14 @@
       <Configuration>Debug</Configuration>
       <Platform>x64</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -22,7 +38,7 @@
     <ProjectName>s_openal</ProjectName>
     <ProjectGuid>{E662D0B3-412D-4D55-A5EC-8CBD680DDCBE}</ProjectGuid>
     <RootNamespace>s_openal</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@@ -31,22 +47,44 @@
     <CharacterSet>MultiByte</CharacterSet>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <CharacterSet>MultiByte</CharacterSet>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <CharacterSet>MultiByte</CharacterSet>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
     <PlatformToolset>v140</PlatformToolset>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -54,37 +92,69 @@
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC60.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</LinkIncremental>
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\</OutDir>
     <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.\..\..\..\objs\VC10\$(Platform)\$(Configuration)\s_openal\</IntDir>
     <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</IgnoreImportLibrary>
+    <IgnoreImportLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</IgnoreImportLibrary>
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <Midl>
@@ -132,6 +202,50 @@
       <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <Midl>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\s_openal\s_openal.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;__WIN32__;__MSC__;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)s_openal.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>OpenAL32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <OutputFile>$(OutDir)s_openal.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)s_openal.pdb</ProgramDatabaseFile>
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)s_openal.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <Midl>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -178,6 +292,50 @@
       <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <Midl>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\s_openal\s_openal.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalOptions>/MP %(AdditionalOptions)</AdditionalOptions>
+      <Optimization>MaxSpeed</Optimization>
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;__WIN32__;__MSC__;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <StringPooling>true</StringPooling>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)s_openal.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level3</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>OpenAL32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <OutputFile>$(OutDir)s_openal.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)s_openal.pdb</ProgramDatabaseFile>
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)s_openal.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <Midl>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -225,6 +383,50 @@
       <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\s_openal\s_openal.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <AdditionalOptions>/MP /SAFESEH:OFF %(AdditionalOptions)</AdditionalOptions>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;__WIN32__;__MSC__;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)s_openal.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level4</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>OpenAL32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <OutputFile>$(OutDir)s_openal.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)s_openal.pdb</ProgramDatabaseFile>
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)s_openal.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Midl>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@@ -272,12 +474,60 @@
       <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
     </Bscmake>
   </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <Midl>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <MkTypLibCompatible>true</MkTypLibCompatible>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <TypeLibraryName>.\..\..\..\bin\VC10\$(Platform)\$(Configuration)\s_openal\s_openal.tlb</TypeLibraryName>
+      <HeaderFileName>
+      </HeaderFileName>
+    </Midl>
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;__WIN32__;__MSC__;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+      <ProgramDataBaseFileName>$(IntDir)s_openal.pdb</ProgramDataBaseFileName>
+      <BrowseInformation>true</BrowseInformation>
+      <WarningLevel>Level4</WarningLevel>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <CompileAs>CompileAsC</CompileAs>
+      <AdditionalOptions>/MP /SAFESEH:OFF %(AdditionalOptions)</AdditionalOptions>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <Culture>0x0409</Culture>
+    </ResourceCompile>
+    <Link>
+      <AdditionalDependencies>OpenAL32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <OutputFile>$(OutDir)s_openal.dll</OutputFile>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ProgramDatabaseFile>$(OutDir)s_openal.pdb</ProgramDatabaseFile>
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>
+      <DataExecutionPrevention>
+      </DataExecutionPrevention>
+      <ImportLibrary>$(IntDir)s_openal.lib</ImportLibrary>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+    <Bscmake>
+      <SuppressStartupBanner>true</SuppressStartupBanner>
+      <OutputFile>$(OutDir)s_openal.bsc</OutputFile>
+    </Bscmake>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="s_openal.c">
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 28b20f61e287d6826ea2680d135cbcef154f726c..3bc643c3c2537f691e9545ceccfeec370c9a2539 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -69,10 +69,12 @@ patch_t *nightsnum[10]; // 0-9
 // Level title and credits fonts
 patch_t *lt_font[LT_FONTSIZE];
 patch_t *cred_font[CRED_FONTSIZE];
+patch_t *ttlnum[20]; // act numbers (0-19)
 
 static player_t *plr;
 boolean chat_on; // entering a chat message?
 static char w_chat[HU_MAXMSGLEN];
+static size_t c_input = 0; // let's try to make the chat input less shitty.
 static boolean headsupactive = false;
 boolean hu_showscores; // draw rankings
 static char hu_tick;
@@ -89,8 +91,7 @@ patch_t *tallinfin;
 //              coop hud
 //-------------------------------------------
 
-patch_t *emeraldpics[7];
-patch_t *tinyemeraldpics[7];
+patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
 static patch_t *emblemicon;
 patch_t *tokenicon;
 static patch_t *exiticon;
@@ -238,6 +239,13 @@ void HU_LoadGraphics(void)
 	tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX);
 	tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX);
 
+	// cache act numbers for level titles
+	for (i = 0; i < 20; i++)
+	{
+		sprintf(buffer, "TTL%.2d", i);
+		ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
 	// cache the crosshairs, don't bother to know which one is being used,
 	// just cache all 3, they're so small anyway.
 	for (i = 0; i < HU_CROSSHAIRS; i++)
@@ -250,20 +258,32 @@ void HU_LoadGraphics(void)
 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
 	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
 
-	emeraldpics[0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
-	emeraldpics[1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
-	emeraldpics[2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
-	emeraldpics[3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
-	emeraldpics[4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
-	emeraldpics[5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
-	emeraldpics[6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
-	tinyemeraldpics[0] = W_CachePatchName("TEMER1", PU_HUDGFX);
-	tinyemeraldpics[1] = W_CachePatchName("TEMER2", PU_HUDGFX);
-	tinyemeraldpics[2] = W_CachePatchName("TEMER3", PU_HUDGFX);
-	tinyemeraldpics[3] = W_CachePatchName("TEMER4", PU_HUDGFX);
-	tinyemeraldpics[4] = W_CachePatchName("TEMER5", PU_HUDGFX);
-	tinyemeraldpics[5] = W_CachePatchName("TEMER6", PU_HUDGFX);
-	tinyemeraldpics[6] = W_CachePatchName("TEMER7", PU_HUDGFX);
+	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
+	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
+	emeraldpics[0][2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
+	emeraldpics[0][3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
+	emeraldpics[0][4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
+	emeraldpics[0][5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
+	emeraldpics[0][6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
+	emeraldpics[0][7] = W_CachePatchName("CHAOS8", PU_HUDGFX);
+
+	emeraldpics[1][0] = W_CachePatchName("TEMER1", PU_HUDGFX);
+	emeraldpics[1][1] = W_CachePatchName("TEMER2", PU_HUDGFX);
+	emeraldpics[1][2] = W_CachePatchName("TEMER3", PU_HUDGFX);
+	emeraldpics[1][3] = W_CachePatchName("TEMER4", PU_HUDGFX);
+	emeraldpics[1][4] = W_CachePatchName("TEMER5", PU_HUDGFX);
+	emeraldpics[1][5] = W_CachePatchName("TEMER6", PU_HUDGFX);
+	emeraldpics[1][6] = W_CachePatchName("TEMER7", PU_HUDGFX);
+	//emeraldpics[1][7] = W_CachePatchName("TEMER8", PU_HUDGFX); -- unused
+
+	emeraldpics[2][0] = W_CachePatchName("EMBOX1", PU_HUDGFX);
+	emeraldpics[2][1] = W_CachePatchName("EMBOX2", PU_HUDGFX);
+	emeraldpics[2][2] = W_CachePatchName("EMBOX3", PU_HUDGFX);
+	emeraldpics[2][3] = W_CachePatchName("EMBOX4", PU_HUDGFX);
+	emeraldpics[2][4] = W_CachePatchName("EMBOX5", PU_HUDGFX);
+	emeraldpics[2][5] = W_CachePatchName("EMBOX6", PU_HUDGFX);
+	emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX);
+	//emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused
 }
 
 // Initialise Heads up
@@ -308,6 +328,88 @@ void HU_Start(void)
 //======================================================================
 
 #ifndef NONET
+
+// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM
+
+static UINT32 chat_nummsg_log = 0;
+static UINT32 chat_nummsg_min = 0;
+static UINT32 chat_scroll = 0;
+static tic_t chat_scrolltime = 0;
+
+static UINT32 chat_maxscroll = 0; // how far can we scroll?
+
+//static chatmsg_t chat_mini[CHAT_BUFSIZE]; // Display the last few messages sent.
+//static chatmsg_t chat_log[CHAT_BUFSIZE]; // Keep every message sent to us in memory so we can scroll n shit, it's cool.
+
+static char chat_log[CHAT_BUFSIZE][255]; // hold the last 48 or so messages in that log.
+static char chat_mini[8][255]; // display up to 8 messages that will fade away / get overwritten
+static tic_t chat_timers[8];
+
+static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message.
+
+// remove text from minichat table
+
+static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away
+
+static void HU_removeChatText_Mini(void)
+{
+    // MPC: Don't create new arrays, just iterate through an existing one
+	size_t i;
+    for(i=0;i<chat_nummsg_min-1;i++) {
+        strcpy(chat_mini[i], chat_mini[i+1]);
+        chat_timers[i] = chat_timers[i+1];
+    }
+	chat_nummsg_min--; // lost 1 msg.
+
+	// use addy and make shit slide smoothly af.
+	addy += (vid.width < 640) ? 8 : 6;
+
+}
+
+// same but w the log. TODO: optimize this and maybe merge in a single func? im bad at C.
+static void HU_removeChatText_Log(void)
+{
+	// MPC: Don't create new arrays, just iterate through an existing one
+	size_t i;
+    for(i=0;i<chat_nummsg_log-1;i++) {
+        strcpy(chat_log[i], chat_log[i+1]);
+    }
+    chat_nummsg_log--; // lost 1 msg.
+}
+#endif
+
+void HU_AddChatText(const char *text, boolean playsound)
+{
+#ifndef NONET
+	if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat.
+		S_StartSound(NULL, sfx_radio);
+	// reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game.
+
+	if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages!
+		HU_removeChatText_Log();
+
+	strcpy(chat_log[chat_nummsg_log], text);
+	chat_nummsg_log++;
+
+	if (chat_nummsg_min >= 8)
+		HU_removeChatText_Mini();
+
+	strcpy(chat_mini[chat_nummsg_min], text);
+	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
+	chat_nummsg_min++;
+
+	if (OLDCHAT) // if we're using oldchat, print directly in console
+		CONS_Printf("%s\n", text);
+	else			// if we aren't, still save the message to log.txt
+		CON_LogMessage(va("%s\n", text));
+#else
+	(void)playsound;
+	CONS_Printf("%s\n", text);
+#endif
+}
+
+#ifndef NONET
+
 /** Runs a say command, sending an ::XD_SAY message.
   * A say command consists of a signed 8-bit integer for the target, an
   * unsigned 8-bit flag variable, and then the message itself.
@@ -326,6 +428,8 @@ void HU_Start(void)
   * \sa Command_Say_f, Command_Sayteam_f, Command_Sayto_f, Got_Saycmd
   * \author Graue <graue@oceanbase.org>
   */
+
+
 static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 {
 	char buf[254];
@@ -336,14 +440,14 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 	numwords = COM_Argc() - usedargs;
 	I_Assert(numwords > 0);
 
-	if (cv_mute.value && !(server || adminplayer == consoleplayer))
+	if (CHAT_MUTE) // TODO: Per Player mute.
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
+		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
 		return;
 	}
 
 	// Only servers/admins can CSAY.
-	if(!server && adminplayer != consoleplayer)
+	if(!server && !(IsPlayerAdmin(consoleplayer)))
 		flags &= ~HU_CSAY;
 
 	// We handle HU_SERVER_SAY, not the caller.
@@ -362,6 +466,57 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 		strlcat(msg, COM_Argv(ix + usedargs), msgspace);
 	}
 
+	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
+	{
+		// what we're gonna do now is check if the node exists
+		// with that logic, characters 4 and 5 are our numbers:
+		const char *newmsg;
+		char *nodenum = (char*) malloc(3);
+		INT32 spc = 1; // used if nodenum[1] is a space.
+
+		strncpy(nodenum, msg+3, 3);
+		// check for undesirable characters in our "number"
+		if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
+		{
+			// check if nodenum[1] is a space
+			if (nodenum[1] == ' ')
+				spc = 0;
+				// let it slide
+			else
+			{
+				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
+				free(nodenum);
+				return;
+			}
+		}
+		// I'm very bad at C, I swear I am, additional checks eww!
+			if (spc != 0)
+			{
+				if (msg[5] != ' ')
+				{
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
+					free(nodenum);
+					return;
+				}
+			}
+
+		target = atoi((const char*) nodenum); // turn that into a number
+		free(nodenum);
+		//CONS_Printf("%d\n", target);
+
+		// check for target player, if it doesn't exist then we can't send the message!
+		if (target < MAXPLAYERS && playeringame[target]) // player exists
+			target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+		else
+		{
+			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
+			return;
+		}
+		buf[0] = target;
+		newmsg = msg+5+spc;
+		strlcpy(msg, newmsg, 252);
+	}
+
 	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
 }
 
@@ -437,7 +592,7 @@ static void Command_CSay_f(void)
 		return;
 	}
 
-	if(!server && adminplayer != consoleplayer)
+	if(!server && !IsPlayerAdmin(consoleplayer))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n"));
 		return;
@@ -445,6 +600,7 @@ static void Command_CSay_f(void)
 
 	DoSayCommand(0, 1, HU_CSAY);
 }
+static tic_t stop_spamming[MAXPLAYERS];
 
 /** Receives a message, processing an ::XD_SAY command.
   * \sa DoSayCommand
@@ -458,6 +614,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	char *msg;
 	boolean action = false;
 	char *ptr;
+	INT32 spam_eatmsg = 0;
 
 	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
 
@@ -466,7 +623,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	msg = (char *)*p;
 	SKIPSTRING(*p);
 
-	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && playernum != adminplayer)
+	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
 	{
 		CONS_Alert(CONS_WARNING, cv_mute.value ?
 			M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"),
@@ -504,11 +661,28 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		}
 	}
 
+	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
+
+	//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
+	if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
+	{
+		CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]);
+		stop_spamming[playernum] = 4;
+		spam_eatmsg = 1;
+	}
+	else
+		stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
+
+	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
+
 #ifdef HAVE_BLUA
 	if (LUAh_PlayerMsg(playernum, target, flags, msg))
 		return;
 #endif
 
+	if (spam_eatmsg)
+		return; // don't proceed if we were supposed to eat the message.
+
 	// If it's a CSAY, just CECHO and be done with it.
 	if (flags & HU_CSAY)
 	{
@@ -548,31 +722,146 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	|| target == 0 // To everyone
 	|| consoleplayer == target-1) // To you
 	{
-		const char *cstart = "", *cend = "", *adminchar = "~", *remotechar = "@", *fmt;
+		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80";
 		char *tempchar = NULL;
 
-		// In CTF and team match, color the player's name.
-		if (G_GametypeHasTeams())
+		// player is a spectator?
+        if (players[playernum].spectator)
+		{
+			cstart = "\x86";    // grey name
+			textcolor = "\x86";
+		}
+		else if (target == -1) // say team
 		{
-			cend = "\x80";
 			if (players[playernum].ctfteam == 1) // red
+			{
 				cstart = "\x85";
-			else if (players[playernum].ctfteam == 2) // blue
+				textcolor = "\x85";
+			}
+			else // blue
+			{
 				cstart = "\x84";
+				textcolor = "\x84";
+			}
 		}
+		else
+        {
+			const UINT8 color = players[playernum].skincolor;
+
+			cstart = "\x83";
+
+			// Follow palette order at r_draw.c Color_Names
+			switch (color)
+			{
+				default:
+				case SKINCOLOR_WHITE:
+				case SKINCOLOR_BONE:
+				case SKINCOLOR_CLOUDY:
+				case SKINCOLOR_GREY:
+				case SKINCOLOR_SILVER:
+				case SKINCOLOR_AETHER:
+				case SKINCOLOR_SLATE:
+					cstart = "\x80"; // white
+					break;
+				case SKINCOLOR_CARBON:
+				case SKINCOLOR_JET:
+				case SKINCOLOR_BLACK:
+					cstart = "\x86"; // V_GRAYMAP
+					break;
+				case SKINCOLOR_PINK:
+				case SKINCOLOR_RUBY:
+				case SKINCOLOR_SALMON:
+				case SKINCOLOR_RED:
+				case SKINCOLOR_CRIMSON:
+				case SKINCOLOR_FLAME:
+					cstart = "\x85"; // V_REDMAP
+					break;
+				case SKINCOLOR_YOGURT:
+				case SKINCOLOR_BROWN:
+				case SKINCOLOR_TAN:
+				case SKINCOLOR_BEIGE:
+				case SKINCOLOR_QUAIL:
+					cstart = "\x8d"; // V_BROWNMAP
+					break;
+				case SKINCOLOR_MOSS:
+				case SKINCOLOR_GREEN:
+				case SKINCOLOR_FOREST:
+				case SKINCOLOR_EMERALD:
+				case SKINCOLOR_MINT:
+					cstart = "\x83"; // V_GREENMAP
+					break;
+				case SKINCOLOR_AZURE:
+					cstart = "\x8c"; // V_AZUREMAP
+					break;
+				case SKINCOLOR_LAVENDER:
+				case SKINCOLOR_PASTEL:
+				case SKINCOLOR_PURPLE:
+					cstart = "\x89"; // V_PURPLEMAP
+					break;
+				case SKINCOLOR_PEACHY:
+				case SKINCOLOR_LILAC:
+				case SKINCOLOR_PLUM:
+				case SKINCOLOR_ROSY:
+					cstart = "\x8e"; // V_ROSYMAP
+					break;
+				case SKINCOLOR_SUNSET:
+				case SKINCOLOR_APRICOT:
+				case SKINCOLOR_ORANGE:
+				case SKINCOLOR_RUST:
+					cstart = "\x87"; // V_ORANGEMAP
+					break;
+				case SKINCOLOR_GOLD:
+				case SKINCOLOR_SANDY:
+				case SKINCOLOR_YELLOW:
+				case SKINCOLOR_OLIVE:
+					cstart = "\x82"; // V_YELLOWMAP
+					break;
+				case SKINCOLOR_LIME:
+				case SKINCOLOR_PERIDOT:
+					cstart = "\x8b"; // V_PERIDOTMAP
+					break;
+				case SKINCOLOR_SEAFOAM:
+				case SKINCOLOR_AQUA:
+					cstart = "\x8a"; // V_AQUAMAP
+					break;
+				case SKINCOLOR_TEAL:
+				case SKINCOLOR_WAVE:
+				case SKINCOLOR_CYAN:
+				case SKINCOLOR_SKY:
+				case SKINCOLOR_CERULEAN:
+				case SKINCOLOR_ICY:
+				case SKINCOLOR_SAPPHIRE:
+				case SKINCOLOR_VAPOR:
+					cstart = "\x88"; // V_SKYMAP
+					break;
+				case SKINCOLOR_CORNFLOWER:
+				case SKINCOLOR_BLUE:
+				case SKINCOLOR_COBALT:
+				case SKINCOLOR_DUSK:
+					cstart = "\x84"; // V_BLUEMAP
+					break;
+				case SKINCOLOR_BUBBLEGUM:
+				case SKINCOLOR_MAGENTA:
+				case SKINCOLOR_NEON:
+				case SKINCOLOR_VIOLET:
+					cstart = "\x81"; // V_MAGENTAMAP
+					break;
+			}
+        }
+		prefix = cstart;
 
 		// Give admins and remote admins their symbols.
 		if (playernum == serverplayer)
 			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL);
-		else if (playernum == adminplayer)
+		else if (IsPlayerAdmin(playernum))
 			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
 		if (tempchar)
 		{
-			strcat(tempchar, cstart);
 			if (playernum == serverplayer)
 				strcat(tempchar, adminchar);
 			else
 				strcat(tempchar, remotechar);
+			strcat(tempchar, cstart);
 			cstart = tempchar;
 		}
 
@@ -581,21 +870,39 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		// name, color end, and the message itself.
 		// '\4' makes the message yellow and beeps; '\3' just beeps.
 		if (action)
-			fmt = "\4* %s%s%s \x82%s\n";
-		else if (target == 0) // To everyone
-			fmt = "\3<%s%s%s> %s\n";
+			fmt2 = "* %s%s%s%s \x82%s%s";
 		else if (target-1 == consoleplayer) // To you
-			fmt = "\3*%s%s%s* %s\n";
+		{
+			prefix = "\x82[PM]";
+			cstart = "\x82";
+			textcolor = "\x82";
+			fmt2 = "%s<%s%s>%s\x80 %s%s";
+		}
 		else if (target > 0) // By you, to another player
 		{
 			// Use target's name.
 			dispname = player_names[target-1];
-			fmt = "\3->*%s%s%s* %s\n";
+			prefix = "\x82[TO]";
+			cstart = "\x82";
+			fmt2 = "%s<%s%s>%s\x80 %s%s";
+
 		}
+		else if (target == 0) // To everyone
+			fmt2 = "%s<%s%s%s>\x80 %s%s";
 		else // To your team
-			fmt = "\3>>%s%s%s<< (team) %s\n";
+		{
+			if (players[playernum].ctfteam == 1) // red
+				prefix = "\x85[TEAM]";
+			else if (players[playernum].ctfteam == 2) // blue
+				prefix = "\x84[TEAM]";
+			else
+				prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes
+
+			fmt2 = "%s<%s%s>\x80%s %s%s";
+		}
+
+		HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), cv_chatnotifications.value); // add to chat
 
-		CONS_Printf(fmt, cstart, dispname, cend, msg);
 		if (tempchar)
 			Z_Free(tempchar);
 	}
@@ -608,7 +915,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 		CONS_Printf("Dropped chat: %d %d %s\n", playernum, target, msg);
 #endif
 }
-#endif
 
 // Handles key input and string input
 //
@@ -622,19 +928,53 @@ static inline boolean HU_keyInChatString(char *s, char ch)
 		l = strlen(s);
 		if (l < HU_MAXMSGLEN - 1)
 		{
-			s[l++] = ch;
-			s[l]=0;
+			if (c_input >= strlen(s)) // don't do anything complicated
+			{
+				s[l++] = ch;
+				s[l]=0;
+			}
+			else
+			{
+
+				// move everything past c_input for new characters:
+				size_t m = HU_MAXMSGLEN-1;
+				while (m>=c_input)
+				{
+					if (s[m])
+						s[m+1] = (s[m]);
+					if (m == 0) // prevent overflow
+						break;
+					m--;
+				}
+				s[c_input] = ch; // and replace this.
+			}
+			c_input++;
 			return true;
 		}
 		return false;
 	}
 	else if (ch == KEY_BACKSPACE)
 	{
-		l = strlen(s);
-		if (l)
-			s[--l] = 0;
-		else
+		size_t i = c_input;
+
+		if (c_input <= 0)
 			return false;
+
+		if (!s[i-1])
+			return false;
+
+		if (i >= strlen(s)-1)
+		{
+			s[strlen(s)-1] = 0;
+			c_input--;
+			return false;
+		}
+
+		for (; (i < HU_MAXMSGLEN); i++)
+		{
+			s[i-1] = s[i];
+		}
+		c_input--;
 	}
 	else if (ch != KEY_ENTER)
 		return false; // did not eat key
@@ -642,6 +982,8 @@ static inline boolean HU_keyInChatString(char *s, char ch)
 	return true; // ate the key
 }
 
+#endif
+
 //
 //
 void HU_Ticker(void)
@@ -658,28 +1000,32 @@ void HU_Ticker(void)
 		hu_showscores = false;
 }
 
-#define QUEUESIZE 256
+#ifndef NONET
 
 static boolean teamtalk = false;
-static char chatchars[QUEUESIZE];
-static INT32 head = 0, tail = 0;
+/*static char chatchars[QUEUESIZE];
+static INT32 head = 0, tail = 0;*/
+// WHY DO YOU OVERCOMPLICATE EVERYTHING?????????
 
-//
-// HU_dequeueChatChar
-//
-char HU_dequeueChatChar(void)
+// Clear spaces so we don't end up with messages only made out of emptiness
+static boolean HU_clearChatSpaces(void)
 {
-	char c;
+	size_t i = 0; // Used to just check our message
+	char c; // current character we're iterating.
+	boolean nothingbutspaces = true;
 
-	if (head != tail)
+	for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong.
 	{
-		c = chatchars[tail];
-		tail = (tail + 1) & (QUEUESIZE-1);
-	}
-	else
-		c = 0;
+		c = w_chat[i];
+		if (!c)
+			break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here.
 
-	return c;
+		if (c != ' ') // Isn't a space
+		{
+			nothingbutspaces = false;
+		}
+	}
+	return nothingbutspaces;
 }
 
 //
@@ -690,95 +1036,186 @@ static void HU_queueChatChar(char c)
 	if (c == KEY_ENTER)
 	{
 		char buf[2+256];
+		char *msg = &buf[2];
+		size_t i = 0;
 		size_t ci = 2;
+		INT32 target = 0;
+
+		if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something.
+			return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period.
 
 		do {
-			c = HU_dequeueChatChar();
+			c = w_chat[-2+ci++];
 			if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
-				buf[ci++]=c;
+				buf[ci-1]=c;
 		} while (c);
 
+		for (;(i<HU_MAXMSGLEN);i++)
+			w_chat[i] = 0; // reset this.
+
+		c_input = 0;
+
 		// last minute mute check
-		if (cv_mute.value && !(server || adminplayer == consoleplayer))
+		if (CHAT_MUTE)
 		{
-			CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n"));
+			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
 			return;
 		}
 
+		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
+		{
+			INT32 spc = 1; // used if nodenum[1] is a space.
+			char *nodenum = (char*) malloc(3);
+			const char *newmsg;
+
+			// what we're gonna do now is check if the node exists
+			// with that logic, characters 4 and 5 are our numbers:
+
+			// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
+			if (teamtalk)
+			{
+				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
+				return;
+			}
+
+			strncpy(nodenum, msg+3, 3);
+			// check for undesirable characters in our "number"
+			if 	(((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9')))
+			{
+				// check if nodenum[1] is a space
+				if (nodenum[1] == ' ')
+					spc = 0;
+					// let it slide
+				else
+				{
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
+					free(nodenum);
+					return;
+				}
+			}
+			// I'm very bad at C, I swear I am, additional checks eww!
+			if (spc != 0)
+			{
+				if (msg[5] != ' ')
+				{
+					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false);
+					free(nodenum);
+					return;
+				}
+			}
+
+			target = atoi((const char*) nodenum); // turn that into a number
+			free(nodenum);
+			//CONS_Printf("%d\n", target);
+
+			// check for target player, if it doesn't exist then we can't send the message!
+			if (target < MAXPLAYERS && playeringame[target]) // player exists
+				target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+			else
+			{
+				HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
+				return;
+			}
+
+			// we need to get rid of the /pm<node>
+			newmsg = msg+5+spc;
+			strlcpy(msg, newmsg, 255);
+		}
 		if (ci > 3) // don't send target+flags+empty message.
 		{
 			if (teamtalk)
 				buf[0] = -1; // target
 			else
-				buf[0] = 0; // target
+				buf[0] = target;
+
 			buf[1] = 0; // flags
 			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
 		}
 		return;
 	}
-
-	if (((head + 1) & (QUEUESIZE-1)) == tail)
-		CONS_Printf(M_GetText("[Message unsent]\n")); // message not sent
-	else
-	{
-		if (c == KEY_BACKSPACE)
-		{
-			if (tail != head)
-				head = (head - 1) & (QUEUESIZE-1);
-		}
-		else
-		{
-			chatchars[head] = c;
-			head = (head + 1) & (QUEUESIZE-1);
-		}
-	}
 }
+#endif
 
 void HU_clearChatChars(void)
 {
-	while (tail != head)
-		HU_queueChatChar(KEY_BACKSPACE);
+	size_t i = 0;
+	for (;i<HU_MAXMSGLEN;i++)
+		w_chat[i] = 0; // reset this.
 	chat_on = false;
+	c_input = 0;
 }
 
+#ifndef NONET
+static boolean justscrolleddown;
+static boolean justscrolledup;
+static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop.
+// It's up here since it has to be reset when we open the chat.
+#endif
+
 //
 // Returns true if key eaten
 //
 boolean HU_Responder(event_t *ev)
 {
-	UINT8 c;
+#ifndef NONET
+	INT32 c=0;
+#endif
 
 	if (ev->type != ev_keydown)
 		return false;
 
 	// only KeyDown events now...
 
+	/*// Shoot, to prevent P1 chatting from ruining the game for everyone else, it's either:
+	// A. completely disallow opening chat entirely in online splitscreen
+	// or B. iterate through all controls to make sure it's bound to player 1 before eating
+	// You can see which one I chose.
+	// (Unless if you're sharing a keyboard, since you probably establish when you start chatting that you have dibs on it...)
+	// (Ahhh, the good ol days when I was a kid who couldn't afford an extra USB controller...)
+
+	if (ev->data1 >= KEY_MOUSE1)
+	{
+		INT32 i;
+		for (i = 0; i < num_gamecontrols; i++)
+		{
+			if (gamecontrol[i][0] == ev->data1 || gamecontrol[i][1] == ev->data1)
+				break;
+		}
+
+		if (i == num_gamecontrols)
+			return false;
+	}*/	//We don't actually care about that unless we get splitscreen netgames. :V
+
+#ifndef NONET
+	c = (INT32)ev->data1;
+
 	if (!chat_on)
 	{
 		// enter chat mode
 		if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
-			&& netgame && (!cv_mute.value || server || (adminplayer == consoleplayer)))
+			&& netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
 		{
-			if (cv_mute.value && !(server || adminplayer == consoleplayer))
-				return false;
 			chat_on = true;
 			w_chat[0] = 0;
 			teamtalk = false;
+			chat_scrollmedown = true;
+			typelines = 1;
 			return true;
 		}
 		if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
-			&& netgame && (!cv_mute.value || server || (adminplayer == consoleplayer)))
+			&& netgame && !OLD_MUTE)
 		{
-			if (cv_mute.value && !(server || adminplayer == consoleplayer))
-				return false;
 			chat_on = true;
 			w_chat[0] = 0;
-			teamtalk = true;
+			teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
+			chat_scrollmedown = true;
+			typelines = 1;
 			return true;
 		}
 	}
 	else // if chat_on
 	{
+
 		// Ignore modifier keys
 		// Note that we do this here so users can still set
 		// their chat keys to one of these, if they so desire.
@@ -787,34 +1224,622 @@ boolean HU_Responder(event_t *ev)
 		 || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT)
 			return true;
 
-		c = (UINT8)ev->data1;
+		c = (INT32)ev->data1;
 
-		// use console translations
-		if (shiftdown)
-			c = shiftxform[c];
+		// I know this looks very messy but this works. If it ain't broke, don't fix it!
+		// shift LETTERS to uppercase if we have capslock or are holding shift
+		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+		{
+			if (shiftdown ^ capslock)
+				c = shiftxform[c];
+		}
+		else	// if we're holding shift we should still shift non letter symbols
+		{
+			if (shiftdown)
+				c = shiftxform[c];
+		}
+
+		// pasting. pasting is cool. chat is a bit limited, though :(
+		if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
+		{
+			const char *paste = I_ClipboardPaste();
+			size_t chatlen;
+			size_t pastelen;
 
-		if (HU_keyInChatString(w_chat,c))
+			// create a dummy string real quickly
+
+			if (paste == NULL)
+				return true;
+
+			chatlen = strlen(w_chat);
+			pastelen = strlen(paste);
+			if (chatlen+pastelen > HU_MAXMSGLEN)
+				return true; // we can't paste this!!
+
+			if (c_input >= strlen(w_chat)) // add it at the end of the string.
+			{
+				memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that.
+				c_input += pastelen;
+				/*size_t i = 0;
+				for (;i<pastelen;i++)
+				{
+					HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.)
+				}*/
+				return true;
+			}
+			else	// otherwise, we need to shift everything and make space, etc etc
+			{
+				size_t i = HU_MAXMSGLEN-1;
+				while (i >= c_input)
+				{
+					if (w_chat[i])
+						w_chat[i+pastelen] = w_chat[i];
+					if (i == 0) // prevent overflow
+						break;
+					i--;
+				}
+				memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
+				c_input += pastelen;
+				return true;
+			}
+		}
+
+		if (!CHAT_MUTE && HU_keyInChatString(w_chat,c))
+		{
 			HU_queueChatChar(c);
+		}
 		if (c == KEY_ENTER)
+		{
 			chat_on = false;
-		else if (c == KEY_ESCAPE)
+			c_input = 0; // reset input cursor
+			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
+		}
+		else if (c == KEY_ESCAPE
+			|| ((c == gamecontrol[gc_talkkey][0] || c == gamecontrol[gc_talkkey][1]
+			|| c == gamecontrol[gc_teamkey][0] || c == gamecontrol[gc_teamkey][1])
+			&& c >= KEY_MOUSE1)) // If it's not a keyboard key, then the chat button is used as a toggle.
+		{
 			chat_on = false;
-
+			c_input = 0; // reset input cursor
+		}
+		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS!
+		{
+			chat_scroll--;
+			justscrolledup = true;
+			chat_scrolltime = 4;
+		}
+		else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0 && !OLDCHAT)
+		{
+			chat_scroll++;
+			justscrolleddown = true;
+			chat_scrolltime = 4;
+		}
+		else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back
+			c_input--;
+		else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat) && !OLDCHAT) // don't need to check for admin or w/e here since the chat won't ever contain anything if it's muted.
+			c_input++;
 		return true;
 	}
+#endif
+
 	return false;
 }
 
+
 //======================================================================
 //                         HEADS UP DRAWING
 //======================================================================
 
+#ifndef NONET
+
+// Precompile a wordwrapped string to any given width.
+// This is a muuuch better method than V_WORDWRAP.
+// again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day.
+// this one is simplified for the chat drawer.
+static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
+{
+	INT32 c;
+	size_t chw, i, lastusablespace = 0;
+	size_t slen;
+	char *newstring = Z_StrDup(string);
+	INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4;
+
+	slen = strlen(string);
+	x = 0;
+
+	for (i = 0; i < slen; ++i)
+	{
+		c = newstring[i];
+		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
+			continue;
+
+		if (c == '\n')
+		{
+			x = 0;
+			lastusablespace = 0;
+			continue;
+		}
+
+		if (!(option & V_ALLOWLOWERCASE))
+			c = toupper(c);
+		c -= HU_FONTSTART;
+
+		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
+		{
+			chw = spacewidth;
+			lastusablespace = i;
+		}
+		else
+			chw = charwidth;
+
+		x += chw;
+
+		if (lastusablespace != 0 && x > w)
+		{
+			//CONS_Printf("Wrap at index %d\n", i);
+			newstring[lastusablespace] = '\n';
+			i = lastusablespace+1;
+			lastusablespace = 0;
+			x = 0;
+		}
+	}
+	return newstring;
+}
+
+
+// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.
+
+INT16 chatx = 13, chaty = 169; // let's use this as our coordinates
+
+// chat stuff by VincyTM LOL XD!
+
+// HU_DrawMiniChat
+
+static void HU_drawMiniChat(void)
+{
+	INT32 x = chatx+2;
+	INT32 charwidth = 4, charheight = 6;
+	INT32 boxw = cv_chatwidth.value;
+	INT32 dx = 0, dy = 0;
+	size_t i = chat_nummsg_min;
+	boolean prev_linereturn = false; // a hack to prevent double \n while I have no idea why they happen in the first place.
+
+	INT32 msglines = 0;
+	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
+	INT32 y;
+
+	if (!chat_nummsg_min)
+		return; // needless to say it's useless to do anything if we don't have anything to draw.
+
+	/*if (splitscreen > 1)
+		boxw = max(64, boxw/2);*/
+
+	for (; i>0; i--)
+	{
+		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
+		size_t j = 0;
+		INT32 linescount = 0;
+
+		while(msg[j]) // iterate through msg
+		{
+			if (msg[j] < HU_FONTSTART) // don't draw
+			{
+				if (msg[j] == '\n') // get back down.
+				{
+					++j;
+					if (!prev_linereturn)
+					{
+						linescount += 1;
+						dx = 0;
+					}
+					prev_linereturn = true;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					++j;
+					continue;
+				}
+
+				++j;
+			}
+			else
+			{
+				j++;
+			}
+			prev_linereturn = false;
+			dx += charwidth;
+			if (dx >= boxw)
+			{
+				dx = 0;
+				linescount += 1;
+			}
+		}
+		dy = 0;
+		dx = 0;
+		msglines += linescount+1;
+	}
+
+	y = chaty - charheight*(msglines+1);
+
+	/*if (splitscreen)
+	{
+		y -= BASEVIDHEIGHT/2;
+		if (splitscreen > 1)
+			y += 16;
+	}*/
+
+	dx = 0;
+	dy = 0;
+	i = 0;
+	prev_linereturn = false;
+
+	for (; i<=(chat_nummsg_min-1); i++) // iterate through our hot messages
+	{
+		INT32 clrflag = 0;
+		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9; // see below...
+		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0; // you can make bad jokes out of this one.
+		size_t j = 0;
+		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it.
+		UINT8 *colormap = NULL;
+
+		while(msg[j]) // iterate through msg
+		{
+			if (msg[j] < HU_FONTSTART) // don't draw
+			{
+				if (msg[j] == '\n') // get back down.
+				{
+					++j;
+					if (!prev_linereturn)
+					{
+						dy += charheight;
+						dx = 0;
+					}
+					prev_linereturn = true;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
+					colormap = V_GetStringColormap(clrflag);
+					++j;
+					continue;
+				}
+
+				++j;
+			}
+			else
+			{
+				if (cv_chatbacktint.value) // on request of wolfy
+					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+
+				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
+			}
+
+			dx += charwidth;
+			prev_linereturn = false;
+			if (dx >= boxw)
+			{
+				dx = 0;
+				dy += charheight;
+			}
+		}
+		dy += charheight;
+		dx = 0;
+	}
+
+	// decrement addy and make that shit smooth:
+	addy /= 2;
+
+}
+
+// HU_DrawChatLog
+
+static void HU_drawChatLog(INT32 offset)
+{
+	INT32 charwidth = 4, charheight = 6;
+	INT32 boxw = cv_chatwidth.value, boxh = cv_chatheight.value;
+	INT32 x = chatx+2, y, dx = 0, dy = 0;
+	UINT32 i = 0;
+	INT32 chat_topy, chat_bottomy;
+	boolean atbottom = false;
+
+	// make sure that our scroll position isn't "illegal";
+	if (chat_scroll > chat_maxscroll)
+		chat_scroll = chat_maxscroll;
+
+#ifdef NETSPLITSCREEN
+	if (splitscreen)
+	{
+		boxh = max(6, boxh/2);
+		if (splitscreen > 1)
+			boxw = max(64, boxw/2);
+	}
+#endif
+
+	y = chaty - offset*charheight - (chat_scroll*charheight) - boxh*charheight - 12;
+
+#ifdef NETSPLITSCREEN
+	if (splitscreen)
+	{
+		y -= BASEVIDHEIGHT/2;
+		//if (splitscreen > 1)
+			//y += 16;
+	}
+#endif
+
+	chat_topy = y + chat_scroll*charheight;
+	chat_bottomy = chat_topy + boxh*charheight;
+
+	V_DrawFillConsoleMap(chatx, chat_topy, boxw, boxh*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); // log box
+
+	for (i=0; i<chat_nummsg_log; i++) // iterate through our chatlog
+	{
+		INT32 clrflag = 0;
+		INT32 j = 0;
+		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it.
+		UINT8 *colormap = NULL;
+		while(msg[j]) // iterate through msg
+		{
+			if (msg[j] < HU_FONTSTART) // don't draw
+			{
+				if (msg[j] == '\n') // get back down.
+				{
+					++j;
+					dy += charheight;
+					dx = 0;
+					continue;
+				}
+				else if (msg[j] & 0x80) // stolen from video.c, nice.
+				{
+					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
+					colormap = V_GetStringColormap(clrflag);
+					++j;
+					continue;
+				}
+
+				++j;
+			}
+			else
+			{
+				if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
+					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap);
+				else
+					j++; // don't forget to increment this or we'll get stuck in the limbo.
+			}
+
+			dx += charwidth;
+			if (dx >= boxw-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!!
+			{
+				dx = 0;
+				dy += charheight;
+			}
+		}
+		dy += charheight;
+		dx = 0;
+	}
+
+
+	if (((chat_scroll >= chat_maxscroll) || (chat_scrollmedown)) && !(justscrolleddown || justscrolledup || chat_scrolltime)) // was already at the bottom of the page before new maxscroll calculation and was NOT scrolling.
+	{
+		atbottom = true; // we should scroll
+	}
+	chat_scrollmedown = false;
+
+	// getmaxscroll through a lazy hack. We do all these loops, so let's not do more loops that are gonna lag the game more. :P
+	chat_maxscroll = (dy/charheight); // welcome to C, we don't know what min() and max() are.
+	if (chat_maxscroll <= (UINT32)cv_chatheight.value)
+		chat_maxscroll = 0;
+	else
+		chat_maxscroll -= cv_chatheight.value;
+
+	// if we're not bound by the time, autoscroll for next frame:
+	if (atbottom)
+		chat_scroll = chat_maxscroll;
+
+	// draw arrows to indicate that we can (or not) scroll.
+	// account for Y = -1 offset in tinyfont
+	if (chat_scroll > 0)
+		V_DrawThinString(chatx-8, ((justscrolledup) ? (chat_topy-1) : (chat_topy)) - 1, V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1A"); // up arrow
+	if (chat_scroll < chat_maxscroll)
+		V_DrawThinString(chatx-8, chat_bottomy-((justscrolleddown) ? 5 : 6) - 1, V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1B"); // down arrow
+
+	justscrolleddown = false;
+	justscrolledup = false;
+}
+
 //
 // HU_DrawChat
 //
 // Draw chat input
 //
+
 static void HU_DrawChat(void)
+{
+	INT32 charwidth = 4, charheight = 6;
+	INT32 boxw = cv_chatwidth.value;
+	INT32 t = 0, c = 0, y = chaty - (typelines*charheight);
+	UINT32 i = 0, saylen = strlen(w_chat); // You learn new things everyday!
+	INT32 cflag = 0;
+	const char *ntalk = "Say: ", *ttalk = "Team: ";
+	const char *talk = ntalk;
+	const char *mute = "Chat has been muted.";
+
+#ifdef NETSPLITSCREEN
+	if (splitscreen)
+	{
+		y -= BASEVIDHEIGHT/2;
+		if (splitscreen > 1)
+		{
+			y += 16;
+			boxw = max(64, boxw/2);
+		}
+	}
+#endif
+
+	if (teamtalk)
+	{
+		talk = ttalk;
+#if 0
+		if (players[consoleplayer].ctfteam == 1)
+			t = 0x500;  // Red
+		else if (players[consoleplayer].ctfteam == 2)
+			t = 0x400; // Blue
+#endif
+	}
+
+	if (CHAT_MUTE)
+	{
+		talk = mute;
+		typelines = 1;
+		cflag = V_GRAYMAP; // set text in gray if chat is muted.
+	}
+
+	V_DrawFillConsoleMap(chatx, y-1, boxw, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);
+
+	while (talk[i])
+	{
+		if (talk[i] < HU_FONTSTART)
+			++i;
+		else
+		{
+			V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, !cv_allcaps.value, V_GetStringColormap(talk[i]|cflag));
+			i++;
+		}
+
+		c += charwidth;
+	}
+
+	// if chat is muted, just draw the log and get it over with, no need to draw anything else.
+	if (CHAT_MUTE)
+	{
+		HU_drawChatLog(0);
+		return;
+	}
+
+	i = 0;
+	typelines = 1;
+
+	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
+		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+
+	while (w_chat[i])
+	{
+		boolean skippedline = false;
+		if (c_input == (i+1))
+		{
+			INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down.
+			INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
+			if (hu_tick < 4)
+				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+
+			if (cursorx == chatx+1 && saylen == i) // a weirdo hack
+			{
+				typelines += 1;
+				skippedline = true;
+			}
+		}
+
+		//Hurdler: isn't it better like that?
+		if (w_chat[i] < HU_FONTSTART)
+			++i;
+		else
+			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL);
+
+		c += charwidth;
+		if (c > boxw-(charwidth*2) && !skippedline)
+		{
+			c = 0;
+			y += charheight;
+			typelines += 1;
+		}
+	}
+
+	// handle /pm list. It's messy, horrible and I don't care.
+	if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai
+	{
+		INT32 count = 0;
+		INT32 p_dispy = chaty - charheight -1;
+#ifdef NETSPLITSCREEN
+		if (splitscreen)
+		{
+			p_dispy -= BASEVIDHEIGHT/2;
+			if (splitscreen > 1)
+				p_dispy += 16;
+		}
+#endif
+
+		i = 0;
+		for(i=0; (i<MAXPLAYERS); i++)
+		{
+
+			// filter: (code needs optimization pls help I'm bad with C)
+			if (w_chat[3])
+			{
+				char *nodenum;
+				UINT32 n;
+				// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
+				if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' '))
+					break;
+
+
+				nodenum = (char*) malloc(3);
+				strncpy(nodenum, w_chat+3, 3);
+				n = atoi((const char*) nodenum); // turn that into a number
+				free(nodenum);
+				// special cases:
+
+				if ((n == 0) && !(w_chat[4] == '0'))
+				{
+					if (!(i<10))
+						continue;
+				}
+				else if ((n == 1) && !(w_chat[3] == '0'))
+				{
+					if (!((i == 1) || ((i >= 10) && (i <= 19))))
+						continue;
+				}
+				else if ((n == 2) && !(w_chat[3] == '0'))
+				{
+					if (!((i == 2) || ((i >= 20) && (i <= 29))))
+						continue;
+				}
+				else if ((n == 3) && !(w_chat[3] == '0'))
+				{
+					if (!((i == 3) || ((i >= 30) && (i <= 31))))
+						continue;
+				}
+				else	// general case.
+				{
+					if (i != n)
+						continue;
+				}
+			}
+
+			if (playeringame[i])
+			{
+				char name[MAXPLAYERNAME+1];
+				strlcpy(name, player_names[i], 7); // shorten name to 7 characters.
+				V_DrawFillConsoleMap(chatx+ boxw + 2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud.
+				V_DrawSmallString(chatx+ boxw + 4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
+				count++;
+			}
+		}
+		if (count == 0) // no results.
+		{
+			V_DrawFillConsoleMap(chatx+boxw+2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud.
+			V_DrawSmallString(chatx+boxw+4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT.");
+		}
+	}
+
+	HU_drawChatLog(typelines-1); // typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space.
+
+}
+
+
+// For anyone who, for some godforsaken reason, likes oldchat.
+
+static void HU_DrawChat_Old(void)
 {
 	INT32 t = 0, c = 0, y = HU_INPUTY;
 	size_t i = 0;
@@ -822,7 +1847,6 @@ static void HU_DrawChat(void)
 	const char *talk = ntalk;
 	INT32 charwidth = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
 	INT32 charheight = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
-
 	if (teamtalk)
 	{
 		talk = ttalk;
@@ -849,9 +1873,20 @@ static void HU_DrawChat(void)
 		c += charwidth;
 	}
 
+	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
+		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+
 	i = 0;
 	while (w_chat[i])
 	{
+
+		if (c_input == (i+1) && hu_tick < 4)
+		{
+			INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down.
+			INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
+			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+		}
+
 		//Hurdler: isn't it better like that?
 		if (w_chat[i] < HU_FONTSTART)
 		{
@@ -875,7 +1910,7 @@ static void HU_DrawChat(void)
 	if (hu_tick < 4)
 		V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true);
 }
-
+#endif
 
 // draw the Crosshair, at the exact center of the view.
 //
@@ -939,7 +1974,7 @@ static void HU_DrawCEcho(void)
 	INT32 y = (BASEVIDHEIGHT/2)-4;
 	INT32 pnumlines = 0;
 
-	UINT32 realflags = cechoflags;
+	UINT32 realflags = cechoflags|V_PERPLAYER; // requested as part of splitscreen's stuff
 	INT32 realalpha = (INT32)((cechoflags & V_ALPHAMASK) >> V_ALPHASHIFT);
 
 	char *line;
@@ -982,6 +2017,12 @@ static void HU_DrawCEcho(void)
 		*line = '\0';
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
+		if (splitscreen)
+		{
+			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+			V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
+			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+		}
 		y += ((realflags & V_RETURN8) ? 8 : 12);
 
 		echoptr = line;
@@ -1015,36 +2056,78 @@ UINT16 hu_demorings;
 
 static void HU_DrawDemoInfo(void)
 {
-	V_DrawString(4, 188-24, V_YELLOWMAP, va(M_GetText("%s's replay"), player_names[0]));
+	INT32 h = 188;
+	if (modeattacking == ATTACKING_NIGHTS)
+		h -= 12;
+
+	V_DrawString(4, h-24, V_YELLOWMAP|V_ALLOWLOWERCASE, va(M_GetText("%s's replay"), player_names[0]));
 	if (modeattacking)
 	{
-		V_DrawString(4, 188-16, V_YELLOWMAP|V_MONOSPACE, "SCORE:");
-		V_DrawRightAlignedString(120, 188-16, V_MONOSPACE, va("%d", hu_demoscore));
+		V_DrawString(4, h-16, V_YELLOWMAP|V_MONOSPACE, "SCORE:");
+		V_DrawRightAlignedString(120, h-16, V_MONOSPACE, va("%d", hu_demoscore));
 
-		V_DrawString(4, 188- 8, V_YELLOWMAP|V_MONOSPACE, "TIME:");
+		V_DrawString(4, h-8, V_YELLOWMAP|V_MONOSPACE, "TIME:");
 		if (hu_demotime != UINT32_MAX)
-			V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, va("%i:%02i.%02i",
+			V_DrawRightAlignedString(120, h-8, V_MONOSPACE, va("%i:%02i.%02i",
 				G_TicsToMinutes(hu_demotime,true),
 				G_TicsToSeconds(hu_demotime),
 				G_TicsToCentiseconds(hu_demotime)));
 		else
-			V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, "--:--.--");
+			V_DrawRightAlignedString(120, h-8, V_MONOSPACE, "--:--.--");
+
+		if (modeattacking == ATTACKING_RECORD)
+		{
+			V_DrawString(4, h, V_YELLOWMAP|V_MONOSPACE, "RINGS:");
+			V_DrawRightAlignedString(120, h, V_MONOSPACE, va("%d", hu_demorings));
+		}
+	}
+}
+
+// Heads up displays drawer, call each frame
+//
+void HU_Drawer(void)
+{
+#ifndef NONET
+	// draw chat string plus cursor
+	if (chat_on)
+	{
+		// count down the scroll timer.
+		if (chat_scrolltime > 0)
+			chat_scrolltime--;
+		if (!OLDCHAT)
+			HU_DrawChat();
+		else
+			HU_DrawChat_Old();
+	}
+	else
+	{
+		typelines = 1;
+		chat_scrolltime = 0;
+		if (!OLDCHAT && cv_consolechat.value < 2 && netgame) // Don't display minimized chat if you set the mode to Window (Hidden)
+			HU_drawMiniChat(); // draw messages in a cool fashion.
+	}
+
+	if (netgame) // would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh)
+	{
+		size_t i = 0;
+
+		// handle spam while we're at it:
+		for(; (i<MAXPLAYERS); i++)
+		{
+			if (stop_spamming[i] > 0)
+				stop_spamming[i]--;
+		}
 
-		if (modeattacking == ATTACKING_RECORD)
+		// handle chat timers
+		for (i=0; (i<chat_nummsg_min); i++)
 		{
-			V_DrawString(4, 188   , V_YELLOWMAP|V_MONOSPACE, "RINGS:");
-			V_DrawRightAlignedString(120, 188   , V_MONOSPACE, va("%d", hu_demorings));
+			if (chat_timers[i] > 0)
+				chat_timers[i]--;
+			else
+				HU_removeChatText_Mini();
 		}
 	}
-}
-
-// Heads up displays drawer, call each frame
-//
-void HU_Drawer(void)
-{
-	// draw chat string plus cursor
-	if (chat_on)
-		HU_DrawChat();
+#endif
 
 	if (cechotimer)
 		HU_DrawCEcho();
@@ -1055,7 +2138,7 @@ void HU_Drawer(void)
 	if (!Playing()
 	 || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
 	 || gamestate == GS_CREDITS      || gamestate == GS_EVALUATION
-	 || gamestate == GS_GAMEEND)
+	 || gamestate == GS_ENDING       || gamestate == GS_GAMEEND)
 		return;
 
 	// draw multiplayer rankings
@@ -1102,6 +2185,31 @@ void HU_Drawer(void)
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
 	}
+
+	if (modeattacking && pausedelay > 0 && !pausebreakkey)
+	{
+		INT32 strength = ((pausedelay - 1 - NEWTICRATE/2)*10)/(NEWTICRATE/3);
+		INT32 y = hudinfo[HUD_LIVES].y - 13;
+
+		if (players[consoleplayer].powers[pw_carry] == CR_NIGHTSMODE)
+			y -= 16;
+		else
+		{
+			if (players[consoleplayer].pflags & PF_AUTOBRAKE)
+				y -= 8;
+			if (players[consoleplayer].pflags & PF_ANALOGMODE)
+				y -= 8;
+		}
+
+		V_DrawThinString(hudinfo[HUD_LIVES].x-2, y,
+			hudinfo[HUD_LIVES].f|((leveltime & 4) ? V_SKYMAP : V_BLUEMAP),
+			"HOLD TO RETRY...");
+
+		if (strength > 9)
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
+		else if (strength > 0)
+			V_DrawFadeScreen(0, strength);
+	}
 }
 
 //======================================================================
@@ -1140,9 +2248,9 @@ void HU_Erase(void)
 	// clear the message lines that go away, so use _oldclearlines_
 	bottomline = oldclearlines;
 	oldclearlines = con_clearlines;
-	if (chat_on)
+	if (chat_on && OLDCHAT)
 		if (bottomline < 8)
-			bottomline = 8;
+			bottomline = 8; // only do it for consolechat. consolechat is gay.
 
 	if (automapactive || viewwindowx == 0) // hud msgs don't need to be cleared
 		return;
@@ -1179,7 +2287,42 @@ void HU_Erase(void)
 //======================================================================
 
 #define supercheckdef ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS1] || players[tab[i].num].mo->state >= &states[S_PLAY_SUPER_TRANS6])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin].flags & SF_SUPER))
-#define greycheckdef ((players[tab[i].num].mo && players[tab[i].num].mo->health <= 0) || players[tab[i].num].spectator)
+#define greycheckdef (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD || (G_IsSpecialStage(gamemap) && players[tab[i].num].exiting))
+
+//
+// HU_drawPing
+//
+void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext)
+{
+	UINT8 numbars = 1; // how many ping bars do we draw?
+	UINT8 barcolor = 35; // color we use for the bars (green, yellow or red)
+	SINT8 i = 0;
+	SINT8 yoffset = 6;
+	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping), V_ALLOWLOWERCASE)/2);
+
+	if (ping < 128)
+	{
+		numbars = 3;
+		barcolor = 112;
+	}
+	else if (ping < 256)
+	{
+		numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it.
+		barcolor = 73;
+	}
+
+	if (!notext || vid.width >= 640) // how sad, we're using a shit resolution.
+		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE, va("%dms", ping));
+
+	for (i=0; (i<3); i++) // Draw the ping bar
+	{
+		V_DrawFill(x+2 *(i-1), y+yoffset-4, 2, 8-yoffset, 31);
+		if (i < numbars)
+			V_DrawFill(x+2 *(i-1), y+yoffset-3, 1, 8-yoffset-1, barcolor);
+
+		yoffset -= 2;
+	}
+}
 
 //
 // HU_DrawTabRankings
@@ -1203,13 +2346,23 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		greycheck = greycheckdef;
 		supercheck = supercheckdef;
 
+		if (!splitscreen) // don't draw it on splitscreen,
+		{
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 253, y+2, playerpingtable[tab[i].num], false);
+			//else
+			//	V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
+		}
+
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
 		             | (greycheck ? V_60TRANS : 0)
 		             | V_ALLOWLOWERCASE, tab[i].name);
 
 		// Draw emeralds
-		if (!players[tab[i].num].powers[pw_super]
+		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
+			HU_DrawEmeralds(x-12,y+2,255);
+		else if (!players[tab[i].num].powers[pw_super]
 			|| ((leveltime/7) & 1))
 		{
 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
@@ -1250,7 +2403,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			}
 		}
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != 0x7f)) //show lives
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 		{
@@ -1282,6 +2435,126 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 	}
 }
 
+//
+// HU_Draw32Emeralds
+//
+static void HU_Draw32Emeralds(INT32 x, INT32 y, INT32 pemeralds)
+{
+	//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
+	if (pemeralds & EMERALD1)
+		V_DrawSmallScaledPatch(x  , y, 0, emeraldpics[1][0]);
+
+	if (pemeralds & EMERALD2)
+		V_DrawSmallScaledPatch(x+4, y, 0, emeraldpics[1][1]);
+
+	if (pemeralds & EMERALD3)
+		V_DrawSmallScaledPatch(x+8, y, 0, emeraldpics[1][2]);
+
+	if (pemeralds & EMERALD4)
+		V_DrawSmallScaledPatch(x+12  , y, 0, emeraldpics[1][3]);
+
+	if (pemeralds & EMERALD5)
+		V_DrawSmallScaledPatch(x+16, y, 0, emeraldpics[1][4]);
+
+	if (pemeralds & EMERALD6)
+		V_DrawSmallScaledPatch(x+20, y, 0, emeraldpics[1][5]);
+
+	if (pemeralds & EMERALD7)
+		V_DrawSmallScaledPatch(x+24,   y,   0, emeraldpics[1][6]);
+}
+
+//
+// HU_Draw32TeamTabRankings
+//
+static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
+{
+	INT32 i,x,y;
+	INT32 redplayers = 0, blueplayers = 0;
+	const UINT8 *colormap;
+	char name[MAXPLAYERNAME+1];
+	boolean greycheck, supercheck;
+
+	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams.
+	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
+	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (players[tab[i].num].spectator)
+			continue; //ignore them.
+
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
+		if (tab[i].color == skincolor_redteam) //red
+		{
+			redplayers++;
+			x = 14 + (BASEVIDWIDTH/2);
+			y = (redplayers * 9) + 20;
+		}
+		else if (tab[i].color == skincolor_blueteam) //blue
+		{
+			blueplayers++;
+			x = 14;
+			y = (blueplayers * 9) + 20;
+		}
+		else //er?  not on red or blue, so ignore them
+			continue;
+
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
+		strlcpy(name, tab[i].name, 8);
+		V_DrawString(x + 10, y,
+		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+		             | (greycheck ? 0 : V_TRANSLUCENT)
+		             | V_ALLOWLOWERCASE, name);
+
+		if (gametype == GT_CTF)
+		{
+			if (players[tab[i].num].gotflag & GF_REDFLAG) // Red
+				V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, rflagico, 0);
+			else if (players[tab[i].num].gotflag & GF_BLUEFLAG) // Blue
+				V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, bflagico, 0);
+		}
+
+		// Draw emeralds
+		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
+		{
+			HU_Draw32Emeralds(x+60, y+2, 255);
+			//HU_DrawEmeralds(x-12,y+2,255);
+		}
+		else if (!players[tab[i].num].powers[pw_super]
+			|| ((leveltime/7) & 1))
+		{
+			HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds);
+			//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
+		}
+
+		if (supercheck)
+		{
+			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+			V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap);
+		}
+		else
+		{
+			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+			if (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD)
+				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap);
+			else
+				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap);
+		}
+		V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+		if (!splitscreen)
+		{
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 135, y+3, playerpingtable[tab[i].num], true);
+		//else
+			//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+		}
+	}
+}
+
 //
 // HU_DrawTeamTabRankings
 //
@@ -1289,14 +2562,51 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 {
 	INT32 i,x,y;
 	INT32 redplayers = 0, blueplayers = 0;
+	boolean smol = false;
 	const UINT8 *colormap;
 	char name[MAXPLAYERNAME+1];
 	boolean greycheck, supercheck;
 
+	// before we draw, we must count how many players are in each team. It makes an additional loop, but we need to know if we have to draw a big or a small ranking.
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (players[tab[i].num].spectator)
+			continue; //ignore them.
+
+		if (tab[i].color == skincolor_redteam) //red
+		{
+			if (redplayers++ > 8)
+			{
+				smol = true;
+				break; // don't make more loops than we need to.
+			}
+		}
+		else if (tab[i].color == skincolor_blueteam) //blue
+		{
+			if (blueplayers++ > 8)
+			{
+				smol = true;
+				break;
+			}
+		}
+		else //er?  not on red or blue, so ignore them
+			continue;
+
+	}
+
+	// I'll be blunt with you, this may add more lines, but I'm not adding weird cases for this, so we're executing a separate function.
+	if (smol == true || cv_compactscoreboard.value)
+	{
+		HU_Draw32TeamTabRankings(tab, whiteplayer);
+		return;
+	}
+
 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams.
 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
 	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
 
+	i=0, redplayers=0, blueplayers=0;
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (players[tab[i].num].spectator)
@@ -1322,7 +2632,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		greycheck = greycheckdef;
 		supercheck = supercheckdef;
 
-		strlcpy(name, tab[i].name, 9);
+		strlcpy(name, tab[i].name, 7);
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
 		             | (greycheck ? V_TRANSLUCENT : 0)
@@ -1337,7 +2647,9 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		}
 
 		// Draw emeralds
-		if (!players[tab[i].num].powers[pw_super]
+		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
+			HU_DrawEmeralds(x-12,y+2,255);
+		else if (!players[tab[i].num].powers[pw_super]
 			|| ((leveltime/7) & 1))
 		{
 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
@@ -1356,7 +2668,14 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 			else
 				V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
 		}
-		V_DrawRightAlignedThinString(x+120, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
+		V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
+		if (!splitscreen)
+		{
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
+		//else
+		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
+		}
 	}
 }
 
@@ -1382,13 +2701,18 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		greycheck = greycheckdef;
 		supercheck = supercheckdef;
 
-		strlcpy(name, tab[i].name, 9);
+		strlcpy(name, tab[i].name, 7);
+		if (!(tab[i].num == serverplayer))
+			HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false);
+		//else
+		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
+
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
 		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != 0x7f)) //show lives
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
@@ -1397,7 +2721,9 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
 
 		// Draw emeralds
-		if (!players[tab[i].num].powers[pw_super]
+		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
+			HU_DrawEmeralds(x-12,y+2,255);
+		else if (!players[tab[i].num].powers[pw_super]
 			|| ((leveltime/7) & 1))
 		{
 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
@@ -1440,15 +2766,15 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			if (circuitmap)
 			{
 				if (players[tab[i].num].exiting)
-					V_DrawRightAlignedThinString(x+156, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
+					V_DrawRightAlignedThinString(x+146, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
 				else
-					V_DrawRightAlignedThinString(x+156, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
+					V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 			}
 			else
-				V_DrawRightAlignedThinString(x+156, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+				V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
 		}
 		else
-			V_DrawRightAlignedThinString(x+120, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
+			V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 
 		y += 16;
 		if (y > 160)
@@ -1459,6 +2785,116 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 	}
 }
 
+//
+// HU_Draw32TabRankings
+//
+static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer)
+{
+	INT32 i;
+	const UINT8 *colormap;
+	char name[MAXPLAYERNAME+1];
+	boolean greycheck, supercheck;
+
+	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two sides.
+	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
+	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
+
+	for (i = 0; i < scorelines; i++)
+	{
+		if (players[tab[i].num].spectator && gametype != GT_COOP)
+			continue; //ignore them.
+
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
+		strlcpy(name, tab[i].name, 7);
+		if (!splitscreen) // don't draw it on splitscreen,
+		{
+			if (!(tab[i].num == serverplayer))
+				HU_drawPing(x+ 135, y+3, playerpingtable[tab[i].num], true);
+		//else
+		//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+		}
+
+		V_DrawString(x + 10, y,
+		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+		             | (greycheck ? 0 : V_TRANSLUCENT)
+		             | V_ALLOWLOWERCASE, name);
+
+		if (G_GametypeUsesLives()) //show lives
+			V_DrawRightAlignedThinString(x-1, y, V_ALLOWLOWERCASE, va("%d", players[tab[i].num].lives));
+		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
+			V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, tagico, 0);
+
+		// Draw emeralds
+		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
+		{
+			HU_Draw32Emeralds(x+60, y+2, 255);
+			//HU_DrawEmeralds(x-12,y+2,255);
+		}
+		else if (!players[tab[i].num].powers[pw_super]
+			|| ((leveltime/7) & 1))
+		{
+			HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds);
+			//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
+		}
+
+		//V_DrawSmallScaledPatch (x, y-4, 0, livesback);
+		if (tab[i].color == 0)
+		{
+			colormap = colormaps;
+			if (players[tab[i].num].powers[pw_super])
+				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], 0);
+			else
+			{
+				if (greycheck)
+					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], 0);
+				else
+					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], 0);
+			}
+		}
+		else
+		{
+			if (supercheck)
+			{
+				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap);
+			}
+			else
+			{
+				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+				if (greycheck)
+					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap);
+				else
+					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap);
+			}
+		}
+
+		// All data drawn with thin string for space.
+		if (gametype == GT_RACE)
+		{
+			if (circuitmap)
+			{
+				if (players[tab[i].num].exiting)
+					V_DrawRightAlignedThinString(x+128, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
+				else
+					V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+			}
+			else
+				V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+		}
+		else
+			V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+
+		y += 9;
+		if (i == 16)
+		{
+			y = 32;
+			x += BASEVIDWIDTH/2;
+		}
+	}
+}
+
 //
 // HU_DrawEmeralds
 //
@@ -1466,25 +2902,25 @@ void HU_DrawEmeralds(INT32 x, INT32 y, INT32 pemeralds)
 {
 	//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
 	if (pemeralds & EMERALD1)
-		V_DrawSmallScaledPatch(x  , y-6, 0, tinyemeraldpics[0]);
+		V_DrawSmallScaledPatch(x  , y-6, 0, emeraldpics[1][0]);
 
 	if (pemeralds & EMERALD2)
-		V_DrawSmallScaledPatch(x+4, y-3, 0, tinyemeraldpics[1]);
+		V_DrawSmallScaledPatch(x+4, y-3, 0, emeraldpics[1][1]);
 
 	if (pemeralds & EMERALD3)
-		V_DrawSmallScaledPatch(x+4, y+3, 0, tinyemeraldpics[2]);
+		V_DrawSmallScaledPatch(x+4, y+3, 0, emeraldpics[1][2]);
 
 	if (pemeralds & EMERALD4)
-		V_DrawSmallScaledPatch(x  , y+6, 0, tinyemeraldpics[3]);
+		V_DrawSmallScaledPatch(x  , y+6, 0, emeraldpics[1][3]);
 
 	if (pemeralds & EMERALD5)
-		V_DrawSmallScaledPatch(x-4, y+3, 0, tinyemeraldpics[4]);
+		V_DrawSmallScaledPatch(x-4, y+3, 0, emeraldpics[1][4]);
 
 	if (pemeralds & EMERALD6)
-		V_DrawSmallScaledPatch(x-4, y-3, 0, tinyemeraldpics[5]);
+		V_DrawSmallScaledPatch(x-4, y-3, 0, emeraldpics[1][5]);
 
 	if (pemeralds & EMERALD7)
-		V_DrawSmallScaledPatch(x,   y,   0, tinyemeraldpics[6]);
+		V_DrawSmallScaledPatch(x,   y,   0, emeraldpics[1][6]);
 }
 
 //
@@ -1539,7 +2975,7 @@ static inline void HU_DrawSpectatorTicker(void)
 					templength = length;
 				}
 
-				V_DrawString(templength, height + 8, V_TRANSLUCENT, current);
+				V_DrawString(templength, height + 8, V_TRANSLUCENT|V_ALLOWLOWERCASE, current);
 			}
 
 			length += (signed)strlen(player_names[i]) * 8 + 16;
@@ -1551,7 +2987,6 @@ static inline void HU_DrawSpectatorTicker(void)
 //
 static void HU_DrawRankings(void)
 {
-	patch_t *p;
 	playersort_t tab[MAXPLAYERS];
 	INT32 i, j, scorelines;
 	boolean completed[MAXPLAYERS];
@@ -1560,43 +2995,12 @@ static void HU_DrawRankings(void)
 	// draw the current gametype in the lower right
 	HU_drawGametype();
 
-	if (G_GametypeHasTeams())
-	{
-		if (gametype == GT_CTF)
-			p = bflagico;
-		else
-			p = bmatcico;
-
-		V_DrawSmallScaledPatch(128 - SHORT(p->width)/4, 4, 0, p);
-		V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
-
-		if (gametype == GT_CTF)
-			p = rflagico;
-		else
-			p = rmatcico;
-
-		V_DrawSmallScaledPatch(192 - SHORT(p->width)/4, 4, 0, p);
-		V_DrawCenteredString(192, 16, 0, va("%u", redscore));
-	}
-
 	if (gametype != GT_RACE && gametype != GT_COMPETITION && gametype != GT_COOP)
 	{
 		if (cv_timelimit.value && timelimitintics > 0)
 		{
-			INT32 timeval = (timelimitintics+1-leveltime)/TICRATE;
-
-			if (leveltime <= timelimitintics)
-			{
-				V_DrawCenteredString(64, 8, 0, "TIME LEFT");
-				V_DrawCenteredString(64, 16, 0, va("%u", timeval));
-			}
-
-			// overtime
-			if ((leveltime > (timelimitintics + TICRATE/2)) && cv_overtime.value)
-			{
-				V_DrawCenteredString(64, 8, 0, "TIME LEFT");
-				V_DrawCenteredString(64, 16, 0, "OVERTIME");
-			}
+			V_DrawCenteredString(64, 8, 0, "TIME");
+			V_DrawCenteredString(64, 16, 0, va("%i:%02i", G_TicsToMinutes(stplyr->realtime, true), G_TicsToSeconds(stplyr->realtime)));
 		}
 
 		if (cv_pointlimit.value > 0)
@@ -1711,15 +3115,18 @@ static void HU_DrawRankings(void)
 		scorelines++;
 	}
 
-	if (scorelines > 20)
-		scorelines = 20; //dont draw past bottom of screen, show the best only
+	//if (scorelines > 20)
+	//	scorelines = 20; //dont draw past bottom of screen, show the best only
+	// shush, we'll do it anyway.
 
 	if (G_GametypeHasTeams())
-		HU_DrawTeamTabRankings(tab, whiteplayer); //separate function for Spazzo's silly request
-	else if (scorelines <= 9)
+		HU_DrawTeamTabRankings(tab, whiteplayer);
+	else if (scorelines <= 9 && !cv_compactscoreboard.value)
 		HU_DrawTabRankings(40, 32, tab, scorelines, whiteplayer);
-	else
+	else if (scorelines <= 20 && !cv_compactscoreboard.value)
 		HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);
+	else
+		HU_Draw32TabRankings(14, 28, tab, scorelines, whiteplayer);
 
 	// draw spectators in a ticker across the bottom
 	if (!splitscreen && G_GametypeHasSpectators())
@@ -1753,25 +3160,35 @@ static void HU_DrawCoopOverlay(void)
 #endif
 
 	if (emeralds & EMERALD1)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0][0]);
 	if (emeralds & EMERALD2)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[1]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][1]);
 	if (emeralds & EMERALD3)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[2]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][2]);
 	if (emeralds & EMERALD4)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[3]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[0][3]);
 	if (emeralds & EMERALD5)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[4]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][4]);
 	if (emeralds & EMERALD6)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[5]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][5]);
 	if (emeralds & EMERALD7)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[6]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[0][6]);
 }
 
 static void HU_DrawNetplayCoopOverlay(void)
 {
 	int i;
 
+	if (token
+#ifdef HAVE_BLUA
+	&& LUA_HudEnabled(hud_tokens)
+#endif
+	)
+	{
+		V_DrawString(168, 10, 0, va("- %d", token));
+		V_DrawSmallScaledPatch(148, 6, 0, tokenicon);
+	}
+
 #ifdef HAVE_BLUA
 	if (!LUA_HudEnabled(hud_coopemeralds))
 		return;
@@ -1780,7 +3197,7 @@ static void HU_DrawNetplayCoopOverlay(void)
 	for (i = 0; i < 7; ++i)
 	{
 		if (emeralds & (1 << i))
-			V_DrawScaledPatch(20 + (i * 20), 6, 0, emeraldpics[i]);
+			V_DrawScaledPatch(20 + (i * 10), 9, 0, emeraldpics[1][i]);
 	}
 }
 
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 2dbeb556d7056e65b62f38b02785e3ef38dee942..ab77e67b6f9a36cd3f390344a0337f9a5e666c4c 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -57,14 +57,28 @@ typedef struct
 //           chat stuff
 //------------------------------------
 #define HU_MAXMSGLEN 224
+#define CHAT_BUFSIZE 64		// that's enough messages, right? We'll delete the older ones when that gets out of hand.
+#ifdef NETSPLITSCREEN
+#define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640)
+#else
+#define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640 || splitscreen)
+#endif
+#define CHAT_MUTE (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))	// this still allows to open the chat but not to type. That's used for scrolling and whatnot.
+#define OLD_MUTE (OLDCHAT && cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))	// this is used to prevent oldchat from opening when muted.
+
+// some functions
+void HU_AddChatText(const char *text, boolean playsound);
+
+// set true when entering a chat message
+extern boolean chat_on;
 
 extern patch_t *hu_font[HU_FONTSIZE], *tny_font[HU_FONTSIZE];
 extern patch_t *tallnum[10];
 extern patch_t *nightsnum[10];
 extern patch_t *lt_font[LT_FONTSIZE];
 extern patch_t *cred_font[CRED_FONTSIZE];
-extern patch_t *emeraldpics[7];
-extern patch_t *tinyemeraldpics[7];
+extern patch_t *ttlnum[20];
+extern patch_t *emeraldpics[3][8];
 extern patch_t *rflagico;
 extern patch_t *bflagico;
 extern patch_t *rmatcico;
@@ -74,9 +88,6 @@ extern patch_t *tallminus;
 extern patch_t *tallinfin;
 extern patch_t *tokenicon;
 
-// set true when entering a chat message
-extern boolean chat_on;
-
 // set true whenever the tab rankings are being shown for any reason
 extern boolean hu_showscores;
 
@@ -86,15 +97,15 @@ void HU_Init(void);
 void HU_LoadGraphics(void);
 
 // reset heads up when consoleplayer respawns.
-FUNCMATH void HU_Start(void);
+void HU_Start(void);
 
 boolean HU_Responder(event_t *ev);
-
 void HU_Ticker(void);
 void HU_Drawer(void);
 char HU_dequeueChatChar(void);
 void HU_Erase(void);
 void HU_clearChatChars(void);
+void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext);	// Lat': Ping drawer for scoreboard.
 void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);
 void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer);
 void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);
diff --git a/src/i_addrinfo.c b/src/i_addrinfo.c
index 208f470f4c4e525b53986072010dc95d198109a4..94b9214610777079de3c753de193602872c38105 100644
--- a/src/i_addrinfo.c
+++ b/src/i_addrinfo.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2011-2016 by Sonic Team Junior.
+// Copyright (C) 2011-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -68,10 +68,10 @@ static int inet_aton(const char *cp, struct in_addr *addr)
 
 #ifdef USE_WINSOCK2
 static HMODULE ipv6dll = NULL;
-typedef int (WSAAPI *p_getaddrinfo) (const char *node, const char *service,
-                              const struct my_addrinfo *hints,
-                              struct my_addrinfo **res);
-typedef void (WSAAPI *p_freeaddrinfo) (struct my_addrinfo *res);
+typedef int (WSAAPI *p_getaddrinfo) (const char *, const char *,
+                                     const struct my_addrinfo *,
+                                     struct my_addrinfo **);
+typedef void (WSAAPI *p_freeaddrinfo) (struct my_addrinfo *);
 
 static p_getaddrinfo WS_getaddrinfo = NULL;
 static p_freeaddrinfo WS_freeaddrinfo = NULL;
@@ -80,10 +80,10 @@ static HMODULE WS_getfunctions(HMODULE tmp)
 {
 	if (tmp != NULL)
 	{
-		WS_getaddrinfo = (p_getaddrinfo)GetProcAddress(tmp, "getaddrinfo");
+		WS_getaddrinfo = (p_getaddrinfo)(LPVOID)GetProcAddress(tmp, "getaddrinfo");
 		if (WS_getaddrinfo == NULL)
 			return NULL;
-		WS_freeaddrinfo = (p_freeaddrinfo)GetProcAddress(tmp, "freeaddrinfo");
+		WS_freeaddrinfo = (p_freeaddrinfo)(LPVOID)GetProcAddress(tmp, "freeaddrinfo");
 		if (WS_freeaddrinfo == NULL)
 		{
 			WS_getaddrinfo = NULL;
diff --git a/src/i_addrinfo.h b/src/i_addrinfo.h
index 4cda8968b9bb4bb09f9b62ebd715abdb3f1a407b..7c3afc614f0c181d295ff0d26b96ed6be773a0c4 100644
--- a/src/i_addrinfo.h
+++ b/src/i_addrinfo.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2011-2016 by Sonic Team Junior.
+// Copyright (C) 2011-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_joy.h b/src/i_joy.h
index 5cba1af0abff4e8514d2797377d099a4857e1e6d..27013f011671382e34623809e70031600231ed36 100644
--- a/src/i_joy.h
+++ b/src/i_joy.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_net.h b/src/i_net.h
index 2bfa5eac7d14a1f567a99f406aaec5afdcee23d7..0e17077bbcea85fcd80e9c7fc830d981745ba218 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_sound.h b/src/i_sound.h
index 084479ee1860485b23537983653b067e7b2d0094..4b299985762a5f552a662e3202194e10d7f87adf 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -18,6 +18,23 @@
 #include "sounds.h"
 #include "command.h"
 
+// copied from SDL mixer, plus GME
+typedef enum {
+	MU_NONE,
+	MU_CMD,
+	MU_WAV,
+	MU_MOD,
+	MU_MID,
+	MU_OGG,
+	MU_MP3,
+	MU_MP3_MAD_UNUSED, // use MU_MP3 instead
+	MU_FLAC,
+	MU_MODPLUG_UNUSED, // use MU_MOD instead
+	MU_GME,
+	MU_MOD_EX, // libopenmpt
+	MU_MID_EX // Non-native MIDI
+} musictype_t;
+
 /**	\brief Sound subsystem runing and waiting
 */
 extern UINT8 sound_started;
@@ -51,9 +68,9 @@ void I_StartupSound(void);
 */
 void I_ShutdownSound(void);
 
-//
-//  SFX I/O
-//
+/// ------------------------
+///  SFX I/O
+/// ------------------------
 
 /**	\brief	Starts a sound in a particular sound channel.
 	\param	id	sfxid
@@ -64,7 +81,7 @@ void I_ShutdownSound(void);
 
 	\return	sfx handle
 */
-INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority);
+INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel);
 
 /**	\brief	Stops a sound channel.
 
@@ -105,9 +122,10 @@ void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch);
 */
 void I_SetSfxVolume(UINT8 volume);
 
-//
-//  MUSIC I/O
-//
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
+
 /** \brief Init the music systems
 */
 void I_InitMusic(void);
@@ -116,41 +134,35 @@ void I_InitMusic(void);
 */
 void I_ShutdownMusic(void);
 
-/**	\brief	PAUSE game handling.
-
-	\param	handle	song handle
-
-	\return	void
-*/
-void I_PauseSong(INT32 handle);
-
-/**	\brief	RESUME game handling
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
 
-	\param	handle	song handle
+musictype_t I_SongType(void);
+boolean I_SongPlaying(void);
+boolean I_SongPaused(void);
 
-	\return	void
-*/
-void I_ResumeSong(INT32 handle);
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
 
-//
-//  MIDI I/O
-//
+boolean I_SetSongSpeed(float speed);
 
-/**	\brief Startup the MIDI music system
-*/
-void I_InitMIDIMusic(void);
+/// ------------------------
+//  MUSIC SEEKING
+/// ------------------------
 
-/**	\brief Shutdown the MIDI music system
-*/
-void I_ShutdownMIDIMusic(void);
+UINT32 I_GetSongLength(void);
 
-/**	\brief	The I_SetMIDIMusicVolume function
+boolean I_SetSongLoopPoint(UINT32 looppoint);
+UINT32 I_GetSongLoopPoint(void);
 
-	\param	volume	volume to set at
+boolean I_SetSongPosition(UINT32 position);
+UINT32 I_GetSongPosition(void);
 
-	\return	void
-*/
-void I_SetMIDIMusicVolume(UINT8 volume);
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
 
 /**	\brief	Registers a song handle to song data.
 
@@ -161,7 +173,16 @@ void I_SetMIDIMusicVolume(UINT8 volume);
 
 	\todo Remove this
 */
-INT32 I_RegisterSong(void *data, size_t len);
+boolean I_LoadSong(char *data, size_t len);
+
+/**	\brief	See ::I_LoadSong, then think backwards
+
+	\param	handle	song handle
+
+	\sa I_LoadSong
+	\todo remove midi handle
+*/
+void I_UnloadSong(void);
 
 /**	\brief	Called by anything that wishes to start music
 
@@ -172,7 +193,7 @@ INT32 I_RegisterSong(void *data, size_t len);
 
 	\todo pass music name, not handle
 */
-boolean I_PlaySong(INT32 handle, boolean looping);
+boolean I_PlaySong(boolean looping);
 
 /**	\brief	Stops a song over 3 seconds
 
@@ -181,58 +202,48 @@ boolean I_PlaySong(INT32 handle, boolean looping);
 
 	/todo drop handle
 */
-void I_StopSong(INT32 handle);
+void I_StopSong(void);
 
-/**	\brief	See ::I_RegisterSong, then think backwards
+/**	\brief	PAUSE game handling.
 
 	\param	handle	song handle
 
-	\sa I_RegisterSong
-	\todo remove midi handle
-*/
-void I_UnRegisterSong(INT32 handle);
-
-//
-//  DIGMUSIC I/O
-//
-
-/**	\brief Startup the music system
-*/
-void I_InitDigMusic(void);
-
-/**	\brief Shutdown the music system
+	\return	void
 */
-void I_ShutdownDigMusic(void);
+void I_PauseSong(void);
 
-boolean I_SetSongSpeed(float speed);
-
-boolean I_SetSongTrack(INT32 track);
-
-/**	\brief The I_StartDigSong function
-
-	\param	musicname	music lump name
-	\param	looping	if true, loop the song
+/**	\brief	RESUME game handling
 
-	\return	if true, song playing
-*/
-boolean I_StartDigSong(const char *musicname, boolean looping);
+	\param	handle	song handle
 
-/**	\brief stop non-MIDI song
+	\return	void
 */
-void I_StopDigSong(void);
+void I_ResumeSong(void);
 
-/**	\brief The I_SetDigMusicVolume function
+/**	\brief	The I_SetMusicVolume function
 
 	\param	volume	volume to set at
 
 	\return	void
 */
-void I_SetDigMusicVolume(UINT8 volume);
+void I_SetMusicVolume(UINT8 volume);
 
-//
-// CD MUSIC I/O
-//
+boolean I_SetSongTrack(INT32 track);
+
+/// ------------------------
+/// MUSIC FADING
+/// ------------------------
+
+void I_SetInternalMusicVolume(UINT8 volume);
+void I_StopFadingSong(void);
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void));
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
+boolean I_FadeOutStopSong(UINT32 ms);
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping);
 
+/// ------------------------
+//  CD MUSIC I/O
+/// ------------------------
 
 /**	\brief  cd music interface
 */
diff --git a/src/i_system.h b/src/i_system.h
index d61f2d16e7730c8516b556d3d1f9705e5fb565fc..a8d707d162d53d590df28bd89887dba49ec76f98 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 9bb4ec630320fb7129bf39f9ad7dd71ce4fc9200..f2b4336dc313faba1f0c3bebe84e31e89fb923ab 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -225,6 +225,33 @@ static void wattcp_outch(char s)
 }
 #endif
 
+#ifdef USE_WINSOCK
+// stupid microsoft makes things complicated
+static char *get_WSAErrorStr(int e)
+{
+	static char buf[256]; // allow up to 255 bytes
+
+	buf[0] = '\0';
+
+	FormatMessageA(
+		FORMAT_MESSAGE_FROM_SYSTEM |
+		FORMAT_MESSAGE_IGNORE_INSERTS,
+		NULL,
+		(DWORD)e,
+		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+		(LPTSTR)buf,
+		sizeof (buf),
+		NULL);
+
+	if (!buf[0]) // provide a fallback error message if no message is available for some reason
+		sprintf(buf, "Unknown error");
+
+	return buf;
+}
+#undef strerror
+#define strerror get_WSAErrorStr
+#endif
+
 #ifdef USE_WINSOCK2
 #define inet_ntop inet_ntopA
 #define HAVE_NTOP
@@ -548,7 +575,7 @@ static boolean SOCK_Get(void)
 		if (c != ERRSOCKET)
 		{
 			// find remote node number
-			for (j = 0; j <= MAXNETNODES; j++) //include LAN
+			for (j = 1; j <= MAXNETNODES; j++) //include LAN
 			{
 				if (SOCK_cmpaddr(&fromaddress, &clientaddress[j], 0))
 				{
@@ -649,14 +676,29 @@ static boolean SOCK_CanGet(void)
 #endif
 
 #ifndef NONET
-static void SOCK_Send(void)
+static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr)
 {
-	ssize_t c = ERRSOCKET;
 	socklen_t d4 = (socklen_t)sizeof(struct sockaddr_in);
 #ifdef HAVE_IPV6
 	socklen_t d6 = (socklen_t)sizeof(struct sockaddr_in6);
 #endif
 	socklen_t d, da = (socklen_t)sizeof(mysockaddr_t);
+
+	switch (sockaddr->any.sa_family)
+	{
+		case AF_INET:  d = d4; break;
+#ifdef HAVE_IPV6
+		case AF_INET6: d = d6; break;
+#endif
+		default:       d = da; break;
+	}
+
+	return sendto(socket, (char *)&doomcom->data, doomcom->datalength, 0, &sockaddr->any, d);
+}
+
+static void SOCK_Send(void)
+{
+	ssize_t c = ERRSOCKET;
 	size_t i, j;
 
 	if (!nodeconnected[doomcom->remotenode])
@@ -669,19 +711,7 @@ static void SOCK_Send(void)
 			for (j = 0; j < broadcastaddresses; j++)
 			{
 				if (myfamily[i] == broadcastaddress[j].any.sa_family)
-				{
-					if (broadcastaddress[i].any.sa_family == AF_INET)
-						d = d4;
-#ifdef HAVE_IPV6
-					else if (broadcastaddress[i].any.sa_family == AF_INET6)
-						d = d6;
-#endif
-					else
-						d = da;
-
-					c = sendto(mysockets[i], (char *)&doomcom->data, doomcom->datalength, 0,
-						&broadcastaddress[j].any, d);
-				}
+					SOCK_SendToAddr(mysockets[i], &broadcastaddress[j]);
 			}
 		}
 		return;
@@ -691,40 +721,22 @@ static void SOCK_Send(void)
 		for (i = 0; i < mysocketses; i++)
 		{
 			if (myfamily[i] == clientaddress[doomcom->remotenode].any.sa_family)
-			{
-				if (clientaddress[doomcom->remotenode].any.sa_family == AF_INET)
-					d = d4;
-#ifdef HAVE_IPV6
-				else if (clientaddress[doomcom->remotenode].any.sa_family == AF_INET6)
-					d = d6;
-#endif
-				else
-					d = da;
-
-				sendto(mysockets[i], (char *)&doomcom->data, doomcom->datalength, 0,
-					&clientaddress[doomcom->remotenode].any, d);
-			}
+				SOCK_SendToAddr(mysockets[i], &clientaddress[doomcom->remotenode]);
 		}
 		return;
 	}
 	else
 	{
-		if (clientaddress[doomcom->remotenode].any.sa_family == AF_INET)
-			d = d4;
-#ifdef HAVE_IPV6
-		else if (clientaddress[doomcom->remotenode].any.sa_family == AF_INET6)
-			d = d6;
-#endif
-		else
-			d = da;
-
-		c = sendto(nodesocket[doomcom->remotenode], (char *)&doomcom->data, doomcom->datalength, 0,
-			&clientaddress[doomcom->remotenode].any, d);
+		c = SOCK_SendToAddr(nodesocket[doomcom->remotenode], &clientaddress[doomcom->remotenode]);
 	}
 
-	if (c == ERRSOCKET && errno != ECONNREFUSED && errno != EWOULDBLOCK)
-		I_Error("SOCK_Send, error sending to node %d (%s) #%u: %s", doomcom->remotenode,
-			SOCK_GetNodeAddress(doomcom->remotenode), errno, strerror(errno));
+	if (c == ERRSOCKET)
+	{
+		int e = errno; // save error code so it can't be modified later
+		if (e != ECONNREFUSED && e != EWOULDBLOCK)
+			I_Error("SOCK_Send, error sending to node %d (%s) #%u: %s", doomcom->remotenode,
+				SOCK_GetNodeAddress(doomcom->remotenode), e, strerror(e));
+	}
 }
 #endif
 
@@ -1007,7 +1019,7 @@ static boolean UDP_Socket(void)
 	if (gaie == 0)
 	{
 		runp = ai;
-		while (runp != NULL)
+		while (runp != NULL && s < MAXNETNODES+1)
 		{
 			memcpy(&clientaddress[s], runp->ai_addr, runp->ai_addrlen);
 			s++;
@@ -1022,12 +1034,15 @@ static boolean UDP_Socket(void)
 		clientaddress[s].ip4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //GetLocalAddress(); // my own ip
 		s++;
 	}
+
+	s = 0;
+
 	// setup broadcast adress to BROADCASTADDR entry
 	gaie = I_getaddrinfo("255.255.255.255", "0", &hints, &ai);
 	if (gaie == 0)
 	{
 		runp = ai;
-		while (runp != NULL)
+		while (runp != NULL && s < MAXNETNODES+1)
 		{
 			memcpy(&broadcastaddress[s], runp->ai_addr, runp->ai_addrlen);
 			s++;
@@ -1050,7 +1065,7 @@ static boolean UDP_Socket(void)
 		if (gaie == 0)
 		{
 			runp = ai;
-			while (runp != NULL)
+			while (runp != NULL && s < MAXNETNODES+1)
 			{
 				memcpy(&broadcastaddress[s], runp->ai_addr, runp->ai_addrlen);
 				s++;
@@ -1241,7 +1256,7 @@ void I_ShutdownTcpDriver(void)
 static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 {
 	SINT8 newnode = -1;
-	struct my_addrinfo *ai, *runp, hints;
+	struct my_addrinfo *ai = NULL, *runp, hints;
 	int gaie;
 
 	 if (!port || !port[0])
@@ -1271,8 +1286,12 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 	while (runp != NULL)
 	{
 		// find ip of the server
-		memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
-		runp = NULL;
+		if (sendto(mysockets[0], NULL, 0, 0, runp->ai_addr, runp->ai_addrlen) == 0)
+		{
+			memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
+			break;
+		}
+		runp = runp->ai_next;
 	}
 	I_freeaddrinfo(ai);
 	return newnode;
diff --git a/src/i_tcp.h b/src/i_tcp.h
index a084abb8459e601c3aef810faeddc34aefc9d318..06680cd9bc5fcdbd889a9249127f5633bbb92467 100644
--- a/src/i_tcp.h
+++ b/src/i_tcp.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_video.h b/src/i_video.h
index 7ee07f000efd0d29f968b028a8455c6f9a8a6e13..4bb2c58293e3cc96309e6cf577540fcd6f63abd9 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/info.c b/src/info.c
index aa9be6400483f6912156f0ab889a6db36b324e8f..3f2e95ad4e58ac94953bd213e04f1692b048202a 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -16,6 +16,7 @@
 #include "doomstat.h"
 #include "sounds.h"
 #include "p_mobj.h"
+#include "p_local.h" // DMG_ constants
 #include "m_misc.h"
 #include "z_zone.h"
 #include "d_player.h"
@@ -35,33 +36,38 @@ char sprnames[NUMSPRITES + 1][5] =
 	"PLAY",
 
 	// Enemies
-	"POSS",
-	"SPOS",
-	"FISH", // Greenflower Fish
+	"POSS", // Crawla (Blue)
+	"SPOS", // Crawla (Red)
+	"FISH", // SDURF
 	"BUZZ", // Buzz (Gold)
 	"RBUZ", // Buzz (Red)
 	"JETB", // Jetty-Syn Bomber
-	"JETW", // Jetty-Syn Water Bomber
 	"JETG", // Jetty-Syn Gunner
 	"CCOM", // Crawla Commander
 	"DETN", // Deton
 	"SKIM", // Skim mine dropper
-	"TRET",
+	"TRET", // Industrial Turret
 	"TURR", // Pop-Up Turret
 	"SHRP", // Sharp
+	"CRAB", // Crushstacean
 	"JJAW", // Jet Jaw
 	"SNLR", // Snailer
-	"VLTR", // Vulture
+	"VLTR", // BASH
 	"PNTY", // Pointy
 	"ARCH", // Robo-Hood
-	"CBFS", // CastleBot FaceStabber (Egg Knight?)
+	"CBFS", // Castlebot Facestabber
+	"STAB", // Castlebot Facestabber spear aura
 	"SPSH", // Egg Guard
-	"ESHI", // Egg Shield for Egg Guard
+	"ESHI", // Egg Guard's shield
 	"GSNP", // Green Snapper
+	"GSNL", // Green Snapper leg
+	"GSNH", // Green Snapper head
 	"MNUS", // Minus
+	"MNUD", // Minus dirt
 	"SSHL", // Spring Shell
 	"UNID", // Unidus
-	"BBUZ", // AquaBuzz, for Azure Temple
+	"CANA", // Canarivore
+	"CANG", // Canarivore gas
 
 	// Generic Boss Items
 	"JETF", // Boss jet fumes
@@ -84,9 +90,14 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Boss 4 (Castle Eggman)
 	"EGGP",
 	"EFIR", // Boss 4 jet flame
+	"EGR1", // Boss 4 Spectator Eggrobo
 
 	// Boss 5 (Arid Canyon)
-	"EGGQ",
+	"FANG", // replaces EGGQ
+	"FBOM",
+	"FSGN",
+	"BARX", // bomb explosion (also used by barrel)
+	"BARD", // bomb dust (also used by barrel)
 
 	// Boss 6 (Red Volcano)
 	"EGGR",
@@ -117,16 +128,16 @@ char sprnames[NUMSPRITES + 1][5] =
 	"TOKE", // Special Stage Token
 	"RFLG", // Red CTF Flag
 	"BFLG", // Blue CTF Flag
-	"NWNG", // NiGHTS Wing collectable item.
+	"SPHR", // Sphere
+	"NCHP", // NiGHTS chip
+	"NSTR", // NiGHTS star
 	"EMBM", // Emblem
 	"CEMG", // Chaos Emeralds
-	"EMER", // Emerald Hunt
+	"SHRD", // Emerald hunt shards
 
 	// Interactive Objects
-	"FANS",
 	"BBLS", // water bubble source
 	"SIGN", // Level end sign
-	"STEM", // Steam riser
 	"SPIK", // Spike Ball
 	"SFLM", // Spin fire
 	"USPK", // Floor spike
@@ -182,16 +193,20 @@ char sprnames[NUMSPRITES + 1][5] =
 	"FWR4",
 	"BUS1", // GFZ Bush w/ berries
 	"BUS2", // GFZ Bush w/o berries
+	"BUS3", // GFZ Bush w/ BLUE berries
 	// Trees (both GFZ and misc)
 	"TRE1", // GFZ
 	"TRE2", // Checker
 	"TRE3", // Frozen Hillside
 	"TRE4", // Polygon
 	"TRE5", // Bush tree
+	"TRE6", // Spring tree
 
 	// Techno Hill Scenery
-	"THZP", // Techno Hill Zone Plant
-	"FWR5", // Another one
+	"THZP", // THZ1 Steam Flower
+	"FWR5", // THZ1 Spin flower (red)
+	"FWR6", // THZ1 Spin flower (yellow)
+	"THZT", // Steam Whistle tree/bush
 	"ALRM", // THZ2 Alarm
 
 	// Deep Sea Scenery
@@ -202,6 +217,9 @@ char sprnames[NUMSPRITES + 1][5] =
 	"CRL2", // Coral 2
 	"CRL3", // Coral 3
 	"BCRY", // Blue Crystal
+	"KELP", // Kelp
+	"DSTG", // DSZ Stalagmites
+	"LIBE", // DSZ Light beam
 
 	// Castle Eggman Scenery
 	"CHAN", // CEZ Chain
@@ -215,11 +233,37 @@ char sprnames[NUMSPRITES + 1][5] =
 	"RSPB", // Red spring on a ball
 	"SFBR", // Small Firebar
 	"BFBR", // Big Firebar
+	"BANR", // Banner
+	"PINE", // Pine Tree
+	"CEZB", // Bush
+	"CNDL", // Candle/pricket
+	"FLMH", // Flame holder
+	"CTRC", // Fire torch
+	"CFLG", // Waving flag/segment
+	"CSTA", // Crawla statue
+	"CBBS", // Facestabber statue
+	"CABR", // Brambles
 
 	// Arid Canyon Scenery
 	"BTBL", // Big tumbleweed
 	"STBL", // Small tumbleweed
 	"CACT", // Cacti sprites
+	"WWSG", // Caution Sign
+	"WWS2", // Cacti Sign
+	"WWS3", // Sharp Turn Sign
+	"OILL", // Oil lamp
+	"OILF", // Oil lamp flare
+	"BARR", // TNT barrel
+	"REMT", // TNT proximity shell
+	"TAZD", // Dust devil
+	"ADST", // Arid dust
+	"MCRT", // Minecart
+	"MCSP", // Minecart spark
+	"NON2", // Saloon door thinker
+	"SALD", // Saloon door
+	"TRAE", // Train cameo locomotive
+	"TRAI", // Train cameo wagon
+	"STEA", // Train steam
 
 	// Red Volcano Scenery
 	"FLME", // Flame jet
@@ -235,12 +279,25 @@ char sprnames[NUMSPRITES + 1][5] =
 	"XMS3", // Snowman
 	"XMS4", // Lamppost
 	"XMS5", // Hanging Star
+	"FHZI", // FHZ ice
+
+	// Halloween Scenery
+	"PUMK", // Pumpkins
+	"HHPL", // Dr Seuss Trees
+	"SHRM", // Mushroom
+	"HHZM", // Misc
 
 	// Botanic Serenity Scenery
 	"BSZ1", // Tall flowers
 	"BSZ2", // Medium flowers
 	"BSZ3", // Small flowers
-	"BSZ4", // Tulip
+	//"BSZ4", -- Tulips
+	"BST1", // Red tulip
+	"BST2", // Purple tulip
+	"BST3", // Blue tulip
+	"BST4", // Cyan tulip
+	"BST5", // Yellow tulip
+	"BST6", // Orange tulip
 	"BSZ5", // Cluster of Tulips
 	"BSZ6", // Bush
 	"BSZ7", // Vine
@@ -286,13 +343,20 @@ char sprnames[NUMSPRITES + 1][5] =
 	"FL14", // Dove
 	"FL15", // Cat
 	"FL16", // Canary
+	"FS01", // Spider
+	"FS02", // Bat
 
 	// Springs
-	"SPRY", // yellow spring
-	"SPRR", // red spring
-	"SPRB", // Blue springs
+	"FANS", // Fan
+	"STEM", // Steam riser
+	"BUMP", // Bumpers
+	"BLON", // Balloons
+	"SPRY", // Yellow spring
+	"SPRR", // Red spring
+	"SPRB", // Blue spring
 	"YSPR", // Yellow Diagonal Spring
 	"RSPR", // Red Diagonal Spring
+	"BSPR", // Blue Diagonal Spring
 	"SSWY", // Yellow Side Spring
 	"SSWR", // Red Side Spring
 	"SSWB", // Blue Side Spring
@@ -319,6 +383,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"GFLG", // Got Flag sign
 
 	"CORK",
+	"LHRT",
 
 	// Ring Weapons
 	"RRNG", // Red Ring
@@ -366,13 +431,33 @@ char sprnames[NUMSPRITES + 1][5] =
 	"NSCR", // NiGHTS score sprite
 	"NPRU", // Nights Powerups
 	"CAPS", // Capsule thingy for NiGHTS
+	"IDYA", // Ideya
+	"NTPN", // Nightopian
+	"SHLP", // Shleep
+
+	// Secret badniks and hazards, shhhh
+	"PENG",
+	"POPH",
+	"HIVE",
+	"BUMB",
+	"BBUZ",
+	"FMCE",
+	"HMCE",
+	"CACO",
+	"BAL2",
+	"SBOB",
+	"SBFL",
+	"SBSK",
+	"HBAT",
 
 	// Debris
-	"SPRK", // spark
+	"SPRK", // Sparkle
 	"BOM1", // Robot Explosion
 	"BOM2", // Boss Explosion 1
 	"BOM3", // Boss Explosion 2
 	"BOM4", // Underwater Explosion
+	"BMNB", // Mine Explosion
+	"WDDB", // Wood Debris
 
 	// Crumbly rocks
 	"ROIA",
@@ -392,8 +477,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"ROIO",
 	"ROIP",
 
-	// Blue Spheres
-	"BBAL",
+	// Bricks
+	"BRIC",
 
 	// Gravity Well Objects
 	"GWLG",
@@ -493,7 +578,8 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TALB",
 
 	"SIGN",
-	"LIFE"
+	"LIFE",
+	"XTRA",
 };
 playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
 
@@ -510,7 +596,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_DEAD, // SPR2_DRWN,
 	0, // SPR2_ROLL,
 	SPR2_SPNG, // SPR2_GASP,
-	0, // SPR2_JUMP, (conditional)
+	0, // SPR2_JUMP, (conditional, will never be referenced)
 	SPR2_FALL, // SPR2_SPNG,
 	SPR2_WALK, // SPR2_FALL,
 	0, // SPR2_EDGE,
@@ -520,7 +606,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 
 	SPR2_SPNG, // SPR2_FLY ,
 	SPR2_FLY , // SPR2_SWIM,
-	0, // SPR2_TIRE, (conditional)
+	0, // SPR2_TIRE, (conditional, will never be referenced)
 
 	SPR2_FLY , // SPR2_GLID,
 	SPR2_CLMB, // SPR2_CLNG,
@@ -547,7 +633,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_NSTN, // SPR2_NPUL,
 	FF_SPR2SUPER|SPR2_ROLL, // SPR2_NATK,
 
-	0, // SPR2_NGT0, (should never be referenced)
+	0, // SPR2_NGT0, (will never be referenced unless skin 0 lacks this)
 	SPR2_NGT0, // SPR2_NGT1,
 	SPR2_NGT1, // SPR2_NGT2,
 	SPR2_NGT2, // SPR2_NGT3,
@@ -575,7 +661,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_NGTB, // SPR2_DRLB,
 	SPR2_NGTC, // SPR2_DRLC,
 
-	0, // SPR2_TAL0,
+	0, // SPR2_TAL0, (this will look mighty stupid but oh well)
 	SPR2_TAL0, // SPR2_TAL1,
 	SPR2_TAL1, // SPR2_TAL2,
 	SPR2_TAL2, // SPR2_TAL3,
@@ -590,6 +676,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 
 	0, // SPR2_SIGN,
 	0, // SPR2_LIFE,
+	0, // SPR2_XTRA (should never be referenced)
 };
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
@@ -662,25 +749,25 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_FIRE,                 15, {NULL},        S_PLAY_STND, 0, S_PLAY_STND},   // S_PLAY_FIRE_FINISH
 
 	// CA_TWINSPIN
-	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
+	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
 
 	// CA2_MELEE
-	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
+	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
 	{SPR_PLAY, SPR2_MLEE,                70, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
 	{SPR_PLAY, SPR2_MLEL,                35, {NULL},                   0, 0, S_PLAY_WALK},  // S_PLAY_MELEE_LANDING
 
 	// SF_SUPER
-	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER,                3, {NULL},          0, 0, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS1
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_ANIMATE,     7, {NULL},          0, 4, S_PLAY_SUPER_TRANS2}, // S_PLAY_SUPER_TRANS1
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER,                3, {NULL},          0, 0, S_PLAY_SUPER_TRANS3}, // S_PLAY_SUPER_TRANS2
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS4}, // S_PLAY_SUPER_TRANS3
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS5}, // S_PLAY_SUPER_TRANS4
 	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_SUPER_TRANS6}, // S_PLAY_SUPER_TRANS5
-	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT, 20, {A_FadeOverlay}, 0, 0, S_PLAY_FALL},         // S_PLAY_SUPER_TRANS6
+	{SPR_PLAY, SPR2_TRNS|FF_SPR2SUPER|FF_FULLBRIGHT, 19, {A_FadeOverlay}, 0, 0, S_PLAY_FALL},         // S_PLAY_SUPER_TRANS6
 
 	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_OBJPLACE_DUMMY}, //S_OBJPLACE_DUMMY
 
 	// 1-Up box sprites (uses player sprite)
-	{SPR_PLAY, SPR2_LIFE,  2, {NULL}, 0, 16, S_PLAY_BOX2},  // S_PLAY_BOX1
+	{SPR_PLAY, SPR2_LIFE,  2, {NULL}, 0, 18, S_PLAY_BOX2},  // S_PLAY_BOX1
 	{SPR_NULL,         0,  1, {NULL}, 0,  0, S_PLAY_BOX1},  // S_PLAY_BOX2
 	{SPR_PLAY, SPR2_LIFE,  4, {NULL}, 0,  4, S_PLAY_ICON2}, // S_PLAY_ICON1
 	{SPR_NULL,         0, 12, {NULL}, 0,  0, S_PLAY_ICON3}, // S_PLAY_ICON2
@@ -690,12 +777,12 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_SIGN, 1, {NULL}, 0, 24, S_PLAY_SIGN},         // S_PLAY_SIGN
 
 	// NiGHTS Player, transforming
-	{SPR_PLAY, SPR2_TRNS,                3, {NULL},          0, 0, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS1
+	{SPR_PLAY, SPR2_TRNS|FF_ANIMATE,     7, {NULL},          0, 4, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS1
 	{SPR_PLAY, SPR2_TRNS,                3, {NULL},          0, 0, S_PLAY_NIGHTS_TRANS3}, // S_PLAY_NIGHTS_TRANS2
 	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_NIGHTS_TRANS4}, // S_PLAY_NIGHTS_TRANS3
 	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_NIGHTS_TRANS5}, // S_PLAY_NIGHTS_TRANS4
 	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT,  2, {NULL},          0, 0, S_PLAY_NIGHTS_TRANS6}, // S_PLAY_NIGHTS_TRANS5
-	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT, 25, {A_FadeOverlay}, 4, 0, S_PLAY_NIGHTS_FLOAT},  // S_PLAY_NIGHTS_TRANS5
+	{SPR_PLAY, SPR2_TRNS|FF_FULLBRIGHT, 21, {A_FadeOverlay}, 2, 0, S_PLAY_NIGHTS_FLOAT},  // S_PLAY_NIGHTS_TRANS5
 
 	// NiGHTS Player, stand, float, pain, pull and attack
 	{SPR_PLAY, SPR2_NSTD, 7, {NULL}, 0, 0, S_PLAY_NIGHTS_STAND},  // S_PLAY_NIGHTS_STAND
@@ -782,10 +869,6 @@ state_t states[NUMSTATES] =
 	{SPR_RBUZ, 0, 2, {A_BuzzFly}, sfx_buzz4, 0, S_RBUZZFLY2}, // S_RBUZZFLY1
 	{SPR_RBUZ, 1, 2, {A_BuzzFly}, 0, 0, S_RBUZZFLY1}, // S_RBUZZFLY2
 
-	// AquaBuzz
-	{SPR_BBUZ, 0, 2, {NULL}, 0, 0, S_BBUZZFLY2}, // S_BBUZZFLY1
-	{SPR_BBUZ, 1, 2, {NULL}, 0, 0, S_BBUZZFLY1}, // S_BBUZZFLY2
-
 	// Jetty-Syn Bomber
 	{SPR_JETB, 0, 4, {A_Look}, 0, 0, S_JETBLOOK2},      // S_JETBLOOK1
 	{SPR_JETB, 1, 4, {A_Look}, 0, 0, S_JETBLOOK1},      // S_JETBLOOK2
@@ -822,7 +905,6 @@ state_t states[NUMSTATES] =
 	{SPR_DETN, 3,  1, {A_DetonChase}, 0, 0, S_DETON14}, // S_DETON13
 	{SPR_DETN, 2,  1, {A_DetonChase}, 0, 0, S_DETON15}, // S_DETON14
 	{SPR_DETN, 1,  1, {A_DetonChase}, 0, 0, S_DETON2},  // S_DETON15
-	{SPR_DETN, 0, -1, {NULL},         0, 0, S_DETON16}, // S_DETON16
 
 	// Skim Mine Dropper
 	{SPR_SKIM, 0,  1, {A_SkimChase}, 0, 0, S_SKIM2},    // S_SKIM1
@@ -841,7 +923,7 @@ state_t states[NUMSTATES] =
 	{SPR_TRET, FF_FULLBRIGHT|2, 7, {A_Pain}, 0, 0, S_TURRETSHOCK7},       // S_TURRETSHOCK6
 	{SPR_TRET, FF_FULLBRIGHT|3, 7, {NULL}, 0, 0, S_TURRETSHOCK8},         // S_TURRETSHOCK7
 	{SPR_TRET, FF_FULLBRIGHT|4, 7, {NULL}, 0, 0, S_TURRETSHOCK9},         // S_TURRETSHOCK8
-	{SPR_TRET, FF_FULLBRIGHT|4, 7, {A_LinedefExecute}, 32000, 0, S_XPLD1}, // S_TURRETSHOCK9
+	{SPR_TRET, FF_FULLBRIGHT|4, 7, {A_LinedefExecute}, LE_TURRET, 0, S_XPLD1}, // S_TURRETSHOCK9
 
 	{SPR_TURR, 0, 1, {A_Look}, 1, 0, S_TURRETPOPDOWN8},          // S_TURRETLOOK
 	{SPR_TURR, 0, 0, {A_FaceTarget}, 0, 0, S_TURRETPOPUP1},  // S_TURRETSEE
@@ -863,14 +945,40 @@ state_t states[NUMSTATES] =
 	{SPR_TURR, 1, 2, {NULL}, 0, 0, S_TURRETPOPDOWN8},        // S_TURRETPOPDOWN7
 	{SPR_TURR, 0, 69,{A_SetTics}, 0, 1, S_TURRETLOOK},       // S_TURRETPOPDOWN8
 
-	// Sharp
-	{SPR_SHRP, 0, 1, {A_Look},       0, 0, S_SHARP_ROAM1}, // S_SHARP_ROAM1,
-	{SPR_SHRP, 0, 1, {A_SharpChase}, 0, 0, S_SHARP_ROAM2}, // S_SHARP_ROAM2,
-	{SPR_SHRP, 1, 2, {A_FaceTarget}, 0, 0, S_SHARP_AIM2}, // S_SHARP_AIM1,
-	{SPR_SHRP, 2, 2, {A_FaceTarget}, 0, 0, S_SHARP_AIM3}, // S_SHARP_AIM2,
-	{SPR_SHRP, 3, 2, {A_FaceTarget}, 0, 0, S_SHARP_AIM4}, // S_SHARP_AIM3,
-	{SPR_SHRP, 4, 7, {A_FaceTarget}, 0, 0, S_SHARP_SPIN}, // S_SHARP_AIM4,
-	{SPR_SHRP, 4, 1, {A_SharpSpin},  0, 0, S_SHARP_SPIN}, // S_SHARP_SPIN,
+	// Spincushion
+	{SPR_SHRP, 0,  2, {A_Look},                 0, 0, S_SPINCUSHION_LOOK},   // S_SPINCUSHION_LOOK
+	{SPR_SHRP, 1,  2, {A_SharpChase},           0, 0, S_SPINCUSHION_CHASE2}, // S_SPINCUSHION_CHASE1
+	{SPR_SHRP, 2,  2, {A_SharpChase},           0, 0, S_SPINCUSHION_CHASE3}, // S_SPINCUSHION_CHASE2
+	{SPR_SHRP, 3,  2, {A_SharpChase},           0, 0, S_SPINCUSHION_CHASE4}, // S_SPINCUSHION_CHASE3
+	{SPR_SHRP, 0,  2, {A_SharpChase},           0, 0, S_SPINCUSHION_CHASE1}, // S_SPINCUSHION_CHASE4
+	{SPR_SHRP, 0,  2, {NULL},                   0, 0, S_SPINCUSHION_AIM2},   // S_SPINCUSHION_AIM1
+	{SPR_SHRP, 4,  2, {NULL},                   0, 0, S_SPINCUSHION_AIM3},   // S_SPINCUSHION_AIM2
+	{SPR_SHRP, 5,  2, {A_SetObjectFlags}, MF_PAIN, 2, S_SPINCUSHION_AIM4},   // S_SPINCUSHION_AIM3
+	{SPR_SHRP, 6, 16, {A_MultiShotDist}, (MT_DUST<<16)|6, -32, S_SPINCUSHION_AIM5}, // S_SPINCUSHION_AIM4
+	{SPR_SHRP, 6,  0, {A_PlaySound},   sfx_shrpgo, 1, S_SPINCUSHION_SPIN1},  // S_SPINCUSHION_AIM5
+	{SPR_SHRP, 6,  1, {A_SharpSpin},            0, 0, S_SPINCUSHION_SPIN2},  // S_SPINCUSHION_SPIN1
+	{SPR_SHRP, 8,  1, {A_SharpSpin},            0, 0, S_SPINCUSHION_SPIN3},  // S_SPINCUSHION_SPIN2
+	{SPR_SHRP, 7,  1, {A_SharpSpin},            0, 0, S_SPINCUSHION_SPIN4},  // S_SPINCUSHION_SPIN3
+	{SPR_SHRP, 8,  1, {A_SharpSpin},  MT_SPINDUST, 0, S_SPINCUSHION_SPIN1},  // S_SPINCUSHION_SPIN4
+	{SPR_SHRP, 6,  1, {A_PlaySound},    sfx_s3k69, 1, S_SPINCUSHION_STOP2},  // S_SPINCUSHION_STOP1
+	{SPR_SHRP, 6,  4, {A_SharpDecel},           0, 0, S_SPINCUSHION_STOP2},  // S_SPINCUSHION_STOP2
+	{SPR_SHRP, 5,  4, {A_FaceTarget},           0, 0, S_SPINCUSHION_STOP4},  // S_SPINCUSHION_STOP3
+	{SPR_SHRP, 4,  4, {A_SetObjectFlags}, MF_PAIN, 1, S_SPINCUSHION_LOOK},   // S_SPINCUSHION_STOP4
+
+	// Crushstacean
+	{SPR_CRAB, 0,  3, {A_CrushstaceanWalk},  0, S_CRUSHSTACEAN_ROAMPAUSE, S_CRUSHSTACEAN_ROAM2},     // S_CRUSHSTACEAN_ROAM1
+	{SPR_CRAB, 1,  3, {A_CrushstaceanWalk},  0, S_CRUSHSTACEAN_ROAMPAUSE, S_CRUSHSTACEAN_ROAM3},     // S_CRUSHSTACEAN_ROAM2
+	{SPR_CRAB, 0,  3, {A_CrushstaceanWalk},  0, S_CRUSHSTACEAN_ROAMPAUSE, S_CRUSHSTACEAN_ROAM4},     // S_CRUSHSTACEAN_ROAM3
+	{SPR_CRAB, 2,  3, {A_CrushstaceanWalk},  0, S_CRUSHSTACEAN_ROAMPAUSE, S_CRUSHSTACEAN_ROAM1},     // S_CRUSHSTACEAN_ROAM4
+	{SPR_CRAB, 0, 40, {NULL},                0,                        0, S_CRUSHSTACEAN_ROAM1},     // S_CRUSHSTACEAN_ROAMPAUSE
+	{SPR_CRAB, 0, 10, {NULL},                0,                        0, S_CRUSHSTACEAN_PUNCH2},    // S_CRUSHSTACEAN_PUNCH1
+	{SPR_CRAB, 0, -1, {A_CrushstaceanPunch}, 0,                        0, S_CRUSHSTACEAN_ROAMPAUSE}, // S_CRUSHSTACEAN_PUNCH2
+	{SPR_CRAB, 3,  1, {A_CrushclawAim},   40,               20, S_CRUSHCLAW_AIM}, // S_CRUSHCLAW_AIM
+	{SPR_CRAB, 3,  1, {A_CrushclawLaunch}, 0, S_CRUSHCLAW_STAY, S_CRUSHCLAW_OUT}, // S_CRUSHCLAW_OUT
+	{SPR_CRAB, 3, 10, {NULL},              0,                0, S_CRUSHCLAW_IN},  // S_CRUSHCLAW_STAY
+	{SPR_CRAB, 3,  1, {A_CrushclawLaunch}, 1, S_CRUSHCLAW_WAIT, S_CRUSHCLAW_IN},  // S_CRUSHCLAW_IN
+	{SPR_CRAB, 3, 37, {NULL},              0,                0, S_CRUSHCLAW_AIM}, // S_CRUSHCLAW_WAIT
+	{SPR_CRAB, 4, -1, {NULL}, 0, 0, S_NULL}, // S_CRUSHCHAIN
 
 	// Jet Jaw
 	{SPR_JJAW, 0, 1, {A_JetJawRoam},  0, 0, S_JETJAW_ROAM2},   // S_JETJAW_ROAM1
@@ -902,16 +1010,11 @@ state_t states[NUMSTATES] =
 	{SPR_SNLR, 0, 1, {A_SnailerThink}, 0, 0, S_SNAILER1}, // S_SNAILER1
 
 	// Vulture
-	{SPR_VLTR, 4, 35,        {A_Look},  1, 0, S_VULTURE_STND},  // S_VULTURE_STND
-	{SPR_VLTR, 4, 1,  {A_VultureVtol},  0, 0, S_VULTURE_VTOL2}, // S_VULTURE_VTOL1
-	{SPR_VLTR, 5, 1,  {A_VultureVtol},  0, 0, S_VULTURE_VTOL3}, // S_VULTURE_VTOL2
-	{SPR_VLTR, 6, 1,  {A_VultureVtol},  0, 0, S_VULTURE_VTOL4}, // S_VULTURE_VTOL3
-	{SPR_VLTR, 7, 1,  {A_VultureVtol},  0, 0, S_VULTURE_VTOL1}, // S_VULTURE_VTOL4
-	{SPR_VLTR, 0, 1,       {A_Thrust}, 30, 1, S_VULTURE_ZOOM2}, // S_VULTURE_ZOOM1
-	{SPR_VLTR, 0, 1, {A_VultureCheck},  0, 0, S_VULTURE_ZOOM3}, // S_VULTURE_ZOOM2
-	{SPR_VLTR, 1, 1, {A_VultureCheck},  0, 0, S_VULTURE_ZOOM4}, // S_VULTURE_ZOOM3
-	{SPR_VLTR, 2, 1, {A_VultureCheck},  0, 0, S_VULTURE_ZOOM5}, // S_VULTURE_ZOOM4
-	{SPR_VLTR, 3, 1, {A_VultureCheck},  0, 0, S_VULTURE_ZOOM2}, // S_VULTURE_ZOOM5
+	{SPR_VLTR, 4, 35,        {A_Look},         1, 0, S_VULTURE_STND},  // S_VULTURE_STND
+	{SPR_VLTR, 4, 3,         {A_VultureHover}, 0, 0, S_VULTURE_DRIFT}, // S_VULTURE_DRIFT
+	{SPR_VLTR, 0, 6,         {A_VultureBlast}, 0, 0, S_VULTURE_ZOOM2}, // S_VULTURE_ZOOM1
+	{SPR_VLTR, 0, 3,         {A_VultureFly},   0, 0, S_VULTURE_ZOOM2}, // S_VULTURE_ZOOM2
+	{SPR_VLTR, 0, 3*TICRATE, {NULL},           0, 0, S_VULTURE_DRIFT}, // S_VULTURE_STUNNED
 
 	// Pointy
 	{SPR_PNTY, 0,  1, {A_PointyThink}, 0, 0, S_POINTY1}, // S_POINTY1
@@ -920,24 +1023,31 @@ state_t states[NUMSTATES] =
 	{SPR_PNTY, 1,  1, {A_CheckBuddy}, 0, 0, S_POINTYBALL1}, // S_POINTYBALL1
 
 	// Robo-Hood
-	{SPR_ARCH, 0, 14,       {A_Look}, (512<<16),   0, S_ROBOHOOD_LOOK},  // S_ROBOHOOD_LOOK
-	{SPR_ARCH, 2,  1,  {A_HoodThink},         0,   0, S_ROBOHOOD_STND},  // S_ROBOHOOD_STND
-	{SPR_ARCH, 0, 35,   {A_FireShot},  MT_ARROW, -24, S_ROBOHOOD_STND},  // S_ROBOHOOD_SHOOT
-	{SPR_ARCH, 1,  1,   {A_BunnyHop},         8,   5, S_ROBOHOOD_JUMP2}, // S_ROBOHOOD_JUMP
-	{SPR_ARCH, 1,  1,  {A_HoodThink},         0,   0, S_ROBOHOOD_JUMP2}, // S_ROBOHOOD_JUMP2
-	{SPR_ARCH, 0,  1,  {A_HoodThink},         0,   0, S_ROBOHOOD_FALL},  // S_ROBOHOOD_FALL
-
-	// CastleBot FaceStabber
-	{SPR_CBFS, 0,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND2},   // S_FACESTABBER_STND1
-	{SPR_CBFS, 1,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND3},   // S_FACESTABBER_STND2
-	{SPR_CBFS, 2,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND4},   // S_FACESTABBER_STND3
-	{SPR_CBFS, 3,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND5},   // S_FACESTABBER_STND4
-	{SPR_CBFS, 4,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND6},   // S_FACESTABBER_STND5
-	{SPR_CBFS, 5,  1,   {A_FaceStabChase},  0, 0, S_FACESTABBER_STND1},   // S_FACESTABBER_STND6
-	{SPR_CBFS, 6, 14, {A_PlayActiveSound},  0, 0, S_FACESTABBER_CHARGE2}, // S_FACESTABBER_CHARGE1
-	{SPR_CBFS, 6,  0, {A_PlayAttackSound},  0, 0, S_FACESTABBER_CHARGE3}, // S_FACESTABBER_CHARGE2
-	{SPR_CBFS, 6,  0,      {A_FaceTarget},  0, 0, S_FACESTABBER_CHARGE4}, // S_FACESTABBER_CHARGE3
-	{SPR_CBFS, 7, 35,          {A_Thrust}, 20, 1, S_FACESTABBER_STND1},   // S_FACESTABBER_CHARGE4
+	{SPR_ARCH, 0,       4,            {A_Look}, 2048<<FRACBITS,   0, S_ROBOHOOD_LOOK},  // S_ROBOHOOD_LOOK
+	{SPR_ARCH, 0,       1,       {A_HoodThink},              0,   0, S_ROBOHOOD_STAND}, // S_ROBOHOOD_STAND
+	{SPR_ARCH, 2, TICRATE, {A_PlayActiveSound},              0,   0, S_ROBOHOOD_FIRE2}, // S_ROBOHOOD_FIRE1
+	{SPR_ARCH, 2,      20,        {A_HoodFire},       MT_ARROW,   0, S_ROBOHOOD_STAND}, // S_ROBOHOOD_FIRE2
+	{SPR_ARCH, 1,       1,      {A_FaceTarget},              0,   0, S_ROBOHOOD_JUMP2}, // S_ROBOHOOD_JUMP1
+	{SPR_ARCH, 1,       1,        {A_BunnyHop},              4, -10, S_ROBOHOOD_JUMP3}, // S_ROBOHOOD_JUMP2
+	{SPR_ARCH, 1,       1,        {A_HoodFall},              0,   0, S_ROBOHOOD_JUMP3}, // S_ROBOHOOD_JUMP3
+
+	// Castlebot Facestabber
+	{SPR_CBFS, 0,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND2},   // S_FACESTABBER_STND1
+	{SPR_CBFS, 1,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND3},   // S_FACESTABBER_STND2
+	{SPR_CBFS, 2,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND4},   // S_FACESTABBER_STND3
+	{SPR_CBFS, 3,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND5},   // S_FACESTABBER_STND4
+	{SPR_CBFS, 4,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND6},   // S_FACESTABBER_STND5
+	{SPR_CBFS, 5,  1,        {A_Chase},  0, 0, S_FACESTABBER_STND1},   // S_FACESTABBER_STND6
+	{SPR_CBFS, 0,  1,  {A_FaceStabRev},                  20, S_FACESTABBER_CHARGE2, S_FACESTABBER_CHARGE1}, // S_FACESTABBER_CHARGE1
+	{SPR_CBFS, 0,  0,   {A_FaceTarget},                   0,                     0, S_FACESTABBER_CHARGE3}, // S_FACESTABBER_CHARGE2
+	{SPR_CBFS, 7,  1, {A_FaceStabHurl},                   6, S_FACESTABBER_CHARGE4, S_FACESTABBER_CHARGE3}, // S_FACESTABBER_CHARGE3
+	{SPR_CBFS, 7,  1, {A_FaceStabMiss}, 0,   S_FACESTABBER_STND1, S_FACESTABBER_CHARGE4}, // S_FACESTABBER_CHARGE4
+	{SPR_CBFS, 0, 35,         {A_Pain}, 0,                     0, S_FACESTABBER_STND1}, // S_FACESTABBER_PAIN
+	{SPR_CBFS, 0,  2,   {A_BossScream}, 1, MT_SONIC3KBOSSEXPLODE, S_FACESTABBER_DIE2},  // S_FACESTABBER_DIE1
+	{SPR_NULL, 0,  2,   {A_BossScream}, 1, MT_SONIC3KBOSSEXPLODE, S_FACESTABBER_DIE3},  // S_FACESTABBER_DIE2
+	{SPR_NULL, 0,  0,       {A_Repeat}, 7, S_FACESTABBER_DIE1,    S_XPLD_FLICKY},       // S_FACESTABBER_DIE3
+
+	{SPR_STAB, FF_PAPERSPRITE|FF_TRANS50|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_FACESTABBERSPEAR
 
 	// Egg Guard
 	{SPR_SPSH,  0,  1,       {A_Look}, 0, 0, S_EGGGUARD_STND},  // S_EGGGUARD_STND
@@ -953,19 +1063,35 @@ state_t states[NUMSTATES] =
 	{SPR_SPSH, 10,  1, {A_GuardChase}, 0, 0, S_EGGGUARD_RUN4},  // S_EGGGUARD_RUN3
 	{SPR_SPSH, 11,  1, {A_GuardChase}, 0, 0, S_EGGGUARD_RUN1},  // S_EGGGUARD_RUN4
 
-	// Egg Shield for Egg Guard
-	{SPR_ESHI, 0, 8, {A_EggShield}, 0, 0, S_EGGSHIELD}, // S_EGGSHIELD
+	{SPR_ESHI, 0, 8, {A_EggShield}, 0, 0, S_EGGSHIELD},  // S_EGGSHIELD
+	{SPR_ESHI, 0, TICRATE/2, {NULL}, 0, 0, S_NULL}, // S_EGGSHIELDBREAK
 
 	// Green Snapper
-	{SPR_GSNP, 0, 1,  {A_Look}, 0, 0, S_GSNAPPER_STND}, // S_GSNAPPER_STND
-	{SPR_GSNP, 0, 2, {A_Chase}, 0, 0, S_GSNAPPER2},     // S_GSNAPPER1
-	{SPR_GSNP, 1, 2, {A_Chase}, 0, 0, S_GSNAPPER3},     // S_GSNAPPER2
-	{SPR_GSNP, 2, 2, {A_Chase}, 0, 0, S_GSNAPPER4},     // S_GSNAPPER3
-	{SPR_GSNP, 3, 2, {A_Chase}, 0, 0, S_GSNAPPER1},     // S_GSNAPPER4
+	{SPR_GSNP, 0, TICRATE, {NULL},             0,              0,               S_SNAPPER_SPAWN2}, // S_SNAPPER_SPAWN
+	{SPR_GSNP, 0, 2,       {A_SnapperSpawn},   MT_SNAPPER_LEG, MT_SNAPPER_HEAD, S_GSNAPPER_STND},  // S_SNAPPER_SPAWN2
+	{SPR_GSNP, 0, 1,       {A_SnapperThinker}, 0,              0,               S_GSNAPPER_STND},  // S_GSNAPPER_STND
+	{SPR_GSNP, 0, 2,       {A_Chase},          0,              0,               S_GSNAPPER2},      // S_GSNAPPER1
+	{SPR_GSNP, 1, 2,       {A_Chase},          0,              0,               S_GSNAPPER3},      // S_GSNAPPER2
+	{SPR_GSNP, 2, 2,       {A_Chase},          0,              0,               S_GSNAPPER4},      // S_GSNAPPER3
+	{SPR_GSNP, 3, 2,       {A_Chase},          0,              0,               S_GSNAPPER1},      // S_GSNAPPER4
+	{SPR_GSNP, 0, 0,       {A_KillSegments},   0,              0,               S_XPLD_FLICKY},    // S_SNAPPER_XPLD
+	{SPR_GSNL, 0, -1,      {NULL},             0,              0,               S_NULL},           // S_SNAPPER_LEG
+	{SPR_GSNL, 1, -1,      {NULL},             0,              0,               S_NULL},           // S_SNAPPER_LEGRAISE
+	{SPR_GSNH, 0, -1,      {NULL},             0,              0,               S_NULL},           // S_SNAPPER_HEAD
 
 	// Minus
-	{SPR_NULL,  0, 1, {A_Look},         0, 0, S_MINUS_STND},      // S_MINUS_STND
-	{SPR_MNUS, 16, 1, {A_MinusDigging}, 0, 0, S_MINUS_DIGGING},   // S_MINUS_DIGGING
+	{SPR_MNUD, 0,            1,  {NULL},           0, 0, S_MINUS_STND},     // S_MINUS_INIT (required for objectplace to work)
+	{SPR_NULL, 0,            10, {A_Look},         0, 0, S_MINUS_STND},     // S_MINUS_STND
+	{SPR_NULL, 0,            1,  {A_MinusDigging}, 1, 0, S_MINUS_DIGGING2}, // S_MINUS_DIGGING1
+	{SPR_NULL, 0,            1,  {A_MinusDigging}, 0, 0, S_MINUS_DIGGING3}, // S_MINUS_DIGGING2
+	{SPR_NULL, 0,            1,  {A_MinusDigging}, 0, 0, S_MINUS_DIGGING4}, // S_MINUS_DIGGING3
+	{SPR_NULL, 0,            1,  {A_MinusDigging}, 0, 0, S_MINUS_DIGGING1}, // S_MINUS_DIGGING4
+	{SPR_NULL, 0,            25, {NULL},           0, 0, S_MINUS_POPUP},    // S_MINUS_BURST0
+	{SPR_MNUD, FF_ANIMATE,   5,  {NULL},           1, 2, S_MINUS_BURST2},   // S_MINUS_BURST1
+	{SPR_MNUD, 1|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUS_BURST3},   // S_MINUS_BURST2
+	{SPR_MNUD, 2|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUS_BURST4},   // S_MINUS_BURST3
+	{SPR_MNUD, 3|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUS_BURST5},   // S_MINUS_BURST4
+	{SPR_MNUD, 4|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUSDIRT2},     // S_MINUS_BURST5
 	{SPR_MNUS,  0, 1, {A_MinusPopup},   0, 0, S_MINUS_UPWARD1},   // S_MINUS_POPUP
 	{SPR_MNUS,  0, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD2},   // S_MINUS_UPWARD1
 	{SPR_MNUS,  1, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD3},   // S_MINUS_UPWARD2
@@ -984,6 +1110,14 @@ state_t states[NUMSTATES] =
 	{SPR_MNUS, 14, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD8}, // S_MINUS_DOWNWARD7
 	{SPR_MNUS, 15, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD1}, // S_MINUS_DOWNWARD8
 
+	{SPR_MNUD, FF_ANIMATE, 6, {NULL}, 1, 5, S_MINUSDIRT2}, // S_MINUSDIRT1
+	{SPR_MNUD, 5,          8, {NULL}, 3, 5, S_MINUSDIRT3}, // S_MINUSDIRT2
+	{SPR_MNUD, 4,          8, {NULL}, 3, 5, S_MINUSDIRT4}, // S_MINUSDIRT3
+	{SPR_MNUD, 3,          8, {NULL}, 3, 5, S_MINUSDIRT5}, // S_MINUSDIRT4
+	{SPR_MNUD, 2,          8, {NULL}, 3, 5, S_MINUSDIRT6}, // S_MINUSDIRT5
+	{SPR_MNUD, 1,          8, {NULL}, 3, 5, S_MINUSDIRT7}, // S_MINUSDIRT6
+	{SPR_MNUD, 0,          8, {NULL}, 3, 5, S_NULL},       // S_MINUSDIRT7
+
 	// Spring Shell
 	{SPR_SSHL,  0,  4, {A_Look},  0, 0, S_SSHELL_STND},    // S_SSHELL_STND
 	{SPR_SSHL,  0,  4, {A_Chase}, 0, 0, S_SSHELL_RUN2},    // S_SSHELL_RUN1
@@ -1011,14 +1145,31 @@ state_t states[NUMSTATES] =
 	{SPR_UNID, 0, 1, {A_Chase},      0, 0, S_UNIDUS_RUN }, // S_UNIDUS_RUN
 	{SPR_UNID, 1, 1, {A_UnidusBall}, 1, 0, S_UNIDUS_BALL}, // S_UNIDUS_BALL
 
+	// Canarivore
+	{SPR_CANA, 0, 5,  {A_Look},          1200*FRACUNIT+1,   1,                 S_CANARIVORE_LOOK},      // S_CANARIVORE_LOOK
+	{SPR_CANA, 0, 3,  {A_PlaySound},     sfx_s3k76,         1,                 S_CANARIVORE_AWAKEN2},   // S_CANARIVORE_AWAKEN1
+	{SPR_CANA, 1, 5,  {NULL},            0,                 0,                 S_CANARIVORE_AWAKEN3},   // S_CANARIVORE_AWAKEN2
+	{SPR_CANA, 2, 8,  {NULL},            0,                 0,                 S_CANARIVORE_GAS1},      // S_CANARIVORE_AWAKEN3
+	{SPR_CANA, 2, 15, {A_PlaySound},     sfx_s3k93,         1,                 S_CANARIVORE_GAS2},      // S_CANARIVORE_GAS1
+	{SPR_CANA, 1, 4,  {NULL},            0,                 0,                 S_CANARIVORE_GAS3},      // S_CANARIVORE_GAS2
+	{SPR_CANA, 2, 0,  {A_PlaySound},     sfx_s3k97,         1,                 S_CANARIVORE_GAS4},      // S_CANARIVORE_GAS3
+	{SPR_CANA, 2, 5,  {A_CanarivoreGas}, MT_CANARIVORE_GAS, 0,                 S_CANARIVORE_GAS5},      // S_CANARIVORE_GAS4
+	{SPR_CANA, 1, 5,  {NULL},            0,                 0,                 S_CANARIVORE_GASREPEAT}, // S_CANARIVORE_GAS5
+	{SPR_CANA, 2, 0,  {A_Repeat},        6,                 S_CANARIVORE_GAS4, S_CANARIVORE_CLOSE1},    // S_CANARIVORE_GASREPEAT
+	{SPR_CANA, 1, 8,  {NULL},            0,                 0,                 S_CANARIVORE_CLOSE2},     // S_CANARIVORE_CLOSE1
+	{SPR_CANA, 0, 90, {NULL},            sfx_s3k5d,         1,                 S_CANARIVORE_LOOK},      // S_CANARIVORE_CLOSE2
+
+	{SPR_CANG, 0|FF_TRANS90, 2,         {NULL},            0, 0,       S_CANARIVOREGAS_2}, // S_CANARIVOREGAS_1
+	{SPR_CANG, 0|FF_TRANS30, 2*TICRATE, {A_SetRandomTics}, 2, TICRATE, S_CANARIVOREGAS_3}, // S_CANARIVOREGAS_2
+	{SPR_CANG, 0|FF_TRANS40, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_4}, // S_CANARIVOREGAS_3
+	{SPR_CANG, 0|FF_TRANS50, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_5}, // S_CANARIVOREGAS_4
+	{SPR_CANG, 0|FF_TRANS60, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_6}, // S_CANARIVOREGAS_5
+	{SPR_CANG, 0|FF_TRANS70, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_7}, // S_CANARIVOREGAS_6
+	{SPR_CANG, 0|FF_TRANS80, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_8}, // S_CANARIVOREGAS_7
+	{SPR_CANG, 0|FF_TRANS90, 10,        {NULL},            0, 0,       S_NULL},            // S_CANARIVOREGAS_8
+
 	// Boss Explosion
-	{SPR_BOM2, FF_FULLBRIGHT,   5, {NULL}, 0, 0, S_BPLD2}, // S_BPLD1
-	{SPR_BOM2, FF_FULLBRIGHT|1, 5, {NULL}, 0, 0, S_BPLD3}, // S_BPLD2
-	{SPR_BOM2, FF_FULLBRIGHT|2, 5, {NULL}, 0, 0, S_BPLD4}, // S_BPLD3
-	{SPR_BOM2, FF_FULLBRIGHT|3, 5, {NULL}, 0, 0, S_BPLD5}, // S_BPLD4
-	{SPR_BOM2, FF_FULLBRIGHT|4, 5, {NULL}, 0, 0, S_BPLD6}, // S_BPLD5
-	{SPR_BOM2, FF_FULLBRIGHT|5, 5, {NULL}, 0, 0, S_BPLD7}, // S_BPLD6
-	{SPR_BOM2, FF_FULLBRIGHT|6, 5, {NULL}, 0, 0, S_NULL},  // S_BPLD7
+	{SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE
 
 	// S3&K Boss Explosion
 	{SPR_BOM3, FF_FULLBRIGHT,   1, {NULL}, 0, 0, S_SONIC3KBOSSEXPLOSION2}, // S_SONIC3KBOSSEXPLOSION1
@@ -1121,6 +1272,11 @@ state_t states[NUMSTATES] =
 
 	// Boss 3
 	{SPR_EGGO,  0,   1, {NULL},                    0, 0, S_EGGMOBILE3_STND},    // S_EGGMOBILE3_STND
+	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH2},  // S_EGGMOBILE3_LAUGH1
+	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH3},  // S_EGGMOBILE3_LAUGH2
+	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH4},  // S_EGGMOBILE3_LAUGH3
+	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH5},  // S_EGGMOBILE3_LAUGH4
+	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_ATK1},  // S_EGGMOBILE3_LAUGH5
 	{SPR_EGGO,  1,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK2},    // S_EGGMOBILE3_ATK1
 	{SPR_EGGO,  2,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK3A},   // S_EGGMOBILE3_ATK2
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 2, S_EGGMOBILE3_ATK3B},   // S_EGGMOBILE3_ATK3A
@@ -1128,12 +1284,7 @@ state_t states[NUMSTATES] =
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 3, S_EGGMOBILE3_ATK3D},   // S_EGGMOBILE3_ATK3C
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 5, S_EGGMOBILE3_ATK4},    // S_EGGMOBILE3_ATK3D
 	{SPR_EGGO,  4,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK5},    // S_EGGMOBILE3_ATK4
-	{SPR_EGGO,  5,   2, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH1},  // S_EGGMOBILE3_ATK5
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH2},  // S_EGGMOBILE3_LAUGH1
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH3},  // S_EGGMOBILE3_LAUGH2
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH4},  // S_EGGMOBILE3_LAUGH3
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH5},  // S_EGGMOBILE3_LAUGH4
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH6},  // S_EGGMOBILE3_LAUGH5
+	{SPR_EGGO,  5,   2, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH6},  // S_EGGMOBILE3_ATK5
 	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH7},  // S_EGGMOBILE3_LAUGH6
 	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH8},  // S_EGGMOBILE3_LAUGH7
 	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH9},  // S_EGGMOBILE3_LAUGH8
@@ -1180,14 +1331,14 @@ state_t states[NUMSTATES] =
 	// Boss 3 Pinch
 	{SPR_FAKE, 0,  1, {A_BossJetFume},            1, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_INIT
 	{SPR_FAKE, 0,  1, {A_Boss3Path},              0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK2},  // S_FAKEMOBILE_ATK1
+	{SPR_FAKE, 0, 22, {NULL},                     0, 0, S_FAKEMOBILE_ATK2},  // S_FAKEMOBILE_ATK1
 	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK3A}, // S_FAKEMOBILE_ATK2
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 2, S_FAKEMOBILE_ATK3B}, // S_FAKEMOBILE_ATK3A
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 4, S_FAKEMOBILE_ATK3C}, // S_FAKEMOBILE_ATK3B
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 3, S_FAKEMOBILE_ATK3D}, // S_FAKEMOBILE_ATK3C
-	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 5, S_FAKEMOBILE_ATK4},  // S_FAKEMOBILE_ATK3D
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK5},  // S_FAKEMOBILE_ATK4
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_ATK5
+	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 5, S_FAKEMOBILE},       // S_FAKEMOBILE_ATK3D
+	{SPR_FAKE, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE2},  // S_FAKEMOBILE_DIE1
+	{SPR_NULL, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE1},  // S_FAKEMOBILE_DIE2
 
 	// Boss 4
 	{SPR_EGGP, 0, -1, {NULL},           0,          0, S_NULL},              // S_EGGMOBILE4_STND
@@ -1204,16 +1355,9 @@ state_t states[NUMSTATES] =
 	{SPR_EGGP, 9,150, {A_Boss4SpeedUp}, sfx_mswing, 0, S_EGGMOBILE4_RATK6},  // S_EGGMOBILE4_RATK5
 	{SPR_EGGP,10,  2, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_RATK6
 	{SPR_EGGP, 0, 20, {A_Boss4Raise},   sfx_doord1, 0, S_EGGMOBILE4_RAISE2}, // S_EGGMOBILE4_RAISE1
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE3}, // S_EGGMOBILE4_RAISE2
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE4}, // S_EGGMOBILE4_RAISE3
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE5}, // S_EGGMOBILE4_RAISE4
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE6}, // S_EGGMOBILE4_RAISE5
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE7}, // S_EGGMOBILE4_RAISE6
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE8}, // S_EGGMOBILE4_RAISE7
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE9}, // S_EGGMOBILE4_RAISE8
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE10},// S_EGGMOBILE4_RAISE9
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_RAISE10
-	{SPR_EGGP,11, 24, {A_Pain},         0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_PAIN
+	{SPR_EGGP,13|FF_ANIMATE, -1, {NULL},        1,        10, S_NULL},             // S_EGGMOBILE4_RAISE2
+	{SPR_EGGP,11,  0, {A_Boss4Reverse}, sfx_alarm, sfx_s3k60, S_EGGMOBILE4_PAIN2}, // S_EGGMOBILE4_PAIN1
+	{SPR_EGGP,11, 24, {A_Pain},                 0,         0, S_EGGMOBILE4_STND},  // S_EGGMOBILE4_PAIN2
 	{SPR_EGGP,12,  8, {A_Fall},         0,          0, S_EGGMOBILE4_DIE2},   // S_EGGMOBILE4_DIE1
 	{SPR_EGGP,12,  8, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE3},   // S_EGGMOBILE4_DIE2
 	{SPR_EGGP,12,  8, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE4},   // S_EGGMOBILE4_DIE3
@@ -1231,11 +1375,131 @@ state_t states[NUMSTATES] =
 	{SPR_EGGP,13,  5, {NULL},           0,          0, S_EGGMOBILE4_FLEE2},  // S_EGGMOBILE4_FLEE1
 	{SPR_EGGP,14,  5, {NULL},           0,          0, S_EGGMOBILE4_FLEE1},  // S_EGGMOBILE4_FLEE2
 	{SPR_BMCE, 0, -1, {NULL},           0,          0, S_NULL},              // S_EGGMOBILE4_MACE
-
-	// Boss 4 Jet flame
-	{SPR_EFIR, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_JETFLAME2}, // S_JETFLAME1
-	{SPR_EFIR, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_JETFLAME1}, // S_JETFLAME2
-
+	{SPR_BMCE, 0,  2, {A_BossScream},   1, MT_SONIC3KBOSSEXPLODE, S_EGGMOBILE4_MACE_DIE2},  // S_EGGMOBILE4_MACE_DIE1
+	{SPR_NULL, 0,  2, {A_BossScream},   1, MT_SONIC3KBOSSEXPLODE, S_EGGMOBILE4_MACE_DIE3},  // S_EGGMOBILE4_MACE_DIE2
+	{SPR_NULL, 0,  0, {A_Repeat},       7, S_EGGMOBILE4_MACE_DIE1,    S_BOSSEXPLODE},       // S_EGGMOBILE4_MACE_DIE3
+
+	// Boss 4 jet flame
+	{SPR_EFIR, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_JETFLAME
+
+	// Boss 4 Spectator Eggrobo
+	{SPR_EGR1,            0, -1, {NULL}, 0, 0, S_NULL},            // S_EGGROBO1_STND
+	{SPR_EGR1,            5,  2, {NULL}, 0, 0, S_EGGROBO1_BSLAP2}, // S_EGGROBO1_BSLAP1
+	{SPR_EGR1, FF_ANIMATE|6, 35, {NULL}, 1, 2, S_EGGROBO1_STND},   // S_EGGROBO1_BSLAP2
+	{SPR_EGR1, FF_ANIMATE|3, -1, {NULL}, 1, 2, S_NULL},            // S_EGGROBO1_PISSED
+
+	// Boss 4 Spectator Eggrobo jet flame
+	{SPR_EFIR, FF_FULLBRIGHT|2,          -1, {NULL}, 0, 0, S_NULL}, // S_EGGROBOJET
+
+	// Boss 5
+	{SPR_FANG, 2, 16, {A_Look}, 1, 0, S_FANG_IDLE2}, // S_FANG_IDLE1
+	{SPR_FANG, 3, 16, {A_Look}, 1, 0, S_FANG_IDLE3}, // S_FANG_IDLE2
+	{SPR_FANG, 3, 16, {A_Look}, 1, 0, S_FANG_IDLE4}, // S_FANG_IDLE3
+	{SPR_FANG, 3, 16, {A_Look}, 1, 0, S_FANG_IDLE5}, // S_FANG_IDLE4
+	{SPR_FANG, 2, 16, {A_Look}, 1, 0, S_FANG_IDLE6}, // S_FANG_IDLE5
+	{SPR_FANG, 1, 16, {A_Look}, 1, 0, S_FANG_IDLE7}, // S_FANG_IDLE6
+	{SPR_FANG, 1, 16, {A_Look}, 1, 0, S_FANG_IDLE8}, // S_FANG_IDLE7
+	{SPR_FANG, 1, 16, {A_Look}, 1, 0, S_FANG_IDLE1}, // S_FANG_IDLE8
+
+	{SPR_FANG, 14, 0, {A_DoNPCPain}, FRACUNIT, 0, S_FANG_PAIN2}, // S_FANG_PAIN1
+	{SPR_FANG, 14, 1, {A_Boss5CheckOnGround}, S_FANG_PATHINGSTART1, S_FANG_PINCHPATHINGSTART1, S_FANG_PAIN2}, // S_FANG_PAIN2
+
+	{SPR_FANG,  8, 0, {A_Boss5ExtraRepeat}, 5, 4, S_FANG_PATHINGSTART2}, // S_FANG_PATHINGSTART1
+	{SPR_FANG,  8, 0, {A_PlayActiveSound}, 0, 0, S_FANG_PATHING}, // S_FANG_PATHINGSTART2
+	{SPR_FANG,  8, 0, {A_Boss5FindWaypoint}, 0, 0, S_FANG_BOUNCE1}, // S_FANG_PATHING
+
+	{SPR_FANG,  8, 2, {A_Thrust}, 0, 1, S_FANG_BOUNCE2}, // S_FANG_BOUNCE1
+	{SPR_FANG,  9, 2, {NULL}, 0, 0, S_FANG_BOUNCE3}, // S_FANG_BOUNCE2
+	{SPR_FANG, 10, 1, {A_Boss5Jump}, 0, 0, S_FANG_BOUNCE4}, // S_FANG_BOUNCE3
+	{SPR_FANG, 10, 1, {A_Boss5CheckFalling}, S_FANG_CHECKPATH1, S_FANG_FALL1, S_FANG_BOUNCE4}, // S_FANG_BOUNCE4
+
+	{SPR_FANG, 12, 1, {A_Boss5CheckOnGround}, S_FANG_CHECKPATH1, 0, S_FANG_FALL2}, // S_FANG_FALL1
+	{SPR_FANG, 13, 1, {A_Boss5CheckOnGround}, S_FANG_CHECKPATH1, 0, S_FANG_FALL1}, // S_FANG_FALL2
+
+	{SPR_FANG,  8, 0, {A_Boss5Calm}, 0, 0, S_FANG_CHECKPATH2}, // S_FANG_CHECKPATH1
+	{SPR_FANG,  8, 0, {A_Repeat}, 0, S_FANG_PATHINGCONT1, S_FANG_SKID1}, // S_FANG_CHECKPATH2
+
+	{SPR_FANG,  9, 0, {A_Boss5PinchShot}, MT_FBOMB, -16, S_FANG_PATHINGCONT2}, // S_FANG_PATHINGCONT1
+	{SPR_FANG,  9, 0, {A_PlayActiveSound}, 0, 0, S_FANG_PATHINGCONT3}, // S_FANG_PATHINGCONT2
+	{SPR_FANG,  9, 2, {A_Thrust}, 0, 1, S_FANG_PATHING}, // S_FANG_PATHINGCONT3
+
+	{SPR_FANG,  4,  0, {A_PlayAttackSound}, 0, 0, S_FANG_SKID2}, // S_FANG_SKID1
+	{SPR_FANG,  4,  1, {A_DoNPCSkid}, S_FANG_SKID3, 0, S_FANG_SKID2}, // S_FANG_SKID2
+	{SPR_FANG,  4, 10, {NULL}, 0, 0, S_FANG_CHOOSEATTACK}, // S_FANG_SKID3
+
+	{SPR_FANG,  0, 0, {A_RandomState}, S_FANG_LOBSHOT1, S_FANG_FIRESTART1, S_NULL}, // S_FANG_CHOOSEATTACK
+
+	{SPR_FANG,  5,  0, {A_PrepareRepeat}, 3, 0, S_FANG_FIRESTART2}, // S_FANG_FIRESTART1 // Reset loop
+	{SPR_FANG,  5, 18, {A_LookForBetter}, 1, 0, S_FANG_FIRE1}, // S_FANG_FIRESTART2
+	{SPR_FANG,  5,  5, {A_FireShot}, MT_CORK, -16, S_FANG_FIRE2}, // S_FANG_FIRE1 // Start of loop
+	{SPR_FANG,  6,  5, {NULL}, 0, 0, S_FANG_FIRE3}, // S_FANG_FIRE2
+	{SPR_FANG,  7,  5, {NULL}, 0, 0, S_FANG_FIRE4}, // S_FANG_FIRE3
+	{SPR_FANG,  5,  5, {NULL}, 2, 0, S_FANG_FIREREPEAT}, // S_FANG_FIRE4
+	{SPR_FANG,  5,  0, {A_Repeat}, 3, S_FANG_FIRE1, S_FANG_WAIT1}, // S_FANG_FIREREPEAT // End of loop
+
+	{SPR_FANG, 19, 18, {A_LookForBetter}, 1, 0, S_FANG_LOBSHOT2}, // S_FANG_LOBSHOT1
+	{SPR_FANG, 20, 18, {A_BrakLobShot}, MT_FBOMB, 32+(1<<16), S_FANG_WAIT1}, // S_FANG_LOBSHOT2
+
+	{SPR_FANG, FF_ANIMATE|15, 70, {NULL}, 1, 5, S_FANG_WAIT2}, // S_FANG_WAIT1
+	{SPR_FANG,             0, 35, {A_Look}, 1, 0, S_FANG_IDLE1}, // S_FANG_WAIT2
+
+	{SPR_FANG, 12, 1, {A_Boss5CheckOnGround}, S_FANG_PATHINGSTART2, S_FANG_PINCHPATHINGSTART1, S_FANG_WALLHIT}, // S_FANG_WALLHIT
+
+	{SPR_FANG,  8,  0, {A_PrepareRepeat}, 1, 0, S_FANG_PINCHPATHINGSTART2}, // S_FANG_PINCHPATHINGSTART1
+	{SPR_FANG,  8,  0, {A_PlayActiveSound}, 0, 0, S_FANG_PINCHPATHING}, // S_FANG_PINCHPATHINGSTART2
+	{SPR_FANG,  8,  0, {A_Boss5FindWaypoint}, 1, 0, S_FANG_PINCHBOUNCE1}, // S_FANG_PINCHPATHING
+	{SPR_FANG,  8,  2, {A_Thrust}, 0, 1, S_FANG_PINCHBOUNCE2}, // S_FANG_PINCHBOUNCE1
+	{SPR_FANG,  9,  2, {NULL}, 0, 0, S_FANG_PINCHBOUNCE3}, // S_FANG_PINCHBOUNCE2
+	{SPR_FANG, 10,  2, {A_Boss5Jump}, 0, 0, S_FANG_PINCHBOUNCE4}, // S_FANG_PINCHBOUNCE3
+	{SPR_FANG, 10,  1, {A_Boss5CheckFalling}, S_FANG_PINCHSKID1, S_FANG_PINCHFALL1, S_FANG_PINCHBOUNCE4}, // S_FANG_PINCHBOUNCE4
+	{SPR_FANG, 12,  1, {A_Boss5CheckOnGround}, S_FANG_PINCHSKID1, 0, S_FANG_PINCHFALL2}, // S_FANG_PINCHFALL1
+	{SPR_FANG, 13,  1, {A_Boss5CheckOnGround}, S_FANG_PINCHSKID1, 0, S_FANG_PINCHFALL1}, // S_FANG_PINCHFALL2
+	{SPR_FANG,  4,  0, {A_PlayAttackSound}, 0, 0, S_FANG_PINCHSKID2}, // S_FANG_PINCHSKID1
+	{SPR_FANG,  4,  1, {A_DoNPCSkid}, S_FANG_PINCHLOBSHOT1, 0, S_FANG_PINCHSKID2}, // S_FANG_PINCHSKID2
+	{SPR_FANG, 19, 18, {A_FaceTarget}, 3, 0, S_FANG_PINCHLOBSHOT2}, // S_FANG_PINCHLOBSHOT1
+	{SPR_FANG, 20, 30, {A_Boss5MakeItRain}, MT_FBOMB, -16, S_FANG_PINCHLOBSHOT3}, // S_FANG_PINCHLOBSHOT2
+	{SPR_FANG, 19, 18, {A_LinedefExecute}, LE_BOSS4DROP, 0, S_FANG_PINCHLOBSHOT4}, // S_FANG_PINCHLOBSHOT3
+	{SPR_FANG, 19,  0, {A_Boss5Calm}, 0, 0, S_FANG_PATHINGSTART1}, // S_FANG_PINCHLOBSHOT4
+
+	{SPR_FANG, 14, 0, {A_DoNPCPain}, 0, 0, S_FANG_DIE2}, // S_FANG_DIE1
+	{SPR_FANG, 14, 1, {A_Boss5CheckOnGround}, S_FANG_DIE3, 0, S_FANG_DIE2}, // S_FANG_DIE2
+
+	{SPR_FANG, 17,   0, {A_Scream}, 0, 0, S_FANG_DIE4}, // S_FANG_DIE3
+	{SPR_FANG, 17, 104, {NULL}, 0, 0, S_FANG_DIE5}, // S_FANG_DIE4
+
+	{SPR_FANG, 11, 0, {A_PlaySound}, sfx_jump, 0, S_FANG_DIE6}, // S_FANG_DIE5
+	{SPR_FANG, 11, 1, {A_ZThrust}, 6, (1<<16)|1, S_FANG_DIE7}, // S_FANG_DIE6
+	{SPR_FANG, 11, 1, {A_Boss5CheckFalling}, S_FANG_FLEEPATHING1, S_FANG_DIE8, S_FANG_DIE7}, // S_FANG_DIE7
+	{SPR_FANG, 12, 1, {A_Boss5CheckOnGround}, S_FANG_FLEEPATHING1, 0, S_FANG_DIE8}, // S_FANG_DIE8
+
+	{SPR_FANG,  9,  0, {A_PlayActiveSound}, 0, 0, S_FANG_FLEEPATHING2}, // S_FANG_FLEEPATHING1
+	{SPR_FANG,  8,  2, {A_Boss5FindWaypoint}, 2, 0, S_FANG_FLEEBOUNCE1}, // S_FANG_FLEEPATHING2
+	{SPR_FANG,  9,  2, {NULL}, 0, 0, S_FANG_FLEEBOUNCE2}, // S_FANG_FLEEBOUNCE1
+	{SPR_FANG, 10, -1, {A_BossDeath}, 0, 0, S_NULL}, // S_FANG_FLEEBOUNCE2
+
+	{SPR_FANG, 18, 7*TICRATE, {NULL}, 0, 0, S_NULL}, // S_FANG_KO
+
+	{SPR_FBOM, 0, 1, {A_GhostMe}, 0, 0, S_FBOMB2}, // S_FBOMB1
+	{SPR_FBOM, 1, 1, {A_GhostMe}, 0, 0, S_FBOMB1}, // S_FBOMB2
+	{SPR_BARX, 0|FF_FULLBRIGHT, 3, {A_SetObjectFlags}, MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP, 0, S_FBOMB_EXPL2}, // S_FBOMB_EXPL1
+	{SPR_BARX, 1|FF_FULLBRIGHT, 2, {A_Boss5BombExplode}, MT_TNTDUST, 0, S_FBOMB_EXPL3}, // S_FBOMB_EXPL2
+	{SPR_BARX, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_FBOMB_EXPL4}, // S_FBOMB_EXPL3
+	{SPR_BARX, 2|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_FBOMB_EXPL5}, // S_FBOMB_EXPL4
+	{SPR_BARX, 3|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_FBOMB_EXPL6}, // S_FBOMB_EXPL5
+	{SPR_NULL, 0, 2*TICRATE, {NULL}, 0, 0, S_NULL}, // S_FBOMB_EXPL6
+	{SPR_BARD, 0|FF_TRANS90, 2, {NULL}, 0, 0, S_TNTDUST_2}, // S_TNTDUST_1
+	{SPR_BARD, 0|FF_TRANS30, 2*TICRATE, {A_SetRandomTics}, 2, TICRATE, S_TNTDUST_3}, // S_TNTDUST_2
+	{SPR_BARD, 0|FF_TRANS40, 10, {NULL}, 0, 0, S_TNTDUST_4}, // S_TNTDUST_3
+	{SPR_BARD, 0|FF_TRANS50, 10, {NULL}, 0, 0, S_TNTDUST_5}, // S_TNTDUST_4
+	{SPR_BARD, 0|FF_TRANS60, 10, {NULL}, 0, 0, S_TNTDUST_6}, // S_TNTDUST_5
+	{SPR_BARD, 0|FF_TRANS70, 10, {NULL}, 0, 0, S_TNTDUST_7}, // S_TNTDUST_6
+	{SPR_BARD, 0|FF_TRANS80, 10, {NULL}, 0, 0, S_TNTDUST_8}, // S_TNTDUST_7
+	{SPR_BARD, 0|FF_TRANS90, 10, {NULL}, 0, 0, S_NULL}, // S_TNTDUST_8
+	{SPR_FSGN, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_FSGNA
+	{SPR_FSGN, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_FSGNB
+	{SPR_FSGN, 2|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_FSGNC
+
+	// Black Eggman (Boss 7)
 	{SPR_BRAK, 0, 1, {A_SetReactionTime}, 0, 0, S_BLACKEGG_STND2}, // S_BLACKEGG_STND
 	{SPR_BRAK, 0, 7, {A_Look}, 1, 0, S_BLACKEGG_STND2}, // S_BLACKEGG_STND2
 	{SPR_BRAK, 1, 7, {NULL}, 0, 0, S_BLACKEGG_WALK2}, // S_BLACKEGG_WALK1
@@ -1305,7 +1569,7 @@ state_t states[NUMSTATES] =
 
 	{SPR_BRAK, 21, 3*TICRATE, {NULL}, 0, 0, S_BLACKEGG_DESTROYPLAT2}, // S_BLACKEGG_DESTROYPLAT1
 	{SPR_BRAK, 21, 1, {A_PlaySound}, sfx_s3k54, 0, S_BLACKEGG_DESTROYPLAT3}, // S_BLACKEGG_DESTROYPLAT2
-	{SPR_BRAK, 21, 14, {A_LinedefExecute}, 4200, 0, S_BLACKEGG_STND}, // S_BLACKEGG_DESTROYPLAT3
+	{SPR_BRAK, 21, 14, {A_LinedefExecute}, LE_BRAKPLATFORM, 0, S_BLACKEGG_STND}, // S_BLACKEGG_DESTROYPLAT3
 
 	{SPR_NULL, 0, 1, {A_CapeChase}, (160 - 20) << 16, 0, S_BLACKEGG_HELPER}, // S_BLACKEGG_HELPER
 
@@ -1369,12 +1633,12 @@ state_t states[NUMSTATES] =
 	{SPR_RCKT, 2 + FF_FULLBRIGHT, 6, {A_NapalmScatter}, MT_CYBRAKDEMON_NAPALM_FLAMES + (6<<16), 32 + (16<<16), S_CYBRAKDEMONMISSILE_EXPLODE3}, // S_CYBRAKDEMONMISSILE_EXPLODE2
 	{SPR_RCKT, 3 + FF_FULLBRIGHT, 4, {NULL}, 0, 0, S_NULL}, // S_CYBRAKDEMONMISSILE_EXPLODE3
 
-	{SPR_FLME, FF_TRANS20|FF_FULLBRIGHT  , 15, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY2}, // S_CYBRAKDEMONFLAMESHOT_FLY1
-	{SPR_FLME, FF_TRANS20|FF_FULLBRIGHT|1, 15, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY3}, // S_CYBRAKDEMONFLAMESHOT_FLY2
-	{SPR_FLME, FF_TRANS20|FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY3}, // S_CYBRAKDEMONFLAMESHOT_FLY3
-	{SPR_FLME, FF_TRANS20|FF_FULLBRIGHT|2, 0, {A_SpawnObjectRelative}, 0, MT_CYBRAKDEMON_FLAMEREST, S_NULL}, // S_CYBRAKDEMONFLAMESHOT_DIE
+	{SPR_FLME, FF_FULLBRIGHT  , 15, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY2}, // S_CYBRAKDEMONFLAMESHOT_FLY1
+	{SPR_FLME, FF_FULLBRIGHT|1, 15, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY3}, // S_CYBRAKDEMONFLAMESHOT_FLY2
+	{SPR_FLME, FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_CYBRAKDEMONFLAMESHOT_FLY3}, // S_CYBRAKDEMONFLAMESHOT_FLY3
+	{SPR_FLME, FF_FULLBRIGHT|2, 0, {A_SpawnObjectRelative}, 0, MT_CYBRAKDEMON_FLAMEREST, S_NULL}, // S_CYBRAKDEMONFLAMESHOT_DIE
 
-	{SPR_FLAM, FF_TRANS20|FF_FULLBRIGHT|5, 3, {A_SetFuse}, 10*TICRATE, 0, S_FLAMEREST}, // S_CYBRAKDEMONFLAMEREST
+	{SPR_FLAM, FF_FULLBRIGHT, 0, {A_SetFuse}, 10*TICRATE, 0, S_FLAMEREST}, // S_CYBRAKDEMONFLAMEREST
 
 	{SPR_ELEC, 0 + FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_CYBRAKDEMONELECTRICBARRIER_INIT2}, // S_CYBRAKDEMONELECTRICBARRIER_INIT1
 	{SPR_ELEC, 0 + FF_FULLBRIGHT, 0, {A_RemoteAction}, -1, S_CYBRAKDEMON_INVINCIBLERIZE, S_CYBRAKDEMONELECTRICBARRIER_PLAYSOUND}, // S_CYBRAKDEMONELECTRICBARRIER_INIT2
@@ -1490,20 +1754,23 @@ state_t states[NUMSTATES] =
 	{SPR_METL,  9,  2, {NULL}, 0, 0, S_METALSONIC_RUN1},  // S_METALSONIC_RUN4
 
 	{SPR_METL,  4, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_FLOAT
-	{SPR_METL, 12, -1, {NULL},         0, 0, S_METALSONIC_STUN},  // S_METALSONIC_VECTOR
-	{SPR_METL,  0, -1, {NULL},         0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN
-	{SPR_METL, 13, 40, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE
+	{SPR_METL, 12|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_METALSONIC_STUN}, // S_METALSONIC_VECTOR
+	{SPR_METL, 11, -1, {NULL},         0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN
+	{SPR_METL, 13, 20, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE
 	{SPR_METL, 14, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_GATHER
 	{SPR_METL, 15, -1, {NULL},         0, 0, S_METALSONIC_BOUNCE},// S_METALSONIC_DASH
 	{SPR_METL, 14, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_BOUNCE
 	{SPR_METL, 16, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_BADBOUNCE
 	{SPR_METL, 13, -1, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_SHOOT
 	{SPR_METL, 11, 40, {A_Pain},       0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_PAIN
-	{SPR_METL, 11, -1, {A_BossDeath},  0, 0, S_NULL},             // S_METALSONIC_DEATH
-	{SPR_METL,  3,  4, {NULL},         0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
-	{SPR_METL,  4,  4, {A_BossScream}, 0, 0, S_METALSONIC_FLEE3}, // S_METALSONIC_FLEE2
-	{SPR_METL,  5,  4, {NULL},         0, 0, S_METALSONIC_FLEE4}, // S_METALSONIC_FLEE3
-	{SPR_METL,  4,  4, {NULL},         0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE4
+	{SPR_METL, 13,  8, {A_Fall},       0, 0, S_METALSONIC_DEATH2},// S_METALSONIC_DEATH1
+	{SPR_METL, 13,  8, {A_BossScream}, 0, 0, S_METALSONIC_DEATH3},// S_METALSONIC_DEATH2
+	{SPR_METL, 13, 0, {A_Repeat}, 11, S_METALSONIC_DEATH2, S_METALSONIC_DEATH4}, // S_METALSONIC_DEATH3
+	{SPR_METL, 13, -1, {A_BossDeath},  0, 0, S_NULL},             // S_METALSONIC_DEATH4
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
+	{SPR_METL, 11,  4, {A_BossScream}, 0, 0, S_METALSONIC_FLEE3}, // S_METALSONIC_FLEE2
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE4}, // S_METALSONIC_FLEE3
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE4
 
 	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 0, 1, {NULL}, 0, 0, S_MSSHIELD_F2},  // S_MSSHIELD_F1
 	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 1, 1, {NULL}, 0, 0, S_MSSHIELD_F3},  // S_MSSHIELD_F2
@@ -1521,18 +1788,32 @@ state_t states[NUMSTATES] =
 	// Ring
 	{SPR_RING, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_RING}, // S_RING
 
-	// Blue Sphere Replacement for special stages
-	{SPR_BBAL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLUEBALL
-	{SPR_BBAL, 0, 20, {NULL}, 0, 0, S_NULL}, // S_BLUEBALLSPARK
+	// Blue Sphere for special stages
+	{SPR_SPHR, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERE
+	{SPR_SPHR, FF_FULLBRIGHT
+#ifdef MANIASPHERES
+							|FF_ANIMATE|FF_RANDOMANIM
+#endif
+													, -1, {NULL}, 1, 4, S_NULL}, // S_BLUESPHEREBONUS
+	{SPR_SPHR, 0, 20, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERESPARK
 
-	// Gravity Well sprites for Egg Rock's Special Stage
-	{SPR_GWLG, 0, 1, {NULL}, 0, 0, S_GRAVWELLGREEN2}, // S_GRAVWELLGREEN
-	{SPR_GWLG, 1, 1, {NULL}, 0, 0, S_GRAVWELLGREEN3}, // S_GRAVWELLGREEN2
-	{SPR_GWLG, 2, 1, {NULL}, 0, 0, S_GRAVWELLGREEN},  // S_GRAVWELLGREEN3
+	// Bomb Sphere
+	{SPR_SPHR, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_BOMBSPHERE2}, // S_BOMBSPHERE1
+	{SPR_SPHR, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE3}, // S_BOMBSPHERE2
+	{SPR_SPHR, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_BOMBSPHERE4}, // S_BOMBSPHERE3
+	{SPR_SPHR, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE1}, // S_BOMBSPHERE4
 
-	{SPR_GWLR, 0, 1, {NULL}, 0, 0, S_GRAVWELLRED2},   // S_GRAVWELLRED
-	{SPR_GWLR, 1, 1, {NULL}, 0, 0, S_GRAVWELLRED3},   // S_GRAVWELLRED2
-	{SPR_GWLR, 2, 1, {NULL}, 0, 0, S_GRAVWELLRED},    // S_GRAVWELLRED3
+	// NiGHTS Chip
+	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE,    -1, {NULL}, 15, 2, S_NULL}, // S_NIGHTSCHIP
+	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE|16, -1, {NULL}, 15, 2, S_NULL}, // S_NIGHTSCHIPBONUS
+
+	// NiGHTS Star
+	{SPR_NSTR, FF_ANIMATE, -1, {NULL}, 14, 2, S_NULL}, // S_NIGHTSSTAR
+	{SPR_NSTR, 15, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSSTARXMAS
+
+	// Gravity Well sprites for Egg Rock's Special Stage
+	{SPR_GWLG, FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_GRAVWELLGREEN
+	{SPR_GWLR, FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_GRAVWELLRED
 
 	// Individual Team Rings (now with shield attracting action! =P)
 	{SPR_TRNG, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_TEAMRING},  // S_TEAMRING
@@ -1581,15 +1862,10 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT|5, -1, {NULL}, 0, 0, S_NULL}, // S_CEMG6
 	{SPR_CEMG, FF_FULLBRIGHT|6, -1, {NULL}, 0, 0, S_NULL}, // S_CEMG7
 
-	// Emeralds (for hunt)
-	{SPR_EMER, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EMER1
-
-	// Fan
-	{SPR_FANS, 0, 1, {A_FanBubbleSpawn}, 2048, 0, S_FAN2}, // S_FAN
-	{SPR_FANS, 1, 1, {A_FanBubbleSpawn}, 1024, 0, S_FAN3}, // S_FAN2
-	{SPR_FANS, 2, 1, {A_FanBubbleSpawn},  512, 0, S_FAN4}, // S_FAN3
-	{SPR_FANS, 3, 1, {A_FanBubbleSpawn}, 1024, 0, S_FAN5}, // S_FAN4
-	{SPR_FANS, 4, 1, {A_FanBubbleSpawn},  512, 0, S_FAN},  // S_FAN5
+	// Emerald hunt shards
+	{SPR_SHRD, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD1
+	{SPR_SHRD, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD2
+	{SPR_SHRD, 2, -1, {NULL}, 0, 0, S_NULL}, // S_SHRD3
 
 	// Bubble Source
 	{SPR_BBLS, 0, 8, {A_BubbleSpawn}, 2048, 0, S_BUBBLES2}, // S_BUBBLES1
@@ -1652,25 +1928,15 @@ state_t states[NUMSTATES] =
 	{SPR_SIGN, 3, -1, {NULL}, 0, 0, S_NULL},         // S_SIGN52 Eggman
 	{SPR_SIGN, 7, -1, {A_SignPlayer}, 0, 0, S_NULL}, // S_SIGN53 Blank
 
-	// Steam Riser
-	{SPR_STEM, 0, 2, {A_SetSolidSteam}, 0, 0, S_STEAM2},   // S_STEAM1
-	{SPR_STEM, 1, 2, {A_UnsetSolidSteam}, 0, 0, S_STEAM3}, // S_STEAM2
-	{SPR_STEM, 2, 2, {NULL}, 0, 0, S_STEAM4},              // S_STEAM3
-	{SPR_STEM, 3, 2, {NULL}, 0, 0, S_STEAM5},              // S_STEAM4
-	{SPR_STEM, 4, 2, {NULL}, 0, 0, S_STEAM6},              // S_STEAM5
-	{SPR_STEM, 5, 2, {NULL}, 0, 0, S_STEAM7},              // S_STEAM6
-	{SPR_STEM, 6, 2, {NULL}, 0, 0, S_STEAM8},              // S_STEAM7
-	{SPR_STEM, 7, 18, {NULL}, 0, 0, S_STEAM1},             // S_STEAM8
-
 	// Spike Ball
-	{SPR_SPIK, 0, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL2}, // S_SPIKEBALL1
-	{SPR_SPIK, 1, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL3}, // S_SPIKEBALL2
-	{SPR_SPIK, 2, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL4}, // S_SPIKEBALL3
-	{SPR_SPIK, 3, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL5}, // S_SPIKEBALL4
-	{SPR_SPIK, 4, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL6}, // S_SPIKEBALL5
-	{SPR_SPIK, 5, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL7}, // S_SPIKEBALL6
-	{SPR_SPIK, 6, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL8}, // S_SPIKEBALL7
-	{SPR_SPIK, 7, 1, {A_RotateSpikeBall}, 0, 0, S_SPIKEBALL1}, // S_SPIKEBALL8
+	{SPR_SPIK, 0, 1, {NULL}, 0, 0, S_SPIKEBALL2}, // S_SPIKEBALL1
+	{SPR_SPIK, 1, 1, {NULL}, 0, 0, S_SPIKEBALL3}, // S_SPIKEBALL2
+	{SPR_SPIK, 2, 1, {NULL}, 0, 0, S_SPIKEBALL4}, // S_SPIKEBALL3
+	{SPR_SPIK, 3, 1, {NULL}, 0, 0, S_SPIKEBALL5}, // S_SPIKEBALL4
+	{SPR_SPIK, 4, 1, {NULL}, 0, 0, S_SPIKEBALL6}, // S_SPIKEBALL5
+	{SPR_SPIK, 5, 1, {NULL}, 0, 0, S_SPIKEBALL7}, // S_SPIKEBALL6
+	{SPR_SPIK, 6, 1, {NULL}, 0, 0, S_SPIKEBALL8}, // S_SPIKEBALL7
+	{SPR_SPIK, 7, 1, {NULL}, 0, 0, S_SPIKEBALL1}, // S_SPIKEBALL8
 
 	// Elemental Shield's Spawn
 	{SPR_SFLM, FF_FULLBRIGHT,   2, {NULL}, 0, 0, S_SPINFIRE2}, // S_SPINFIRE1
@@ -1709,14 +1975,18 @@ state_t states[NUMSTATES] =
 	{SPR_STPT, FF_ANIMATE|15,  2, {NULL},  1, 1, S_STARPOST_FLASH}, // S_STARPOST_ENDSPIN
 
 	// Big floating mine
-	{SPR_BMNE, 0, 5, {NULL}, 0, 0, S_BIGMINE2},    // S_BIGMINE1
-	{SPR_BMNE, 1, 5, {NULL}, 0, 0, S_BIGMINE3},    // S_BIGMINE2
-	{SPR_BMNE, 2, 5, {NULL}, 0, 0, S_BIGMINE4},    // S_BIGMINE3
-	{SPR_BMNE, 3, 5, {NULL}, 0, 0, S_BIGMINE5},    // S_BIGMINE4
-	{SPR_BMNE, 4, 5, {NULL}, 0, 0, S_BIGMINE6},    // S_BIGMINE5
-	{SPR_BMNE, 5, 5, {NULL}, 0, 0, S_BIGMINE7},    // S_BIGMINE6
-	{SPR_BMNE, 6, 5, {NULL}, 0, 0, S_BIGMINE8},    // S_BIGMINE7
-	{SPR_BMNE, 7, 5, {NULL}, 0, 0, S_BIGMINE1},    // S_BIGMINE8
+	{SPR_BMNE, 0,  2, {A_Look},      ((224<<FRACBITS)|1), 0, S_BIGMINE_IDLE},   // S_BIGMINE_IDLE
+	{SPR_BMNE, 1,  2, {A_MineRange}, 112,                 0, S_BIGMINE_ALERT2}, // S_BIGMINE_ALERT1
+	{SPR_BMNE, 2,  2, {A_MineRange}, 112,                 0, S_BIGMINE_ALERT3}, // S_BIGMINE_ALERT2
+	{SPR_BMNE, 0,  1, {A_Look},      ((224<<FRACBITS)|1), 1, S_BIGMINE_IDLE},   // S_BIGMINE_ALERT3
+	{SPR_BMNE, 3, 25, {A_Pain},           0,            0, S_BIGMINE_SET2},   // S_BIGMINE_SET1
+	{SPR_BMNE, 3, 10, {A_SetObjectFlags}, MF_SHOOTABLE, 1, S_BIGMINE_SET3},   // S_BIGMINE_SET1
+	{SPR_BMNE, 3,  1, {A_MineExplode},    0,            0, S_BIGMINE_BLAST1}, // S_BIGMINE_SET3
+	{SPR_BMNB,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_BIGMINE_BLAST2}, // S_BIGMINE_BLAST1
+	{SPR_BMNB, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_BIGMINE_BLAST3}, // S_BIGMINE_BLAST2
+	{SPR_BMNB, 2|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BIGMINE_BLAST4}, // S_BIGMINE_BLAST3
+	{SPR_BMNB, 3|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_BIGMINE_BLAST5}, // S_BIGMINE_BLAST4
+	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BIGMINE_BLAST5
 
 	// Cannon launcher
 	{SPR_NULL, 0, 1,    {A_FindTarget},     MT_PLAYER,         0, S_CANNONLAUNCHER2}, // S_CANNONLAUNCHER1
@@ -1724,9 +1994,10 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, 2, {A_SetRandomTics},     TICRATE/2, 3*TICRATE, S_CANNONLAUNCHER1}, // S_CANNONLAUNCHER3
 
 	// Monitor Miscellany
-	{SPR_NSPK, FF_TRANS40, 20, {NULL}, 0, 0, S_BOXSPARKLE2}, // S_BOXSPARKLE1
-	{SPR_NSPK, FF_TRANS60, 10, {NULL}, 0, 0, S_BOXSPARKLE3}, // S_BOXSPARKLE2
-	{SPR_NSPK, FF_TRANS80,  5, {NULL}, 0, 0, S_NULL}, // S_BOXSPARKLE3
+	{SPR_NSPK, 0, 16, {NULL}, 0, 0, S_BOXSPARKLE2}, // S_BOXSPARKLE1
+	{SPR_NSPK, 1, 12, {NULL}, 0, 0, S_BOXSPARKLE3}, // S_BOXSPARKLE2
+	{SPR_NSPK, 2,  8, {NULL}, 0, 0, S_BOXSPARKLE4}, // S_BOXSPARKLE3
+	{SPR_NSPK, 3,  4, {NULL}, 0, 0, S_NULL},        // S_BOXSPARKLE4
 
 	{SPR_MSTV, 0,  1, {NULL}, 0, 0, S_SPAWNSTATE},  // S_BOX_FLICKER
 	{SPR_MSTV, 0,  4, {A_MonitorPop}, 0, 0, S_BOX_POP2}, // S_BOX_POP1
@@ -1875,24 +2146,19 @@ state_t states[NUMSTATES] =
 
 	{SPR_CBLL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CANNONBALL1
 
-	{SPR_AROW, 0, 1, {A_ArrowCheck}, 0, 0, S_ARROW},     // S_ARROW
-	{SPR_AROW, 1, 1, {A_ArrowCheck}, 0, 0, S_ARROWUP},   // S_ARROWUP
-	{SPR_AROW, 2, 1, {A_ArrowCheck}, 0, 0, S_ARROWDOWN}, // S_ARROWDOWN
+	{SPR_AROW, 0, -1, {NULL}, 0, 0, S_NULL}, // S_ARROW
+	{SPR_AROW, FF_ANIMATE, TICRATE, {A_ArrowBonks}, 7, 2, S_NULL}, // S_ARROWBONK
 
-	{SPR_CFIR, FF_FULLBRIGHT,   2, {NULL}, 0, 0, S_DEMONFIRE2}, // S_DEMONFIRE1
-	{SPR_CFIR, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_DEMONFIRE3}, // S_DEMONFIRE2
-	{SPR_CFIR, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEMONFIRE4}, // S_DEMONFIRE3
-	{SPR_CFIR, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_DEMONFIRE5}, // S_DEMONFIRE4
-	{SPR_CFIR, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_DEMONFIRE6}, // S_DEMONFIRE5
-	{SPR_CFIR, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_DEMONFIRE1}, // S_DEMONFIRE6
+	{SPR_CFIR, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 5, 2, S_NULL}, // S_DEMONFIRE
 
-	// GFZ Flower
+	// GFZ flowers
 	{SPR_FWR1, FF_ANIMATE, -1, {NULL},  7, 3, S_NULL}, // S_GFZFLOWERA
 	{SPR_FWR2, FF_ANIMATE, -1, {NULL}, 19, 3, S_NULL}, // S_GFZFLOWERB
 	{SPR_FWR3, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_GFZFLOWERC
 
-	{SPR_BUS1, 0, -1, {NULL}, 0, 0, S_NULL},       // S_BERRYBUSH
-	{SPR_BUS2, 0, -1, {NULL}, 0, 0, S_NULL},       // S_BUSH
+	{SPR_BUS3, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLUEBERRYBUSH
+	{SPR_BUS1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BERRYBUSH
+	{SPR_BUS2, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BUSH
 
 	// Trees
 	{SPR_TRE1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_GFZTREE
@@ -1905,9 +2171,28 @@ state_t states[NUMSTATES] =
 	{SPR_TRE4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_POLYGONTREE
 	{SPR_TRE5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHTREE
 	{SPR_TRE5, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHREDTREE
+	{SPR_TRE6, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SPRINGTREE
 
+	// THZ flowers
 	{SPR_THZP, FF_ANIMATE, -1, {NULL},  7, 4, S_NULL}, // S_THZFLOWERA
 	{SPR_FWR5, FF_ANIMATE, -1, {NULL}, 19, 2, S_NULL}, // S_THZFLOWERB
+	{SPR_FWR6, FF_ANIMATE, -1, {NULL}, 19, 2, S_NULL}, // S_THZFLOWERC
+
+	// THZ Steam Whistle tree/bush
+	{SPR_THZT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_THZTREE
+	{SPR_THZT,  1|FF_PAPERSPRITE, 40, {NULL}, 0, 0, S_THZTREEBRANCH2}, // S_THZTREEBRANCH1
+	{SPR_THZT,  2|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH3}, // S_THZTREEBRANCH2
+	{SPR_THZT,  3|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH4}, // S_THZTREEBRANCH3
+	{SPR_THZT,  4|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH5}, // S_THZTREEBRANCH4
+	{SPR_THZT,  5|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH6}, // S_THZTREEBRANCH5
+	{SPR_THZT,  6|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH7}, // S_THZTREEBRANCH6
+	{SPR_THZT,  7|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH8}, // S_THZTREEBRANCH7
+	{SPR_THZT,  8|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH9}, // S_THZTREEBRANCH8
+	{SPR_THZT,  9|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH10}, // S_THZTREEBRANCH9
+	{SPR_THZT, 10|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH11}, // S_THZTREEBRANCH10
+	{SPR_THZT, 11|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH12}, // S_THZTREEBRANCH11
+	{SPR_THZT, 12|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH13}, // S_THZTREEBRANCH12
+	{SPR_THZT, 13|FF_PAPERSPRITE,  4, {NULL}, 0, 0, S_THZTREEBRANCH1}, // S_THZTREEBRANCH13
 
 	// THZ Alarm
 	{SPR_ALRM, FF_FULLBRIGHT, 35, {A_Scream}, 0, 0, S_ALARM1}, // S_ALARM1
@@ -1945,19 +2230,34 @@ state_t states[NUMSTATES] =
 	// Blue Crystal
 	{SPR_BCRY, FF_TRANS30, -1, {NULL}, 0, 0, S_NULL}, // S_BLUECRYSTAL1
 
+	// Kelp
+	{SPR_KELP, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KELP
+
+	// DSZ Stalagmites
+	{SPR_DSTG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_DSZSTALAGMITE
+	{SPR_DSTG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_DSZ2STALAGMITE
+
+	// DSZ Light beam
+	{SPR_LIBE, 0|FF_TRANS80|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {A_LightBeamReset}, 0, 0, S_LIGHTBEAM2}, // S_LIGHTBEAM1
+	{SPR_LIBE, 0|FF_TRANS70|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {NULL}, 0, 0, S_LIGHTBEAM3},  // S_LIGHTBEAM2
+	{SPR_LIBE, 0|FF_TRANS60|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {NULL}, 0, 0, S_LIGHTBEAM4},  // S_LIGHTBEAM3
+	{SPR_LIBE, 0|FF_TRANS50|FF_FULLBRIGHT|FF_PAPERSPRITE, 2, {NULL}, 0, 0, S_LIGHTBEAM5},  // S_LIGHTBEAM4
+	{SPR_LIBE, 0|FF_TRANS40|FF_FULLBRIGHT|FF_PAPERSPRITE, 2, {NULL}, 0, 0, S_LIGHTBEAM6},  // S_LIGHTBEAM5
+	{SPR_LIBE, 0|FF_TRANS30|FF_FULLBRIGHT|FF_PAPERSPRITE, 9, {NULL}, 0, 0, S_LIGHTBEAM7},  // S_LIGHTBEAM6
+	{SPR_LIBE, 0|FF_TRANS40|FF_FULLBRIGHT|FF_PAPERSPRITE, 2, {NULL}, 0, 0, S_LIGHTBEAM8},  // S_LIGHTBEAM7
+	{SPR_LIBE, 0|FF_TRANS50|FF_FULLBRIGHT|FF_PAPERSPRITE, 2, {NULL}, 0, 0, S_LIGHTBEAM9},  // S_LIGHTBEAM8
+	{SPR_LIBE, 0|FF_TRANS60|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {NULL}, 0, 0, S_LIGHTBEAM10}, // S_LIGHTBEAM9
+	{SPR_LIBE, 0|FF_TRANS70|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {NULL}, 0, 0, S_LIGHTBEAM11}, // S_LIGHTBEAM10
+	{SPR_LIBE, 0|FF_TRANS80|FF_FULLBRIGHT|FF_PAPERSPRITE, 4, {NULL}, 0, 0, S_LIGHTBEAM12}, // S_LIGHTBEAM11
+	{SPR_NULL, 0, 2, {A_SetRandomTics}, 4, 35, S_LIGHTBEAM1}, // S_LIGHTBEAM12
+
 	// CEZ Chain
 	{SPR_CHAN, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZCHAIN
 
 	// Flame
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20,    3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME2}, // S_FLAME1
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|1,  3,            {NULL}, 0, 0         , S_FLAME3}, // S_FLAME2
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|2,  3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME4}, // S_FLAME3
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|3,  3,            {NULL}, 0, 0         , S_FLAME5}, // S_FLAME4
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|4,  3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME6}, // S_FLAME5
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|5,  3,            {NULL}, 0, 0         , S_FLAME1}, // S_FLAME6
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS10|6, 24,            {NULL}, 0, 0         , S_NULL},   // S_FLAMEPARTICLE
-
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|FF_ANIMATE, -1, {NULL}, 5, 3, S_FLAME2}, // S_FLAMEREST
+	{SPR_FLAM, FF_FULLBRIGHT|FF_ANIMATE,       3*8, {A_FlameParticle}, 7, 3, S_FLAME}, // S_FLAME
+	{SPR_FLAM, FF_FULLBRIGHT|FF_ANIMATE|8, TICRATE,            {NULL}, 3, 3, S_NULL},  // S_FLAMEPARTICLE
+	{SPR_FLAM, FF_FULLBRIGHT|FF_ANIMATE,        -1,            {NULL}, 7, 3, S_NULL},  // S_FLAMEREST
 
 	// Eggman statue
 	{SPR_ESTA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EGGSTATUE1
@@ -1967,10 +2267,12 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, -1, {A_SlingAppear}, 0, 0, S_NULL},   // S_SLING2
 
 	// CEZ maces and chains
-	{SPR_SMCH, 0, -1, {NULL}, 0, 0, S_SMALLMACECHAIN}, // S_SMALLMACECHAIN
-	{SPR_BMCH, 0, -1, {NULL}, 0, 0, S_BIGMACECHAIN},   // S_BIGMACECHAIN
-	{SPR_SMCE, 0, -1, {NULL}, 0, 0, S_SMALLMACE},      // S_SMALLMACE
-	{SPR_BMCE, 0, -1, {NULL}, 0, 0, S_BIGMACE},        // S_BIGMACE
+	{SPR_SMCH, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SMALLMACECHAIN
+	{SPR_BMCH, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BIGMACECHAIN
+	{SPR_SMCE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SMALLMACE
+	{SPR_BMCE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BIGMACE
+	{SPR_SMCH, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SMALLGRABCHAIN
+	{SPR_BMCH, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BIGGRABCHAIN
 
 	// Yellow spring on a ball
 	{SPR_YSPB, 0, -1, {NULL},   0, 0, S_NULL},              // S_YELLOWSPRINGBALL
@@ -1987,43 +2289,71 @@ state_t states[NUMSTATES] =
 	{SPR_RSPB, 1,  1, {NULL},   0, 0, S_REDSPRINGBALL},  // S_REDSPRINGBALL5
 
 	// Small Firebar
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20,     1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR2},  // S_SMALLFIREBAR1
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 1,  1, {NULL},            0, 0,          S_SMALLFIREBAR3},  // S_SMALLFIREBAR2
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 2,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR4},  // S_SMALLFIREBAR3
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 3,  1, {NULL},            0, 0,          S_SMALLFIREBAR5},  // S_SMALLFIREBAR4
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 4,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR6},  // S_SMALLFIREBAR5
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 5,  1, {NULL},            0, 0,          S_SMALLFIREBAR7},  // S_SMALLFIREBAR6
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 6,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR8},  // S_SMALLFIREBAR7
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 7,  1, {NULL},            0, 0,          S_SMALLFIREBAR9},  // S_SMALLFIREBAR8
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 8,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR10}, // S_SMALLFIREBAR9
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 9,  1, {NULL},            0, 0,          S_SMALLFIREBAR11}, // S_SMALLFIREBAR10
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|10,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR12}, // S_SMALLFIREBAR11
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|11,  1, {NULL},            0, 0,          S_SMALLFIREBAR13}, // S_SMALLFIREBAR12
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|12,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR14}, // S_SMALLFIREBAR13
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|13,  1, {NULL},            0, 0,          S_SMALLFIREBAR15}, // S_SMALLFIREBAR14
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|14,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR16}, // S_SMALLFIREBAR15
-	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|15,  1, {NULL},            0, 0,          S_SMALLFIREBAR1},  // S_SMALLFIREBAR16
+	{SPR_SFBR, FF_FULLBRIGHT,     1, {NULL},            0, 0, S_SMALLFIREBAR2},  // S_SMALLFIREBAR1
+	{SPR_SFBR, FF_FULLBRIGHT| 1,  1, {NULL},            0, 0, S_SMALLFIREBAR3},  // S_SMALLFIREBAR2
+	{SPR_SFBR, FF_FULLBRIGHT| 2,  1, {A_FlameParticle}, 0, 0, S_SMALLFIREBAR4},  // S_SMALLFIREBAR3
+	{SPR_SFBR, FF_FULLBRIGHT| 3,  1, {NULL},            0, 0, S_SMALLFIREBAR5},  // S_SMALLFIREBAR4
+	{SPR_SFBR, FF_FULLBRIGHT| 4,  1, {NULL},            0, 0, S_SMALLFIREBAR6},  // S_SMALLFIREBAR5
+	{SPR_SFBR, FF_FULLBRIGHT| 5,  1, {NULL},            0, 0, S_SMALLFIREBAR7},  // S_SMALLFIREBAR6
+	{SPR_SFBR, FF_FULLBRIGHT| 6,  1, {A_FlameParticle}, 0, 0, S_SMALLFIREBAR8},  // S_SMALLFIREBAR7
+	{SPR_SFBR, FF_FULLBRIGHT| 7,  1, {NULL},            0, 0, S_SMALLFIREBAR9},  // S_SMALLFIREBAR8
+	{SPR_SFBR, FF_FULLBRIGHT| 8,  1, {NULL},            0, 0, S_SMALLFIREBAR10}, // S_SMALLFIREBAR9
+	{SPR_SFBR, FF_FULLBRIGHT| 9,  1, {NULL},            0, 0, S_SMALLFIREBAR11}, // S_SMALLFIREBAR10
+	{SPR_SFBR, FF_FULLBRIGHT|10,  1, {A_FlameParticle}, 0, 0, S_SMALLFIREBAR12}, // S_SMALLFIREBAR11
+	{SPR_SFBR, FF_FULLBRIGHT|11,  1, {NULL},            0, 0, S_SMALLFIREBAR13}, // S_SMALLFIREBAR12
+	{SPR_SFBR, FF_FULLBRIGHT|12,  1, {NULL},            0, 0, S_SMALLFIREBAR14}, // S_SMALLFIREBAR13
+	{SPR_SFBR, FF_FULLBRIGHT|13,  1, {NULL},            0, 0, S_SMALLFIREBAR15}, // S_SMALLFIREBAR14
+	{SPR_SFBR, FF_FULLBRIGHT|14,  1, {A_FlameParticle}, 0, 0, S_SMALLFIREBAR16}, // S_SMALLFIREBAR15
+	{SPR_SFBR, FF_FULLBRIGHT|15,  1, {NULL},            0, 0, S_SMALLFIREBAR1},  // S_SMALLFIREBAR16
 
 	// Big Firebar
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20,     1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR2},  // S_BIGFIREBAR1
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 1,  1, {NULL},            0, 0,          S_BIGFIREBAR3},  // S_BIGFIREBAR2
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 2,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR4},  // S_BIGFIREBAR3
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 3,  1, {NULL},            0, 0,          S_BIGFIREBAR5},  // S_BIGFIREBAR4
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 4,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR6},  // S_BIGFIREBAR5
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 5,  1, {NULL},            0, 0,          S_BIGFIREBAR7},  // S_BIGFIREBAR6
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 6,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR8},  // S_BIGFIREBAR7
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 7,  1, {NULL},            0, 0,          S_BIGFIREBAR9},  // S_BIGFIREBAR8
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 8,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR10}, // S_BIGFIREBAR9
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 9,  1, {NULL},            0, 0,          S_BIGFIREBAR11}, // S_BIGFIREBAR10
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|10,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR12}, // S_BIGFIREBAR11
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|11,  1, {NULL},            0, 0,          S_BIGFIREBAR13}, // S_BIGFIREBAR12
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|12,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR14}, // S_BIGFIREBAR13
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|13,  1, {NULL},            0, 0,          S_BIGFIREBAR15}, // S_BIGFIREBAR14
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|14,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR16}, // S_BIGFIREBAR15
-	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|15,  1, {NULL},            0, 0,          S_BIGFIREBAR1},  // S_BIGFIREBAR16
-
-	// CEZ Flower
-	{SPR_FWR4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZFLOWER1
+	{SPR_BFBR, FF_FULLBRIGHT,     1, {NULL},            0, 0, S_BIGFIREBAR2},  // S_BIGFIREBAR1
+	{SPR_BFBR, FF_FULLBRIGHT| 1,  1, {NULL},            0, 0, S_BIGFIREBAR3},  // S_BIGFIREBAR2
+	{SPR_BFBR, FF_FULLBRIGHT| 2,  1, {A_FlameParticle}, 0, 0, S_BIGFIREBAR4},  // S_BIGFIREBAR3
+	{SPR_BFBR, FF_FULLBRIGHT| 3,  1, {NULL},            0, 0, S_BIGFIREBAR5},  // S_BIGFIREBAR4
+	{SPR_BFBR, FF_FULLBRIGHT| 4,  1, {NULL},            0, 0, S_BIGFIREBAR6},  // S_BIGFIREBAR5
+	{SPR_BFBR, FF_FULLBRIGHT| 5,  1, {NULL},            0, 0, S_BIGFIREBAR7},  // S_BIGFIREBAR6
+	{SPR_BFBR, FF_FULLBRIGHT| 6,  1, {A_FlameParticle}, 0, 0, S_BIGFIREBAR8},  // S_BIGFIREBAR7
+	{SPR_BFBR, FF_FULLBRIGHT| 7,  1, {NULL},            0, 0, S_BIGFIREBAR9},  // S_BIGFIREBAR8
+	{SPR_BFBR, FF_FULLBRIGHT| 8,  1, {NULL},            0, 0, S_BIGFIREBAR10}, // S_BIGFIREBAR9
+	{SPR_BFBR, FF_FULLBRIGHT| 9,  1, {NULL},            0, 0, S_BIGFIREBAR11}, // S_BIGFIREBAR10
+	{SPR_BFBR, FF_FULLBRIGHT|10,  1, {A_FlameParticle}, 0, 0, S_BIGFIREBAR12}, // S_BIGFIREBAR11
+	{SPR_BFBR, FF_FULLBRIGHT|11,  1, {NULL},            0, 0, S_BIGFIREBAR13}, // S_BIGFIREBAR12
+	{SPR_BFBR, FF_FULLBRIGHT|12,  1, {NULL},            0, 0, S_BIGFIREBAR14}, // S_BIGFIREBAR13
+	{SPR_BFBR, FF_FULLBRIGHT|13,  1, {NULL},            0, 0, S_BIGFIREBAR15}, // S_BIGFIREBAR14
+	{SPR_BFBR, FF_FULLBRIGHT|14,  1, {A_FlameParticle}, 0, 0, S_BIGFIREBAR16}, // S_BIGFIREBAR15
+	{SPR_BFBR, FF_FULLBRIGHT|15,  1, {NULL},            0, 0, S_BIGFIREBAR1},  // S_BIGFIREBAR16
+
+	{SPR_FWR4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZFLOWER
+	{SPR_BANR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZPOLE
+
+	{SPR_BANR, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBANNER1
+	{SPR_BANR, FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBANNER2
+
+	{SPR_PINE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_PINETREE
+	{SPR_CEZB, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBUSH1
+	{SPR_CEZB, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBUSH2
+
+	{SPR_CNDL, FF_FULLBRIGHT,   -1, {NULL}, 0, 0, S_NULL}, // S_CANDLE
+	{SPR_CNDL, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_NULL}, // S_CANDLEPRICKET
+
+	{SPR_FLMH, 0, -1, {NULL}, 0, 0, S_NULL}, // S_FLAMEHOLDER
+
+	{SPR_CTRC, FF_FULLBRIGHT|FF_ANIMATE, 8*3, {A_FlameParticle}, 3, 3, S_FIRETORCH}, // S_FIRETORCH
+
+	{SPR_CFLG,                0, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAG
+	{SPR_CFLG, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAGSEG1
+	{SPR_CFLG, FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAGSEG2
+
+	{SPR_CSTA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CRAWLASTATUE
+
+	{SPR_CBBS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_FACESTABBERSTATUE
+
+	{SPR_CBBS, 0, 5, {A_Look}, 768*FRACUNIT, 0, S_SUSPICIOUSFACESTABBERSTATUE_WAIT},   // S_SUSPICIOUSFACESTABBERSTATUE_WAIT
+	{SPR_CBBS, FF_ANIMATE, 23, {NULL},    6, 1, S_SUSPICIOUSFACESTABBERSTATUE_BURST2}, // S_SUSPICIOUSFACESTABBERSTATUE_BURST1
+	{SPR_NULL, 0, 40, {A_StatueBurst}, MT_FACESTABBER, S_FACESTABBER_CHARGE2, S_NULL}, // S_SUSPICIOUSFACESTABBERSTATUE_BURST2
+
+	{SPR_CABR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BRAMBLES
 
 	// Big Tumbleweed
 	{SPR_BTBL, 0, -1, {NULL}, 0, 0, S_NULL},                // S_BIGTUMBLEWEED
@@ -2052,14 +2382,102 @@ state_t states[NUMSTATES] =
 	{SPR_CACT, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI2
 	{SPR_CACT, 2, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI3
 	{SPR_CACT, 3, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI4
+	{SPR_CACT, 4, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI5
+	{SPR_CACT, 5, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI6
+	{SPR_CACT, 6, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI7
+	{SPR_CACT, 7, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI8
+	{SPR_CACT, 8, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI9
+
+	// Warning Signs
+	{SPR_WWSG, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_ARIDSIGN_CAUTION
+	{SPR_WWS2, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_ARIDSIGN_CACTI
+	{SPR_WWS3, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_ARIDSIGN_SHARPTURN
+
+	// Oil lamp
+	{SPR_OILL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_OILLAMP
+	{SPR_OILF, FF_TRANS90|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_OILLAMPFLARE
+
+	// TNT barrel
+	{SPR_BARR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_STND1
+	{SPR_BARX, 0|FF_FULLBRIGHT, 3, {A_SetObjectFlags}, MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP, 0, S_TNTBARREL_EXPL2}, // S_TNTBARREL_EXPL1
+	{SPR_BARX, 1|FF_FULLBRIGHT, 2, {A_TNTExplode}, MT_TNTDUST, 0, S_TNTBARREL_EXPL3}, // S_TNTBARREL_EXPL2
+	{SPR_BARX, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_TNTBARREL_EXPL4}, // S_TNTBARREL_EXPL3
+	{SPR_BARX, 2|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL5}, // S_TNTBARREL_EXPL4
+	{SPR_BARX, 3|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL6}, // S_TNTBARREL_EXPL5
+	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_EXPL6
+	{SPR_BARR, 1|FF_ANIMATE, -1, {NULL}, 7, 2, S_NULL}, // S_TNTBARREL_FLYING
+
+	// TNT proximity shell
+	{SPR_REMT, 0, 10, {A_Look}, 33554433, 0, S_PROXIMITY_TNT}, // S_PROXIMITY_TNT
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER2}, // S_PROXIMITY_TNT_TRIGGER1
+	{SPR_REMT, 0, 16, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER3}, // S_PROXIMITY_TNT_TRIGGER2
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER4}, // S_PROXIMITY_TNT_TRIGGER3
+	{SPR_REMT, 0, 16, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER5}, // S_PROXIMITY_TNT_TRIGGER4
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER6}, // S_PROXIMITY_TNT_TRIGGER5
+	{SPR_REMT, 0, 4, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER7}, // S_PROXIMITY_TNT_TRIGGER6
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER8}, // S_PROXIMITY_TNT_TRIGGER7
+	{SPR_REMT, 0, 4, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER9}, // S_PROXIMITY_TNT_TRIGGER8
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER10}, // S_PROXIMITY_TNT_TRIGGER9
+	{SPR_REMT, 0, 4, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER11}, // S_PROXIMITY_TNT_TRIGGER10
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER12}, // S_PROXIMITY_TNT_TRIGGER11
+	{SPR_REMT, 0, 4, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER13}, // S_PROXIMITY_TNT_TRIGGER12
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER14}, // S_PROXIMITY_TNT_TRIGGER13
+	{SPR_REMT, 0, 2, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER15}, // S_PROXIMITY_TNT_TRIGGER14
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER16}, // S_PROXIMITY_TNT_TRIGGER15
+	{SPR_REMT, 0, 2, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER17}, // S_PROXIMITY_TNT_TRIGGER16
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER18}, // S_PROXIMITY_TNT_TRIGGER17
+	{SPR_REMT, 0, 2, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER19}, // S_PROXIMITY_TNT_TRIGGER18
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER20}, // S_PROXIMITY_TNT_TRIGGER19
+	{SPR_REMT, 0, 2, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER21}, // S_PROXIMITY_TNT_TRIGGER20
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_PROXIMITY_TNT_TRIGGER22}, // S_PROXIMITY_TNT_TRIGGER21
+	{SPR_REMT, 0, 2, {NULL}, 0, 0, S_PROXIMITY_TNT_TRIGGER23}, // S_PROXIMITY_TNT_TRIGGER22
+	{SPR_REMT, 1|FF_FULLBRIGHT, 1, {A_PlayActiveSound}, 0, 0, S_TNTBARREL_EXPL1}, // S_PROXIMITY_TNT_TRIGGER23
+
+	// Dust devil
+	{SPR_NULL, 0, 1, {A_DustDevilThink}, 0, 0, S_DUSTDEVIL}, //S_DUSTDEVIL
+	{SPR_TAZD, 1|FF_PAPERSPRITE|FF_TRANS70, 2 * TICRATE, {NULL}, 0, 0, S_DUSTLAYER2}, // S_DUSTLAYER1
+	{SPR_TAZD, 1|FF_PAPERSPRITE|FF_TRANS70, 5, {NULL}, 0, 0, S_DUSTLAYER3}, // S_DUSTLAYER2
+	{SPR_TAZD, 1|FF_PAPERSPRITE|FF_TRANS80, 5, {NULL}, 0, 0, S_DUSTLAYER4}, // S_DUSTLAYER3
+	{SPR_TAZD, 1|FF_PAPERSPRITE|FF_TRANS80, 5, {NULL}, 0, 0, S_DUSTLAYER5}, // S_DUSTLAYER4
+	{SPR_TAZD, 1|FF_PAPERSPRITE|FF_TRANS90, 5, {NULL}, 0, 0, S_NULL}, // S_DUSTLAYER5
+	{SPR_ADST, 0|FF_ANIMATE, 24, {NULL}, 3, 8, S_NULL}, // S_ARIDDUST1
+	{SPR_ADST, 3|FF_ANIMATE, 24, {NULL}, 3, 8, S_NULL}, // S_ARIDDUST2
+	{SPR_ADST, 6|FF_ANIMATE, 24, {NULL}, 3, 8, S_NULL}, // S_ARIDDUST3
+
+	// Minecart
+	{SPR_NULL, 0,                            1, {NULL},                 0, 0, S_MINECART_IDLE},   // S_MINECART_IDLE
+	{SPR_NULL, 0,                            0, {A_KillSegments},       0, 0, S_TNTBARREL_EXPL3}, // S_MINECART_DTH1
+	{SPR_MCRT, 8|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTEND
+	{SPR_MCRT, 0|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTSEG_FRONT
+	{SPR_MCRT, 1|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTSEG_BACK
+	{SPR_MCRT, 2|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL},                 2, 3, S_NULL},            // S_MINECARTSEG_LEFT
+	{SPR_MCRT, 5|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL},                 2, 3, S_NULL},            // S_MINECARTSEG_RIGHT
+	{SPR_LCKN, 2|FF_FULLBRIGHT,              2, {NULL},                 0, 0, S_NULL},            // S_MINECARTSIDEMARK1
+	{SPR_LCKN, 0|FF_FULLBRIGHT,              2, {NULL},                 0, 0, S_NULL},            // S_MINECARTSIDEMARK2
+	{SPR_MCSP, FF_FULLBRIGHT,                1, {A_MinecartSparkThink}, 0, 0, S_MINECARTSPARK},   // S_MINECARTSPARK
+
+	// Saloon door
+	{SPR_SALD, 0|FF_PAPERSPRITE, -1, {NULL},              0, 0, S_NULL}, // S_SALOONDOOR
+	{SPR_NON2, 0,                -1, {A_SaloonDoorSpawn}, 0, 0, S_NULL}, // S_SALONDOORTHINKER
+
+	// Train cameo
+	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_TRAINCAMEOSPAWNER_2}, // S_TRAINCAMEOSPAWNER_1
+	{SPR_NULL, 0, 14, {A_TrainCameo}, 20, 18, S_TRAINCAMEOSPAWNER_3}, // S_TRAINCAMEOSPAWNER_2
+	{SPR_NULL, 0, 1, {A_Repeat}, 1, 0, S_TRAINCAMEOSPAWNER_4}, // S_TRAINCAMEOSPAWNER_3
+	{SPR_NULL, 0, 18, {A_TrainCameo2}, 20, 44, S_TRAINCAMEOSPAWNER_5}, // S_TRAINCAMEOSPAWNER_4
+	{SPR_NULL, 0, 1, {A_Repeat}, 5, S_TRAINCAMEOSPAWNER_4, S_NULL}, // S_TRAINCAMEOSPAWNER_5
+	{SPR_NULL, 0, 2, {A_SmokeTrailer}, MT_SMOKE, 0, S_TRAINPUFFMAKER}, // S_TRAINPUFFMAKER
+
+	{SPR_ADST, FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_TRAINDUST
+	{SPR_STEA, FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_TRAINSTEAM
 
 	// Flame jet
 	{SPR_NULL, 0, 2*TICRATE, {NULL},             0, 0, S_FLAMEJETSTART}, // S_FLAMEJETSTND
 	{SPR_NULL, 0, 3*TICRATE, {A_ToggleFlameJet}, 0, 0,  S_FLAMEJETSTOP}, // S_FLAMEJETSTART
 	{SPR_NULL, 0,         1, {A_ToggleFlameJet}, 0, 0,  S_FLAMEJETSTND}, // S_FLAMEJETSTOP
-	{SPR_FLME, FF_FULLBRIGHT|FF_TRANS50  ,  4, {NULL}, 0, 0, S_FLAMEJETFLAME2}, // S_FLAMEJETFLAME1
-	{SPR_FLME, FF_FULLBRIGHT|FF_TRANS60|1,  5, {NULL}, 0, 0, S_FLAMEJETFLAME3}, // S_FLAMEJETFLAME2
-	{SPR_FLME, FF_FULLBRIGHT|FF_TRANS70|2, 11, {NULL}, 0, 0,           S_NULL}, // S_FLAMEJETFLAME3
+	{SPR_FLME, FF_FULLBRIGHT  ,  4, {NULL}, 0, 0, S_FLAMEJETFLAME2}, // S_FLAMEJETFLAME1
+	{SPR_FLME, FF_FULLBRIGHT|1,  5, {NULL}, 0, 0, S_FLAMEJETFLAME3}, // S_FLAMEJETFLAME2
+	{SPR_FLME, FF_FULLBRIGHT|2, 11, {NULL}, 0, 0,           S_NULL}, // S_FLAMEJETFLAME3
 
 	// Spinning flame jets
 	// A: Counter-clockwise
@@ -2130,8 +2548,57 @@ state_t states[NUMSTATES] =
 	{SPR_XMS4, 1, -1, {NULL}, 0, 0, S_NULL}, // S_LAMPPOST2
 	{SPR_XMS5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_HANGSTAR
 	// Xmas GFZ bushes
-	{SPR_BUS1, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BERRYBUSH
-	{SPR_BUS2, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BUSH
+	{SPR_BUS3, 1, -1, {NULL}, 0, 0, S_NULL}, // S_XMASBLUEBERRYBUSH
+	{SPR_BUS1, 1, -1, {NULL}, 0, 0, S_NULL}, // S_XMASBERRYBUSH
+	{SPR_BUS2, 1, -1, {NULL}, 0, 0, S_NULL}, // S_XMASBUSH
+	// FHZ
+	{SPR_FHZI, 0, -1, {NULL}, 0, 0, S_NULL}, // S_FHZICE1
+	{SPR_FHZI, 1, -1, {NULL}, 0, 0, S_NULL}, // S_FHZICE2
+
+	// Halloween Scenery
+	// Pumpkins
+	{SPR_PUMK,  0, -1, {NULL}, 0, 0, S_NULL}, // S_JACKO1
+	{SPR_PUMK,  3|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO1OVERLAY_2}, // S_JACKO1OVERLAY_1
+	{SPR_PUMK,  4|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO1OVERLAY_3}, // S_JACKO1OVERLAY_2
+	{SPR_PUMK,  5|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO1OVERLAY_4}, // S_JACKO1OVERLAY_3
+	{SPR_PUMK,  4|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO1OVERLAY_1}, // S_JACKO1OVERLAY_4
+	{SPR_PUMK,  1, -1, {NULL}, 0, 0, S_NULL}, // S_JACKO2
+	{SPR_PUMK,  6|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO2OVERLAY_2}, // S_JACKO2OVERLAY_1
+	{SPR_PUMK,  7|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO2OVERLAY_3}, // S_JACKO2OVERLAY_2
+	{SPR_PUMK,  8|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO2OVERLAY_4}, // S_JACKO2OVERLAY_3
+	{SPR_PUMK,  7|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO2OVERLAY_1}, // S_JACKO2OVERLAY_4
+	{SPR_PUMK,  2, -1, {NULL}, 0, 0, S_NULL}, // S_JACKO3
+	{SPR_PUMK,  9|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO3OVERLAY_2}, // S_JACKO3OVERLAY_1
+	{SPR_PUMK, 10|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO3OVERLAY_3}, // S_JACKO3OVERLAY_2
+	{SPR_PUMK, 11|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO3OVERLAY_4}, // S_JACKO3OVERLAY_3
+	{SPR_PUMK, 10|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_JACKO3OVERLAY_1}, // S_JACKO3OVERLAY_4
+	// Dr Seuss Trees
+	{SPR_HHPL, 2, -1, {A_ConnectToGround}, MT_HHZTREE_PART, 0, S_NULL}, // S_HHZTREE_TOP,
+	{SPR_HHPL, 1, -1, {NULL}, 0, 0, S_NULL}, // S_HHZTREE_TRUNK,
+	{SPR_HHPL, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_HHZTREE_LEAF,
+	// Mushroom
+	{SPR_SHRM, 4,  3, {NULL}, 0, 0, S_HHZSHROOM_2},  // S_HHZSHROOM_1,
+	{SPR_SHRM, 3,  3, {NULL}, 0, 0, S_HHZSHROOM_3},  // S_HHZSHROOM_2,
+	{SPR_SHRM, 2,  2, {NULL}, 0, 0, S_HHZSHROOM_4},  // S_HHZSHROOM_3,
+	{SPR_SHRM, 1,  1, {NULL}, 0, 0, S_HHZSHROOM_5},  // S_HHZSHROOM_4,
+	{SPR_SHRM, 0,  1, {NULL}, 0, 0, S_HHZSHROOM_6},  // S_HHZSHROOM_5,
+	{SPR_SHRM, 1,  4, {NULL}, 0, 0, S_HHZSHROOM_7},  // S_HHZSHROOM_6,
+	{SPR_SHRM, 2,  2, {NULL}, 0, 0, S_HHZSHROOM_8},  // S_HHZSHROOM_7,
+	{SPR_SHRM, 3,  3, {NULL}, 0, 0, S_HHZSHROOM_9},  // S_HHZSHROOM_8,
+	{SPR_SHRM, 4,  3, {NULL}, 0, 0, S_HHZSHROOM_10}, // S_HHZSHROOM_9,
+	{SPR_SHRM, 3,  3, {NULL}, 0, 0, S_HHZSHROOM_11}, // S_HHZSHROOM_10,
+	{SPR_SHRM, 5,  2, {NULL}, 0, 0, S_HHZSHROOM_12}, // S_HHZSHROOM_11,
+	{SPR_SHRM, 6,  1, {NULL}, 0, 0, S_HHZSHROOM_13}, // S_HHZSHROOM_12,
+	{SPR_SHRM, 7,  1, {NULL}, 0, 0, S_HHZSHROOM_14}, // S_HHZSHROOM_13,
+	{SPR_SHRM, 6,  4, {NULL}, 0, 0, S_HHZSHROOM_15}, // S_HHZSHROOM_14,
+	{SPR_SHRM, 5,  2, {NULL}, 0, 0, S_HHZSHROOM_16}, // S_HHZSHROOM_15,
+	{SPR_SHRM, 3,  3, {NULL}, 0, 0, S_HHZSHROOM_1},  // S_HHZSHROOM_16,
+	// Misc
+	{SPR_HHZM, 0, -1, {NULL}, 0, 0, S_NULL}, // S_HHZGRASS,
+	{SPR_HHZM, 1, -1, {NULL}, 0, 0, S_NULL}, // S_HHZTENT1,
+	{SPR_HHZM, 2, -1, {NULL}, 0, 0, S_NULL}, // S_HHZTENT2,
+	{SPR_HHZM, 4, -1, {NULL}, 0, 0, S_NULL}, // S_HHZSTALAGMITE_TALL,
+	{SPR_HHZM, 5, -1, {NULL}, 0, 0, S_NULL}, // S_HHZSTALAGMITE_SHORT,
 
 	// Loads of Botanic Serenity bullshit
 	{SPR_BSZ1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTALLFLOWER_RED
@@ -2152,12 +2619,12 @@ state_t states[NUMSTATES] =
 	{SPR_BSZ3, 3, -1, {NULL}, 0, 0, S_NULL}, // S_BSZSHORTFLOWER_CYAN
 	{SPR_BSZ3, 4, -1, {NULL}, 0, 0, S_NULL}, // S_BSZSHORTFLOWER_YELLOW
 	{SPR_BSZ3, 5, -1, {NULL}, 0, 0, S_NULL}, // S_BSZSHORTFLOWER_ORANGE
-	{SPR_BSZ4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_RED
-	{SPR_BSZ4, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_PURPLE
-	{SPR_BSZ4, 2, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_BLUE
-	{SPR_BSZ4, 3, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_CYAN
-	{SPR_BSZ4, 4, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_YELLOW
-	{SPR_BSZ4, 5, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTULIP_ORANGE
+	{SPR_BST1, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_RED
+	{SPR_BST2, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_PURPLE
+	{SPR_BST3, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_BLUE
+	{SPR_BST4, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_CYAN
+	{SPR_BST5, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_YELLOW
+	{SPR_BST6, FF_ANIMATE, -1, {NULL}, 11, 4, S_NULL}, // S_BSZTULIP_ORANGE
 	{SPR_BSZ5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BSZCLUSTER_RED
 	{SPR_BSZ5, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BSZCLUSTER_PURPLE
 	{SPR_BSZ5, 2, -1, {NULL}, 0, 0, S_NULL}, // S_BSZCLUSTER_BLUE
@@ -2179,9 +2646,9 @@ state_t states[NUMSTATES] =
 	{SPR_BSZ8, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BSZSHRUB
 	{SPR_BSZ8, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BSZCLOVER
 	{SPR_BSZ8, 2, -1, {NULL}, 0, 0, S_NULL}, // S_BIG_PALMTREE_TRUNK
-	{SPR_BSZ8, 3, -1, {NULL}, 0, 0, S_NULL}, // S_BIG_PALMTREE_TOP
+	{SPR_BSZ8, 3, -1, {A_ConnectToGround}, MT_BIG_PALMTREE_TRUNK, 0, S_NULL}, // S_BIG_PALMTREE_TOP
 	{SPR_BSZ8, 4, -1, {NULL}, 0, 0, S_NULL}, // S_PALMTREE_TRUNK
-	{SPR_BSZ8, 5, -1, {NULL}, 0, 0, S_NULL}, // S_PALMTREE_TOP
+	{SPR_BSZ8, 5, -1, {A_ConnectToGround},     MT_PALMTREE_TRUNK, 0, S_NULL}, // S_PALMTREE_TOP
 
 	// Disco ball
 	{SPR_DBAL, FF_FULLBRIGHT,   5, {NULL}, 0, 0, S_DBALL2}, // S_DBALL1
@@ -2211,39 +2678,71 @@ state_t states[NUMSTATES] =
 	{SPR_ARMA, FF_TRANS40|14, 2, {NULL}, 0, 0, S_ARMA16}, // S_ARMA15
 	{SPR_ARMA, FF_TRANS40|15, 2, {NULL}, 0, 0, S_ARMA1 }, // S_ARMA16
 
-	{SPR_ARMF, FF_FULLBRIGHT   , 3, {NULL}, 0, 0, S_ARMF2 }, // S_ARMF1
-	{SPR_ARMF, FF_FULLBRIGHT| 1, 3, {NULL}, 0, 0, S_ARMF3 }, // S_ARMF2
-	{SPR_ARMF, FF_FULLBRIGHT| 2, 3, {NULL}, 0, 0, S_ARMF4 }, // S_ARMF3
-	{SPR_ARMF, FF_FULLBRIGHT| 3, 3, {NULL}, 0, 0, S_ARMF5 }, // S_ARMF4
-	{SPR_ARMF, FF_FULLBRIGHT| 4, 3, {NULL}, 0, 0, S_ARMF6 }, // S_ARMF5
-	{SPR_ARMF, FF_FULLBRIGHT| 5, 3, {NULL}, 0, 0, S_ARMF7 }, // S_ARMF6
-	{SPR_ARMF, FF_FULLBRIGHT| 6, 3, {NULL}, 0, 0, S_ARMF8 }, // S_ARMF7
-	{SPR_ARMF, FF_FULLBRIGHT| 7, 3, {NULL}, 0, 0, S_ARMF9 }, // S_ARMF8
-	{SPR_ARMF, FF_FULLBRIGHT| 8, 3, {NULL}, 0, 0, S_ARMF10}, // S_ARMF9
-	{SPR_ARMF, FF_FULLBRIGHT| 9, 3, {NULL}, 0, 0, S_ARMF11}, // S_ARMF10
-	{SPR_ARMF, FF_FULLBRIGHT|10, 3, {NULL}, 0, 0, S_ARMF12}, // S_ARMF11
-	{SPR_ARMF, FF_FULLBRIGHT|11, 3, {NULL}, 0, 0, S_ARMF13}, // S_ARMF12
-	{SPR_ARMF, FF_FULLBRIGHT|12, 3, {NULL}, 0, 0, S_ARMF14}, // S_ARMF13
-	{SPR_ARMF, FF_FULLBRIGHT|13, 3, {NULL}, 0, 0, S_ARMF15}, // S_ARMF14
-	{SPR_ARMF, FF_FULLBRIGHT|14, 3, {NULL}, 0, 0, S_ARMF16}, // S_ARMF15
-	{SPR_ARMF, FF_FULLBRIGHT|15, 3, {NULL}, 0, 0, S_ARMF1 }, // S_ARMF16
-
-	{SPR_ARMB, FF_FULLBRIGHT| 0, 3, {NULL}, 1, 0, S_ARMB2 }, // S_ARMB1
-	{SPR_ARMB, FF_FULLBRIGHT| 1, 3, {NULL}, 1, 0, S_ARMB3 }, // S_ARMB2
-	{SPR_ARMB, FF_FULLBRIGHT| 2, 3, {NULL}, 1, 0, S_ARMB4 }, // S_ARMB3
-	{SPR_ARMB, FF_FULLBRIGHT| 3, 3, {NULL}, 1, 0, S_ARMB5 }, // S_ARMB4
-	{SPR_ARMB, FF_FULLBRIGHT| 4, 3, {NULL}, 1, 0, S_ARMB6 }, // S_ARMB5
-	{SPR_ARMB, FF_FULLBRIGHT| 5, 3, {NULL}, 1, 0, S_ARMB7 }, // S_ARMB6
-	{SPR_ARMB, FF_FULLBRIGHT| 6, 3, {NULL}, 1, 0, S_ARMB8 }, // S_ARMB7
-	{SPR_ARMB, FF_FULLBRIGHT| 7, 3, {NULL}, 1, 0, S_ARMB9 }, // S_ARMB8
-	{SPR_ARMB, FF_FULLBRIGHT| 8, 3, {NULL}, 1, 0, S_ARMB10}, // S_ARMB9
-	{SPR_ARMB, FF_FULLBRIGHT| 9, 3, {NULL}, 1, 0, S_ARMB11}, // S_ARMB10
-	{SPR_ARMB, FF_FULLBRIGHT|10, 3, {NULL}, 1, 0, S_ARMB12}, // S_ARMB11
-	{SPR_ARMB, FF_FULLBRIGHT|11, 3, {NULL}, 1, 0, S_ARMB13}, // S_ARMB12
-	{SPR_ARMB, FF_FULLBRIGHT|12, 3, {NULL}, 1, 0, S_ARMB14}, // S_ARMB13
-	{SPR_ARMB, FF_FULLBRIGHT|13, 3, {NULL}, 1, 0, S_ARMB15}, // S_ARMB14
-	{SPR_ARMB, FF_FULLBRIGHT|14, 3, {NULL}, 1, 0, S_ARMB16}, // S_ARMB15
-	{SPR_ARMB, FF_FULLBRIGHT|15, 3, {NULL}, 1, 0, S_ARMB1 }, // S_ARMB16
+	{SPR_ARMF, FF_FULLBRIGHT   , 2, {NULL}, 0, 0, S_ARMF2 }, // S_ARMF1
+	{SPR_ARMF, FF_FULLBRIGHT| 1, 2, {NULL}, 0, 0, S_ARMF3 }, // S_ARMF2
+	{SPR_ARMF, FF_FULLBRIGHT| 2, 2, {NULL}, 0, 0, S_ARMF4 }, // S_ARMF3
+	{SPR_ARMF, FF_FULLBRIGHT| 3, 2, {NULL}, 0, 0, S_ARMF5 }, // S_ARMF4
+	{SPR_ARMF, FF_FULLBRIGHT| 4, 2, {NULL}, 0, 0, S_ARMF6 }, // S_ARMF5
+	{SPR_ARMF, FF_FULLBRIGHT| 5, 2, {NULL}, 0, 0, S_ARMF7 }, // S_ARMF6
+	{SPR_ARMF, FF_FULLBRIGHT| 6, 2, {NULL}, 0, 0, S_ARMF8 }, // S_ARMF7
+	{SPR_ARMF, FF_FULLBRIGHT| 7, 2, {NULL}, 0, 0, S_ARMF9 }, // S_ARMF8
+	{SPR_ARMF, FF_FULLBRIGHT| 8, 2, {NULL}, 0, 0, S_ARMF10}, // S_ARMF9
+	{SPR_ARMF, FF_FULLBRIGHT| 9, 2, {NULL}, 0, 0, S_ARMF11}, // S_ARMF10
+	{SPR_ARMF, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_ARMF12}, // S_ARMF11
+	{SPR_ARMF, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_ARMF13}, // S_ARMF12
+	{SPR_ARMF, FF_FULLBRIGHT|12, 2, {NULL}, 0, 0, S_ARMF14}, // S_ARMF13
+	{SPR_ARMF, FF_FULLBRIGHT|13, 2, {NULL}, 0, 0, S_ARMF15}, // S_ARMF14
+	{SPR_ARMF, FF_FULLBRIGHT|14, 2, {NULL}, 0, 0, S_ARMF16}, // S_ARMF15
+	{SPR_ARMF, FF_FULLBRIGHT|15, 2, {NULL}, 0, 0, S_ARMF17}, // S_ARMF16
+	{SPR_ARMB, FF_FULLBRIGHT   , 2, {NULL}, 0, 0, S_ARMF18}, // S_ARMF17
+	{SPR_ARMB, FF_FULLBRIGHT| 1, 2, {NULL}, 0, 0, S_ARMF19}, // S_ARMF18
+	{SPR_ARMB, FF_FULLBRIGHT| 2, 2, {NULL}, 0, 0, S_ARMF20}, // S_ARMF19
+	{SPR_ARMB, FF_FULLBRIGHT| 3, 2, {NULL}, 0, 0, S_ARMF21}, // S_ARMF20
+	{SPR_ARMB, FF_FULLBRIGHT| 4, 2, {NULL}, 0, 0, S_ARMF22}, // S_ARMF21
+	{SPR_ARMB, FF_FULLBRIGHT| 5, 2, {NULL}, 0, 0, S_ARMF23}, // S_ARMF22
+	{SPR_ARMB, FF_FULLBRIGHT| 6, 2, {NULL}, 0, 0, S_ARMF24}, // S_ARMF23
+	{SPR_ARMB, FF_FULLBRIGHT| 7, 2, {NULL}, 0, 0, S_ARMF25}, // S_ARMF24
+	{SPR_ARMB, FF_FULLBRIGHT| 8, 2, {NULL}, 0, 0, S_ARMF26}, // S_ARMF25
+	{SPR_ARMB, FF_FULLBRIGHT| 9, 2, {NULL}, 0, 0, S_ARMF27}, // S_ARMF26
+	{SPR_ARMB, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_ARMF28}, // S_ARMF27
+	{SPR_ARMB, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_ARMF29}, // S_ARMF28
+	{SPR_ARMB, FF_FULLBRIGHT|12, 2, {NULL}, 0, 0, S_ARMF30}, // S_ARMF29
+	{SPR_ARMB, FF_FULLBRIGHT|13, 2, {NULL}, 0, 0, S_ARMF31}, // S_ARMF30
+	{SPR_ARMB, FF_FULLBRIGHT|14, 2, {NULL}, 0, 0, S_ARMF32}, // S_ARMF31
+	{SPR_ARMB, FF_FULLBRIGHT|15, 2, {NULL}, 0, 0, S_ARMF1 }, // S_ARMF32
+
+	{SPR_ARMB, FF_FULLBRIGHT   , 2, {NULL}, 1, 0, S_ARMB2 }, // S_ARMB1
+	{SPR_ARMB, FF_FULLBRIGHT| 1, 2, {NULL}, 1, 0, S_ARMB3 }, // S_ARMB2
+	{SPR_ARMB, FF_FULLBRIGHT| 2, 2, {NULL}, 1, 0, S_ARMB4 }, // S_ARMB3
+	{SPR_ARMB, FF_FULLBRIGHT| 3, 2, {NULL}, 1, 0, S_ARMB5 }, // S_ARMB4
+	{SPR_ARMB, FF_FULLBRIGHT| 4, 2, {NULL}, 1, 0, S_ARMB6 }, // S_ARMB5
+	{SPR_ARMB, FF_FULLBRIGHT| 5, 2, {NULL}, 1, 0, S_ARMB7 }, // S_ARMB6
+	{SPR_ARMB, FF_FULLBRIGHT| 6, 2, {NULL}, 1, 0, S_ARMB8 }, // S_ARMB7
+	{SPR_ARMB, FF_FULLBRIGHT| 7, 2, {NULL}, 1, 0, S_ARMB9 }, // S_ARMB8
+	{SPR_ARMB, FF_FULLBRIGHT| 8, 2, {NULL}, 1, 0, S_ARMB10}, // S_ARMB9
+	{SPR_ARMB, FF_FULLBRIGHT| 9, 2, {NULL}, 1, 0, S_ARMB11}, // S_ARMB10
+	{SPR_ARMB, FF_FULLBRIGHT|10, 2, {NULL}, 1, 0, S_ARMB12}, // S_ARMB11
+	{SPR_ARMB, FF_FULLBRIGHT|11, 2, {NULL}, 1, 0, S_ARMB13}, // S_ARMB12
+	{SPR_ARMB, FF_FULLBRIGHT|12, 2, {NULL}, 1, 0, S_ARMB14}, // S_ARMB13
+	{SPR_ARMB, FF_FULLBRIGHT|13, 2, {NULL}, 1, 0, S_ARMB15}, // S_ARMB14
+	{SPR_ARMB, FF_FULLBRIGHT|14, 2, {NULL}, 1, 0, S_ARMB16}, // S_ARMB15
+	{SPR_ARMB, FF_FULLBRIGHT|15, 2, {NULL}, 1, 0, S_ARMB17}, // S_ARMB16
+	{SPR_ARMF, FF_FULLBRIGHT   , 2, {NULL}, 1, 0, S_ARMB18}, // S_ARMB17
+	{SPR_ARMF, FF_FULLBRIGHT| 1, 2, {NULL}, 1, 0, S_ARMB19}, // S_ARMB18
+	{SPR_ARMF, FF_FULLBRIGHT| 2, 2, {NULL}, 1, 0, S_ARMB20}, // S_ARMB19
+	{SPR_ARMF, FF_FULLBRIGHT| 3, 2, {NULL}, 1, 0, S_ARMB21}, // S_ARMB20
+	{SPR_ARMF, FF_FULLBRIGHT| 4, 2, {NULL}, 1, 0, S_ARMB22}, // S_ARMB21
+	{SPR_ARMF, FF_FULLBRIGHT| 5, 2, {NULL}, 1, 0, S_ARMB23}, // S_ARMB22
+	{SPR_ARMF, FF_FULLBRIGHT| 6, 2, {NULL}, 1, 0, S_ARMB24}, // S_ARMB23
+	{SPR_ARMF, FF_FULLBRIGHT| 7, 2, {NULL}, 1, 0, S_ARMB25}, // S_ARMB24
+	{SPR_ARMF, FF_FULLBRIGHT| 8, 2, {NULL}, 1, 0, S_ARMB26}, // S_ARMB25
+	{SPR_ARMF, FF_FULLBRIGHT| 9, 2, {NULL}, 1, 0, S_ARMB27}, // S_ARMB26
+	{SPR_ARMF, FF_FULLBRIGHT|10, 2, {NULL}, 1, 0, S_ARMB28}, // S_ARMB27
+	{SPR_ARMF, FF_FULLBRIGHT|11, 2, {NULL}, 1, 0, S_ARMB29}, // S_ARMB28
+	{SPR_ARMF, FF_FULLBRIGHT|12, 2, {NULL}, 1, 0, S_ARMB30}, // S_ARMB29
+	{SPR_ARMF, FF_FULLBRIGHT|13, 2, {NULL}, 1, 0, S_ARMB31}, // S_ARMB30
+	{SPR_ARMF, FF_FULLBRIGHT|14, 2, {NULL}, 1, 0, S_ARMB32}, // S_ARMB31
+	{SPR_ARMF, FF_FULLBRIGHT|15, 2, {NULL}, 1, 0, S_ARMB1 }, // S_ARMB32
 
 	{SPR_WIND, FF_TRANS70  , 2, {NULL}, 0, 0, S_WIND2}, // S_WIND1
 	{SPR_WIND, FF_TRANS70|1, 2, {NULL}, 0, 0, S_WIND3}, // S_WIND2
@@ -2321,12 +2820,18 @@ state_t states[NUMSTATES] =
 	{SPR_ELEM, FF_FULLBRIGHT|20, 1, {NULL}, 0, 0, S_ELEMF10}, // S_ELEMF9
 	{SPR_NULL, 0,                1, {NULL}, 0, 0, S_ELEMF1 }, // S_ELEMF10
 
-	{SPR_PITY, FF_TRANS30  , 2, {NULL}, 0, 0, S_PITY2}, // S_PITY1
-	{SPR_PITY, FF_TRANS30|1, 2, {NULL}, 0, 0, S_PITY3}, // S_PITY2
-	{SPR_PITY, FF_TRANS30|2, 2, {NULL}, 0, 0, S_PITY4}, // S_PITY3
-	{SPR_PITY, FF_TRANS20|3, 2, {NULL}, 0, 0, S_PITY5}, // S_PITY4
-	{SPR_PITY, FF_TRANS30|4, 2, {NULL}, 0, 0, S_PITY6}, // S_PITY5
-	{SPR_PITY, FF_TRANS20|5, 2, {NULL}, 0, 0, S_PITY1}, // S_PITY6
+	{SPR_PITY, FF_TRANS30   , 2, {NULL}, 0, 0, S_PITY2},  // S_PITY1
+	{SPR_PITY, FF_TRANS30| 1, 2, {NULL}, 0, 0, S_PITY3},  // S_PITY2
+	{SPR_PITY, FF_TRANS30| 2, 2, {NULL}, 0, 0, S_PITY4},  // S_PITY3
+	{SPR_PITY, FF_TRANS30| 3, 2, {NULL}, 0, 0, S_PITY5},  // S_PITY4
+	{SPR_PITY, FF_TRANS30| 4, 2, {NULL}, 0, 0, S_PITY6},  // S_PITY5
+	{SPR_PITY, FF_TRANS30| 5, 2, {NULL}, 0, 0, S_PITY7},  // S_PITY6
+	{SPR_PITY, FF_TRANS30| 6, 2, {NULL}, 0, 0, S_PITY8},  // S_PITY7
+	{SPR_PITY, FF_TRANS30| 7, 2, {NULL}, 0, 0, S_PITY9},  // S_PITY8
+	{SPR_PITY, FF_TRANS30| 8, 2, {NULL}, 0, 0, S_PITY10}, // S_PITY9
+	{SPR_PITY, FF_TRANS30| 9, 2, {NULL}, 0, 0, S_PITY11}, // S_PITY10
+	{SPR_PITY, FF_TRANS30|10, 2, {NULL}, 0, 0, S_PITY12}, // S_PITY11
+	{SPR_PITY, FF_TRANS30|11, 2, {NULL}, 0, 0, S_PITY1},  // S_PITY12
 
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40  , 2, {NULL}, 0, 0, S_FIRS2}, // S_FIRS1
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40|1, 2, {NULL}, 0, 0, S_FIRS3}, // S_FIRS2
@@ -2404,7 +2909,7 @@ state_t states[NUMSTATES] =
 	{SPR_NULL,                           0, 15*2, {NULL}, 0, 0, S_ZAPSB2 }, // S_ZAPSB11
 
 	// Thunder spark
-	{SPR_SSPK, FF_ANIMATE, 18, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
+	{SPR_SSPK, FF_ANIMATE, -1, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
 
 	// Invincibility Sparkles
 	{SPR_IVSP, FF_ANIMATE, 32, {NULL}, 31, 1, S_NULL},   // S_IVSP
@@ -2424,6 +2929,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL01, 1, 3, {A_FlickyFly},          4*FRACUNIT,       16*FRACUNIT, S_FLICKY_01_FLAP2}, // S_FLICKY_01_FLAP1
 	{SPR_FL01, 2, 3, {A_FlickyFly},          4*FRACUNIT,       16*FRACUNIT, S_FLICKY_01_FLAP3}, // S_FLICKY_01_FLAP2
 	{SPR_FL01, 3, 3, {A_FlickyFly},          4*FRACUNIT,       16*FRACUNIT, S_FLICKY_01_FLAP1}, // S_FLICKY_01_FLAP3
+	{SPR_FL01, FF_ANIMATE|1, -1, {NULL}, 2, 3, S_NULL},                                         // S_FLICKY_01_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_01, 384*FRACUNIT, S_FLICKY_01_CENTER},        // S_FLICKY_01_CENTER
 
 	// Rabbit
 	{SPR_FL02, 0, 2, {A_FlickyCheck}, S_FLICKY_02_AIM,                0, S_FLICKY_02_OUT},  // S_FLICKY_02_OUT
@@ -2431,6 +2938,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL02, 1, 1, {A_FlickyHop},        6*FRACUNIT,       4*FRACUNIT, S_FLICKY_02_UP},   // S_FLICKY_02_HOP
 	{SPR_FL02, 2, 2, {A_FlickyCheck}, S_FLICKY_02_AIM, S_FLICKY_02_DOWN, S_FLICKY_02_UP},   // S_FLICKY_02_UP
 	{SPR_FL02, 3, 2, {A_FlickyCheck}, S_FLICKY_02_AIM,                0, S_FLICKY_02_DOWN}, // S_FLICKY_02_DOWN
+	{SPR_FL02, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_02_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_02, 384*FRACUNIT, S_FLICKY_02_CENTER},        // S_FLICKY_02_CENTER
 
 	// Chicken
 	{SPR_FL03, 0, 2, {A_FlickyCheck},   S_FLICKY_03_AIM, S_FLICKY_03_FLAP1, S_FLICKY_03_OUT},   // S_FLICKY_03_OUT
@@ -2439,6 +2948,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL03, 2, 2, {A_FlickyFlutter}, S_FLICKY_03_HOP, S_FLICKY_03_FLAP1, S_FLICKY_03_UP},    // S_FLICKY_03_UP
 	{SPR_FL03, 3, 2, {A_FlickyFlutter}, S_FLICKY_03_HOP,                 0, S_FLICKY_03_FLAP2}, // S_FLICKY_03_FLAP1
 	{SPR_FL03, 4, 2, {A_FlickyFlutter}, S_FLICKY_03_HOP,                 0, S_FLICKY_03_FLAP1}, // S_FLICKY_03_FLAP2
+	{SPR_FL03, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_03_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_03, 384*FRACUNIT, S_FLICKY_03_CENTER},        // S_FLICKY_03_CENTER
 
 	// Seal
 	{SPR_FL04, 0, 2, {A_FlickyCheck}, S_FLICKY_04_AIM,                 0, S_FLICKY_04_OUT},   // S_FLICKY_04_OUT
@@ -2450,6 +2961,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL04, 4, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_04_SWIM1, S_FLICKY_04_SWIM3}, // S_FLICKY_04_SWIM2
 	{SPR_FL04, 3, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_04_SWIM1, S_FLICKY_04_SWIM4}, // S_FLICKY_04_SWIM3
 	{SPR_FL04, 5, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_04_SWIM1, S_FLICKY_04_SWIM1}, // S_FLICKY_04_SWIM4
+	{SPR_FL04, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_04_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_04, 384*FRACUNIT, S_FLICKY_04_CENTER},        // S_FLICKY_04_CENTER
 
 	// Pig
 	{SPR_FL05, 0, 2, {A_FlickyCheck}, S_FLICKY_05_AIM,                0, S_FLICKY_05_OUT},  // S_FLICKY_05_OUT
@@ -2457,6 +2970,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL05, 1, 1, {A_FlickyHop},        4*FRACUNIT,       3*FRACUNIT, S_FLICKY_05_UP},   // S_FLICKY_05_HOP
 	{SPR_FL05, 2, 2, {A_FlickyCheck}, S_FLICKY_05_AIM, S_FLICKY_05_DOWN, S_FLICKY_05_UP},   // S_FLICKY_05_UP
 	{SPR_FL05, 3, 2, {A_FlickyCheck}, S_FLICKY_05_AIM,                0, S_FLICKY_05_DOWN}, // S_FLICKY_05_DOWN
+	{SPR_FL05, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_05_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_05, 384*FRACUNIT, S_FLICKY_05_CENTER},        // S_FLICKY_05_CENTER
 
 	// Chipmunk
 	{SPR_FL06, 0, 2, {A_FlickyCheck}, S_FLICKY_06_AIM,                0, S_FLICKY_06_OUT},  // S_FLICKY_06_OUT
@@ -2464,6 +2979,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL06, 1, 1, {A_FlickyHop},        5*FRACUNIT,       6*FRACUNIT, S_FLICKY_06_UP},   // S_FLICKY_06_HOP
 	{SPR_FL06, 2, 2, {A_FlickyCheck}, S_FLICKY_06_AIM, S_FLICKY_06_DOWN, S_FLICKY_06_UP},   // S_FLICKY_06_UP
 	{SPR_FL06, 3, 2, {A_FlickyCheck}, S_FLICKY_06_AIM,                0, S_FLICKY_06_DOWN}, // S_FLICKY_06_DOWN
+	{SPR_FL06, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_06_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_06, 384*FRACUNIT, S_FLICKY_06_CENTER},        // S_FLICKY_06_CENTER
 
 	// Penguin
 	{SPR_FL07, 0, 2, {A_FlickyCheck}, S_FLICKY_07_AIML,                 0, S_FLICKY_07_OUT},   // S_FLICKY_07_OUT
@@ -2478,6 +2995,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL07, 4, 4, {A_FlickyFly},         3*FRACUNIT,       72*FRACUNIT, S_FLICKY_07_SWIM2}, // S_FLICKY_07_SWIM1
 	{SPR_FL07, 5, 4, {A_FlickyCoast},         FRACUNIT, S_FLICKY_07_SWIM1, S_FLICKY_07_SWIM3}, // S_FLICKY_07_SWIM2
 	{SPR_FL07, 6, 4, {A_FlickyCoast},       2*FRACUNIT, S_FLICKY_07_SWIM1, S_FLICKY_07_SWIM3}, // S_FLICKY_07_SWIM3
+	{SPR_FL07, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_07_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_07, 384*FRACUNIT, S_FLICKY_07_CENTER},        // S_FLICKY_07_CENTER
 
 	// Fish
 	{SPR_FL08, 0, 2, {A_FlickyCheck}, S_FLICKY_08_AIM,                 0, S_FLICKY_08_OUT},   // S_FLICKY_08_OUT
@@ -2491,6 +3010,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL08, 1, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_08_SWIM1, S_FLICKY_08_SWIM3}, // S_FLICKY_08_SWIM2
 	{SPR_FL08, 0, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_08_SWIM1, S_FLICKY_08_SWIM4}, // S_FLICKY_08_SWIM3
 	{SPR_FL08, 2, 4, {A_FlickyCoast},        FRACUNIT, S_FLICKY_08_SWIM1, S_FLICKY_08_SWIM4}, // S_FLICKY_08_SWIM4
+	{SPR_FL08, FF_ANIMATE, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_08_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_08, 384*FRACUNIT, S_FLICKY_08_CENTER},        // S_FLICKY_08_CENTER
 
 	// Ram
 	{SPR_FL09, 0, 2, {A_FlickyCheck}, S_FLICKY_09_AIM,                0, S_FLICKY_09_OUT},  // S_FLICKY_09_OUT
@@ -2498,11 +3019,15 @@ state_t states[NUMSTATES] =
 	{SPR_FL09, 1, 1, {A_FlickyHop},        7*FRACUNIT,       2*FRACUNIT, S_FLICKY_09_UP},   // S_FLICKY_09_HOP
 	{SPR_FL09, 2, 2, {A_FlickyCheck}, S_FLICKY_09_AIM, S_FLICKY_09_DOWN, S_FLICKY_09_UP},   // S_FLICKY_09_UP
 	{SPR_FL09, 3, 2, {A_FlickyCheck}, S_FLICKY_09_AIM,                0, S_FLICKY_09_DOWN}, // S_FLICKY_09_DOWN
+	{SPR_FL09, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_09_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_09, 384*FRACUNIT, S_FLICKY_09_CENTER},        // S_FLICKY_09_CENTER
 
 	// Puffin
 	{SPR_FL10, 0, 2, {A_FlickyCheck}, S_FLICKY_10_FLAP1, S_FLICKY_10_FLAP1, S_FLICKY_10_OUT},   // S_FLICKY_10_OUT
 	{SPR_FL10, 1, 3, {A_FlickySoar},         4*FRACUNIT,       16*FRACUNIT, S_FLICKY_10_FLAP2}, // S_FLICKY_10_FLAP1
 	{SPR_FL10, 2, 3, {A_FlickySoar},         4*FRACUNIT,       16*FRACUNIT, S_FLICKY_10_FLAP1}, // S_FLICKY_10_FLAP2
+	{SPR_FL10, FF_ANIMATE|1, -1, {NULL}, 1, 3, S_NULL}, // S_FLICKY_10_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_10, 384*FRACUNIT, S_FLICKY_10_CENTER},        // S_FLICKY_10_CENTER
 
 	// Cow
 	{SPR_FL11, 0, 2, {A_FlickyCheck}, S_FLICKY_11_AIM,           0, S_FLICKY_11_OUT},  // S_FLICKY_11_OUT
@@ -2510,6 +3035,8 @@ state_t states[NUMSTATES] =
 	{SPR_FL11, 1, 3, {A_FlickyHop},        FRACUNIT/2,  2*FRACUNIT, S_FLICKY_11_RUN2}, // S_FLICKY_11_RUN1
 	{SPR_FL11, 2, 4, {A_FlickyHop},        FRACUNIT/2,  2*FRACUNIT, S_FLICKY_11_RUN3}, // S_FLICKY_11_RUN2
 	{SPR_FL11, 3, 4, {A_FlickyHop},        FRACUNIT/2,  2*FRACUNIT, S_FLICKY_11_AIM},  // S_FLICKY_11_RUN3
+	{SPR_FL11, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_11_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_11, 384*FRACUNIT, S_FLICKY_11_CENTER},        // S_FLICKY_11_CENTER
 
 	// Rat
 	{SPR_FL12, 0, 2, {A_FlickyCheck}, S_FLICKY_12_AIM,           0, S_FLICKY_12_OUT},  // S_FLICKY_12_OUT
@@ -2517,19 +3044,25 @@ state_t states[NUMSTATES] =
 	{SPR_FL12, 1, 2, {A_FlickyHop},                 1, 12*FRACUNIT, S_FLICKY_12_RUN2}, // S_FLICKY_12_RUN1
 	{SPR_FL12, 2, 3, {A_FlickyHop},                 1, 12*FRACUNIT, S_FLICKY_12_RUN3}, // S_FLICKY_12_RUN2
 	{SPR_FL12, 3, 3, {A_FlickyHop},                 1, 12*FRACUNIT, S_FLICKY_12_AIM},  // S_FLICKY_12_RUN3
+	{SPR_FL12, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_12_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_12, 384*FRACUNIT, S_FLICKY_12_CENTER},        // S_FLICKY_12_CENTER
 
 	// Bear
-	{SPR_FL13, 0, 2, {A_FlickyCheck}, S_FLICKY_13_AIM,                0, S_FLICKY_13_OUT}, // S_FLICKY_13_OUT
-	{SPR_FL13, 1, 1, {A_FlickyAim},             ANG30,      32*FRACUNIT, S_FLICKY_13_HOP}, // S_FLICKY_13_AIM
-	{SPR_FL13, 1, 1, {A_FlickyHop},        5*FRACUNIT,       3*FRACUNIT, S_FLICKY_13_UP}, // S_FLICKY_13_HOP
-	{SPR_FL13, 2, 2, {A_FlickyCheck}, S_FLICKY_13_AIM, S_FLICKY_13_DOWN, S_FLICKY_13_UP}, // S_FLICKY_13_UP
+	{SPR_FL13, 0, 2, {A_FlickyCheck}, S_FLICKY_13_AIM,                0, S_FLICKY_13_OUT},  // S_FLICKY_13_OUT
+	{SPR_FL13, 1, 1, {A_FlickyAim},             ANG30,      32*FRACUNIT, S_FLICKY_13_HOP},  // S_FLICKY_13_AIM
+	{SPR_FL13, 1, 1, {A_FlickyHop},        5*FRACUNIT,       3*FRACUNIT, S_FLICKY_13_UP},   // S_FLICKY_13_HOP
+	{SPR_FL13, 2, 2, {A_FlickyCheck}, S_FLICKY_13_AIM, S_FLICKY_13_DOWN, S_FLICKY_13_UP},   // S_FLICKY_13_UP
 	{SPR_FL13, 3, 2, {A_FlickyCheck}, S_FLICKY_13_AIM,                0, S_FLICKY_13_DOWN}, // S_FLICKY_13_DOWN
+	{SPR_FL13, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_13_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_13, 384*FRACUNIT, S_FLICKY_13_CENTER},        // S_FLICKY_13_CENTER
 
 	// Dove
 	{SPR_FL14, 0, 2, {A_FlickyCheck}, S_FLICKY_14_FLAP1, S_FLICKY_14_FLAP1, S_FLICKY_14_OUT},   // S_FLICKY_14_OUT
 	{SPR_FL14, 1, 3, {A_FlickySoar},         4*FRACUNIT,       32*FRACUNIT, S_FLICKY_14_FLAP2}, // S_FLICKY_14_FLAP1
 	{SPR_FL14, 2, 3, {A_FlickySoar},         4*FRACUNIT,       32*FRACUNIT, S_FLICKY_14_FLAP3}, // S_FLICKY_14_FLAP2
 	{SPR_FL14, 3, 3, {A_FlickySoar},         4*FRACUNIT,       32*FRACUNIT, S_FLICKY_14_FLAP1}, // S_FLICKY_14_FLAP3
+	{SPR_FL14, FF_ANIMATE|1, -1, {NULL}, 2, 3, S_NULL}, // S_FLICKY_14_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_14, 384*FRACUNIT, S_FLICKY_14_CENTER},        // S_FLICKY_14_CENTER
 
 	// Cat
 	{SPR_FL15, 0, 2, {A_FlickyCheck}, S_FLICKY_15_AIM,                0, S_FLICKY_15_OUT},  // S_FLICKY_15_OUT
@@ -2537,12 +3070,63 @@ state_t states[NUMSTATES] =
 	{SPR_FL15, 1, 1, {A_FlickyFlounder},   2*FRACUNIT,       6*FRACUNIT, S_FLICKY_15_UP},   // S_FLICKY_15_HOP
 	{SPR_FL15, 2, 2, {A_FlickyCheck}, S_FLICKY_15_AIM, S_FLICKY_15_DOWN, S_FLICKY_15_UP},   // S_FLICKY_15_UP
 	{SPR_FL15, 3, 2, {A_FlickyCheck}, S_FLICKY_15_AIM,                0, S_FLICKY_15_DOWN}, // S_FLICKY_15_DOWN
+	{SPR_FL15, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_FLICKY_15_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_15, 384*FRACUNIT, S_FLICKY_15_CENTER},        // S_FLICKY_15_CENTER
 
 	// Canary
 	{SPR_FL16, 0, 2, {A_FlickyHeightCheck}, S_FLICKY_16_FLAP1,          0, S_FLICKY_16_OUT},   // S_FLICKY_16_OUT
 	{SPR_FL16, 1, 3, {A_FlickyFly},                4*FRACUNIT, 8*FRACUNIT, S_FLICKY_16_FLAP2}, // S_FLICKY_16_FLAP1
 	{SPR_FL16, 2, 3, {A_SetObjectFlags},         MF_NOGRAVITY,          1, S_FLICKY_16_FLAP3}, // S_FLICKY_16_FLAP2
 	{SPR_FL16, 3, 3, {A_FlickyHeightCheck}, S_FLICKY_16_FLAP1,          0, S_FLICKY_16_FLAP3}, // S_FLICKY_16_FLAP3
+	{SPR_FL16, FF_ANIMATE|1, -1, {NULL}, 2, 3, S_NULL}, // S_FLICKY_16_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_FLICKY_16, 384*FRACUNIT, S_FLICKY_16_CENTER},        // S_FLICKY_16_CENTER
+
+	// Spider
+	{SPR_FS01, 0, 2, {A_FlickyCheck}, S_SECRETFLICKY_01_AIM,                      0, S_SECRETFLICKY_01_OUT},  // S_SECRETFLICKY_01_OUT
+	{SPR_FS01, 1, 1, {A_FlickyAim},                   ANG30,            32*FRACUNIT, S_SECRETFLICKY_01_HOP},  // S_SECRETFLICKY_01_AIM
+	{SPR_FS01, 1, 1, {A_FlickyFlounder},         2*FRACUNIT,             6*FRACUNIT, S_SECRETFLICKY_01_UP},   // S_SECRETFLICKY_01_HOP
+	{SPR_FS01, 2, 2, {A_FlickyCheck}, S_SECRETFLICKY_01_AIM, S_SECRETFLICKY_01_DOWN, S_SECRETFLICKY_01_UP},   // S_SECRETFLICKY_01_UP
+	{SPR_FS01, 3, 2, {A_FlickyCheck}, S_SECRETFLICKY_01_AIM,                      0, S_SECRETFLICKY_01_DOWN}, // S_SECRETFLICKY_01_DOWN
+	{SPR_FS01, FF_ANIMATE|1, -1, {NULL}, 2, 4, S_NULL}, // S_SECRETFLICKY_01_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_SECRETFLICKY_01, 384*FRACUNIT, S_SECRETFLICKY_01_CENTER},        // S_SECRETFLICKY_01_CENTER
+
+	// Bat
+	{SPR_FS02, 0, 2, {A_FlickyHeightCheck}, S_SECRETFLICKY_02_FLAP1, S_SECRETFLICKY_02_FLAP1, S_SECRETFLICKY_02_OUT},   // S_SECRETFLICKY_02_OUT
+	{SPR_FS02, 1, 3, {A_FlickyFly},                      4*FRACUNIT,             16*FRACUNIT, S_SECRETFLICKY_02_FLAP2}, // S_SECRETFLICKY_02_FLAP1
+	{SPR_FS02, 2, 3, {A_FlickyFly},                      4*FRACUNIT,             16*FRACUNIT, S_SECRETFLICKY_02_FLAP3}, // S_SECRETFLICKY_02_FLAP2
+	{SPR_FS02, 3, 3, {A_FlickyFly},                      4*FRACUNIT,             16*FRACUNIT, S_SECRETFLICKY_02_FLAP1}, // S_SECRETFLICKY_02_FLAP3
+	{SPR_FS02, FF_ANIMATE|1, -1, {NULL}, 2, 2, S_NULL}, // S_SECRETFLICKY_02_STAND
+	{SPR_NULL, 0, 15, {A_FlickyCenter}, MT_SECRETFLICKY_02, 384*FRACUNIT, S_SECRETFLICKY_02_CENTER},        // S_SECRETFLICKY_02_CENTER
+
+	// Fan
+	{SPR_FANS, 0, 1, {A_FanBubbleSpawn}, 2048, 0, S_FAN2}, // S_FAN
+	{SPR_FANS, 1, 1, {A_FanBubbleSpawn}, 1024, 0, S_FAN3}, // S_FAN2
+	{SPR_FANS, 2, 1, {A_FanBubbleSpawn},  512, 0, S_FAN4}, // S_FAN3
+	{SPR_FANS, 3, 1, {A_FanBubbleSpawn}, 1024, 0, S_FAN5}, // S_FAN4
+	{SPR_FANS, 4, 1, {A_FanBubbleSpawn},  512, 0, S_FAN},  // S_FAN5
+
+	// Steam Riser
+	{SPR_STEM, 0, 2, {A_SetSolidSteam}, 0, 0, S_STEAM2},   // S_STEAM1
+	{SPR_STEM, 1, 2, {A_UnsetSolidSteam}, 0, 0, S_STEAM3}, // S_STEAM2
+	{SPR_STEM, 2, 2, {NULL}, 0, 0, S_STEAM4},              // S_STEAM3
+	{SPR_STEM, 3, 2, {NULL}, 0, 0, S_STEAM5},              // S_STEAM4
+	{SPR_STEM, 4, 2, {NULL}, 0, 0, S_STEAM6},              // S_STEAM5
+	{SPR_STEM, 5, 2, {NULL}, 0, 0, S_STEAM7},              // S_STEAM6
+	{SPR_STEM, 6, 2, {NULL}, 0, 0, S_STEAM8},              // S_STEAM7
+	{SPR_NULL, 0, 18, {NULL}, 0, 0, S_STEAM1},             // S_STEAM8
+
+	// Bumpers
+	{SPR_BUMP, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL},   3, 4, S_NULL},   // S_BUMPER
+	{SPR_BUMP, FF_ANIMATE|4,             12, {A_Pain}, 1, 3, S_BUMPER}, //S_BUMPERHIT
+
+	// Balloons
+	{SPR_BLON, FF_ANIMATE, -1, {NULL}, 2, 5, S_NULL}, // S_BALLOON
+	{SPR_BLON, 3, 0, {A_RemoteDamage},   0, 1, S_BALLOONPOP2}, // S_BALLOONPOP1
+	{SPR_BLON, 3, 1, {A_Pain},           0, 0, S_BALLOONPOP3}, // S_BALLOONPOP2
+	{SPR_BLON, 4, 1, {NULL},             0, 0, S_BALLOONPOP4}, // S_BALLOONPOP3
+	{SPR_NULL, 0, TICRATE, {A_CheckFlags2}, MF2_AMBUSH, S_BALLOONPOP5, S_NULL}, // S_BALLOONPOP4
+	{SPR_NULL, 0, 15*TICRATE, {NULL},    0, 0, S_BALLOONPOP6}, // S_BALLOONPOP5
+	{SPR_NULL, 0, 0, {A_SpawnFreshCopy}, 0, 0, S_NULL},        // S_BALLOONPOP6
 
 	// Yellow Spring
 	{SPR_SPRY, 0, -1, {NULL}, 0, 0, S_NULL},           // S_YELLOWSPRING
@@ -2585,6 +3169,16 @@ state_t states[NUMSTATES] =
 	{SPR_RSPR, 2, 1, {NULL}, 0, 0, S_RDIAG8},   // S_RDIAG7
 	{SPR_RSPR, 1, 1, {NULL}, 0, 0, S_RDIAG1},   // S_RDIAG8
 
+	// Blue Diagonal Spring
+	{SPR_BSPR, 0, -1, {NULL}, 0, 0, S_NULL},    // S_BDIAG1
+	{SPR_BSPR, 1, 1, {A_Pain}, 0, 0, S_BDIAG3}, // S_BDIAG2
+	{SPR_BSPR, 2, 1, {NULL}, 0, 0, S_BDIAG4},   // S_BDIAG3
+	{SPR_BSPR, 3, 1, {NULL}, 0, 0, S_BDIAG5},   // S_BDIAG4
+	{SPR_BSPR, 4, 1, {NULL}, 0, 0, S_BDIAG6},   // S_BDIAG5
+	{SPR_BSPR, 3, 1, {NULL}, 0, 0, S_BDIAG7},   // S_BDIAG6
+	{SPR_BSPR, 2, 1, {NULL}, 0, 0, S_BDIAG8},   // S_BDIAG7
+	{SPR_BSPR, 1, 1, {NULL}, 0, 0, S_BDIAG1},   // S_BDIAG8
+
 	// Yellow Side Spring
 	{SPR_SSWY, 0, -1, {NULL}, 0, 0, S_NULL},    // S_YHORIZ1
 	{SPR_SSWY, 1, 1, {A_Pain}, 0, 0, S_YHORIZ3}, // S_YHORIZ2
@@ -2616,8 +3210,8 @@ state_t states[NUMSTATES] =
 	{SPR_SSWB, 1, 1, {NULL}, 0, 0, S_BHORIZ1},   // S_BHORIZ8
 
 	// Rain
-	{SPR_RAIN, FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_RAIN1
-	{SPR_RAIN, FF_TRANS50, 1, {NULL}, 0, 0, S_RAIN1}, // S_RAINRETURN
+	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_RAIN1
+	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, 1, {NULL}, 0, 0, S_RAIN1}, // S_RAINRETURN
 
 	// Snowflake
 	{SPR_SNO1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SNOW1
@@ -2695,8 +3289,7 @@ state_t states[NUMSTATES] =
 	{SPR_SEED, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 2, 2, S_NULL}, // S_SEED
 
 	// Particle sprite
-	{SPR_PRTL, FF_FULLBRIGHT|FF_TRANS70, 2*TICRATE, {NULL}, 0, 0, S_NULL}, // S_PARTICLE
-	{SPR_NULL,     0,          3, {A_ParticleSpawn}, 0, 0, S_PARTICLEGEN}, // S_PARTICLEGEN
+	{SPR_PRTL, 0, 2*TICRATE, {NULL}, 0, 0, S_NULL}, // S_PARTICLE
 
 	{SPR_SCOR, 0, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRA  - 100
 	{SPR_SCOR, 1, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRB  - 200
@@ -2709,6 +3302,7 @@ state_t states[NUMSTATES] =
 	{SPR_SCOR, 8, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRI  - 4000 (mario mode)
 	{SPR_SCOR, 9, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRJ  - 8000 (mario mode)
 	{SPR_SCOR, 10, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRK - 1UP (mario mode)
+	{SPR_SCOR, 11, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRL - 10
 
 	// Drowning Timer Numbers
 	{SPR_DRWN, 0, 40, {NULL}, 0, 0, S_NULL}, // S_ZERO1
@@ -2727,15 +3321,21 @@ state_t states[NUMSTATES] =
 
 	{SPR_LCKN,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON1
 	{SPR_LCKN, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON2
+	{SPR_LCKN, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON3
+	{SPR_LCKN, 3|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON4
+
+	{SPR_LCKN,   FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF1
+	{SPR_LCKN, 1|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF2
+	{SPR_LCKN, 2|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF3
+	{SPR_LCKN, 3|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF4
 
 	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG
 
 	// CTF Sign
 	{SPR_GFLG,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
-	{SPR_GFLG, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTREDFLAG
-	{SPR_GFLG, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTBLUEFLAG
 
-	{SPR_CORK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
+	{SPR_CORK,             0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
+	{SPR_LHRT, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LHRT
 
 	// Red Rings (thrown)
 	{SPR_RRNG, FF_FULLBRIGHT,   1, {A_ThrownRing}, 0, 0, S_RRNG2}, // S_RRNG1
@@ -2974,8 +3574,8 @@ state_t states[NUMSTATES] =
 	{SPR_TOAD, 0, -1, {NULL}, 0, 0, S_NULL}, // S_TOAD
 
 	// Nights Drone
-	{SPR_NDRN, 0, -1, {NULL}, 0, 0, S_NIGHTSDRONE2}, // S_NIGHTSDRONE1
-	{SPR_NDRN, 0, -1, {NULL}, 0, 0, S_NIGHTSDRONE1}, // S_NIGHTSDRONE2
+	{SPR_NDRN, 0, -1, {NULL}, 0, 0, S_NIGHTSDRONE_MAN2}, // S_NIGHTSDRONE_MAN1
+	{SPR_NDRN, 0, -1, {NULL}, 0, 0, S_NIGHTSDRONE_MAN1}, // S_NIGHTSDRONE_MAN2
 
 	// Sparkling point (RETURN TO THE GOAL, etc)
 	{SPR_IVSP, 0, 1, {A_GhostMe}, 0, 0, S_NIGHTSDRONE_SPARKLING2},   // S_NIGHTSDRONE_SPARKLING1
@@ -2996,10 +3596,10 @@ state_t states[NUMSTATES] =
 	{SPR_IVSP, 30, 1, {A_GhostMe}, 0, 0, S_NIGHTSDRONE_SPARKLING1}, // S_NIGHTSDRONE_SPARKLING16
 
 	// NiGHTS GOAL banner (inside the sparkles!)
-	{SPR_GOAL, 0, 4, {NULL}, 0, 0, S_NIGHTSGOAL2}, // S_NIGHTSGOAL1
-	{SPR_GOAL, 1, 4, {NULL}, 0, 0, S_NIGHTSGOAL3}, // S_NIGHTSGOAL2
-	{SPR_GOAL, 2, 4, {NULL}, 0, 0, S_NIGHTSGOAL4}, // S_NIGHTSGOAL3
-	{SPR_GOAL, 3, 4, {NULL}, 0, 0, S_NIGHTSGOAL1}, // S_NIGHTSGOAL4
+	{SPR_GOAL, 0, 4, {NULL}, 0, 0, S_NIGHTSDRONE_GOAL2}, // S_NIGHTSDRONE_GOAL1
+	{SPR_GOAL, 1, 4, {NULL}, 0, 0, S_NIGHTSDRONE_GOAL3}, // S_NIGHTSDRONE_GOAL2
+	{SPR_GOAL, 2, 4, {NULL}, 0, 0, S_NIGHTSDRONE_GOAL4}, // S_NIGHTSDRONE_GOAL3
+	{SPR_GOAL, 3, 4, {NULL}, 0, 0, S_NIGHTSDRONE_GOAL1}, // S_NIGHTSDRONE_GOAL4
 
 	// Nights Sparkle
 	{SPR_NSPK, FF_FULLBRIGHT, 140, {NULL}, 0, 0, S_NIGHTSPARKLE2},   // S_NIGHTSPARKLE1
@@ -3055,9 +3655,6 @@ state_t states[NUMSTATES] =
 	{SPR_NSCR, FF_FULLBRIGHT|18, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSCORE90_2
 	{SPR_NSCR, FF_FULLBRIGHT|19, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSCORE100_2
 
-	{SPR_NWNG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSWING
-	{SPR_NWNG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSWING_XMAS
-
 	// NiGHTS Paraloop Powerups
 	{SPR_NPRU, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSSUPERLOOP
 	{SPR_NPRU, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSDRILLREFILL
@@ -3067,7 +3664,7 @@ state_t states[NUMSTATES] =
 
 	{SPR_CAPS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EGGCAPSULE
 
-	// Orbiting Chaos Emeralds for NiGHTS
+	// Orbiting Chaos Emeralds/Ideya for NiGHTS
 	{SPR_CEMG, FF_FULLBRIGHT,   1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM1}, // S_ORBITEM1
 	{SPR_CEMG, FF_FULLBRIGHT|1, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM2}, // S_ORBITEM2
 	{SPR_CEMG, FF_FULLBRIGHT|2, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM3}, // S_ORBITEM3
@@ -3076,14 +3673,11 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT|5, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM6}, // S_ORBITEM6
 	{SPR_CEMG, FF_FULLBRIGHT|6, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM7}, // S_ORBITEM7
 	{SPR_CEMG, FF_FULLBRIGHT|7, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM8
-	{SPR_CEMG, FF_FULLBRIGHT|8, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM9
-	{SPR_CEMG, FF_FULLBRIGHT|9, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM10
-	{SPR_CEMG, FF_FULLBRIGHT|10, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM11
-	{SPR_CEMG, FF_FULLBRIGHT|11, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM12
-	{SPR_CEMG, FF_FULLBRIGHT|12, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM13
-	{SPR_CEMG, FF_FULLBRIGHT|13, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM14
-	{SPR_CEMG, FF_FULLBRIGHT|14, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM15
-	{SPR_CEMG, FF_FULLBRIGHT|15, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBITEM8}, // S_ORBITEM16
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT,   1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA1}, // S_ORBIDYA1
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|1, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA2}, // S_ORBIDYA2
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|2, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA3}, // S_ORBIDYA3
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|3, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA4}, // S_ORBIDYA4
+	{SPR_IDYA, FF_TRANS20|FF_FULLBRIGHT|4, 1, {A_OrbitNights}, ANG2*2, 0, S_ORBIDYA5}, // S_ORBIDYA5
 
 	// Flicky helper for NiGHTS
 	{SPR_FL01, 1, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER2}, // S_NIGHTOPIANHELPER1
@@ -3096,33 +3690,160 @@ state_t states[NUMSTATES] =
 	{SPR_FL01, 3, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER9}, // S_NIGHTOPIANHELPER8
 	{SPR_FL01, 3, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER1}, // S_NIGHTOPIANHELPER9
 
-	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_CRUMBLE2},  // S_CRUMBLE1
+	// Nightopian
+	{SPR_NTPN, 0, 4, {A_Look}, 0, 0, S_PIAN0}, // S_PIAN0
+	{SPR_NTPN, 0, 4, {A_JetgThink}, 0, 0, S_PIAN2}, // S_PIAN1
+	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN3}, // S_PIAN2
+	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN4}, // S_PIAN3
+	{SPR_NTPN, 3, 4, {NULL}, 0, 0, S_PIAN5}, // S_PIAN4
+	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN6}, // S_PIAN5
+	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN1}, // S_PIAN6
+	{SPR_NTPN, 5|FF_ANIMATE, 4, {NULL}, 1, 4, S_PIAN1}, // S_PIANSING
+
+	// Shleep
+	{SPR_SHLP, 0, 15, {NULL}, 0, 0, S_SHLEEP2}, // S_SHLEEP1
+	{SPR_SHLP, 1, 15, {NULL}, 0, 0, S_SHLEEP3}, // S_SHLEEP2
+	{SPR_SHLP, 2, 15, {NULL}, 0, 0, S_SHLEEP4}, // S_SHLEEP3
+	{SPR_SHLP, 1, 15, {NULL}, 0, 0, S_SHLEEP1}, // S_SHLEEP4
+	{SPR_SHLP, 3, 1, {A_Scream},  0, 0, S_SHLEEPBOUNCE2}, // S_SHLEEPBOUNCE1
+	{SPR_SHLP, 3, 1, {A_ZThrust}, 9, 0, S_SHLEEPBOUNCE3}, // S_SHLEEPBOUNCE2
+	{SPR_SHLP, 3, 400, {A_SetObjectFlags}, MF_SLIDEME|MF_ENEMY|MF_BOUNCE|MF_NOCLIP|MF_NOCLIPHEIGHT, 0, S_NULL}, // S_SHLEEPBOUNCE3
+
+	// Secret badniks and hazards, shhhh
+	{SPR_PENG, 0, 2, {A_Look},  0, 0, S_PENGUINATOR_LOOK},    // S_PENGUINATOR_LOOK
+	{SPR_PENG, 0, 2, {A_Chase}, 0, 0, S_PENGUINATOR_WADDLE2}, // S_PENGUINATOR_WADDLE1
+	{SPR_PENG, 1, 2, {A_Chase}, 0, 0, S_PENGUINATOR_WADDLE3}, // S_PENGUINATOR_WADDLE2
+	{SPR_PENG, 0, 2, {A_Chase}, 0, 0, S_PENGUINATOR_WADDLE4}, // S_PENGUINATOR_WADDLE3
+	{SPR_PENG, 2, 2, {A_Chase}, 0, 0, S_PENGUINATOR_WADDLE1}, // S_PENGUINATOR_WADDLE4
+	{SPR_PENG, 0,  0, {A_FaceTarget},      0,  0, S_PENGUINATOR_SLIDE2}, // S_PENGUINATOR_SLIDE1
+	{SPR_PENG, 3,  5, {A_BunnyHop},        4, 10, S_PENGUINATOR_SLIDE3}, // S_PENGUINATOR_SLIDE2
+	{SPR_PENG, 4, 90, {A_PlayAttackSound}, 0,  0, S_PENGUINATOR_SLIDE4}, // S_PENGUINATOR_SLIDE3
+	{SPR_PENG, 3,  5, {A_Thrust},          0,  1, S_PENGUINATOR_SLIDE5}, // S_PENGUINATOR_SLIDE4
+	{SPR_PENG, 0,  5, {A_FaceTarget},      0,  0, S_PENGUINATOR_LOOK},   // S_PENGUINATOR_SLIDE5
+
+	{SPR_POPH, 0,  2, {A_Look},  (2048<<16)|1,           0, S_POPHAT_LOOK},   // S_POPHAT_LOOK
+	{SPR_POPH, 1,  2, {A_LobShot}, MT_POPSHOT, (70<<16)|60, S_POPHAT_SHOOT2}, // S_POPHAT_SHOOT1
+	{SPR_POPH, 2,  1, {NULL},               0,           0, S_POPHAT_SHOOT3}, // S_POPHAT_SHOOT2
+	{SPR_POPH, 0, 57, {NULL},               0,           0, S_POPHAT_LOOK},   // S_POPHAT_SHOOT3
+
+	{SPR_HIVE, 0,  5, {A_Look}, 1, 1, S_HIVEELEMENTAL_LOOK}, // S_HIVEELEMENTAL_LOOK
+	{SPR_HIVE, 0, 14, {A_PlaySound}, sfx_s3k76, 1, S_HIVEELEMENTAL_PREPARE2}, // S_HIVEELEMENTAL_PREPARE1
+	{SPR_HIVE, 0,  6, {A_PlaySound}, sfx_s3k8c, 1, S_HIVEELEMENTAL_SHOOT1}, // S_HIVEELEMENTAL_PREPARE2
+	{SPR_HIVE, 1,  4, {A_WhoCaresIfYourSonIsABee}, (MT_BUMBLEBORE<<16)|4, (1<<16)|32, S_HIVEELEMENTAL_SHOOT2}, // S_HIVEELEMENTAL_SHOOT1
+	{SPR_HIVE, 2,  2, {NULL}, 0, 0, S_HIVEELEMENTAL_DORMANT}, // S_HIVEELEMENTAL_SHOOT2
+	{SPR_HIVE, 0,  5, {A_ParentTriesToSleep}, S_HIVEELEMENTAL_PREPARE1, 0, S_HIVEELEMENTAL_DORMANT}, // S_HIVEELEMENTAL_DORMANT
+	{SPR_HIVE, 3, 35, {A_Pain}, 0, 0, S_HIVEELEMENTAL_LOOK}, // S_HIVEELEMENTAL_PAIN
+	{SPR_HIVE, 3,  2, {A_BossScream}, 1, MT_SONIC3KBOSSEXPLODE, S_HIVEELEMENTAL_DIE2}, // S_HIVEELEMENTAL_DIE1
+	{SPR_NULL, 0,  2, {A_BossScream}, 1, MT_SONIC3KBOSSEXPLODE, S_HIVEELEMENTAL_DIE3}, // S_HIVEELEMENTAL_DIE2
+	{SPR_NULL, 0,  0, {A_Repeat}, 7, S_HIVEELEMENTAL_DIE1, S_XPLD_FLICKY}, // S_HIVEELEMENTAL_DIE3
+
+	{SPR_BUMB, 1, 10, {NULL}, 0, 0, S_BUMBLEBORE_LOOK1}, // S_BUMBLEBORE_SPAWN
+	{SPR_BUMB, 0,  4, {A_Look}, 1, 1, S_BUMBLEBORE_LOOK2}, // S_BUMBLEBORE_LOOK1
+	{SPR_BUMB, 1,  4, {A_Look}, 1, 1, S_BUMBLEBORE_LOOK1}, // S_BUMBLEBORE_LOOK2
+	{SPR_BUMB, 0,  4, {A_JetbThink}, 0, 0, S_BUMBLEBORE_FLY2}, // S_BUMBLEBORE_FLY1
+	{SPR_BUMB, 1,  4, {A_JetbThink}, 0, 0, S_BUMBLEBORE_FLY1}, // S_BUMBLEBORE_FLY2
+	{SPR_BUMB, 2|FF_FULLBRIGHT,  12, {A_ZThrust},  4, (1<<16)|1, S_BUMBLEBORE_FALL1},  // S_BUMBLEBORE_RAISE
+	{SPR_BUMB, 2|FF_FULLBRIGHT,   0, {A_ZThrust}, -8, (1<<16)|1, S_BUMBLEBORE_FALL2},  // S_BUMBLEBORE_FALL1
+	{SPR_BUMB, 2|FF_FULLBRIGHT, 300, {NULL},       0,         0, S_BUMBLEBORE_DIE},    // S_BUMBLEBORE_FALL2
+	{SPR_BUMB, 4, 3, {A_MultiShotDist}, (MT_DUST<<16)|6, -40, S_BUMBLEBORE_STUCK2},    // S_BUMBLEBORE_STUCK1
+	{SPR_BUMB, 5, 120, {NULL}, 0, 0, S_BUMBLEBORE_DIE}, // S_BUMBLEBORE_STUCK2
+	{SPR_BUMB, 5, 0, {A_CryingToMomma}, 0, 0, S_XPLD1}, // S_BUMBLEBORE_DIE
+
+	{SPR_BBUZ, 0, 2, {NULL}, 0, 0, S_BBUZZFLY2}, // S_BBUZZFLY1
+	{SPR_BBUZ, 1, 2, {NULL}, 0, 0, S_BBUZZFLY1}, // S_BBUZZFLY2
+
+	{SPR_FMCE, 0, 20, {NULL}, 0, 0, S_SMASHSPIKE_EASE1}, // S_SMASHSPIKE_FLOAT
+	{SPR_FMCE, 0,  4, {A_ZThrust},  4, (1<<16)|1, S_SMASHSPIKE_EASE2}, // S_SMASHSPIKE_EASE1
+	{SPR_FMCE, 0,  4, {A_ZThrust},  0, (1<<16)|1, S_SMASHSPIKE_FALL},  // S_SMASHSPIKE_EASE2
+	{SPR_FMCE, 0,  2, {A_ZThrust}, -6,         1, S_SMASHSPIKE_FALL},  // S_SMASHSPIKE_FALL
+	{SPR_FMCE, 1,  2, {A_MultiShotDist}, (MT_DUST<<16)|10, -48, S_SMASHSPIKE_STOMP2}, // S_SMASHSPIKE_STOMP1
+	{SPR_FMCE, 2, 14, {NULL}, 0, 0, S_SMASHSPIKE_RISE1}, // S_SMASHSPIKE_STOMP2
+	{SPR_FMCE, 1,  2, {NULL}, 0, 0, S_SMASHSPIKE_RISE2}, // S_SMASHSPIKE_RISE1
+	{SPR_FMCE, 0,  2, {A_ZThrust}, 6, (1<<16)|1, S_SMASHSPIKE_RISE2}, // S_SMASHSPIKE_RISE2
+
+	{SPR_CACO, 0,  5, {A_Look}, (1100<<16)|1, 0, S_CACO_LOOK}, // S_CACO_LOOK
+	{SPR_CACO, 1,  0, {A_MultiShotDist}, (MT_DUST<<16)|7, -48, S_CACO_WAKE2}, // S_CACO_WAKE1
+	{SPR_CACO, 1, 10, {A_ZThrust}, 4, (1<<16)|1, S_CACO_WAKE3}, // S_CACO_WAKE2
+	{SPR_CACO, 2,  8, {A_ZThrust}, 2, (1<<16)|1, S_CACO_WAKE4}, // S_CACO_WAKE3
+	{SPR_CACO, 2,  4, {A_ZThrust}, 0, (1<<16)|1, S_CACO_ROAR},  // S_CACO_WAKE4
+	{SPR_CACO, 2, 10, {A_PlayActiveSound}, 0, 0, S_CACO_CHASE}, // S_CACO_ROAR
+	{SPR_CACO, 2,  5, {A_JetChase}, 0, 0, S_CACO_CHASE_REPEAT}, // S_CACO_CHASE
+	{SPR_CACO, 2,  0, {A_Repeat}, 5, S_CACO_CHASE, S_CACO_RANDOM}, // S_CACO_CHASE_REPEAT
+	{SPR_CACO, 2,  0, {A_RandomState}, S_CACO_PREPARE_SOUND, S_CACO_CHASE, S_CACO_RANDOM}, // S_CACO_RANDOM
+	{SPR_CACO, 2,  8, {A_PlaySound}, sfx_s3k95, 1, S_CACO_PREPARE1},  // S_CACO_PREPARE_SOUND
+	{SPR_CACO, 3,               8, {NULL}, 0, 0, S_CACO_PREPARE2},    // S_CACO_PREPARE1
+	{SPR_CACO, 4|FF_FULLBRIGHT, 8, {NULL}, 0, 0, S_CACO_PREPARE3},    // S_CACO_PREPARE2
+	{SPR_CACO, 5|FF_FULLBRIGHT, 8, {NULL}, 0, 0, S_CACO_SHOOT_SOUND}, // S_CACO_PREPARE3
+	{SPR_CACO, 4|FF_FULLBRIGHT, 0, {A_PlaySound}, sfx_s3k4e, 1, S_CACO_SHOOT1}, // S_CACO_SHOOT_SOUND
+	{SPR_CACO, 4|FF_FULLBRIGHT, 0, {A_SpawnParticleRelative}, 0, S_CACOFIRE_EXPLODE1, S_CACO_SHOOT2}, // S_CACO_SHOOT1
+	{SPR_CACO, 4|FF_FULLBRIGHT, 6, {A_FireShot}, MT_CACOFIRE, -24, S_CACO_CLOSE}, // S_CACO_SHOOT2
+	{SPR_CACO, 3,              15, {NULL}, 0, 0, S_CACO_CHASE}, // S_CACO_CLOSE
+	{SPR_CACO, 10, 0, {A_SetObjectFlags}, MF_NOBLOCKMAP, 0, S_CACO_DIE_GIB1}, // S_CACO_DIE_FLAGS
+	{SPR_CACO, 10, 0, {A_NapalmScatter}, (7<<16)|MT_CACOSHARD, (30<<16)|20, S_CACO_DIE_GIB2}, // S_CACO_DIE_GIB1
+	{SPR_CACO, 10, 0, {A_NapalmScatter}, (10<<16)|MT_CACOSHARD, (24<<16)|32, S_CACO_DIE_SCREAM}, // S_CACO_DIE_GIB2
+	{SPR_CACO, 10, 0, {A_Scream}, 0, 0, S_CACO_DIE_SHATTER}, // S_CACO_DIE_SCREAM
+	{SPR_CACO, 10, 0, {A_PlaySound}, sfx_pumpkn, 1, S_CACO_DIE_FALL}, // S_CACO_DIE_SHATTER
+	{SPR_CACO, 10, 250, {A_FlickySpawn}, (1<<16), 0, S_NULL}, // S_CACO_DIE_FALL
+
+	{SPR_CACO, 6, 0, {A_RandomState}, S_CACOSHARD1_1, S_CACOSHARD2_1, S_NULL}, // S_CACOSHARD_RANDOMIZE
+	{SPR_CACO, 6, 3, {NULL}, 0, 0, S_CACOSHARD1_2}, // S_CACOSHARD1_1
+	{SPR_CACO, 7, 3, {NULL}, 0, 0, S_CACOSHARD1_1}, // S_CACOSHARD1_2
+	{SPR_CACO, 8, 3, {NULL}, 0, 0, S_CACOSHARD2_2}, // S_CACOSHARD2_1
+	{SPR_CACO, 9, 3, {NULL}, 0, 0, S_CACOSHARD2_1}, // S_CACOSHARD2_2
+	{SPR_BAL2,   FF_FULLBRIGHT, 2, {A_GhostMe}, 0, 0, S_CACOFIRE2}, // S_CACOFIRE1
+	{SPR_BAL2, 1|FF_FULLBRIGHT, 2, {A_GhostMe}, 0, 0, S_CACOFIRE3}, // S_CACOFIRE2
+	{SPR_BAL2,   FF_FULLBRIGHT, 0, {A_PlayActiveSound}, 0, 0, S_CACOFIRE1}, // S_CACOFIRE3
+	{SPR_BAL2, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_CACOFIRE_EXPLODE2}, // S_CACOFIRE_EXPLODE1
+	{SPR_BAL2, 3|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_CACOFIRE_EXPLODE3}, // S_CACOFIRE_EXPLODE2
+	{SPR_BAL2, 4|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_CACOFIRE_EXPLODE4}, // S_CACOFIRE_EXPLODE3
+	{SPR_BAL2, 5|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL},      // S_CACOFIRE_EXPLODE4
+
+	{SPR_SBOB, 1, 10, {A_ZThrust}, -2, (1<<16)|1, S_SPINBOBERT_MOVE_UP},       // S_SPINBOBERT_MOVE_FLIPUP
+	{SPR_SBOB, 0, 45, {A_ZThrust},  4, (1<<16)|1, S_SPINBOBERT_MOVE_FLIPDOWN}, // S_SPINBOBERT_MOVE_UP
+	{SPR_SBOB, 1, 10, {A_ZThrust},  2, (1<<16)|1, S_SPINBOBERT_MOVE_DOWN},     // S_SPINBOBERT_MOVE_FLIPDOWN
+	{SPR_SBOB, 2, 45, {A_ZThrust}, -4, (1<<16)|1, S_SPINBOBERT_MOVE_FLIPUP},   // S_SPINBOBERT_MOVE_DOWN
+	{SPR_SBSK, FF_FULLBRIGHT, 1, {A_RotateSpikeBall},       0,                        0, S_SPINBOBERT_FIRE_GHOST}, // S_SPINBOBERT_FIRE_MOVE
+	{SPR_SBSK, FF_FULLBRIGHT, 0, {A_SpawnParticleRelative}, 0, S_SPINBOBERT_FIRE_TRAIL1, S_SPINBOBERT_FIRE_MOVE},  // S_SPINBOBERT_FIRE_GHOST
+	{SPR_SBFL, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_SPINBOBERT_FIRE_TRAIL2}, // S_SPINBOBERT_FIRE_TRAIL1
+	{SPR_SBFL, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_SPINBOBERT_FIRE_TRAIL3}, // S_SPINBOBERT_FIRE_TRAIL2
+	{SPR_SBFL,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL},                   // S_SPINBOBERT_FIRE_TRAIL3
+
+	{SPR_HBAT, 0,  5, {A_Look}, (900<<16)|1, 0, S_HANGSTER_LOOK}, // S_HANGSTER_LOOK
+	{SPR_HBAT, 1,  0, {A_MultiShotDist}, (MT_DUST<<16)|10, -34, S_HANGSTER_SWOOP2}, // S_HANGSTER_SWOOP1
+	{SPR_HBAT, 1,  2, {A_ZThrust}, -8, (1<<16)|1, S_HANGSTER_SWOOP2}, // S_HANGSTER_SWOOP2
+	{SPR_HBAT, 1,  6, {A_ZThrust}, -5, (1<<16), S_HANGSTER_ARC2}, // S_HANGSTER_ARC1
+	{SPR_HBAT, 1,  5, {A_ZThrust}, -2, (1<<16), S_HANGSTER_ARC3}, // S_HANGSTER_ARC2
+	{SPR_HBAT, 1,  1, {A_ZThrust},  0, (1<<16), S_HANGSTER_FLY1}, // S_HANGSTER_ARC3
+	{SPR_HBAT, 1,  4, {A_Thrust}, 6, 1, S_HANGSTER_FLY2}, // S_HANGSTER_FLY1
+	{SPR_HBAT, 2,  1, {A_PlaySound}, sfx_s3k52, 1, S_HANGSTER_FLY3}, // S_HANGSTER_FLY2
+	{SPR_HBAT, 3,  4, {A_Thrust}, 6, 1, S_HANGSTER_FLY4}, // S_HANGSTER_FLY3
+	{SPR_HBAT, 2,  1, {A_Thrust}, 6, 1, S_HANGSTER_FLYREPEAT}, // S_HANGSTER_FLY4
+	{SPR_HBAT, 2,  0, {A_Repeat}, 12, S_HANGSTER_FLY1, S_HANGSTER_ARCUP1}, // S_HANGSTER_FLYREPEAT
+	{SPR_HBAT, 1,  5, {A_ZThrust},  2, (1<<16), S_HANGSTER_ARCUP2}, // S_HANGSTER_ARCUP1
+	{SPR_HBAT, 1,  6, {A_ZThrust},  5, (1<<16), S_HANGSTER_ARCUP3}, // S_HANGSTER_ARCUP2
+	{SPR_HBAT, 1,  1, {A_ZThrust},  0, (1<<16), S_HANGSTER_RETURN1}, // S_HANGSTER_ARCUP3
+	{SPR_HBAT, 1,  1, {A_ZThrust},  8, (1<<16), S_HANGSTER_RETURN2}, // S_HANGSTER_RETURN1
+	{SPR_HBAT, 3,  1, {NULL}, 0, 0, S_HANGSTER_RETURN1}, // S_HANGSTER_RETURN2
+	{SPR_HBAT, 0, 15, {NULL}, 0, 0, S_HANGSTER_LOOK}, // S_HANGSTER_RETURN3
+
+	{SPR_NULL, 0,  35, {NULL}, 0, 0, S_CRUMBLE2}, // S_CRUMBLE1
 	{SPR_NULL, 0, 105, {A_Scream}, 0, 0, S_NULL}, // S_CRUMBLE2
 
 	// Spark
-	{SPR_SPRK, FF_TRANS40  , 1, {NULL}, 0, 0, S_SPRK2},  // S_SPRK1
-	{SPR_SPRK, FF_TRANS50|1, 1, {NULL}, 0, 0, S_SPRK3},  // S_SPRK2
-	{SPR_SPRK, FF_TRANS50|2, 1, {NULL}, 0, 0, S_SPRK4},  // S_SPRK3
-	{SPR_SPRK, FF_TRANS50|3, 1, {NULL}, 0, 0, S_SPRK5},  // S_SPRK4
-	{SPR_SPRK, FF_TRANS60  , 1, {NULL}, 0, 0, S_SPRK6},  // S_SPRK5
-	{SPR_SPRK, FF_TRANS60|1, 1, {NULL}, 0, 0, S_SPRK7},  // S_SPRK6
-	{SPR_SPRK, FF_TRANS60|2, 1, {NULL}, 0, 0, S_SPRK8},  // S_SPRK7
-	{SPR_SPRK, FF_TRANS70|3, 1, {NULL}, 0, 0, S_SPRK9},  // S_SPRK8
-	{SPR_SPRK, FF_TRANS70  , 1, {NULL}, 0, 0, S_SPRK10}, // S_SPRK9
-	{SPR_SPRK, FF_TRANS70|1, 1, {NULL}, 0, 0, S_SPRK11}, // S_SPRK10
-	{SPR_SPRK, FF_TRANS80|2, 1, {NULL}, 0, 0, S_SPRK12}, // S_SPRK11
-	{SPR_SPRK, FF_TRANS80|3, 1, {NULL}, 0, 0, S_SPRK13}, // S_SPRK12
-	{SPR_SPRK, FF_TRANS80  , 1, {NULL}, 0, 0, S_SPRK14}, // S_SPRK13
-	{SPR_SPRK, FF_TRANS90|1, 1, {NULL}, 0, 0, S_SPRK15}, // S_SPRK14
-	{SPR_SPRK, FF_TRANS90|2, 1, {NULL}, 0, 0, S_SPRK16}, // S_SPRK15
-	{SPR_SPRK, FF_TRANS90|3, 1, {NULL}, 0, 0, S_NULL},   // S_SPRK16
+	{SPR_NULL, 0, 1, {A_ModuloToState}, 2, S_SPRK2, S_SPRK3},  // S_SPRK1
+	{SPR_SPRK, FF_TRANS20|FF_ANIMATE|0, 18, {NULL}, 8, 2, S_NULL},  // S_SPRK2
+	{SPR_SPRK, FF_TRANS20|FF_ANIMATE|9, 18, {NULL}, 8, 2, S_NULL},  // S_SPRK3
 
 	// Robot Explosion
-	{SPR_BOM1, 0,             0, {A_FlickySpawn}, 0, 0, S_XPLD1}, // S_XPLD_FLICKY
-	{SPR_BOM1, 0,             1, {A_Scream},      0, 0, S_XPLD2}, // S_XPLD1
-	{SPR_BOM1, FF_ANIMATE|1, 15, {NULL},          2, 5, S_NULL},  // S_XPLD2
+	{SPR_BOM1, 0, 0, {A_FlickySpawn}, 0, 0, S_XPLD1}, // S_XPLD_FLICKY
+	{SPR_BOM1, 0, 2, {A_Scream},      0, 0, S_XPLD2}, // S_XPLD1
+	{SPR_BOM1, 1, 2, {NULL},          0, 0, S_XPLD3}, // S_XPLD2
+	{SPR_BOM1, 2, 3, {NULL},          0, 0, S_XPLD4}, // S_XPLD3
+	{SPR_BOM1, 3, 3, {NULL},          0, 0, S_XPLD5}, // S_XPLD4
+	{SPR_BOM1, 4, 4, {NULL},          0, 0, S_XPLD6}, // S_XPLD5
+	{SPR_BOM1, 5, 4, {NULL},          0, 0, S_NULL},  // S_XPLD6
 
-	{SPR_BOM1, FF_ANIMATE,   20, {NULL},          3, 5, S_INVISIBLE}, // S_XPLD_EGGTRAP
+	{SPR_BOM1, FF_ANIMATE,   21, {NULL},          5, 4, S_INVISIBLE}, // S_XPLD_EGGTRAP
 
 	// Underwater Explosion
 	{SPR_BOM4, 0, 3, {A_Scream}, 0, 0, S_WPLD2}, // S_WPLD1
@@ -3132,24 +3853,33 @@ state_t states[NUMSTATES] =
 	{SPR_BOM4, 4, 3, {NULL},     0, 0, S_WPLD6}, // S_WPLD5
 	{SPR_BOM4, 5, 3, {NULL},     0, 0, S_NULL},  // S_WPLD6
 
+	{SPR_DUST,   FF_TRANS40, 4, {NULL}, 0, 0, S_DUST2}, // S_DUST1
+	{SPR_DUST, 1|FF_TRANS50, 5, {NULL}, 0, 0, S_DUST3}, // S_DUST2
+	{SPR_DUST, 2|FF_TRANS60, 3, {NULL}, 0, 0, S_DUST4}, // S_DUST3
+	{SPR_DUST, 3|FF_TRANS70, 2, {NULL}, 0, 0, S_NULL},  // S_DUST4
+
+	{SPR_WDDB, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL},  // S_WOODDEBRIS
+
 	{SPR_NULL, 0, 1, {A_RockSpawn}, 0, 0, S_ROCKSPAWN}, // S_ROCKSPAWN
 
-	{SPR_ROIA, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEA}, // S_ROCKCRUMBLEA
-	{SPR_ROIB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEB}, // S_ROCKCRUMBLEB
-	{SPR_ROIC, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEC}, // S_ROCKCRUMBLEC
-	{SPR_ROID, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLED}, // S_ROCKCRUMBLED
-	{SPR_ROIE, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEE}, // S_ROCKCRUMBLEE
-	{SPR_ROIF, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEF}, // S_ROCKCRUMBLEF
-	{SPR_ROIG, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEG}, // S_ROCKCRUMBLEG
-	{SPR_ROIH, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEH}, // S_ROCKCRUMBLEH
-	{SPR_ROII, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEI}, // S_ROCKCRUMBLEI
-	{SPR_ROIJ, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEJ}, // S_ROCKCRUMBLEJ
-	{SPR_ROIK, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEK}, // S_ROCKCRUMBLEK
-	{SPR_ROIL, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEL}, // S_ROCKCRUMBLEL
-	{SPR_ROIM, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEM}, // S_ROCKCRUMBLEM
-	{SPR_ROIN, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEN}, // S_ROCKCRUMBLEN
-	{SPR_ROIO, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEO}, // S_ROCKCRUMBLEO
-	{SPR_ROIP, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_ROCKCRUMBLEP}, // S_ROCKCRUMBLEP
+	{SPR_ROIA, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEA
+	{SPR_ROIB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEB
+	{SPR_ROIC, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEC
+	{SPR_ROID, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLED
+	{SPR_ROIE, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEE
+	{SPR_ROIF, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEF
+	{SPR_ROIG, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEG
+	{SPR_ROIH, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEH
+	{SPR_ROII, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEI
+	{SPR_ROIJ, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEJ
+	{SPR_ROIK, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEK
+	{SPR_ROIL, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEL
+	{SPR_ROIM, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEM
+	{SPR_ROIN, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEN
+	{SPR_ROIO, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEO
+	{SPR_ROIP, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEP
+
+	{SPR_BRIC, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL}, // S_BRICKDEBRIS
 
 #ifdef SEENAMES
 	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK
@@ -3174,7 +3904,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
+		0,              // speed
 		0,              // radius
 		0,              // height
 		0,              // display offset
@@ -3263,7 +3993,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MT_THOK,        // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SHOOTABLE, // flags
-		(statenum_t)MT_NULL // raisestate
+		MT_NULL         // raisestate
 	},
 
 	{           // MT_TAILSOVERLAY
@@ -3428,33 +4158,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AQUABUZZ
-		124,            // doomednum
-		S_BBUZZFLY1,    // spawnstate
-		1,              // spawnhealth
-		S_BBUZZFLY1,    // seestate
-		sfx_None,       // seesound
-		2,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		TICRATE,        // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_XPLD_FLICKY,  // deathstate
-		S_NULL,         // xdeathstate
-		sfx_pop,        // deathsound
-		6*FRACUNIT,     // speed
-		20*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_gbeep,      // activesound
-		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_JETTBOMBER
 		105,            // doomednum
 		S_JETBLOOK1,    // spawnstate
@@ -3516,7 +4219,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_CCOMMAND3,    // seestate
 		sfx_None,       // seesound
 		2*TICRATE,      // reactiontime
-		sfx_None,       // attacksound
+		sfx_s3k60,      // attacksound
 		S_CCOMMAND3,    // painstate
 		200,            // painchance
 		sfx_dmpain,     // painsound
@@ -3531,7 +4234,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
+		sfx_s3k5d,      // activesound
 		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
 		S_NULL          // raisestate
 	},
@@ -3550,13 +4253,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_XPLD_FLICKY,  // deathstate
-		S_DETON16,      // xdeathstate
+		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1*FRACUNIT,     // speed
 		20*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_ENEMY|MF_SHOOTABLE|MF_NOGRAVITY|MF_MISSILE, // flags
@@ -3644,33 +4347,114 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		(statenum_t)MT_JETTBULLET// raisestate
 	},
 
-	{           // MT_SHARP
+	{           // MT_SPINCUSHION
 		112,            // doomednum
-		S_SHARP_ROAM1,  // spawnstate
+		S_SPINCUSHION_LOOK, // spawnstate
 		1,              // spawnhealth
-		S_SHARP_ROAM2,  // seestate
+		S_SPINCUSHION_CHASE1, // seestate
 		sfx_None,       // seesound
 		3*TICRATE,      // reactiontime
 		sfx_s3kd8s,     // attacksound
 		S_NULL,         // painstate
 		5*TICRATE,      // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_SHARP_AIM1,   // missilestate
+		sfx_shrpsp,     // painsound
+		S_SPINCUSHION_STOP1, // meleestate
+		S_SPINCUSHION_AIM1, // missilestate
 		S_XPLD_FLICKY,  // deathstate
-		S_SHARP_SPIN,   // xdeathstate
+		S_SPINCUSHION_STOP3, // xdeathstate
 		sfx_pop,        // deathsound
 		2,              // speed
 		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
-		sfx_None,       // activesound
+		sfx_s3kaa,      // activesound
 		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_BOUNCE, // flags
 		S_NULL          // raisestate
 	},
 
+	{           // MT_CRUSHSTACEAN
+		126,            // doomednum
+		S_CRUSHSTACEAN_ROAM1, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		32,             // reactiontime
+		sfx_s3k6b,      // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_CRUSHSTACEAN_PUNCH1, // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		8,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CRUSHCLAW
+		-1,             // doomednum
+		S_CRUSHCLAW_AIM, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		16,             // reactiontime
+		sfx_s3k6b,      // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_CRUSHCLAW_OUT,// missilestate
+		S_XPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		1,              // speed
+		22*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		(sfx_s3k49<<8), // mass
+		0,              // damage
+		sfx_s3kd2l,     // activesound
+		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MT_CRUSHCHAIN   // raisestate
+	},
+
+	{           // MT_CRUSHCHAIN
+		-1,             // doomednum
+		S_CRUSHCHAIN,   // spawnstate
+		0,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		0,              // radius
+		0,              // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_JETJAW
 		113,            // doomednum
 		S_JETJAW_ROAM1, // spawnstate
@@ -3729,26 +4513,26 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		115,            // doomednum
 		S_VULTURE_STND, // spawnstate
 		1,              // spawnhealth
-		S_VULTURE_VTOL1,// seestate
+		S_VULTURE_DRIFT,// seestate
 		sfx_None,       // seesound
-		2,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
+		TICRATE/2,      // reactiontime
+		sfx_s3k60,      // attacksound
+		S_VULTURE_STUNNED, // painstate
 		S_NULL,         // painchance
-		sfx_None,       // painsound
+		sfx_s3k96,      // painsound
 		S_NULL,         // meleestate
 		S_VULTURE_ZOOM1,// missilestate
 		S_XPLD_FLICKY,  // deathstate
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
-		3,              // speed
+		5,              // speed
 		12*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
 		TICRATE,        // mass
 		0,              // damage
 		sfx_jet,        // activesound
-		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY|MF_SLIDEME, // flags
 		S_NULL          // raisestate
 	},
 
@@ -3799,7 +4583,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_PAIN|MF_NOGRAVITY, // flags
@@ -3810,56 +4594,83 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		117,              // doomednum
 		S_ROBOHOOD_LOOK,  // spawnstate
 		1,                // spawnhealth
-		S_ROBOHOOD_STND,  // seestate
+		S_ROBOHOOD_STAND, // seestate
 		sfx_None,         // seesound
 		TICRATE,          // reactiontime
 		sfx_None,         // attacksound
-		S_ROBOHOOD_JUMP,  // painstate
+		S_NULL,           // painstate
 		0,                // painchance
 		sfx_None,         // painsound
-		S_NULL,           // meleestate
-		S_ROBOHOOD_SHOOT, // missilestate
+		S_ROBOHOOD_JUMP3, // meleestate
+		S_ROBOHOOD_FIRE1, // missilestate
 		S_XPLD_FLICKY,    // deathstate
-		S_ROBOHOOD_JUMP2, // xdeathstate
+		S_NULL,           // xdeathstate
 		sfx_pop,          // deathsound
-		0,                // speed
+		3,                // speed
 		24*FRACUNIT,      // radius
 		32*FRACUNIT,      // height
 		0,                // display offset
 		100,              // mass
 		0,                // damage
-		sfx_None,         // activesound
+		sfx_s3k4a,        // activesound
 		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
-		S_ROBOHOOD_FALL   // raisestate
+		S_ROBOHOOD_JUMP1  // raisestate
 	},
 
 	{           // MT_FACESTABBER
 		118,            // doomednum
 		S_FACESTABBER_STND1, // spawnstate
-		1,              // spawnhealth
+		2,              // spawnhealth
 		S_FACESTABBER_STND1, // seestate
 		sfx_None,       // seesound
-		35,             // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
+		70,             // reactiontime
+		sfx_zoom,       // attacksound
+		S_FACESTABBER_PAIN, // painstate
 		0,              // painchance
-		sfx_None,       // painsound
+		sfx_dmpain,     // painsound
 		S_FACESTABBER_CHARGE1, // meleestate
-		S_NULL,         // missilestate
-		S_XPLD_FLICKY,  // deathstate
+		S_FACESTABBER_CHARGE1, // missilestate
+		S_FACESTABBER_DIE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_pop,        // deathsound
+		sfx_cybdth,     // deathsound
 		3,              // speed
 		32*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		72*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		sfx_s3kc5s,      // activesound
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_SLIDEME, // flags
 		S_NULL          // raisestate
 	},
 
+	{           // MT_FACESTABBERSPEAR
+		-1,              // doomednum
+		S_FACESTABBERSPEAR, // spawnstate
+		1,               // spawnhealth
+		S_NULL,          // seestate
+		sfx_None,        // seesound
+		35,              // reactiontime
+		sfx_None,        // attacksound
+		S_NULL,          // painstate
+		0,               // painchance
+		sfx_None,        // painsound
+		S_NULL,          // meleestate
+		S_NULL,          // missilestate
+		S_NULL,          // deathstate
+		S_NULL,          // xdeathstate
+		sfx_None,        // deathsound
+		0,               // speed
+		32*FRACUNIT,     // radius
+		72*FRACUNIT,     // height
+		0,               // display offset
+		DMG_SPIKE,       // mass
+		0,               // damage
+		sfx_None,        // activesound
+		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL           // raisestate
+	},
+
 	{           // MT_EGGGUARD
 		119,             // doomednum
 		S_EGGGUARD_STND, // spawnstate
@@ -3900,7 +4711,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_s3k7b,       // painsound
 		S_NULL,          // meleestate
 		S_NULL,          // missilestate
-		S_XPLD1,         // deathstate
+		S_EGGSHIELDBREAK,// deathstate
 		S_NULL,          // xdeathstate
 		sfx_wbreak,      // deathsound
 		3,               // speed
@@ -3916,56 +4727,137 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GSNAPPER
 		120,            // doomednum
-		S_GSNAPPER_STND,// spawnstate
+		S_SNAPPER_SPAWN,// spawnstate
 		1,              // spawnhealth
 		S_GSNAPPER1,    // seestate
 		sfx_None,       // seesound
-		32,             // reactiontime
+		10,             // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_XPLD_FLICKY,  // deathstate
+		S_SNAPPER_XPLD, // deathstate
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
-		3,              // speed
+		4,              // speed
 		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
+		sfx_s3k7e,      // activesound
 		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_MINUS
-		121,            // doomednum
-		S_MINUS_STND,   // spawnstate
+	{           // MT_SNAPPER_LEG
+		-1,             // doomednum
+		S_SNAPPER_LEG,  // spawnstate
 		1,              // spawnhealth
-		S_MINUS_DIGGING,// seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		32,             // reactiontime
-		sfx_s3k82,      // attacksound
+		0,              // reactiontime
+		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		200,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
-		S_MINUS_DOWNWARD1,// meleestate
-		S_MINUS_POPUP,  // missilestate
-		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_pop,        // deathsound
-		12,             // speed
-		24*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		6*FRACUNIT,     // radius
+		12*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_mindig,     // activesound
-		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
-		S_MINUS_UPWARD1  // raisestate
+		sfx_None,       // activesound
+		MF_SCENERY|MF_PAIN|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_SNAPPER_LEGRAISE // raisestate
+	},
+
+	{           // MT_SNAPPER_HEAD
+		-1,             // doomednum
+		S_SNAPPER_HEAD, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		6*FRACUNIT,     // radius
+		12*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_PAIN|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_MINUS
+		121,            // doomednum
+		S_MINUS_INIT,   // spawnstate
+		1,              // spawnhealth
+		S_MINUS_DIGGING1,// seestate
+		sfx_None,       // seesound
+		32,             // reactiontime
+		sfx_s3kccs,     // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_MINUS_BURST0, // meleestate
+		S_MINUS_POPUP,  // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		17,             // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_s3kd3s,     // activesound
+		MF_ENEMY|MF_NOCLIPTHING, // flags
+		S_MINUS_BURST1  // raisestate
+	},
+
+	{           // MT_MINUSDIRT
+		-1,             // doomednum
+		S_MINUSDIRT1,   // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING, // flags
+		S_NULL          // raisestate
 	},
 
 	{           // MT_SPRINGSHELL
@@ -4069,16 +4961,70 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		13*FRACUNIT,    // radius
 		26*FRACUNIT,    // height
 		0,              // display offset
-		0,              // mass
+		DMG_SPIKE,      // mass
 		8*FRACUNIT,     // damage
 		sfx_None,       // activesound
 		MF_PAIN|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING, // flags
 		S_NULL          // raisestate
 	},
 
+	{           // MT_CANARIVORE
+		134,            // doomednum
+		S_CANARIVORE_LOOK,  // spawnstate
+		1,              // spawnhealth
+		S_CANARIVORE_AWAKEN1,   // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		0,              // speed
+		12*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CANARIVORE_GAS
+		-1,             // doomednum
+		S_CANARIVOREGAS_1,  // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k5d,      // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIP|MF_SPECIAL, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BOSSEXPLODE
 		-1,             // doomednum
-		S_BPLD1,        // spawnstate
+		S_BOSSEXPLODE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -4366,7 +5312,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		14*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_FIRE, // flags
@@ -4528,7 +5474,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4*FRACUNIT,     // radius
 		4*FRACUNIT,     // height
 		0,              // display offset
-		4,              // mass
+		DMG_WATER,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_PAIN,        // flags
@@ -4574,7 +5520,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MT_PROPELLER,       // painchance
 		sfx_dmpain,         // painsound
 		S_NULL,             // meleestate
-		S_EGGMOBILE3_ATK1,  // missilestate
+		S_EGGMOBILE3_LAUGH1,// missilestate
 		S_EGGMOBILE3_DIE1,  // deathstate
 		S_EGGMOBILE3_FLEE1, // xdeathstate
 		sfx_cybdth,         // deathsound
@@ -4629,9 +5575,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_s3k7b,          // painsound
 		S_NULL,             // meleestate
 		S_FAKEMOBILE_ATK1,  // missilestate
-		S_XPLD1,            // deathstate
+		S_FAKEMOBILE_DIE1,  // deathstate
 		S_NULL,             // xdeathstate
-		sfx_pop,            // deathsound
+		sfx_mswarp,         // deathsound
 		8*FRACUNIT,         // speed
 		32*FRACUNIT,        // radius
 		116*FRACUNIT,       // height
@@ -4643,6 +5589,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL              // raisestate
 	},
 
+	{           // MT_SHOCK
+		-1,             // doomednum
+		S_THUNDERCOIN_SPARK, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		35*FRACUNIT,    // height
+		0,              // display offset
+		DMG_ELECTRIC|(sfx_buzz2<<8), // mass
+		20,             // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_EGGMOBILE4
 		203,               // doomednum
 		S_EGGMOBILE4_STND, // spawnstate
@@ -4651,7 +5624,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,          // seesound
 		0,                 // reactiontime
 		sfx_None,          // attacksound
-		S_EGGMOBILE4_PAIN, // painstate
+		S_EGGMOBILE4_PAIN1,// painstate
 		0,                 // painchance
 		sfx_dmpain,        // painsound
 		S_EGGMOBILE4_LATK1,// meleestate
@@ -4683,14 +5656,14 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BPLD1,        // deathstate
+		S_EGGMOBILE4_MACE_DIE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_cybdth,     // deathsound
+		sfx_None,       // deathsound
 		48*FRACUNIT,    // speed
 		34*FRACUNIT,    // radius
 		68*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		1,              // damage
 		sfx_mswing,     // activesound
 		MF_PAIN|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
@@ -4699,7 +5672,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_JETFLAME
 		-1,             // doomednum
-		S_JETFLAME1,    // spawnstate
+		S_JETFLAME,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -4717,97 +5690,43 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		20*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		DMG_FIRE,       // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_PAIN|MF_FIRE, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLACKEGGMAN
-		206,               // doomednum
-		S_BLACKEGG_STND,   // spawnstate
-		8,                 // spawnhealth
-		S_BLACKEGG_WALK1,  // seestate
-		sfx_None,          // seesound
-		8*TICRATE,         // reactiontime
-		sfx_None,          // attacksound
-		S_BLACKEGG_PAIN1,  // painstate
-		0,                 // painchance
-		sfx_None,          // painsound
-		S_BLACKEGG_HITFACE1, // meleestate
-		S_BLACKEGG_MISSILE1, // missilestate
-		S_BLACKEGG_DIE1,   // deathstate
-		S_BLACKEGG_GOOP,   // xdeathstate
-		sfx_None,          // deathsound
-		1,                 // speed
-		48*FRACUNIT,       // radius
-		160*FRACUNIT,      // height
-		0,                 // display offset
-		0,                 // mass
-		3,                 // damage
-		sfx_None,          // activesound
-		MF_SPECIAL|MF_BOSS,// flags
-		S_BLACKEGG_JUMP1   // raisestate
-	},
-
-	{           // MT_BLACKEGGMAN_HELPER
-		-1,                // doomednum
-		S_BLACKEGG_HELPER, // spawnstate
-		8,                 // spawnhealth
-		S_NULL,            // seestate
-		sfx_None,          // seesound
-		0,                 // reactiontime
-		sfx_None,          // attacksound
-		S_NULL,            // painstate
-		0,                 // painchance
-		sfx_None,          // painsound
-		S_NULL,            // meleestate
-		S_NULL,            // missilestate
-		S_NULL,            // deathstate
-		S_NULL,            // xdeathstate
-		sfx_None,          // deathsound
-		1,                 // speed
-		48*FRACUNIT,       // radius
-		32*FRACUNIT,       // height
-		0,                 // display offset
-		0,                 // mass
-		1,                 // damage
-		sfx_None,          // activesound
-		MF_SOLID|MF_NOGRAVITY,          // flags
-		S_NULL             // raisestate
-	},
-
-	{           // MT_BLACKEGGMAN_GOOPFIRE
-		-1,             // doomednum
-		S_BLACKEGG_GOOP1, // spawnstate
+	{           // MT_EGGROBO1
+		1127,           // doomednum
+		S_EGGROBO1_STND,// spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
+		sfx_s3ka0,      // seesound
 		8,              // reactiontime
-		sfx_None,       // attacksound
+		sfx_bsnipe,     // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_EGGROBO1_BSLAP1, // meleestate
 		S_NULL,         // missilestate
-		S_BLACKEGG_GOOP3, // deathstate
+		S_EGGROBO1_PISSED, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ghit,       // deathsound
-		30*FRACUNIT,    // speed
-		11*FRACUNIT,    // radius
-		8*FRACUNIT,     // height
-		100,            // display offset
-		100,            // mass
+		sfx_s3ka0,      // deathsound
+		12*FRACUNIT,    // speed
+		20*FRACUNIT,    // radius
+		72*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLACKEGGMAN_MISSILE
+	{           // MT_EGGROBOJET
 		-1,             // doomednum
-		S_BLACKEGG_MISSILE, // spawnstate
+		S_EGGROBOJET,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -4818,42 +5737,311 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BPLD1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_bexpld,     // deathsound
-		10*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		1,              // speed
+		10*FRACUNIT,    // radius
+		28*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
-		1,              // damage
+		0,              // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CYBRAKDEMON
-		209,                // doomednum
-		S_CYBRAKDEMON_IDLE, // spawnstate
-		12,                 // spawnhealth
-		S_CYBRAKDEMON_WALK1,// seestate
-		sfx_None,           // seesound
-		15,                 // reactiontime
-		sfx_None,           // attacksound
-		S_CYBRAKDEMON_PAIN1,// painstate
-		0,                  // painchance
-		sfx_behurt,         // painsound
-		S_CYBRAKDEMON_CHOOSE_ATTACK2, // meleestate
-		S_CYBRAKDEMON_CHOOSE_ATTACK1, // missilestate
-		S_CYBRAKDEMON_DIE1, // deathstate
-		S_NULL,             // xdeathstate
-		sfx_s3kb4,          // deathsound
-		40,                 // speed
-		48*FRACUNIT,        // radius
-		160*FRACUNIT,       // height
-		0,                  // display offset
-		100,                // mass
-		1,                  // damage
+	{           // MT_FANG
+		204,               // doomednum
+		S_FANG_IDLE1,      // spawnstate
+		8,                 // spawnhealth
+		S_FANG_PATHINGSTART1, // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_skid,          // attacksound
+		S_FANG_PAIN1,      // painstate
+		0,                 // painchance
+		sfx_s3k5d,         // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_FANG_DIE1,       // deathstate
+		S_FANG_KO,         // xdeathstate
+		sfx_s3k90,         // deathsound
+		0,                 // speed
+		24*FRACUNIT,       // radius
+		60*FRACUNIT,       // height
+		0,                 // display offset
+		0,                 // mass
+		3,                 // damage
+		sfx_boingf,        // activesound
+		MF_SPECIAL|MF_BOSS|MF_SHOOTABLE|MF_GRENADEBOUNCE, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_FBOMB
+		-1,                // doomednum
+		S_FBOMB1,          // spawnstate
+		1,                 // spawnhealth
+		S_NULL,            // seestate
+		sfx_s3k51,         // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_FBOMB_EXPL1,     // deathstate
+		S_NULL,            // xdeathstate
+		sfx_s3k4e,         // deathsound
+		20*FRACUNIT,       // speed
+		24*FRACUNIT,       // radius
+		48*FRACUNIT,       // height
+		0,                 // display offset
+		0,                 // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_NOBLOCKMAP|MF_MISSILE, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_TNTDUST
+		-1,                // doomednum
+		S_TNTDUST_1,       // spawnstate
+		1,                 // spawnhealth
+		S_NULL,            // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_NULL,            // deathstate
+		S_NULL,            // xdeathstate
+		sfx_None,          // deathsound
+		20*FRACUNIT,       // speed
+		16*FRACUNIT,       // radius
+		32*FRACUNIT,       // height
+		0,                 // display offset
+		100,               // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL             // raisestate
+	},
+	{           // MT_FSGNA
+		-1,                // doomednum
+		S_FSGNA,           // spawnstate
+		1,                 // spawnhealth
+		S_NULL,            // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_NULL,            // deathstate
+		S_NULL,            // xdeathstate
+		sfx_mspogo,        // deathsound
+		0,                 // speed
+		124*FRACUNIT,      // radius
+		124*FRACUNIT,      // height
+		0,                 // display offset
+		0,                 // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_FSGNB
+		-1,                // doomednum
+		S_FSGNB,           // spawnstate
+		1,                 // spawnhealth
+		S_NULL,            // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_FSGNC,           // deathstate
+		S_NULL,            // xdeathstate
+		sfx_None,          // deathsound
+		0,                 // speed
+		124*FRACUNIT,      // radius
+		640*FRACUNIT,      // height
+		0,                 // display offset
+		0,                 // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_SOLID, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_FANGWAYPOINT
+		294,               // doomednum
+		S_INVISIBLE,       // spawnstate
+		1000,              // spawnhealth
+		S_NULL,            // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_NULL,            // deathstate
+		S_NULL,            // xdeathstate
+		sfx_None,          // deathsound
+		0,                 // speed
+		FRACUNIT,          // radius
+		FRACUNIT,          // height
+		0,                 // display offset
+		0,                 // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOTHINK, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_BLACKEGGMAN
+		206,               // doomednum
+		S_BLACKEGG_STND,   // spawnstate
+		8,                 // spawnhealth
+		S_BLACKEGG_WALK1,  // seestate
+		sfx_None,          // seesound
+		8*TICRATE,         // reactiontime
+		sfx_None,          // attacksound
+		S_BLACKEGG_PAIN1,  // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_BLACKEGG_HITFACE1, // meleestate
+		S_BLACKEGG_MISSILE1, // missilestate
+		S_BLACKEGG_DIE1,   // deathstate
+		S_BLACKEGG_GOOP,   // xdeathstate
+		sfx_None,          // deathsound
+		1,                 // speed
+		48*FRACUNIT,       // radius
+		160*FRACUNIT,      // height
+		0,                 // display offset
+		0,                 // mass
+		3,                 // damage
+		sfx_None,          // activesound
+		MF_SPECIAL|MF_BOSS,// flags
+		S_BLACKEGG_JUMP1   // raisestate
+	},
+
+	{           // MT_BLACKEGGMAN_HELPER
+		-1,                // doomednum
+		S_BLACKEGG_HELPER, // spawnstate
+		8,                 // spawnhealth
+		S_NULL,            // seestate
+		sfx_None,          // seesound
+		0,                 // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		0,                 // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_NULL,            // deathstate
+		S_NULL,            // xdeathstate
+		sfx_None,          // deathsound
+		1,                 // speed
+		48*FRACUNIT,       // radius
+		32*FRACUNIT,       // height
+		0,                 // display offset
+		0,                 // mass
+		1,                 // damage
+		sfx_None,          // activesound
+		MF_SOLID|MF_NOGRAVITY,          // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_BLACKEGGMAN_GOOPFIRE
+		-1,             // doomednum
+		S_BLACKEGG_GOOP1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BLACKEGG_GOOP3, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ghit,       // deathsound
+		30*FRACUNIT,    // speed
+		11*FRACUNIT,    // radius
+		8*FRACUNIT,     // height
+		100,            // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BLACKEGGMAN_MISSILE
+		-1,             // doomednum
+		S_BLACKEGG_MISSILE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BOSSEXPLODE,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_bexpld,     // deathsound
+		10*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CYBRAKDEMON
+		209,                // doomednum
+		S_CYBRAKDEMON_IDLE, // spawnstate
+		12,                 // spawnhealth
+		S_CYBRAKDEMON_WALK1,// seestate
+		sfx_None,           // seesound
+		15,                 // reactiontime
+		sfx_None,           // attacksound
+		S_CYBRAKDEMON_PAIN1,// painstate
+		0,                  // painchance
+		sfx_behurt,         // painsound
+		S_CYBRAKDEMON_CHOOSE_ATTACK2, // meleestate
+		S_CYBRAKDEMON_CHOOSE_ATTACK1, // missilestate
+		S_CYBRAKDEMON_DIE1, // deathstate
+		S_NULL,             // xdeathstate
+		sfx_s3kb4,          // deathsound
+		40,                 // speed
+		48*FRACUNIT,        // radius
+		160*FRACUNIT,       // height
+		0,                  // display offset
+		100,                // mass
+		1,                  // damage
 		sfx_bewar1,         // activesound
 		MF_SPECIAL|MF_BOSS|MF_SHOOTABLE, // flags
 		S_NULL              // raisestate
@@ -4879,7 +6067,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		48*FRACUNIT,    // radius
 		160*FRACUNIT,   // height
 		0,              // display offset
-		100,            // mass
+		DMG_ELECTRIC,   // mass
 		1,              // damage
 		sfx_beelec,     // activesound
 		MF_PAIN|MF_FIRE|MF_NOGRAVITY|MF_PUSHABLE, // flags
@@ -4906,7 +6094,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		32*FRACUNIT,    // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -4933,7 +6121,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -4960,7 +6148,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_PAIN|MF_FIRE|MF_RUNSPAWNFUNC, // flags
@@ -4980,7 +6168,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BPLD1,        // deathstate
+		S_BOSSEXPLODE,  // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		10*FRACUNIT,    // speed
@@ -5007,7 +6195,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BPLD1,        // deathstate
+		S_BOSSEXPLODE,  // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		10*FRACUNIT,    // speed
@@ -5030,7 +6218,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		20*TICRATE,     // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -5041,7 +6229,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		20*TICRATE,     // mass
+		0,              // mass
 		48*FRACUNIT,    // damage
 		sfx_s3k5d,      // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE|MF_GRENADEBOUNCE, // flags
@@ -5068,7 +6256,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		32*FRACUNIT,    // damage
 		sfx_s3k99,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE, // flags
@@ -5095,7 +6283,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE, // flags
@@ -5164,20 +6352,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_METALSONIC_DASH,  // seestate
 		sfx_s3k54,          // seesound
 		0,                  // reactiontime
-		sfx_trpowr,         // attacksound
+		sfx_bechrg,         // attacksound
 		S_METALSONIC_PAIN,  // painstate
 		S_METALSONIC_VECTOR,// painchance
 		sfx_dmpain,         // painsound
 		S_METALSONIC_BADBOUNCE, // meleestate
 		S_METALSONIC_SHOOT, // missilestate
-		S_METALSONIC_DEATH, // deathstate
+		S_METALSONIC_DEATH1,// deathstate
 		S_METALSONIC_FLEE1, // xdeathstate
 		sfx_s3k6e,          // deathsound
 		MT_ENERGYBALL,      // speed
 		16*FRACUNIT,        // radius
 		48*FRACUNIT,        // height
 		0,                  // display offset
-		sfx_s3k5a,          // mass
+		0,                  // mass
 		3,                  // damage
 		sfx_mswarp,         // activesound
 		MF_NOGRAVITY|MF_BOSS|MF_SLIDEME, // flags
@@ -5202,7 +6390,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		32*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		52*FRACUNIT,    // height
 		0,              // display offset
 		0,              // mass
 		0,              // damage
@@ -5292,9 +6480,63 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLUEBALL
+	{           // MT_BLUESPHERE
+		1706,           // doomednum
+		S_BLUESPHERE,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGBLUESPHERE,        // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BLUESPHERESPARK, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k65,      // deathsound
+		38*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_BLUESPHEREBONUS // raisestate
+	},
+
+	{           // MT_FLINGBLUESPHERE
 		-1,             // doomednum
-		S_BLUEBALL,    // spawnstate
+		S_BLUESPHERE,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGBLUESPHERE,   // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_BLUESPHERE,        // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BLUESPHERESPARK, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k65,     // deathsound
+		38*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL, // flags
+		S_BLUESPHEREBONUS // raisestate
+	},
+
+	{           // MT_BOMBSPHERE
+		520,            // doomednum
+		S_BOMBSPHERE1,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -5305,9 +6547,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BLUEBALLSPARK, // deathstate
+		S_BOSSEXPLODE,  // deathstate
 		S_NULL,         // xdeathstate
-		sfx_s3k65,      // deathsound
+		sfx_cybdth,     // deathsound
 		38*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
@@ -5388,7 +6630,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_token,      // deathsound
+		sfx_None,       // deathsound
 		0,              // speed
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
@@ -5455,7 +6697,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_EMBLEM
-		-1,             // doomednum
+		322,            // doomednum
 		S_EMBLEM1,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -5666,7 +6908,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_EMERHUNT
 		320,            // doomednum
-		S_EMER1,        // spawnstate
+		S_SHRD1,        // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -5681,8 +6923,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_cgot,       // deathsound
 		8,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		12*FRACUNIT,    // radius
+		42*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
@@ -5692,7 +6934,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_EMERALDSPAWN
-		323,            // doomednum
+		321,            // doomednum
 		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -5799,40 +7041,67 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLUESPRING
-		552,            // doomednum
-		S_BLUESPRING,   // spawnstate
+	{           // MT_BUMPER
+		542,            // doomednum
+		S_BUMPER,       // spawnstate
 		1000,           // spawnhealth
-		S_BLUESPRING2,  // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		5,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
-		sfx_spring,     // painsound
+		-1,             // painchance
+		sfx_s3kaa,      // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		20*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
-		11*FRACUNIT,    // mass
+		16*FRACUNIT,    // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING, // flags
-		S_BLUESPRING2   // raisestate
+		MF_SPRING|MF_NOGRAVITY, // flags
+		S_BUMPERHIT     // raisestate
+	},
+
+	{           // MT_BALLOON
+		543,            // doomednum
+		S_BALLOON,      // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		2,              // painchance
+		sfx_s3k77,      // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BALLOONPOP2,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		20*FRACUNIT,    // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPRING|MF_NOGRAVITY, // flags
+		S_BALLOONPOP1   // raisestate
 	},
 
 	{           // MT_YELLOWSPRING
 		550,            // doomednum
 		S_YELLOWSPRING, // spawnstate
 		1000,           // spawnhealth
-		S_YELLOWSPRING2,// seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5849,7 +7118,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		20*FRACUNIT,    // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING, // flags
+		MF_SPRING, // flags
 		S_YELLOWSPRING2 // raisestate
 	},
 
@@ -5857,9 +7126,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		551,            // doomednum
 		S_REDSPRING,    // spawnstate
 		1000,           // spawnhealth
-		S_REDSPRING2,   // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5876,17 +7145,44 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING, // flags
+		MF_SPRING, // flags
 		S_REDSPRING2    // raisestate
 	},
 
+	{           // MT_BLUESPRING
+		552,            // doomednum
+		S_BLUESPRING,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_spring,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		20*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		11*FRACUNIT,    // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPRING, // flags
+		S_BLUESPRING2   // raisestate
+	},
+
 	{           // MT_YELLOWDIAG
 		555,            // doomednum
 		S_YDIAG1,       // spawnstate
 		1,              // spawnhealth
-		S_YDIAG2,       // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5903,7 +7199,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		20*FRACUNIT,    // mass
 		20*FRACUNIT,    // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING, // flags
+		MF_SPRING, // flags
 		S_YDIAG2        // raisestate
 	},
 
@@ -5911,9 +7207,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		556,            // doomednum
 		S_RDIAG1,       // spawnstate
 		1,              // spawnhealth
-		S_RDIAG2,       // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5930,17 +7226,44 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // mass
 		32*FRACUNIT,    // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING, // flags
+		MF_SPRING, // flags
 		S_RDIAG2        // raisestate
 	},
 
+	{           // MT_BLUEDIAG
+		557,            // doomednum
+		S_BDIAG1,       // spawnstate
+		1,              // spawnhealth
+		S_BDIAG2,       // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_spring,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		11*FRACUNIT,    // mass
+		11*FRACUNIT,    // damage
+		sfx_None,       // activesound
+		MF_SPRING, // flags
+		S_BDIAG2        // raisestate
+	},
+
 	{           // MT_YELLOWHORIZ
 		558,            // doomednum
 		S_YHORIZ1,      // spawnstate
 		1,              // spawnhealth
-		S_YHORIZ2,      // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5957,7 +7280,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		36*FRACUNIT,    // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		MF_SPRING|MF_NOGRAVITY, // flags
 		S_YHORIZ2       // raisestate
 	},
 
@@ -5965,9 +7288,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		559,            // doomednum
 		S_RHORIZ1,      // spawnstate
 		1,              // spawnhealth
-		S_RHORIZ2,      // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -5984,7 +7307,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		72*FRACUNIT,    // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		MF_SPRING|MF_NOGRAVITY, // flags
 		S_RHORIZ2       // raisestate
 	},
 
@@ -5992,9 +7315,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		560,            // doomednum
 		S_BHORIZ1,      // spawnstate
 		1,              // spawnhealth
-		S_BHORIZ2,      // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -6009,9 +7332,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // height
 		0,              // display offset
 		0,              // mass
-		4*FRACUNIT,    // damage
+		1*FRACUNIT,     // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		MF_SPRING|MF_NOGRAVITY, // flags
 		S_BHORIZ2       // raisestate
 	},
 
@@ -6070,33 +7393,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_SPIKEBALL
-		-1,            // doomednum
-		S_SPIKEBALL1,   // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
-		4*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
-		0,              // display offset
-		100,            // mass
-		1,              // damage
-		sfx_None,       // activesound
-		MF_PAIN|MF_NOGRAVITY, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_SPECIALSPIKEBALL
 		521,            // doomednum
 		S_SPIKEBALL1,   // spawnstate
 		1000,           // spawnhealth
@@ -6114,12 +7410,12 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		10*FRACUNIT,    // speed
 		12*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		1,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_NOGRAVITY, // flags
+		MF_PAIN|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -6143,7 +7439,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		14*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_FIRE, // flags
@@ -6161,7 +7457,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_s3k64,      // painsound
-		S_NULL,         // meleestate
+		S_SPIKE4,       // meleestate
 		S_NULL,         // missilestate
 		S_SPIKED1,      // deathstate
 		S_SPIKED2,      // xdeathstate
@@ -6173,7 +7469,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
+		MF_NOBLOCKMAP|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -6188,7 +7484,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_s3k64,      // painsound
-		S_NULL,         // meleestate
+		S_WALLSPIKE4,   // meleestate
 		S_NULL,         // missilestate
 		S_WALLSPIKED1,  // deathstate
 		S_WALLSPIKED2,  // xdeathstate
@@ -6259,61 +7555,61 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_BIGMINE
-		524,            // doomednum
-		S_BIGMINE1,     // spawnstate
+		1012,           // doomednum
+		S_BIGMINE_IDLE, // spawnstate
 		1,              // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
+		S_BIGMINE_ALERT1, // seestate
+		sfx_s3k5c,      // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		TICRATE,        // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		0,              // painchance
+		sfx_s3k86,      // painsound
+		S_BIGMINE_SET1, // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_BIGMINE_SET2, // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		2*FRACUNIT,     // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		28*FRACUNIT,    // radius
+		56*FRACUNIT,    // height
 		0,              // display offset
 		MT_UWEXPLODE,   // mass
-		64*FRACUNIT,    // damage
-		sfx_gbeep,      // activesound
-		MF_SPECIAL|MF_NOGRAVITY,     // flags
+		0,              // damage
+		sfx_s3k9e,      // activesound
+		MF_SPECIAL|MF_NOGRAVITY|MF_SHOOTABLE|MF_ENEMY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BIGAIRMINE
-		527,            // doomednum
-		S_BIGMINE1,     // spawnstate
+	{           // MT_BLASTEXECUTOR
+		756,            // doomednum
+		S_INVISIBLE,    // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		TICRATE,        // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_bexpld,     // deathsound
-		2*FRACUNIT,     // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		8,              // speed
+		32*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		MT_BOSSEXPLODE, // mass
-		64*FRACUNIT,    // damage
-		sfx_gbeep,      // activesound
-		MF_SPECIAL|MF_NOGRAVITY,     // flags
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SHOOTABLE|MF_NOGRAVITY|MF_NOCLIPTHING, // flags
 		S_NULL          // raisestate
 	},
 
 	{           // MT_CANNONLAUNCHER
-		525,            // doomednum
+		1123,           // doomednum
 		S_CANNONLAUNCHER1, // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
@@ -6383,8 +7679,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -6410,8 +7706,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_PITY_ICON,   // damage
@@ -6437,8 +7733,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ATTRACT_ICON,// damage
@@ -6464,8 +7760,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FORCE_ICON,  // damage
@@ -6491,8 +7787,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ARMAGEDDON_ICON, // damage
@@ -6518,8 +7814,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_WHIRLWIND_ICON, // damage
@@ -6545,8 +7841,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ELEMENTAL_ICON, // damage
@@ -6572,8 +7868,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SNEAKERS_ICON, // damage
@@ -6599,8 +7895,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_INVULN_ICON, // damage
@@ -6626,8 +7922,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_1UP_ICON,    // damage
@@ -6653,8 +7949,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_EGGMAN_ICON, // damage
@@ -6680,8 +7976,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_MIXUP_ICON,  // damage
@@ -6707,8 +8003,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_UNKNOWN,     // damage
@@ -6734,8 +8030,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_GRAVITY_ICON, // damage
@@ -6761,8 +8057,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RECYCLER_ICON, // damage
@@ -6788,8 +8084,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SCORE1K_ICON, // damage
@@ -6815,8 +8111,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SCORE10K_ICON, // damage
@@ -6842,8 +8138,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FLAMEAURA_ICON, // damage
@@ -6869,8 +8165,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_BUBBLEWRAP_ICON, // damage
@@ -6896,8 +8192,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_THUNDERCOIN_ICON, // damage
@@ -6923,8 +8219,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_PITY_ICON,   // damage
@@ -6950,8 +8246,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ATTRACT_ICON,// damage
@@ -6977,8 +8273,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FORCE_ICON,  // damage
@@ -7004,8 +8300,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ARMAGEDDON_ICON, // damage
@@ -7031,8 +8327,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_WHIRLWIND_ICON, // damage
@@ -7058,8 +8354,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ELEMENTAL_ICON, // damage
@@ -7085,8 +8381,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SNEAKERS_ICON, // damage
@@ -7112,8 +8408,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_INVULN_ICON, // damage
@@ -7139,8 +8435,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_EGGMAN_ICON, // damage
@@ -7166,8 +8462,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_GRAVITY_ICON, // damage
@@ -7193,8 +8489,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FLAMEAURA_ICON, // damage
@@ -7220,8 +8516,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_BUBBLEWRAP_ICON, // damage
@@ -7247,8 +8543,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_THUNDERCOIN_ICON, // damage
@@ -7274,8 +8570,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -7301,8 +8597,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -7844,7 +9140,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -7871,7 +9167,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -7891,14 +9187,14 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_MINE_BOOM1,   // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_cybdth,     // deathsound
 		20*FRACUNIT,    // speed
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -7918,14 +9214,14 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_MINE_BOOM1,   // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_cybdth,     // deathsound
 		20*FRACUNIT,    // speed
 		11*FRACUNIT,    // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -7937,7 +9233,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_ENERGYBALL1,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_s3k54,      // seesound
+		sfx_bexpld,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -7952,7 +9248,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		60*FRACUNIT,    // radius
 		120*FRACUNIT,   // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
 		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
@@ -7979,7 +9275,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		10*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		64*FRACUNIT,    // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE, // flags
@@ -8006,7 +9302,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -8033,7 +9329,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		12*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -8060,7 +9356,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE, // flags
@@ -8068,7 +9364,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_CANNONBALLDECOR
-		526,            // doomednum
+		1124,           // doomednum
 		S_CANNONBALL1,  // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
@@ -8099,7 +9395,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_ARROW,        // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
+		sfx_s3ka0,      // seesound
 		32,             // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -8107,23 +9403,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_ARROWDOWN,    // xdeathstate
-		sfx_None,       // deathsound
+		S_ARROWBONK,    // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k52,      // deathsound
 		16*FRACUNIT,    // speed
 		4*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		1,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
-		S_ARROWUP       // raisestate
+		sfx_s3k51,      // activesound
+		MF_NOBLOCKMAP|MF_MISSILE, // flags
+		S_NULL          // raisestate
 	},
 
 	{           // MT_DEMONFIRE
 		-1,             // doomednum
-		S_DEMONFIRE1,   // spawnstate
+		S_DEMONFIRE,    // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8141,7 +9437,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
@@ -8229,6 +9525,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BLUEBERRYBUSH
+		803,            // doomednum
+		S_BLUEBERRYBUSH, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BERRYBUSH
 		804,            // doomednum
 		S_BERRYBUSH,    // spawnstate
@@ -8285,7 +9608,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZTREE
 		806,            // doomednum
-		S_GFZTREE,         // spawnstate
+		S_GFZTREE,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8312,7 +9635,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZBERRYTREE
 		807,            // doomednum
-		S_GFZBERRYTREE,         // spawnstate
+		S_GFZBERRYTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8339,7 +9662,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_GFZCHERRYTREE
 		808,            // doomednum
-		S_GFZCHERRYTREE,         // spawnstate
+		S_GFZCHERRYTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8365,8 +9688,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_CHECKERTREE
-		810,            // doomednum
-		S_CHECKERTREE,         // spawnstate
+		809,            // doomednum
+		S_CHECKERTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8392,8 +9715,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_CHECKERSUNSETTREE
-		811,            // doomednum
-		S_CHECKERSUNSETTREE,         // spawnstate
+		810,            // doomednum
+		S_CHECKERSUNSETTREE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8419,8 +9742,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_FHZTREE
-		812,            // doomednum
-		S_FHZTREE,         // spawnstate
+		2102,           // doomednum
+		S_FHZTREE,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8446,8 +9769,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_FHZPINKTREE
-		813,            // doomednum
-		S_FHZPINKTREE,         // spawnstate
+		2103,           // doomednum
+		S_FHZPINKTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8473,8 +9796,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_POLYGONTREE
-		814,            // doomednum
-		S_POLYGONTREE,         // spawnstate
+		811,            // doomednum
+		S_POLYGONTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8500,8 +9823,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_BUSHTREE
-		815,            // doomednum
-		S_BUSHTREE,         // spawnstate
+		812,            // doomednum
+		S_BUSHTREE,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8527,8 +9850,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_BUSHREDTREE
-		816,            // doomednum
-		S_BUSHREDTREE,         // spawnstate
+		813,            // doomednum
+		S_BUSHREDTREE,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8553,6 +9876,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_SPRINGTREE
+		1600,           // doomednum
+		S_SPRINGTREE,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_THZFLOWER1
 		900,            // doomednum
 		S_THZFLOWERA,    // spawnstate
@@ -8597,8 +9947,89 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		8,              // speed
-		8*FRACUNIT,     // radius
-		32*FRACUNIT,    // height
+		16*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THZFLOWER3
+		903,            // doomednum
+		S_THZFLOWERC,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THZTREE
+		904,            // doomednum
+		S_THZTREE,      // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THZTREEBRANCH
+		-1,             // doomednum
+		S_THZTREEBRANCH1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		1*FRACUNIT,     // radius
+		1*FRACUNIT,     // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
@@ -8877,7 +10308,115 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CHAIN
+	{           // MT_KELP
+		1007,           // doomednum
+		S_KELP, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,     // radius
+		292*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_DSZSTALAGMITE
+		1008,           // doomednum
+		S_DSZSTALAGMITE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		116*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_SOLID,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_DSZ2STALAGMITE
+		1011,           // doomednum
+		S_DSZ2STALAGMITE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		116*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_SOLID,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LIGHTBEAM
+		1010,           // doomednum
+		S_LIGHTBEAM1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CHAIN
 		1100,           // doomednum
 		S_CEZCHAIN,     // spawnstate
 		1000,           // spawnhealth
@@ -8906,7 +10445,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_FLAME
 		1101,           // doomednum
-		S_FLAME1,       // spawnstate
+		S_FLAME,        // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8924,7 +10463,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_NOGRAVITY|MF_PAIN|MF_FIRE, // flags
@@ -9008,7 +10547,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9035,7 +10574,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9062,7 +10601,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9089,7 +10628,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9116,7 +10655,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9143,12 +10682,12 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		200,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
 	{           // MT_CUSTOMMACEPOINT
-		1111,           // doomednum
+		1110,           // doomednum
 		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -9170,7 +10709,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		200,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9191,13 +10730,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,           // xdeathstate
 		sfx_None,         // deathsound
 		24*FRACUNIT,      // speed
-		24*FRACUNIT,      // radius
-		48*FRACUNIT,      // height
+		17*FRACUNIT,      // radius
+		34*FRACUNIT,      // height
 		0,                // display offset
 		100,              // mass
 		1,                // damage
 		sfx_None,         // activesound
-		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_NOGRAVITY, // flags
 		S_NULL            // raisestate
 	},
 
@@ -9218,13 +10757,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		48*FRACUNIT,    // speed
-		48*FRACUNIT,    // radius
-		96*FRACUNIT,    // height
+		34*FRACUNIT,    // radius
+		68*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		1,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9248,9 +10787,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		17*FRACUNIT,    // radius
 		34*FRACUNIT,    // height
 		1,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
-		sfx_mswing,     // activesound
+		sfx_s3kc9s, //sfx_mswing, -- activesound
 		MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
@@ -9275,20 +10814,74 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		34*FRACUNIT,    // radius
 		68*FRACUNIT,    // height
 		1,              // display offset
-		100,            // mass
+		0,              // mass
 		1,              // damage
-		sfx_mswing,     // activesound
+		sfx_s3kc9s, //sfx_mswing, -- activesound
 		MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
+	{            // MT_SMALLGRABCHAIN
+		-1,               // doomednum
+		S_SMALLGRABCHAIN, // spawnstate
+		1000,             // spawnhealth
+		S_NULL,           // seestate
+		sfx_None,         // seesound
+		8,                // reactiontime
+		sfx_None,         // attacksound
+		S_NULL,           // painstate
+		0,                // painchance
+		sfx_None,         // painsound
+		S_NULL,           // meleestate
+		S_NULL,           // missilestate
+		S_NULL,           // deathstate
+		S_NULL,           // xdeathstate
+		sfx_None,         // deathsound
+		24*FRACUNIT,      // speed
+		17*FRACUNIT,      // radius
+		34*FRACUNIT,      // height
+		0,                // display offset
+		100,              // mass
+		1,                // damage
+		sfx_None,         // activesound
+		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
+		S_NULL            // raisestate
+	},
+
+	{            // MT_BIGGRABCHAIN
+		-1,             // doomednum
+		S_BIGGRABCHAIN,	// spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		48*FRACUNIT,    // speed
+		34*FRACUNIT,    // radius
+		68*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{            // MT_YELLOWSPRINGBALL
 		-1,             // doomednum
 		S_YELLOWSPRINGBALL, // spawnstate
 		1000,           // spawnhealth
 		S_YELLOWSPRINGBALL2, // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -9305,7 +10898,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		20*FRACUNIT,    // mass
 		0,              // damage
 		sfx_mswing,     // activesound
-		MF_SCENERY|MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_SPRING|MF_NOGRAVITY, // flags
 		S_YELLOWSPRINGBALL2 // raisestate
 	},
 
@@ -9315,7 +10908,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // spawnhealth
 		S_REDSPRINGBALL2, // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -9332,7 +10925,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // mass
 		0,              // damage
 		sfx_mswing,     // activesound
-		MF_SCENERY|MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_SPRING|MF_NOGRAVITY, // flags
 		S_REDSPRINGBALL2 // raisestate
 	},
 
@@ -9356,7 +10949,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		17*FRACUNIT,    // radius
 		34*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_SCENERY|MF_PAIN|MF_FIRE|MF_NOGRAVITY, // flags
@@ -9383,7 +10976,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		34*FRACUNIT,    // radius
 		68*FRACUNIT,    // height
 		1,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_SCENERY|MF_PAIN|MF_FIRE|MF_NOGRAVITY, // flags
@@ -9392,7 +10985,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_CEZFLOWER
 		1103,           // doomednum
-		S_CEZFLOWER1,   // spawnstate
+		S_CEZFLOWER,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9417,11 +11010,11 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BIGTUMBLEWEED
-		1200,           // doomednum
-		S_BIGTUMBLEWEED,// spawnstate
+	{           // MT_CEZPOLE1
+		1117,           // doomednum
+		S_CEZPOLE,      // spawnstate
 		1000,           // spawnhealth
-		S_BIGTUMBLEWEED_ROLL1, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -9434,21 +11027,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		24*FRACUNIT,    // radius
-		48*FRACUNIT,    // height
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_s3k64,      // activesound
-		MF_SPECIAL|MF_BOUNCE,      // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_LITTLETUMBLEWEED
-		1201,           // doomednum
-		S_LITTLETUMBLEWEED,// spawnstate
+	{           // MT_CEZPOLE2
+		1118,           // doomednum
+		S_CEZPOLE,      // spawnstate
 		1000,           // spawnhealth
-		S_LITTLETUMBLEWEED_ROLL1, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -9461,19 +11054,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		12*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_s3k64,      // activesound
-		MF_SPECIAL|MF_BOUNCE,      // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CACTI1
-		1203,           // doomednum
-		S_CACTI1,       // spawnstate
+	{           // MT_CEZBANNER1
+		-1,             // doomednum
+		S_CEZBANNER1,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9488,8 +11081,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -9498,9 +11091,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CACTI2
-		1204,           // doomednum
-		S_CACTI2,       // spawnstate
+	{           // MT_CEZBANNER2
+		-1,             // doomednum
+		S_CEZBANNER2,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9515,8 +11108,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -9525,9 +11118,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CACTI3
-		1205,           // doomednum
-		S_CACTI3,       // spawnstate
+	{           // MT_PINETREE
+		1114,           // doomednum
+		S_PINETREE,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9543,18 +11136,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		628*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_SOLID|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CACTI4
-		1206,           // doomednum
-		S_CACTI4,       // spawnstate
+	{           // MT_CEZBUSH1
+		1115,           // doomednum
+		S_CEZBUSH1,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9570,7 +11163,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		80*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -9579,9 +11172,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAMEJET
-		1300,           // doomednum
-		S_FLAMEJETSTND, // spawnstate
+	{           // MT_CEZBUSH2
+		1116,           // doomednum
+		S_CEZBUSH2,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9596,19 +11189,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		3*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_VERTICALFLAMEJET
-		1301,           // doomednum
-		S_FLAMEJETSTND, // spawnstate
+	{           // MT_CANDLE
+		1119,           // doomednum
+		S_CANDLE,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9623,19 +11216,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		8*FRACUNIT,     // radius
+		48*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAMEJETFLAME
-		-1,             // doomednum
-		S_FLAMEJETFLAME1, // spawnstate
+	{           // MT_CANDLEPRICKET
+		1120,           // doomednum
+		S_CANDLEPRICKET, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9649,20 +11242,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		5*FRACUNIT,     // speed
+		0,              // speed
 		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		176*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_MISSILE|MF_FIRE, // flags
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FJSPINAXISA
-		3575,           // doomednum
-		S_FJSPINAXISA1, // spawnstate
+	{           // MT_FLAMEHOLDER
+		1121,           // doomednum
+		S_FLAMEHOLDER,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9676,50 +11269,50 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		1*FRACUNIT,     // height
+		0,              // speed
+		24*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOSECTOR, // flags
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FJSPINAXISB
-		3576,           // doomednum
-		S_FJSPINAXISB1, // spawnstate
+	{           // MT_FIRETORCH
+		1122,           // doomednum
+		S_FIRETORCH,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		MT_FLAMEPARTICLE, // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
+		0,              // speed
 		16*FRACUNIT,    // radius
-		1*FRACUNIT,     // height
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOSECTOR, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAMEJETFLAMEB
-		-1,             // doomednum
-		S_FLAMEJETFLAMEB1, // spawnstate
+	{           // MT_WAVINGFLAG1
+		1128,           // doomednum
+		S_WAVINGFLAG,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_fire,       // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -9730,20 +11323,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		18,             // speed
-		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		0,              // speed
+		8*FRACUNIT,     // radius
+		208*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_MISSILE|MF_FIRE|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TRAPGOYLE
-		1500,           // doomednum
-		S_TRAPGOYLE,    // spawnstate
+	{           // MT_WAVINGFLAG2
+		1129,           // doomednum
+		S_WAVINGFLAG,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9757,20 +11350,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		0,              // speed
+		8*FRACUNIT,     // radius
+		208*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_statu2,     // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TRAPGOYLEUP
-		1501,           // doomednum
-		S_TRAPGOYLEUP,  // spawnstate
+	{           // MT_WAVINGFLAGSEG1
+		-1,             // doomednum
+		S_WAVINGFLAGSEG1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9784,20 +11377,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		0,              // speed
+		8*FRACUNIT,     // radius
+		1,              // height -- this is not a typo
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_statu2,     // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TRAPGOYLEDOWN
-		1502,           // doomednum
-		S_TRAPGOYLEDOWN,// spawnstate
+	{           // MT_WAVINGFLAGSEG2
+		-1,             // doomednum
+		S_WAVINGFLAGSEG2, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9811,20 +11404,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		0,              // speed
+		8*FRACUNIT,     // radius
+		1,              // height -- this is not a typo
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_statu2,     // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TRAPGOYLELONG
-		1503,           // doomednum
-		S_TRAPGOYLELONG,// spawnstate
+	{           // MT_CRAWLASTATUE
+		1111,           // doomednum
+		S_CRAWLASTATUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9838,50 +11431,50 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21*FRACUNIT,    // speed
+		0,              // speed
 		16*FRACUNIT,    // radius
 		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_statu2,     // activesound
+		0,              // damage
+		sfx_None,       // activesound
 		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TARGET
-		1504,           // doomednum
-		S_TARGET_IDLE,  // spawnstate
-		1,              // spawnhealth
+	{           // MT_FACESTABBERSTATUE
+		1112,           // doomednum
+		S_FACESTABBERSTATUE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		32,             // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		200,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_TARGET_HIT1,  // deathstate
-		S_TARGET_ALLDONE, // xdeathstate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		24*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		32*FRACUNIT,    // radius
+		72*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_SHOOTABLE, // flags
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE0
-		1900,           // doomednum
-		S_STG0,         // spawnstate
+	{           // MT_SUSPICIOUSFACESTABBERSTATUE
+		1113,           // doomednum
+		S_SUSPICIOUSFACESTABBERSTATUE_WAIT, // spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
+		S_SUSPICIOUSFACESTABBERSTATUE_BURST1, // seestate
+		sfx_s3k6f,      // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -9893,19 +11486,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		32*FRACUNIT,    // radius
+		72*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		MT_ROCKCRUMBLE3 // raisestate
 	},
 
-	{           // MT_STALAGMITE1
-		1901,           // doomednum
-		S_STG1,         // spawnstate
+	{           // MT_BRAMBLES
+		1125,           // doomednum
+		S_BRAMBLES,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -9920,21 +11513,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		48*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE2
-		1902,           // doomednum
-		S_STG2,         // spawnstate
+	{           // MT_BIGTUMBLEWEED
+		1200,           // doomednum
+		S_BIGTUMBLEWEED,// spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
+		S_BIGTUMBLEWEED_ROLL1, // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -9947,21 +11540,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		24*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_s3k64,      // activesound
+		MF_SPECIAL|MF_BOUNCE,      // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE3
-		1903,           // doomednum
-		S_STG3,         // spawnstate
+	{           // MT_LITTLETUMBLEWEED
+		1201,           // doomednum
+		S_LITTLETUMBLEWEED,// spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
+		S_LITTLETUMBLEWEED_ROLL1, // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -9974,19 +11567,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		12*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_s3k64,      // activesound
+		MF_SPECIAL|MF_BOUNCE,      // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE4
-		1904,           // doomednum
-		S_STG4,         // spawnstate
+	{           // MT_CACTI1
+		1203,           // doomednum
+		S_CACTI1,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10002,7 +11595,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -10011,9 +11604,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE5
-		1905,           // doomednum
-		S_STG5,         // spawnstate
+	{           // MT_CACTI2
+		1204,           // doomednum
+		S_CACTI2,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10029,7 +11622,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -10038,9 +11631,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE6
-		1906,           // doomednum
-		S_STG6,         // spawnstate
+	{           // MT_CACTI3
+		1205,           // doomednum
+		S_CACTI3,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10056,7 +11649,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -10065,9 +11658,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE7
-		1907,           // doomednum
-		S_STG7,         // spawnstate
+	{           // MT_CACTI4
+		1206,           // doomednum
+		S_CACTI4,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10083,7 +11676,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -10092,9 +11685,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE8
-		1908,           // doomednum
-		S_STG8,         // spawnstate
+	{           // MT_CACTI5
+		1207,           // doomednum
+		S_CACTI5,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10109,19 +11702,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		32*FRACUNIT,    // radius
+		96*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_STALAGMITE9
-		1909,           // doomednum
-		S_STG9,         // spawnstate
+	{           // MT_CACTI6
+		1208,           // doomednum
+		S_CACTI6,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10136,19 +11729,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		128*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_XMASPOLE
-		1850,           // doomednum
-		S_XMASPOLE,     // spawnstate
+	{           // MT_CACTI7
+		1209,           // doomednum
+		S_CACTI7,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10163,19 +11756,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		24*FRACUNIT,    // radius
+		224*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CANDYCANE
-		1851,           // doomednum
-		S_CANDYCANE,    // spawnstate
+	{           // MT_CACTI8
+		1210,           // doomednum
+		S_CACTI8,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10189,20 +11782,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		32*FRACUNIT,    // height
+		0,              // speed
+		24*FRACUNIT,    // radius
+		256*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SNOWMAN
-		1852,           // doomednum
-		S_SNOWMAN,      // spawnstate
+	{           // MT_CACTI9
+		1211,           // doomednum
+		S_CACTI9,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10216,20 +11809,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		25*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		0,              // speed
+		48*FRACUNIT,    // radius
+		96*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SNOWMANHAT
-		1853,           // doomednum
-		S_SNOWMANHAT,   // spawnstate
+	{           // MT_ARIDSIGN_CAUTION
+		1212,           // doomednum
+		S_ARIDSIGN_CAUTION,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10243,20 +11836,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		25*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		80*FRACUNIT,    // height
+		0,              // speed
+		22*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		MF_SCENERY|MF_SOLID|MF_PAPERCOLLISION, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_LAMPPOST1
-		1854,           // doomednum
-		S_LAMPPOST1,    // spawnstate
+	{           // MT_ARIDSIGN_CACTI
+		1213,           // doomednum
+		S_ARIDSIGN_CACTI,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10271,19 +11864,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		120*FRACUNIT,   // height
+		22*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID,       // flags
+		MF_SCENERY|MF_SOLID|MF_PAPERCOLLISION, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_LAMPPOST2
-		1855,           // doomednum
-		S_LAMPPOST2,    // spawnstate
+	{           // MT_ARIDSIGN_SHARPTURN
+		1214,           // doomednum
+		S_ARIDSIGN_SHARPTURN,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10298,23 +11891,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		120*FRACUNIT,   // height
+		16*FRACUNIT,    // radius
+		192*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID,       // flags
+		MF_SCENERY|MF_SOLID|MF_PAPERCOLLISION, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HANGSTAR
-		1856,           // doomednum
-		S_HANGSTAR,     // spawnstate
-		1000,           // spawnhealth
+	{           // MT_OILLAMP
+		1215,           // doomednum
+		S_OILLAMP,      // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10325,50 +11918,50 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		4*FRACUNIT,     // radius
-		80*FRACUNIT,    // height
+		22*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		0,              // damage
+		sfx_s3k4b,      // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SPAWNCEILING, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_XMASBERRYBUSH
-		1857,           // doomednum
-		S_XMASBERRYBUSH, // spawnstate
-		1000,           // spawnhealth
+	{           // MT_TNTBARREL
+		1216,           // doomednum
+		S_TNTBARREL_STND1,      // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
+		0,              // reactiontime
+		sfx_bowl,       // attacksound
+		S_TNTBARREL_EXPL1,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_TNTBARREL_FLYING,         // missilestate
+		S_TNTBARREL_EXPL1,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
+		sfx_s3k4e,      // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // radius
+		63*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SHOOTABLE|MF_ENEMY|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_XMASBUSH
-		1858,           // doomednum
-		S_XMASBUSH,     // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
+	{           // MT_PROXIMITYTNT
+		1217,           // doomednum
+		S_PROXIMITY_TNT,    // spawnstate
+		1,              // spawnhealth
+		S_PROXIMITY_TNT_TRIGGER1,         // seestate
+		sfx_s3k5c,      // seesound
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10377,28 +11970,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
+		sfx_s3k4e,      // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		64*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		sfx_s3k89,      // activesound
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	// No, I did not do all of this by hand.
-	// I made a script to make all of these for me.
-	// Ha HA. ~Inuyasha
-	{           // MT_BSZTALLFLOWER_RED
-		1400,           // doomednum
-		S_BSZTALLFLOWER_RED, // spawnstate
-		1000,           // spawnhealth
+	{           // MT_DUSTDEVIL
+		1218,           // doomednum
+		S_DUSTDEVIL,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10408,24 +11998,24 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		2,              // speed
+		80*FRACUNIT,    // radius
+		416*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		sfx_s3k4b,      // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTALLFLOWER_PURPLE
-		1401,           // doomednum
-		S_BSZTALLFLOWER_PURPLE, // spawnstate
-		1000,           // spawnhealth
+	{           // MT_DUSTLAYER
+		-1,             // doomednum
+		S_DUSTLAYER1,   // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10436,23 +12026,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		64*FRACUNIT,    // radius
+		256*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTALLFLOWER_BLUE
-		1402,           // doomednum
-		S_BSZTALLFLOWER_BLUE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_ARIDDUST
+		-1,             // doomednum
+		S_ARIDDUST1,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10464,49 +12054,49 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTALLFLOWER_CYAN
-		1403,           // doomednum
-		S_BSZTALLFLOWER_CYAN, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECART
+		-1,             // doomednum
+		S_MINECART_IDLE,// spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		24*FRACUNIT,    // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
-		sfx_None,       // painsound
+		sfx_statu2,     // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_MINECART_DTH1,// deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
+		sfx_s3k59,      // deathsound
+		20*FRACUNIT,    // speed
+		22*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		sfx_s3k76,      // activesound
+		MF_PUSHABLE,    // flags
+		MT_MINECARTSIDEMARK // raisestate
 	},
 
-	{           // MT_BSZTALLFLOWER_YELLOW
-		1404,           // doomednum
-		S_BSZTALLFLOWER_YELLOW, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTSEG
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10517,23 +12107,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
+		FRACUNIT,       // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP,    // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTALLFLOWER_ORANGE
-		1405,           // doomednum
-		S_BSZTALLFLOWER_ORANGE, // spawnstate
+	{          // MT_MINECARTSPAWNER
+		1219,           // doomednum
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10544,23 +12134,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		22*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SPECIAL,     // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_RED
-		1410,           // doomednum
-		S_BSZFLOWER_RED, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTEND
+		1220,           // doomednum
+		S_MINECARTEND,  // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10571,23 +12161,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		32*FRACUNIT,    // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_PURPLE
-		1411,           // doomednum
-		S_BSZFLOWER_PURPLE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTENDSOLID
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10598,23 +12188,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_PAPERCOLLISION|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_BLUE
-		1412,           // doomednum
-		S_BSZFLOWER_BLUE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTSIDEMARK
+		-1,             // doomednum
+		S_MINECARTSIDEMARK2, // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10625,23 +12215,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
+		22*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_CYAN
-		1413,           // doomednum
-		S_BSZFLOWER_CYAN, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTSPARK
+		-1,             // doomednum
+		S_MINECARTSPARK,// spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10652,23 +12242,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		2*FRACUNIT,     // radius
+		2*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_BOUNCE|MF_NOCLIPTHING|MF_GRENADEBOUNCE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_YELLOW
-		1414,           // doomednum
-		S_BSZFLOWER_YELLOW, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_SALOONDOOR
+		-1,             // doomednum
+		S_SALOONDOOR,   // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10679,23 +12269,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		48*FRACUNIT,    // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		sfx_s3k90,      // activesound
+		MF_SOLID|MF_NOGRAVITY|MF_RUNSPAWNFUNC|MF_PAPERCOLLISION|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZFLOWER_ORANGE
-		1415,           // doomednum
-		S_BSZFLOWER_ORANGE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_SALOONDOORTHINKER
+		1221,           // doomednum
+		S_SALOONDOORTHINKER,   // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10706,23 +12296,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		96*FRACUNIT,    // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_RED
-		1420,           // doomednum
-		S_BSZSHORTFLOWER_RED, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_TRAINCAMEOSPAWNER
+		1222,           // doomednum
+		S_TRAINCAMEOSPAWNER_1,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10733,23 +12323,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
+		28*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOCLIP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_PURPLE
-		1421,           // doomednum
-		S_BSZSHORTFLOWER_PURPLE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_TRAINSEG
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10760,23 +12350,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_BLUE
-		1422,           // doomednum
-		S_BSZSHORTFLOWER_BLUE, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_TRAINDUSTSPAWNER
+		1223,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10787,23 +12377,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_CYAN
-		1423,           // doomednum
-		S_BSZSHORTFLOWER_CYAN, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_TRAINSTEAMSPAWNER
+		1224,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10814,23 +12404,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_YELLOW
-		1424,           // doomednum
-		S_BSZSHORTFLOWER_YELLOW, // spawnstate
-		1000,           // spawnhealth
+	{          // MT_MINECARTSWITCHPOINT
+		1229,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -10841,19 +12431,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		FRACUNIT,       // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHORTFLOWER_ORANGE
-		1425,           // doomednum
-		S_BSZSHORTFLOWER_ORANGE, // spawnstate
+	{           // MT_FLAMEJET
+		1300,           // doomednum
+		S_FLAMEJETSTND, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10869,18 +12459,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_RED
-		1430,           // doomednum
-		S_BSZTULIP_RED, // spawnstate
+	{           // MT_VERTICALFLAMEJET
+		1301,           // doomednum
+		S_FLAMEJETSTND, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10896,18 +12486,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_PURPLE
-		1431,           // doomednum
-		S_BSZTULIP_PURPLE, // spawnstate
+	{           // MT_FLAMEJETFLAME
+		-1,             // doomednum
+		S_FLAMEJETFLAME1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10921,20 +12511,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		5*FRACUNIT,     // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		DMG_FIRE,       // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_MISSILE|MF_FIRE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_BLUE
-		1432,           // doomednum
-		S_BSZTULIP_BLUE, // spawnstate
+	{           // MT_FJSPINAXISA
+		1302,           // doomednum
+		S_FJSPINAXISA1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10948,20 +12538,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		10*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		1*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_CYAN
-		1433,           // doomednum
-		S_BSZTULIP_CYAN, // spawnstate
+	{           // MT_FJSPINAXISB
+		1303,           // doomednum
+		S_FJSPINAXISB1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10975,23 +12565,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		10*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		1*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_YELLOW
-		1434,           // doomednum
-		S_BSZTULIP_YELLOW, // spawnstate
+	{           // MT_FLAMEJETFLAMEB
+		-1,             // doomednum
+		S_FLAMEJETFLAMEB1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
+		sfx_fire,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -11002,20 +12592,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		18,             // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
-		0,              // damage
+		DMG_FIRE,       // mass
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_MISSILE|MF_FIRE|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZTULIP_ORANGE
-		1435,           // doomednum
-		S_BSZTULIP_ORANGE, // spawnstate
+	{           // MT_TRAPGOYLE
+		1500,           // doomednum
+		S_TRAPGOYLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11029,20 +12619,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		21*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_RED
-		1440,           // doomednum
-		S_BSZCLUSTER_RED, // spawnstate
+	{           // MT_TRAPGOYLEUP
+		1501,           // doomednum
+		S_TRAPGOYLEUP,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11056,20 +12646,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		21*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_PURPLE
-		1441,           // doomednum
-		S_BSZCLUSTER_PURPLE, // spawnstate
+	{           // MT_TRAPGOYLEDOWN
+		1502,           // doomednum
+		S_TRAPGOYLEDOWN,// spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11083,20 +12673,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		21*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_BLUE
-		1442,           // doomednum
-		S_BSZCLUSTER_BLUE, // spawnstate
+	{           // MT_TRAPGOYLELONG
+		1503,           // doomednum
+		S_TRAPGOYLELONG,// spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11110,47 +12700,47 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		21*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_CYAN
-		1443,           // doomednum
-		S_BSZCLUSTER_CYAN, // spawnstate
-		1000,           // spawnhealth
+	{           // MT_TARGET
+		1504,           // doomednum
+		S_TARGET_IDLE,  // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		32,             // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
+		S_TARGET_HIT1,  // deathstate
+		S_TARGET_ALLDONE, // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SPECIAL|MF_SHOOTABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_YELLOW
-		1444,           // doomednum
-		S_BSZCLUSTER_YELLOW, // spawnstate
+	{           // MT_STALAGMITE0
+		1900,           // doomednum
+		S_STG0,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11166,18 +12756,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLUSTER_ORANGE
-		1445,           // doomednum
-		S_BSZCLUSTER_ORANGE, // spawnstate
+	{           // MT_STALAGMITE1
+		1901,           // doomednum
+		S_STG1,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11193,7 +12783,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11202,9 +12792,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_RED
-		1450,           // doomednum
-		S_BSZBUSH_RED,  // spawnstate
+	{           // MT_STALAGMITE2
+		1902,           // doomednum
+		S_STG2,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11220,7 +12810,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11229,9 +12819,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_PURPLE
-		1451,           // doomednum
-		S_BSZBUSH_PURPLE, // spawnstate
+	{           // MT_STALAGMITE3
+		1903,           // doomednum
+		S_STG3,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11247,7 +12837,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11256,9 +12846,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_BLUE
-		1452,           // doomednum
-		S_BSZBUSH_BLUE, // spawnstate
+	{           // MT_STALAGMITE4
+		1904,           // doomednum
+		S_STG4,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11274,7 +12864,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11283,9 +12873,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_CYAN
-		1453,           // doomednum
-		S_BSZBUSH_CYAN, // spawnstate
+	{           // MT_STALAGMITE5
+		1905,           // doomednum
+		S_STG5,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11301,7 +12891,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11310,9 +12900,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_YELLOW
-		1454,           // doomednum
-		S_BSZBUSH_YELLOW, // spawnstate
+	{           // MT_STALAGMITE6
+		1906,           // doomednum
+		S_STG6,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11328,7 +12918,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11337,9 +12927,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZBUSH_ORANGE
-		1455,           // doomednum
-		S_BSZBUSH_ORANGE, // spawnstate
+	{           // MT_STALAGMITE7
+		1907,           // doomednum
+		S_STG7,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11355,7 +12945,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11364,9 +12954,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_RED
-		1460,           // doomednum
-		S_BSZVINE_RED,  // spawnstate
+	{           // MT_STALAGMITE8
+		1908,           // doomednum
+		S_STG8,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11382,7 +12972,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11391,9 +12981,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_PURPLE
-		1461,           // doomednum
-		S_BSZVINE_PURPLE, // spawnstate
+	{           // MT_STALAGMITE9
+		1909,           // doomednum
+		S_STG9,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11409,7 +12999,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11418,9 +13008,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_BLUE
-		1462,           // doomednum
-		S_BSZVINE_BLUE, // spawnstate
+	{           // MT_XMASPOLE
+		1850,           // doomednum
+		S_XMASPOLE,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11436,7 +13026,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11445,11 +13035,11 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_CYAN
-		1463,           // doomednum
-		S_BSZVINE_CYAN, // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_CANDYCANE
+		1851,           // doomednum
+		S_CANDYCANE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -11461,20 +13051,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
+		8,              // speed
+		8*FRACUNIT,     // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_YELLOW
-		1464,           // doomednum
-		S_BSZVINE_YELLOW, // spawnstate
+	{           // MT_SNOWMAN
+		1852,           // doomednum
+		S_SNOWMAN,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11488,20 +13078,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		25*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZVINE_ORANGE
-		1465,           // doomednum
-		S_BSZVINE_ORANGE, // spawnstate
+	{           // MT_SNOWMANHAT
+		1853,           // doomednum
+		S_SNOWMANHAT,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11515,20 +13105,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		25*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZSHRUB
-		1470,           // doomednum
-		S_BSZSHRUB,     // spawnstate
+	{           // MT_LAMPPOST1
+		1854,           // doomednum
+		S_LAMPPOST1,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11543,19 +13133,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		8*FRACUNIT,     // radius
+		120*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BSZCLOVER
-		1471,           // doomednum
-		S_BSZCLOVER,    // spawnstate
+	{           // MT_LAMPPOST2
+		1855,           // doomednum
+		S_LAMPPOST2,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11570,19 +13160,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		8*FRACUNIT,     // radius
+		120*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID,       // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BIG_PALMTREE_TRUNK
-		1472,           // doomednum
-		S_BIG_PALMTREE_TRUNK, // spawnstate
+	{           // MT_HANGSTAR
+		1856,           // doomednum
+		S_HANGSTAR,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11597,19 +13187,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		4*FRACUNIT,     // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BIG_PALMTREE_TOP
-		1473,           // doomednum
-		S_BIG_PALMTREE_TOP, // spawnstate
+	{           // MT_XMASBLUEBERRYBUSH
+		1859,           // doomednum
+		S_XMASBLUEBERRYBUSH, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11634,9 +13224,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PALMTREE_TRUNK
-		1474,           // doomednum
-		S_PALMTREE_TRUNK, // spawnstate
+	{           // MT_XMASBERRYBUSH
+		1857,           // doomednum
+		S_XMASBERRYBUSH, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11661,9 +13251,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PALMTREE_TOP
-		1475,           // doomednum
-		S_PALMTREE_TOP, // spawnstate
+	{           // MT_XMASBUSH
+		1858,           // doomednum
+		S_XMASBUSH,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11688,9 +13278,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_DBALL
-		1875,           // doomednum
-		S_DBALL1,       // spawnstate
+	{           // MT_FHZICE1
+		2100,           // doomednum
+		S_FHZICE1,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11704,20 +13294,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		16*FRACUNIT,    // radius
-		54*FRACUNIT,    // height
+		0,              // speed
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EGGSTATUE2
-		1876,           // doomednum
-		S_EGGSTATUE2,   // spawnstate
+	{           // MT_FHZICE2
+		2101,           // doomednum
+		S_FHZICE2,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11732,263 +13322,263 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		20*FRACUNIT,    // radius
-		96*FRACUNIT,    // height
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_PUSHABLE|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_ELEMENTAL_ORB
-		-1,             // doomednum
-		S_ELEM1,        // spawnstate
+	{           // MT_JACKO1
+		2006,           // doomednum
+		S_JACKO1,       // spawnstate
 		1000,           // spawnhealth
-		S_ELEMF1,       // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_ELEM13,       // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_ELEMENTAL,   // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_ELEMF9        // raisestate
+		MF_NOBLOCKMAP|MF_SCENERY, // flags
+		S_JACKO1OVERLAY_1 // raisestate
 	},
 
-	{           // MT_ATTRACT_ORB
-		-1,             // doomednum
-		S_MAGN1,        // spawnstate
+	{           // MT_JACKO2
+		2007,           // doomednum
+		S_JACKO2,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_MAGN13,       // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_ATTRACT,     // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		MF_NOBLOCKMAP|MF_SCENERY, // flags
+		S_JACKO2OVERLAY_1 // raisestate
 	},
 
-	{           // MT_FORCE_ORB
-		-1,             // doomednum
-		S_FORC1,        // spawnstate
+	{           // MT_JACKO3
+		2008,           // doomednum
+		S_JACKO3,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_FORC11,       // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_FORCE,       // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_FORC21        // raisestate
+		MF_NOBLOCKMAP|MF_SCENERY, // flags
+		S_JACKO3OVERLAY_1 // raisestate
 	},
 
-	{           // MT_ARMAGEDDON_ORB
-		-1,             // doomednum
-		S_ARMA1,        // spawnstate
+	{           // MT_HHZTREE_TOP
+		2010,           // doomednum
+		S_HHZTREE_TOP,  // spawnstate
 		1000,           // spawnhealth
-		S_ARMF1,        // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		SKINCOLOR_NONE, // painchance
+		0,              // painchance
 		sfx_None,       // painsound
-		S_ARMB1,        // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_ARMAGEDDON,  // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		40*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WHIRLWIND_ORB
+	{           // MT_HHZTREE_PART
 		-1,             // doomednum
-		S_WIND1,        // spawnstate
+		S_HHZTREE_TRUNK,// spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
+		S_HHZTREE_LEAF, // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		SKINCOLOR_NONE, // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_WHIRLWIND,        // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		40*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PITY_ORB
-		-1,             // doomednum
-		S_PITY1,        // spawnstate
+	{           // MT_HHZSHROOM
+		2009,           // doomednum
+		S_HHZSHROOM_1,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		SKINCOLOR_NONE, // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_PITY,        // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAMEAURA_ORB
-		-1,             // doomednum
-		S_FIRSB1,       // spawnstate
+	{           // MT_HHZGRASS
+		2001,           // doomednum
+		S_HHZGRASS,     // spawnstate
 		1000,           // spawnhealth
-		S_FIRS1,        // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_FIRSB10,      // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_FLAMEAURA,   // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		-4,             // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_FIRS10        // raisestate
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_BUBBLEWRAP_ORB
-		-1,             // doomednum
-		S_BUBSB1,       // spawnstate
+	{           // MT_HHZTENTACLE1
+		2002,           // doomednum
+		S_HHZTENT1,     // spawnstate
 		1000,           // spawnhealth
-		S_BUBS1,        // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_BUBSB5,       // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_BUBBLEWRAP,  // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		4,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_BUBS10        // raisestate
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_THUNDERCOIN_ORB
-		-1,             // doomednum
-		S_ZAPSB1,       // spawnstate
+	{           // MT_HHZTENTACLE2
+		2003,           // doomednum
+		S_HHZTENT2,     // spawnstate
 		1000,           // spawnhealth
-		S_ZAPS1,        // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_ZAPSB11,      // painstate
-		SKINCOLOR_NONE, // painchance
+		S_NULL,         // painstate
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		SH_THUNDERCOIN, // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		-4,             // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
-		S_ZAPS14        // raisestate
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_THUNDERCOIN_SPARK
-		-1,             // doomednum
-		S_THUNDERCOIN_SPARK, // spawnstate
-		1,              // spawnhealth
+	{           // MT_HHZSTALAGMITE_TALL
+		2004,           // doomednum
+		S_HHZSTALAGMITE_TALL, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
@@ -12002,19 +13592,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_IVSP
-		-1,             // doomednum
-		S_IVSP,         // spawnstate
+	{           // MT_HHZSTALAGMITE_SHORT
+		2005,           // doomednum
+		S_HHZSTALAGMITE_SHORT, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12028,21 +13618,24 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		64*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
-		3,              // display offset
-		16,             // mass
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		MF_NOTHINK|MF_SCENERY|MF_NOBLOCKMAP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SUPERSPARK
-		-1,             // doomednum
-		S_SSPK1,        // spawnstate
-		1,              // spawnhealth
+	// No, I did not do all of this by hand.
+	// I made a script to make all of these for me.
+	// Ha HA. ~Inuyasha
+	{           // MT_BSZTALLFLOWER_RED
+		1400,           // doomednum
+		S_BSZTALLFLOWER_RED, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
@@ -12056,20 +13649,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// Bluebird
-	{           // MT_FLICKY_01
-		-1,             // doomednum
-		S_FLICKY_01_OUT, // spawnstate
+	{           // MT_BSZTALLFLOWER_PURPLE
+		1401,           // doomednum
+		S_BSZTALLFLOWER_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12083,20 +13675,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_02
-		-1,             // doomednum
-		S_FLICKY_02_OUT, // spawnstate
+	{           // MT_BSZTALLFLOWER_BLUE
+		1402,           // doomednum
+		S_BSZTALLFLOWER_BLUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12110,20 +13702,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_03
-		-1,             // doomednum
-		S_FLICKY_03_OUT, // spawnstate
+	{           // MT_BSZTALLFLOWER_CYAN
+		1403,           // doomednum
+		S_BSZTALLFLOWER_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12137,20 +13729,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_04
-		-1,             // doomednum
-		S_FLICKY_04_OUT, // spawnstate
+	{           // MT_BSZTALLFLOWER_YELLOW
+		1404,           // doomednum
+		S_BSZTALLFLOWER_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12159,25 +13751,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_FLICKY_04_SWIM1, // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_05
-		-1,             // doomednum
-		S_FLICKY_05_OUT, // spawnstate
+	{           // MT_BSZTALLFLOWER_ORANGE
+		1405,           // doomednum
+		S_BSZTALLFLOWER_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12191,20 +13783,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_06
-		-1,             // doomednum
-		S_FLICKY_06_OUT, // spawnstate
+	{           // MT_BSZFLOWER_RED
+		1410,           // doomednum
+		S_BSZFLOWER_RED, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12218,20 +13810,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_07
-		-1,             // doomednum
-		S_FLICKY_07_OUT, // spawnstate
+	{           // MT_BSZFLOWER_PURPLE
+		1411,           // doomednum
+		S_BSZFLOWER_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12240,25 +13832,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_FLICKY_07_SWIM1, // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_08
-		-1,             // doomednum
-		S_FLICKY_08_OUT, // spawnstate
+	{           // MT_BSZFLOWER_BLUE
+		1412,           // doomednum
+		S_BSZFLOWER_BLUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12267,25 +13859,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_FLICKY_08_SWIM1, // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_09
-		-1,             // doomednum
-		S_FLICKY_09_OUT, // spawnstate
+	{           // MT_BSZFLOWER_CYAN
+		1413,           // doomednum
+		S_BSZFLOWER_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12299,20 +13891,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_10
-		-1,             // doomednum
-		S_FLICKY_10_OUT, // spawnstate
+	{           // MT_BSZFLOWER_YELLOW
+		1414,           // doomednum
+		S_BSZFLOWER_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12326,20 +13918,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_11
-		-1,             // doomednum
-		S_FLICKY_11_OUT, // spawnstate
+	{           // MT_BSZFLOWER_ORANGE
+		1415,           // doomednum
+		S_BSZFLOWER_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12353,20 +13945,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_12
-		-1,             // doomednum
-		S_FLICKY_12_OUT, // spawnstate
+	{           // MT_BSZSHORTFLOWER_RED
+		1420,           // doomednum
+		S_BSZSHORTFLOWER_RED, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12380,20 +13972,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_13
-		-1,             // doomednum
-		S_FLICKY_13_OUT, // spawnstate
+	{           // MT_BSZSHORTFLOWER_PURPLE
+		1421,           // doomednum
+		S_BSZSHORTFLOWER_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12407,20 +13999,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_14
-		-1,             // doomednum
-		S_FLICKY_14_OUT, // spawnstate
+	{           // MT_BSZSHORTFLOWER_BLUE
+		1422,           // doomednum
+		S_BSZSHORTFLOWER_BLUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12434,20 +14026,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_15
-		-1,             // doomednum
-		S_FLICKY_15_OUT, // spawnstate
+	{           // MT_BSZSHORTFLOWER_CYAN
+		1423,           // doomednum
+		S_BSZSHORTFLOWER_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12461,20 +14053,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FLICKY_16
-		-1,             // doomednum
-		S_FLICKY_16_OUT, // spawnstate
+	{           // MT_BSZSHORTFLOWER_YELLOW
+		1424,           // doomednum
+		S_BSZSHORTFLOWER_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12488,20 +14080,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPTHING, // flags
-		S_FLICKY_BUBBLE // raisestate
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_RAIN
-		-1,             // doomednum
-		S_RAIN1,        // spawnstate
+	{           // MT_BSZSHORTFLOWER_ORANGE
+		1425,           // doomednum
+		S_BSZSHORTFLOWER_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12515,20 +14107,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		-24*FRACUNIT,   // speed
-		1*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SNOWFLAKE
-		-1,             // doomednum
-		S_SNOW1,        // spawnstate
+	{           // MT_BSZTULIP_RED
+		1430,           // doomednum
+		S_BSZTULIP_RED, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12542,20 +14134,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		-2*FRACUNIT,    // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SPLISH
-		-1,             // doomednum
-		S_SPLISH1,      // spawnstate
+	{           // MT_BSZTULIP_PURPLE
+		1431,           // doomednum
+		S_BSZTULIP_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12570,19 +14162,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		6*FRACUNIT,     // radius
-		1*FRACUNIT,     // height
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SMOKE
-		-1,             // doomednum
-		S_SMOKE1,       // spawnstate
+	{           // MT_BSZTULIP_BLUE
+		1432,           // doomednum
+		S_BSZTULIP_BLUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12597,19 +14189,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		20*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SMALLBUBBLE
-		-1,             // doomednum
-		S_SMALLBUBBLE,  // spawnstate
+	{           // MT_BSZTULIP_CYAN
+		1433,           // doomednum
+		S_BSZTULIP_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12623,20 +14215,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_MEDIUMBUBBLE
-		-1,             // doomednum
-		S_MEDIUMBUBBLE, // spawnstate
+	{           // MT_BSZTULIP_YELLOW
+		1434,           // doomednum
+		S_BSZTULIP_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12650,20 +14242,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EXTRALARGEBUBBLE
-		-1,             // doomednum
-		S_LARGEBUBBLE1, // spawnstate
+	{           // MT_BSZTULIP_ORANGE
+		1435,           // doomednum
+		S_BSZTULIP_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12674,23 +14266,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_POP1,         // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_gasp,       // deathsound
-		8,              // speed
-		23*FRACUNIT,    // radius
-		43*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WATERZAP
-		-1,             // doomednum
-		S_WATERZAP,     // spawnstate
+	{           // MT_BSZCLUSTER_RED
+		1440,           // doomednum
+		S_BSZCLUSTER_RED, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12704,20 +14296,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SPINDUST
-		-1,             // doomednum
-		S_SPINDUST1,     // spawnstate
+	{           // MT_BSZCLUSTER_PURPLE
+		1441,           // doomednum
+		S_BSZCLUSTER_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12731,21 +14323,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		4*FRACUNIT,     // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TFOG
-		-1,             // doomednum
-		S_FOG1,         // spawnstate
-		1,              // spawnhealth
+	{           // MT_BSZCLUSTER_BLUE
+		1442,           // doomednum
+		S_BSZCLUSTER_BLUE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
@@ -12763,15 +14355,15 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SEED
-		-1,             // doomednum
-		S_SEED,         // spawnstate
+	{           // MT_BSZCLUSTER_CYAN
+		1443,           // doomednum
+		S_BSZCLUSTER_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12785,20 +14377,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		-2*FRACUNIT,    // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PARTICLE
-		-1,             // doomednum
-		S_PARTICLE,     // spawnstate
+	{           // MT_BSZCLUSTER_YELLOW
+		1444,           // doomednum
+		S_BSZCLUSTER_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12812,20 +14404,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		4*FRACUNIT,     // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PARTICLEGEN
-		757,            // doomednum
-		S_PARTICLEGEN,  // spawnstate
+	{           // MT_BSZCLUSTER_ORANGE
+		1445,           // doomednum
+		S_BSZCLUSTER_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12839,21 +14431,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		1*FRACUNIT,     // radius
-		1*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SCORE
-		-1,             // doomednum
-		S_SCRA,         // spawnstate
-		1,              // spawnhealth
+	{           // MT_BSZBUSH_RED
+		1450,           // doomednum
+		S_BSZBUSH_RED,  // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
@@ -12866,20 +14458,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		3*FRACUNIT,     // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_DROWNNUMBERS
-		-1,             // doomednum
-		S_ZERO1,        // spawnstate
+	{           // MT_BSZBUSH_PURPLE
+		1451,           // doomednum
+		S_BSZBUSH_PURPLE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12893,20 +14485,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
-		113,            // display offset
-		16,             // mass
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOTEMERALD
-		-1,             // doomednum
-		S_CEMG1,        // spawnstate
+	{           // MT_BSZBUSH_BLUE
+		1452,           // doomednum
+		S_BSZBUSH_BLUE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12915,25 +14507,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_ORBITEM1,     // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
-		112,            // display offset
-		16,             // mass
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_LOCKON
-		-1,             // doomednum
-		S_LOCKON1,       // spawnstate
+	{           // MT_BSZBUSH_CYAN
+		1453,           // doomednum
+		S_BSZBUSH_CYAN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12947,20 +14539,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
+		0,              // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		111,            // display offset
-		16,             // mass
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TAG
-		-1,             // doomednum
-		S_TTAG,         // spawnstate
+	{           // MT_BSZBUSH_YELLOW
+		1454,           // doomednum
+		S_BSZBUSH_YELLOW, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12974,20 +14566,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
+		0,              // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		111,            // display offset
-		16,             // mass
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOTFLAG
-		-1,             // doomednum
-		S_GOTFLAG,      // spawnstate
+	{           // MT_BSZBUSH_ORANGE
+		1455,           // doomednum
+		S_BSZBUSH_ORANGE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -13001,24 +14593,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		64*FRACUNIT,    // radius
+		0,              // speed
+		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		111,            // display offset
-		16,             // mass
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 1a (large)
-	{           // MT_AWATERA
-		700,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_RED
+		1460,           // doomednum
+		S_BSZVINE_RED,  // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr1,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13031,22 +14622,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 1b (large)
-	{           // MT_AWATERB
-		701,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_PURPLE
+		1461,           // doomednum
+		S_BSZVINE_PURPLE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr2,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13059,22 +14649,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 2a (medium)
-	{           // MT_AWATERC
-		702,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_BLUE
+		1462,           // doomednum
+		S_BSZVINE_BLUE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr3,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13087,22 +14676,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 2b (medium)
-	{           // MT_AWATERD
-		703,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_CYAN
+		1463,           // doomednum
+		S_BSZVINE_CYAN, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr4,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13115,22 +14703,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 3a (small)
-	{           // MT_AWATERE
-		704,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_YELLOW
+		1464,           // doomednum
+		S_BSZVINE_YELLOW, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr5,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13143,22 +14730,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 3b (small)
-	{           // MT_AWATERF
-		705,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZVINE_ORANGE
+		1465,           // doomednum
+		S_BSZVINE_ORANGE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr6,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13171,22 +14757,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 4a (extra large)
-	{           // MT_AWATERG
-		706,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr7,     // seesound
+	{           // MT_BSZSHRUB
+		1470,           // doomednum
+		S_BSZSHRUB,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13199,22 +14784,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	// ambient water 4b (extra large)
-	{           // MT_AWATERH
-		707,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+	{           // MT_BSZCLOVER
+		1471,           // doomednum
+		S_BSZCLOVER,    // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr8,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13227,25 +14811,25 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_RANDOMAMBIENT
-		708,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		512,            // spawnhealth: repeat speed
+	{           // MT_BIG_PALMTREE_TRUNK
+		-1,             // doomednum
+		S_BIG_PALMTREE_TRUNK, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_ambint,     // seesound
-		0,              // reactiontime
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -13253,26 +14837,26 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		16*FRACUNIT,    // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
-		1000,           // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_RANDOMAMBIENT2
-		709,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		220,            // spawnhealth: repeat speed
+	{           // MT_BIG_PALMTREE_TOP
+		1473,           // doomednum
+		S_BIG_PALMTREE_TOP, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_ambin2,     // seesound
-		0,              // reactiontime
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -13280,212 +14864,2733 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		16*FRACUNIT,    // radius
+		160*FRACUNIT,   // height
 		0,              // display offset
-		1000,           // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
+		MF_RUNSPAWNFUNC|MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CORK
+	{           // MT_PALMTREE_TRUNK
 		-1,             // doomednum
-		S_CORK,         // spawnstate
+		S_PALMTREE_TRUNK, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_corkp,      // seesound
-		0,              // reactiontime
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SMOKE1,       // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_corkh,      // deathsound
-		60*FRACUNIT,    // speed
+		sfx_None,       // deathsound
+		0,              // speed
 		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_REDRING
-		-1,             // doomednum
-		S_RRNG1,        // spawnstate
+	{           // MT_PALMTREE_TOP
+		1475,           // doomednum
+		S_PALMTREE_TOP, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_wepfir,     // seesound
-		0,              // reactiontime
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
+		sfx_None,       // deathsound
+		0,              // speed
 		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_RUNSPAWNFUNC|MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-// Ring ammo: Health = amount given
-	{           // MT_BOUNCERING
-		301,            // doomednum
-		S_BOUNCERINGAMMO, // spawnstate
-		10,             // spawnhealth
+	{           // MT_DBALL
+		1875,           // doomednum
+		S_DBALL1,       // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		54*FRACUNIT,    // height
 		0,              // display offset
-		pw_bouncering,  // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_RAILRING
-		302,            // doomednum
-		S_RAILRINGAMMO, // spawnstate
-		5,              // spawnhealth
+	{           // MT_EGGSTATUE2
+		1876,           // doomednum
+		S_EGGSTATUE2,   // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		20*FRACUNIT,    // radius
+		96*FRACUNIT,    // height
 		0,              // display offset
-		pw_railring,    // mass
-		0,              // damage
+		100,            // mass
+		1,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_SOLID|MF_PUSHABLE|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_INFINITYRING
-		303,            // doomednum
-		S_INFINITYRINGAMMO,// spawnstate
-		80,             // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_ELEMENTAL_ORB
+		-1,             // doomednum
+		S_ELEM1,        // spawnstate
+		1000,           // spawnhealth
+		S_ELEMF1,       // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
+		S_ELEM13,       // painstate
+		SKINCOLOR_NONE, // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		pw_infinityring,// mass
+		sfx_None,       // deathsound
+		SH_ELEMENTAL,   // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_ELEMF9        // raisestate
 	},
 
-	{           // MT_AUTOMATICRING
-		304,            // doomednum
-		S_AUTOMATICRINGAMMO, // spawnstate
-		40,             // spawnhealth
+	{           // MT_ATTRACT_ORB
+		-1,             // doomednum
+		S_MAGN1,        // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
+		S_MAGN13,       // painstate
+		SKINCOLOR_NONE, // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		pw_automaticring, // mass
+		sfx_None,       // deathsound
+		SH_ATTRACT,     // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EXPLOSIONRING
-		305,            // doomednum
-		S_EXPLOSIONRINGAMMO, // spawnstate
-		5,              // spawnhealth
+	{           // MT_FORCE_ORB
+		-1,             // doomednum
+		S_FORC1,        // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
+		S_FORC11,       // painstate
+		SKINCOLOR_NONE, // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		pw_explosionring, // mass
+		sfx_None,       // deathsound
+		SH_FORCE,       // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_FORC21        // raisestate
 	},
 
-	{           // MT_SCATTERRING
-		306,            // doomednum
+	{           // MT_ARMAGEDDON_ORB
+		-1,             // doomednum
+		S_ARMA1,        // spawnstate
+		1000,           // spawnhealth
+		S_ARMF1,        // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_ARMB1,        // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_ARMAGEDDON,  // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WHIRLWIND_ORB
+		-1,             // doomednum
+		S_WIND1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_WHIRLWIND,        // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PITY_ORB
+		-1,             // doomednum
+		S_PITY1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_PITY,        // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLAMEAURA_ORB
+		-1,             // doomednum
+		S_FIRSB1,       // spawnstate
+		1000,           // spawnhealth
+		S_FIRS1,        // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_FIRSB10,      // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_FLAMEAURA,   // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		-4,             // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_FIRS10        // raisestate
+	},
+
+	{           // MT_BUBBLEWRAP_ORB
+		-1,             // doomednum
+		S_BUBSB1,       // spawnstate
+		1000,           // spawnhealth
+		S_BUBS1,        // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_BUBSB5,       // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_BUBBLEWRAP,  // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		4,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_BUBS10        // raisestate
+	},
+
+	{           // MT_THUNDERCOIN_ORB
+		-1,             // doomednum
+		S_ZAPSB1,       // spawnstate
+		1000,           // spawnhealth
+		S_ZAPS1,        // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_ZAPSB11,      // painstate
+		SKINCOLOR_NONE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		SH_THUNDERCOIN, // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		-4,             // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_ZAPS14        // raisestate
+	},
+
+	{           // MT_THUNDERCOIN_SPARK
+		-1,             // doomednum
+		S_THUNDERCOIN_SPARK, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_IVSP
+		-1,             // doomednum
+		S_IVSP,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		64*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		3,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SUPERSPARK
+		-1,             // doomednum
+		S_SSPK1,        // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	// Bluebird
+	{           // MT_FLICKY_01
+		-1,             // doomednum
+		S_FLICKY_01_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_01_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_01_CENTER
+		2200,             // doomednum
+		S_FLICKY_01_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_02
+		-1,             // doomednum
+		S_FLICKY_02_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_02_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_02_CENTER
+		2201,             // doomednum
+		S_FLICKY_02_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_03
+		-1,             // doomednum
+		S_FLICKY_03_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_03_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_03_CENTER
+		2202,             // doomednum
+		S_FLICKY_03_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_04
+		-1,             // doomednum
+		S_FLICKY_04_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_04_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_FLICKY_04_SWIM1, // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLICKY_04_CENTER
+		2203,             // doomednum
+		S_FLICKY_04_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_05
+		-1,             // doomednum
+		S_FLICKY_05_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_05_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_05_CENTER
+		2204,             // doomednum
+		S_FLICKY_05_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_06
+		-1,             // doomednum
+		S_FLICKY_06_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_06_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_06_CENTER
+		2205,             // doomednum
+		S_FLICKY_06_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_07
+		-1,             // doomednum
+		S_FLICKY_07_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_07_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_FLICKY_07_SWIM1, // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLICKY_07_CENTER
+		2206,             // doomednum
+		S_FLICKY_07_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_08
+		-1,             // doomednum
+		S_FLICKY_08_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_08_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_FLICKY_08_SWIM1, // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLICKY_08_CENTER
+		2207,             // doomednum
+		S_FLICKY_08_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_09
+		-1,             // doomednum
+		S_FLICKY_09_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_09_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_09_CENTER
+		2208,             // doomednum
+		S_FLICKY_09_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_10
+		-1,             // doomednum
+		S_FLICKY_10_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_10_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_10_CENTER
+		2209,             // doomednum
+		S_FLICKY_10_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_11
+		-1,             // doomednum
+		S_FLICKY_11_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_11_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_11_CENTER
+		2210,             // doomednum
+		S_FLICKY_11_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_12
+		-1,             // doomednum
+		S_FLICKY_12_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_12_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_12_CENTER
+		2211,             // doomednum
+		S_FLICKY_12_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_13
+		-1,             // doomednum
+		S_FLICKY_13_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_13_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_13_CENTER
+		2212,             // doomednum
+		S_FLICKY_13_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_14
+		-1,             // doomednum
+		S_FLICKY_14_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_14_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_14_CENTER
+		2213,             // doomednum
+		S_FLICKY_14_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_15
+		-1,             // doomednum
+		S_FLICKY_15_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_15_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_15_CENTER
+		2214,             // doomednum
+		S_FLICKY_15_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_FLICKY_16
+		-1,             // doomednum
+		S_FLICKY_16_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_FLICKY_16_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_FLICKY_16_CENTER
+		2215,             // doomednum
+		S_FLICKY_16_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_SECRETFLICKY_01
+		-1,             // doomednum
+		S_SECRETFLICKY_01_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_SECRETFLICKY_01_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_SECRETFLICKY_01_CENTER
+		2216,             // doomednum
+		S_SECRETFLICKY_01_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_SECRETFLICKY_02
+		-1,             // doomednum
+		S_SECRETFLICKY_02_OUT, // spawnstate
+		1000,           // spawnhealth
+		S_SECRETFLICKY_02_STAND, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIPTHING, // flags
+		S_FLICKY_BUBBLE // raisestate
+	},
+
+	{           // MT_SECRETFLICKY_02_CENTER
+		2217,             // doomednum
+		S_SECRETFLICKY_02_CENTER, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		20*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL // raisestate
+	},
+
+	{           // MT_SEED
+		-1,             // doomednum
+		S_SEED,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		-2*FRACUNIT,    // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_RAIN
+		-1,             // doomednum
+		S_RAIN1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		-72*FRACUNIT,   // speed
+		1*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SNOWFLAKE
+		-1,             // doomednum
+		S_SNOW1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		-2*FRACUNIT,    // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SPLISH
+		-1,             // doomednum
+		S_SPLISH1,      // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		6*FRACUNIT,     // radius
+		1*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SMOKE
+		-1,             // doomednum
+		S_SMOKE1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		20*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SMALLBUBBLE
+		-1,             // doomednum
+		S_SMALLBUBBLE,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_MEDIUMBUBBLE
+		-1,             // doomednum
+		S_MEDIUMBUBBLE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EXTRALARGEBUBBLE
+		-1,             // doomednum
+		S_LARGEBUBBLE1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_POP1,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_gasp,       // deathsound
+		8,              // speed
+		23*FRACUNIT,    // radius
+		43*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WATERZAP
+		-1,             // doomednum
+		S_WATERZAP,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SPINDUST
+		-1,             // doomednum
+		S_SPINDUST1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		4*FRACUNIT,     // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TFOG
+		-1,             // doomednum
+		S_FOG1,         // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PARTICLE
+		-1,             // doomednum
+		S_PARTICLE,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		4*FRACUNIT,     // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		1,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PARTICLEGEN
+		757,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		1*FRACUNIT,     // radius
+		1*FRACUNIT,     // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SCORE
+		-1,             // doomednum
+		S_SCRA,         // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		3*FRACUNIT,     // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		1,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_DROWNNUMBERS
+		-1,             // doomednum
+		S_ZERO1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		113,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GOTEMERALD
+		-1,             // doomednum
+		S_CEMG1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_ORBITEM1,     // meleestate
+		S_ORBIDYA1,     // missilestate
+		S_XPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k8a,      // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		112,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LOCKON
+		-1,             // doomednum
+		S_LOCKON1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LOCKONINF
+		1126,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TAG
+		-1,             // doomednum
+		S_TTAG,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GOTFLAG
+		-1,             // doomednum
+		S_GOTFLAG,      // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		64*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 1a (large)
+	{           // MT_AWATERA
+		700,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr1,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 1b (large)
+	{           // MT_AWATERB
+		701,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr2,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 2a (medium)
+	{           // MT_AWATERC
+		702,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr3,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 2b (medium)
+	{           // MT_AWATERD
+		703,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr4,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 3a (small)
+	{           // MT_AWATERE
+		704,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr5,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 3b (small)
+	{           // MT_AWATERF
+		705,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr6,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 4a (extra large)
+	{           // MT_AWATERG
+		706,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr7,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	// ambient water 4b (extra large)
+	{           // MT_AWATERH
+		707,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		35,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_amwtr8,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_RANDOMAMBIENT
+		708,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		512,            // spawnhealth: repeat speed
+		S_NULL,         // seestate
+		sfx_ambint,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		255,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		1000,           // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_RANDOMAMBIENT2
+		709,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		220,            // spawnhealth: repeat speed
+		S_NULL,         // seestate
+		sfx_ambin2,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		255,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		1000,           // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_MACHINEAMBIENCE
+		710,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		24,             // spawnhealth: repeat speed
+		S_NULL,         // seestate
+		sfx_ambmac,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		1*FRACUNIT,     // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		20,             // damage
+		sfx_None,       // activesound
+		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORK
+		-1,             // doomednum
+		S_CORK,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_corkp,      // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SMOKE1,       // deathstate
+		S_NULL,         // xdeathstate
+		sfx_corkh,      // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LHRT
+		-1,             // doomednum
+		S_LHRT,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_SPRK1,        // xdeathstate
+		sfx_None,       // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_REDRING
+		-1,             // doomednum
+		S_RRNG1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_wepfir,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+// Ring ammo: Health = amount given
+	{           // MT_BOUNCERING
+		301,            // doomednum
+		S_BOUNCERINGAMMO, // spawnstate
+		10,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_bouncering,  // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_RAILRING
+		302,            // doomednum
+		S_RAILRINGAMMO, // spawnstate
+		5,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_railring,    // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_INFINITYRING
+		303,            // doomednum
+		S_INFINITYRINGAMMO,// spawnstate
+		80,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_infinityring,// mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_AUTOMATICRING
+		304,            // doomednum
+		S_AUTOMATICRINGAMMO, // spawnstate
+		40,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_automaticring, // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EXPLOSIONRING
+		305,            // doomednum
+		S_EXPLOSIONRINGAMMO, // spawnstate
+		5,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_explosionring, // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SCATTERRING
+		306,            // doomednum
 		S_SCATTERRINGAMMO, // spawnstate
 		5,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_scatterring, // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GRENADERING
+		307,            // doomednum
+		S_GRENADERINGAMMO, // spawnstate
+		10,             // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_grenadering, // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+// Ring panels: Reactiontime = amount given
+	{           // MT_BOUNCEPICKUP
+		330,            // doomednum
+		S_BOUNCEPICKUP, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		10,             // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		1,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BOUNCEPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_bouncering,  // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_RAILPICKUP
+		331,            // doomednum
+		S_RAILPICKUP,   // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		5,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		2,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_RAILPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_railring,    // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_AUTOPICKUP
+		332,            // doomednum
+		S_AUTOPICKUP,   // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		40,             // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		4,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_AUTOPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_automaticring, // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EXPLODEPICKUP
+		333,            // doomednum
+		S_EXPLODEPICKUP,// spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		5,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		8,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_EXPLODEPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_explosionring, // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SCATTERPICKUP
+		334,            // doomednum
+		S_SCATTERPICKUP,// spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		5,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		8,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SCATTERPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_scatterring, // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GRENADEPICKUP
+		335,            // doomednum
+		S_GRENADEPICKUP,// spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		10,             // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		8,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_GRENADEPICKUPFADE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_ncitem,     // deathsound
+		60*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		pw_grenadering, // mass
+		2*TICRATE,      // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNBOUNCE
+		-1,             // doomednum
+		S_THROWNBOUNCE1,// spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_bnce1,      // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_bnce1,      // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_BOUNCE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNINFINITY
+		-1,             // doomednum
+		S_THROWNINFINITY1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_wepfir,     // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13497,212 +17602,453 @@ 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
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNAUTOMATIC
+		-1,             // doomednum
+		S_THROWNAUTOMATIC1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_wepfir,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNSCATTER
+		-1,             // doomednum
+		S_THROWNSCATTER,// spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_bnce2,      // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_itemup,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNEXPLOSION
+		-1,             // doomednum
+		S_THROWNEXPLOSION1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_cannon,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		192*FRACUNIT,   // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_RINGEXPLODE,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_THROWNGRENADE
+		-1,             // doomednum
+		S_THROWNGRENADE1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_wepfir,     // seesound
+		6*TICRATE,      // reactiontime (<-- Looking for the Grenade Ring's fuse? It's right here! Again!)
+		sfx_gbeep,      // attacksound
+		S_NULL,         // painstate
+		192*FRACUNIT,   // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_RINGEXPLODE,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		30*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_s3k5d,      // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE|MF_GRENADEBOUNCE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_COIN
+		1800,           // doomednum
+		S_COIN1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGCOIN,   // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_COINSPARKLE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_mario4,     // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GRENADERING
-		307,            // doomednum
-		S_GRENADERINGAMMO, // spawnstate
-		10,             // spawnhealth
+	{           // MT_FLINGCOIN
+		-1,             // doomednum
+		S_COIN1,        // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		MT_FLINGCOIN,   // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_COIN,        // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_COINSPARKLE1, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_mario4,     // deathsound
+		60*FRACUNIT,    // speed
+		15*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SPECIAL, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_GOOMBA
+		1801,           // doomednum
+		S_GOOMBA1,      // spawnstate
+		1,              // spawnhealth
+		S_GOOMBA2,      // seestate
+		sfx_None,       // seesound
+		32,             // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_GOOMBA_DEAD,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_mario5,     // deathsound
+		6,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BLUEGOOMBA
+		1802,              // doomednum
+		S_BLUEGOOMBA1,     // spawnstate
+		1,                 // spawnhealth
+		S_BLUEGOOMBA2,     // seestate
+		sfx_None,          // seesound
+		32,                // reactiontime
+		sfx_None,          // attacksound
+		S_NULL,            // painstate
+		170,               // painchance
+		sfx_None,          // painsound
+		S_NULL,            // meleestate
+		S_NULL,            // missilestate
+		S_BLUEGOOMBA_DEAD, // deathstate
+		S_NULL,            // xdeathstate
+		sfx_mario5,        // deathsound
+		6,                 // speed
+		24*FRACUNIT,       // radius
+		32*FRACUNIT,       // height
+		0,                 // display offset
+		100,               // mass
+		0,                 // damage
+		sfx_None,          // activesound
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		S_NULL             // raisestate
+	},
+
+	{           // MT_FIREFLOWER
+		1803,           // doomednum
+		S_FIREFLOWER1,  // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FIREBALL
+		-1,             // doomednum
+		S_FIREBALL1,    // spawnstate
+		1000,           // spawnhealth
+		S_FIREBALLEXP1, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
-		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		S_FIREBALLEXP1, // meleestate
+		S_FIREBALLEXP1, // missilestate
+		S_FIREBALLEXP1, // deathstate
+		S_FIREBALLEXP1, // xdeathstate
+		sfx_mario1,     // deathsound
+		10*FRACUNIT,    // speed
+		4*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
 		0,              // display offset
-		pw_grenadering, // mass
-		0,              // damage
+		DMG_FIRE,       // mass
+		1,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_FIRE|MF_MISSILE, // flags
 		S_NULL          // raisestate
 	},
 
-// Ring panels: Reactiontime = amount given
-	{           // MT_BOUNCEPICKUP
-		330,            // doomednum
-		S_BOUNCEPICKUP, // spawnstate
+	{           // MT_SHELL
+		1804,           // doomednum
+		S_SHELL,        // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		10,             // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		1,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BOUNCEPICKUPFADE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		16,             // speed
+		16*FRACUNIT,    // radius
+		20*FRACUNIT,    // height
 		0,              // display offset
-		pw_bouncering,  // mass
-		2*TICRATE,      // damage
-		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		0,              // mass
+		1,              // damage
+		sfx_mario1,     // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_BOUNCE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_RAILPICKUP
-		331,            // doomednum
-		S_RAILPICKUP,   // spawnstate
-		1,              // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_PUMA
+		1805,           // doomednum
+		S_PUMA_START1,  // spawnstate
+		1000,           // spawnhealth
+		S_PUMA_START1,  // seestate
 		sfx_None,       // seesound
-		5,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		2,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_PUMA_DOWN1,   // meleestate
 		S_NULL,         // missilestate
-		S_RAILPICKUPFADE1, // deathstate
-		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		S_NULL,         // deathstate
+		S_PUMA_DOWN3,   // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		pw_railring,    // mass
-		2*TICRATE,      // damage
+		DMG_FIRE,       // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_PAIN|MF_FIRE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AUTOPICKUP
-		332,            // doomednum
-		S_AUTOPICKUP,   // spawnstate
-		1,              // spawnhealth
+	{           // MT_PUMATRAIL
+		-1,             // doomednum
+		S_PUMATRAIL1,   // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		40,             // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		4,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_AUTOPICKUPFADE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		2*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
 		0,              // display offset
-		pw_automaticring, // mass
-		2*TICRATE,      // damage
+		100,            // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EXPLODEPICKUP
-		333,            // doomednum
-		S_EXPLODEPICKUP,// spawnstate
-		1,              // spawnhealth
+	{           // MT_HAMMER
+		-1,             // doomednum
+		S_HAMMER,      // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		5,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		8,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_EXPLODEPICKUPFADE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		4*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
 		0,              // display offset
-		pw_explosionring, // mass
-		2*TICRATE,      // damage
+		0,              // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_PAIN,        // flags
 		S_NULL          // raisestate
 	},
-
-	{           // MT_SCATTERPICKUP
-		334,            // doomednum
-		S_SCATTERPICKUP,// spawnstate
-		1,              // spawnhealth
+	{           // MT_KOOPA
+		1806,           // doomednum
+		S_KOOPA1,       // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		5,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		8,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SCATTERPICKUPFADE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
 		0,              // display offset
-		pw_scatterring, // mass
-		2*TICRATE,      // damage
+		0,              // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_PAIN,        // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GRENADEPICKUP
-		335,            // doomednum
-		S_GRENADEPICKUP,// spawnstate
-		1,              // spawnhealth
+	{           // MT_KOOPAFLAME
+		-1,             // doomednum
+		S_KOOPAFLAME1,  // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		10,             // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		8,              // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_GRENADEPICKUPFADE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncitem,     // deathsound
-		60*FRACUNIT,    // speed
-		24*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		5*FRACUNIT,     // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
 		0,              // display offset
-		pw_grenadering, // mass
-		2*TICRATE,      // damage
+		DMG_FIRE,       // mass
+		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOGRAVITY|MF_MISSILE|MF_FIRE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNBOUNCE
-		-1,             // doomednum
-		S_THROWNBOUNCE1,// spawnstate
+	{           // MT_AXE
+		1807,           // doomednum
+		S_AXE1,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_bnce1,      // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13710,53 +18056,53 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_bnce1,      // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_BOUNCE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL,     // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNINFINITY
-		-1,             // doomednum
-		S_THROWNINFINITY1, // spawnstate
+	{           // MT_MARIOBUSH1
+		1808,           // doomednum
+		S_MARIOBUSH1,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_wepfir,     // seesound
-		0,              // reactiontime
+		sfx_None,       // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
+		sfx_None,       // deathsound
+		0,              // speed
 		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNAUTOMATIC
-		-1,             // doomednum
-		S_THROWNAUTOMATIC1, // spawnstate
+	{           // MT_MARIOBUSH2
+		1809,           // doomednum
+		S_MARIOBUSH2,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_wepfir,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13764,26 +18110,26 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
+		sfx_None,       // deathsound
+		0,              // speed
 		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNSCATTER
-		-1,             // doomednum
-		S_THROWNSCATTER,// spawnstate
+	{           // MT_TOAD
+		1810,           // doomednum
+		S_TOAD,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_bnce2,      // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -13791,240 +18137,240 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_itemup,     // deathsound
-		60*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNEXPLOSION
-		-1,             // doomednum
-		S_THROWNEXPLOSION1, // spawnstate
+	{           // MT_AXIS
+		1700,           // doomednum
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_cannon,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		192*FRACUNIT,   // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_RINGEXPLODE,  // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_pop,        // deathsound
-		60*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		256*FRACUNIT,   // radius
+		1*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THROWNGRENADE
-		-1,             // doomednum
-		S_THROWNGRENADE1, // spawnstate
+	{           // MT_AXISTRANSFER
+		1701,           // doomednum
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_wepfir,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
-		sfx_gbeep,      // attacksound
+		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		192*FRACUNIT,   // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_RINGEXPLODE,  // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_pop,        // deathsound
-		30*FRACUNIT,    // speed
+		sfx_None,       // deathsound
+		10,             // speed
 		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		1,              // height
 		0,              // display offset
-		6*TICRATE,      // mass (<-- Looking for the Grenade Ring's fuse? It's right here!)
-		1,              // damage
-		sfx_s3k5d,      // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE|MF_GRENADEBOUNCE, // flags
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP,    // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_COIN
-		1800,           // doomednum
-		S_COIN1,        // spawnstate
+	{           // MT_AXISTRANSFERLINE
+		1702,           // doomednum
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		MT_FLINGCOIN,   // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_COINSPARKLE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_mario4,     // deathsound
-		60*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		10,             // speed
+		32*FRACUNIT,    // radius
+		1,              // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP,    // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLINGCOIN
-		-1,             // doomednum
-		S_COIN1,        // spawnstate
-		1000,           // spawnhealth
+	{           // MT_NIGHTSDRONE
+		1703,           // doomednum
+		S_INVISIBLE,  // spawnstate
+		120,            // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		MT_FLINGCOIN,   // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		MT_COIN,        // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_COINSPARKLE1, // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_mario4,     // deathsound
-		60*FRACUNIT,    // speed
-		15*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
+		1,              // display offset
+		1000,           // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_SLIDEME|MF_SPECIAL, // flags
+		sfx_ideya,      // activesound
+		MF_NOGRAVITY|MF_NOCLIP|MF_SPECIAL, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GOOMBA
-		1801,           // doomednum
-		S_GOOMBA1,      // spawnstate
-		1,              // spawnhealth
-		S_GOOMBA2,      // seestate
+	{           // MT_NIGHTSDRONE_MAN
+		-1,           // doomednum
+		S_INVISIBLE,  // spawnstate
+		120,            // spawnhealth
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		32,             // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		200,            // painchance
+		255,            // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_NIGHTSDRONE_MAN1, // meleestate
 		S_NULL,         // missilestate
-		S_GOOMBA_DEAD,  // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_mario5,     // deathsound
-		6,              // speed
-		24*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		56*FRACUNIT,    // height
+		1,              // display offset
+		1000,           // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		sfx_None,      // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLUEGOOMBA
-		1802,              // doomednum
-		S_BLUEGOOMBA1,     // spawnstate
-		1,                 // spawnhealth
-		S_BLUEGOOMBA2,     // seestate
-		sfx_None,          // seesound
-		32,                // reactiontime
-		sfx_None,          // attacksound
-		S_NULL,            // painstate
-		170,               // painchance
-		sfx_None,          // painsound
-		S_NULL,            // meleestate
-		S_NULL,            // missilestate
-		S_BLUEGOOMBA_DEAD, // deathstate
-		S_NULL,            // xdeathstate
-		sfx_mario5,        // deathsound
-		6,                 // speed
-		24*FRACUNIT,       // radius
-		32*FRACUNIT,       // height
-		0,                 // display offset
-		100,               // mass
-		0,                 // damage
-		sfx_None,          // activesound
-		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
-		S_NULL             // raisestate
+	{           // MT_NIGHTSDRONE_SPARKLING
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		255,            // painchance
+		sfx_None,       // painsound
+		S_NIGHTSDRONE_SPARKLING1, // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		1,              // display offset
+		1000,           // mass
+		0,              // damage
+		sfx_None,      // activesound
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
 	},
 
-	{           // MT_FIREFLOWER
-		1803,           // doomednum
-		S_FIREFLOWER1,  // spawnstate
+	{           // MT_NIGHTSDRONE_GOAL
+		-1,             // doomednum
+		S_INVISIBLE,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_NIGHTSDRONE_GOAL1,  // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
+		24*FRACUNIT,    // height
+		-1,             // display offset
+		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL,     // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FIREBALL
+	{           // MT_NIGHTSPARKLE
 		-1,             // doomednum
-		S_FIREBALL1,    // spawnstate
+		S_NIGHTSPARKLE1,// spawnstate
 		1000,           // spawnhealth
-		S_FIREBALLEXP1, // seestate
+		S_NIGHTSPARKLESUPER1, // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_FIREBALLEXP1, // meleestate
-		S_FIREBALLEXP1, // missilestate
-		S_FIREBALLEXP1, // deathstate
-		S_FIREBALLEXP1, // xdeathstate
-		sfx_mario1,     // deathsound
-		10*FRACUNIT,    // speed
-		4*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		2*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
+		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_FIRE|MF_MISSILE, // flags
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SHELL
-		1804,           // doomednum
-		S_SHELL,        // spawnstate
-		1,              // spawnhealth
+	{           // MT_NIGHTSLOOPHELPER
+		-1,             // doomednum
+		S_NIGHTSLOOPHELPER,// spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
@@ -14037,49 +18383,49 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		16,             // speed
-		16*FRACUNIT,    // radius
-		20*FRACUNIT,    // height
+		0,              // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_mario1,     // activesound
-		MF_SPECIAL|MF_SHOOTABLE|MF_BOUNCE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PUMA
-		1805,           // doomednum
-		S_PUMA_START1,  // spawnstate
-		1000,           // spawnhealth
-		S_PUMA_START1,  // seestate
-		sfx_None,       // seesound
+	{           // MT_NIGHTSBUMPER
+		1704,           // doomednum
+		S_NIGHTSBUMPER1,// spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_nbmper,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_PUMA_DOWN1,   // meleestate
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
-		S_PUMA_DOWN3,   // xdeathstate
+		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		21000,          // speed
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_PAIN|MF_FIRE, // flags
+		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_PUMATRAIL
+	{           // MT_HOOP
 		-1,             // doomednum
-		S_PUMATRAIL1,   // spawnstate
+		S_HOOP,         // spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
+		S_HOOP_XMASA,   // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
@@ -14091,20 +18437,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		2*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		1,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HAMMER
+	{           // MT_HOOPCOLLIDE
 		-1,             // doomednum
-		S_HAMMER,      // spawnstate
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14118,19 +18464,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		4*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		1,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_PAIN,        // flags
+		MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_SPECIAL|MF_NOTHINK, // flags
 		S_NULL          // raisestate
 	},
-	{           // MT_KOOPA
-		1806,           // doomednum
-		S_KOOPA1,       // spawnstate
+
+	{           // MT_HOOPCENTER
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14144,20 +18491,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		48*FRACUNIT,    // height
+		1,              // speed
+		2*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_PAIN,        // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_KOOPAFLAME
+	{           // MT_NIGHTSCORE
 		-1,             // doomednum
-		S_KOOPAFLAME1,  // spawnstate
+		S_NIGHTSCORE10, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14169,212 +18516,212 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
+		S_NIGHTSCORE10_2, // xdeathstate
 		sfx_None,       // deathsound
-		5*FRACUNIT,     // speed
+		1,              // speed
 		8*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_MISSILE|MF_FIRE, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AXE
-		1807,           // doomednum
-		S_AXE1,         // spawnstate
+	{           // MT_NIGHTSCHIP
+		-1,             // doomednum
+		S_NIGHTSCHIP,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		MT_FLINGNIGHTSCHIP, // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		sfx_s3k33,      // painsound
+		S_RING,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		sfx_ncchip,     // deathsound
+		1,              // speed
+		16*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL,     // flags
-		S_NULL          // raisestate
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NIGHTSCHIPBONUS // raisestate
 	},
 
-	{           // MT_MARIOBUSH1
-		1808,           // doomednum
-		S_MARIOBUSH1,   // spawnstate
+	{           // MT_FLINGNIGHTSCHIP
+		-1,             // doomednum
+		S_NIGHTSCHIP,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		MT_FLINGNIGHTSCHIP,   // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		MT_NIGHTSCHIP,        // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
+		sfx_ncchip,     // deathsound
+		38*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		MF_SLIDEME|MF_SPECIAL, // flags
+		S_NIGHTSCHIPBONUS // raisestate
 	},
 
-	{           // MT_MARIOBUSH2
-		1809,           // doomednum
-		S_MARIOBUSH2,   // spawnstate
+	{           // MT_NIGHTSSTAR
+		-1,             // doomednum
+		S_NIGHTSSTAR,   // spawnstate
 		1000,           // spawnhealth
-		S_NULL,         // seestate
+		S_NIGHTSSTARXMAS, // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		sfx_s3k33,      // painsound
+		S_RING,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
+		sfx_ncitem,     // deathsound
+		1,              // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TOAD
-		1810,           // doomednum
-		S_TOAD,         // spawnstate
+	{           // MT_NIGHTSSUPERLOOP
+		1707,           // doomednum
+		S_NIGHTSSUPERLOOP, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		8*FRACUNIT,     // radius
+		sfx_ncspec,     // deathsound
+		20*TICRATE,     // speed
+		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AXIS
-		1700,           // doomednum
-		S_INVISIBLE,    // spawnstate
+	{           // MT_NIGHTSDRILLREFILL
+		1708,           // doomednum
+		S_NIGHTSDRILLREFILL, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
-		256*FRACUNIT,   // radius
-		1*FRACUNIT,     // height
+		sfx_ncspec,     // deathsound
+		96*20,          // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP, // flags
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AXISTRANSFER
-		1701,           // doomednum
-		S_INVISIBLE,    // spawnstate
+	{           // MT_NIGHTSHELPER
+		1709,           // doomednum
+		S_NIGHTSHELPER, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		10,             // speed
+		sfx_ncspec,     // deathsound
+		20*TICRATE,     // speed
 		16*FRACUNIT,    // radius
-		1,              // height
+		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP,    // flags
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_AXISTRANSFERLINE
-		1702,           // doomednum
-		S_INVISIBLE,    // spawnstate
+	{           // MT_NIGHTSEXTRATIME
+		1711,           // doomednum
+		S_NIGHTSEXTRATIME, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		10,             // speed
-		32*FRACUNIT,    // radius
-		1,              // height
+		sfx_ncspec,     // deathsound
+		30*TICRATE,     // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP,    // flags
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSDRONE
-		1703,           // doomednum
-		S_NIGHTSDRONE1, // spawnstate
-		120,            // spawnhealth
+	{           // MT_NIGHTSLINKFREEZE
+		1712,           // doomednum
+		S_NIGHTSLINKFREEZE, // spawnstate
+		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
@@ -14384,30 +18731,30 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SPRK1,        // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
+		sfx_ncspec,     // deathsound
+		15*TICRATE,     // speed
 		16*FRACUNIT,    // radius
-		56*FRACUNIT,    // height
-		1,              // display offset
+		32*FRACUNIT,    // height
+		0,              // display offset
 		1000,           // mass
 		0,              // damage
-		sfx_ideya,      // activesound
-		MF_SPECIAL,     // flags
+		sfx_None,       // activesound
+		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSGOAL
-		-1,             // doomednum
-		S_NIGHTSGOAL1,  // spawnstate
-		1000,           // spawnhealth
+	{           // MT_EGGCAPSULE
+		1710,           // doomednum
+		S_EGGCAPSULE,   // spawnstate
+		20,             // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -14415,23 +18762,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		56*FRACUNIT,    // height
-		-1,             // display offset
-		1000,           // mass
-		0,              // damage
+		72*FRACUNIT,    // radius
+		144*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOGRAVITY|MF_SPECIAL, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSPARKLE
-		-1,             // doomednum
-		S_NIGHTSPARKLE1,// spawnstate
-		1000,           // spawnhealth
-		S_NIGHTSPARKLESUPER1, // seestate
+	{           // MT_IDEYAANCHOR
+		1714,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		0,              // spawnhealth
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -14442,26 +18789,26 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		2*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		1*FRACUNIT,     // radius
+		2*FRACUNIT,     // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSLOOPHELPER
+	{           // MT_NIGHTOPIANHELPER
 		-1,             // doomednum
-		S_NIGHTSLOOPHELPER,// spawnstate
+		S_NIGHTOPIANHELPER1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		255,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -14469,208 +18816,235 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOGRAVITY,   // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSBUMPER
-		1704,           // doomednum
-		S_NIGHTSBUMPER1,// spawnstate
-		1,              // spawnhealth
-		S_NULL,         // seestate
-		sfx_nbmper,     // seesound
-		8,              // reactiontime
+	{           // MT_PIAN
+		1602,           // doomednum
+		S_PIAN0,        // spawnstate
+		1000,           // spawnhealth
+		S_PIAN1,        // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_PIANSING,     // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21000,          // speed
-		32*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		FRACUNIT,       // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
+		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HOOP
-		-1,             // doomednum
-		S_HOOP,         // spawnstate
-		1000,           // spawnhealth
-		S_HOOP_XMASA,   // seestate
+	{           // MT_SHLEEP
+		1601,           // doomednum
+		S_SHLEEP1,      // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SHLEEPBOUNCE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		1,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		sfx_peww,       // deathsound
+		0,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HOOPCOLLIDE
-		-1,             // doomednum
-		S_INVISIBLE,    // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_PENGUINATOR
+		129,            // doomednum
+		S_PENGUINATOR_LOOK, // spawnstate
+		1,              // spawnhealth
+		S_PENGUINATOR_WADDLE1, // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
+		0,              // reactiontime
+		sfx_ngjump,     // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_PENGUINATOR_SLIDE1, // meleestate
+		S_PENGUINATOR_SLIDE1, // missilestate
+		S_XPLD_FLICKY,  // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		1,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		sfx_pop,        // deathsound
+		5,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_SPECIAL|MF_NOTHINK, // flags
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_SLIDEME, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HOOPCENTER
-		-1,             // doomednum
-		S_INVISIBLE,    // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_POPHAT
+		130,            // doomednum -- happy anniversary!
+		S_POPHAT_LOOK,  // spawnstate
+		1,              // spawnhealth
+		S_POPHAT_SHOOT1, // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		1,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_XPLD_FLICKY,  // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		1,              // speed
-		2*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
+		sfx_pop,        // deathsound
+		0,              // speed
+		24*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSCORE
+	{           // MT_POPSHOT
 		-1,             // doomednum
-		S_NIGHTSCORE10, // spawnstate
-		1000,           // spawnhealth
+		S_ROCKCRUMBLEI, // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
+		sfx_cannon,     // seesound
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NIGHTSCORE10_2, // xdeathstate
-		sfx_None,       // deathsound
-		1,              // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		S_XPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_MISSILE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSWING
-		1706,           // doomednum
-		S_NIGHTSWING,   // spawnstate
-		1000,           // spawnhealth
-		S_NIGHTSWING_XMAS, // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
+	{           // MT_HIVEELEMENTAL
+		127,            // doomednum
+		S_HIVEELEMENTAL_LOOK, // spawnstate
+		2,              // spawnhealth
+		S_HIVEELEMENTAL_PREPARE1, // seestate
+		sfx_s3k74,      // seesound
+		0,              // reactiontime
+		sfx_s3k91,      // attacksound
+		S_HIVEELEMENTAL_PAIN, // painstate
 		0,              // painchance
-		sfx_s3k33,      // painsound
-		S_RING,         // meleestate
+		sfx_dmpain,     // painsound
+		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_HIVEELEMENTAL_DIE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		1,              // speed
-		12*FRACUNIT,    // radius
-		24*FRACUNIT,    // height
+		sfx_cybdth,     // deathsound
+		6*FRACUNIT,     // speed
+		30*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
-		sfx_ncitem,     // activesound
-		MF_SLIDEME|MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		sfx_s3k72,      // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSSUPERLOOP
-		1707,           // doomednum
-		S_NIGHTSSUPERLOOP, // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
+	{           // MT_BUMBLEBORE
+		128,			// doomednum
+		S_BUMBLEBORE_SPAWN, // spawnstate
+		0,              // spawnhealth -- this is how you do drones...
+		S_BUMBLEBORE_FLY1, // seestate
+		sfx_s3k8e,      // seesound
+		2,              // reactiontime
+		sfx_s3k9e,      // attacksound
+		S_BUMBLEBORE_STUCK1, // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_BUMBLEBORE_RAISE, // meleestate
+		S_NULL,         // missilestate
+		S_BUMBLEBORE_DIE, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		4*FRACUNIT,     // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY|MF_SLIDEME, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BUBBLEBUZZ
+		124,            // doomednum
+		S_BBUZZFLY1,    // spawnstate
+		1,              // spawnhealth
+		S_BBUZZFLY1,    // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		2,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		TICRATE,        // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_XPLD_FLICKY,  // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncspec,     // deathsound
-		20*TICRATE,     // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		sfx_pop,        // deathsound
+		6*FRACUNIT,     // speed
+		20*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
 		0,              // display offset
-		1000,           // mass
+		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
+		sfx_gbeep,      // activesound
+		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSDRILLREFILL
-		1708,           // doomednum
-		S_NIGHTSDRILLREFILL, // spawnstate
+	{           // MT_SMASHINGSPIKEBALL
+		2000,           // doomednum
+		S_SMASHSPIKE_FLOAT, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14681,153 +19055,206 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncspec,     // deathsound
-		96*20,          // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		18*FRACUNIT,    // radius
+		28*FRACUNIT,    // height
 		0,              // display offset
-		1000,           // mass
+		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
+		MF_NOGRAVITY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSHELPER
-		1709,           // doomednum
-		S_NIGHTSHELPER, // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		0,              // reactiontime
+	{           // MT_CACOLANTERN
+		132,            // doomednum
+		S_CACO_LOOK,    // spawnstate
+		1,              // spawnhealth
+		S_CACO_WAKE1,   // seestate
+		sfx_s3k8a,      // seesound
+		32,             // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_CACO_DIE_FLAGS, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncspec,     // deathsound
-		20*TICRATE,     // speed
-		16*FRACUNIT,    // radius
+		sfx_lntdie,     // deathsound
+		FRACUNIT,       // speed
+		32*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		1000,           // mass
+		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
+		sfx_lntsit,       // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSEXTRATIME
-		1711,           // doomednum
-		S_NIGHTSEXTRATIME, // spawnstate
+	{           // MT_CACOSHARD
+		-1,             // doomednum
+		S_CACOSHARD_RANDOMIZE, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncspec,     // deathsound
-		30*TICRATE,     // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		sfx_lntdie,     // deathsound
+		FRACUNIT,       // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
 		0,              // display offset
-		1000,           // mass
+		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
+		MF_MISSILE|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_NIGHTSLINKFREEZE
-		1712,           // doomednum
-		S_NIGHTSLINKFREEZE, // spawnstate
+	{           // MT_CACOFIRE
+		-1,             // doomednum
+		S_CACOFIRE1,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
-		0,              // reactiontime
+		sfx_s3k70,      // seesound
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_SPRK1,        // deathstate
+		S_CACOFIRE_EXPLODE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_ncspec,     // deathsound
-		15*TICRATE,     // speed
+		sfx_s3k81,      // deathsound
+		20*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		20,             // damage
+		sfx_s3k48,      // activesound
+		MF_MISSILE|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SPINBOBERT
+		131,            // doomednum
+		S_SPINBOBERT_MOVE_FLIPUP, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_s3ka0,      // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3k92,      // deathsound
+		20*FRACUNIT,    // speed
+		32*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		1000,           // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_NIGHTSITEM,   // flags
+		100,            // mass
+		20,             // damage
+		sfx_s3k48,      // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EGGCAPSULE
-		1710,           // doomednum
-		S_EGGCAPSULE,   // spawnstate
-		20,             // spawnhealth
+	{           // MT_SPINBOBERT_FIRE1
+		-1,             // doomednum
+		S_SPINBOBERT_FIRE_MOVE, // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		72*FRACUNIT,    // radius
-		144*FRACUNIT,   // height
+		10*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
-		1,              // damage
+		(sfx_ghosty<<8),// mass
+		20,             // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_SPECIAL, // flags
+		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
-	// the flicky that orbits the player when they have a Nightopian helper
-	{           // MT_NIGHTOPIANHELPER
+	{           // MT_SPINBOBERT_FIRE2
 		-1,             // doomednum
-		S_NIGHTOPIANHELPER1, // spawnstate
-		1000,           // spawnhealth
+		S_SPINBOBERT_FIRE_MOVE, // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		0,              // reactiontime
+		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		255,            // painchance
+		200,            // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		-10*FRACUNIT,   // speed - only difference from above
 		16*FRACUNIT,    // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
-		0,              // damage
+		(sfx_ghosty<<8),// mass
+		20,             // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY,   // flags
+		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_HANGSTER
+		133,            // doomednum
+		S_HANGSTER_LOOK, // spawnstate
+		1,              // spawnhealth
+		S_HANGSTER_SWOOP1, // seestate
+		sfx_s3ka0,      // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		200,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		20*FRACUNIT,    // speed
+		24*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		20,             // damage
+		sfx_s3k48,      // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY|MF_SPAWNCEILING, // flags
 		S_NULL          // raisestate
 	},
 
@@ -15050,6 +19477,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_ANGLEMAN
+		758,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8,              // radius
+		8,              // height
+		0,              // display offset
+		10,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_POLYANCHOR
 		760,            // doomednum
 		S_INVISIBLE,    // spawnstate
@@ -15154,7 +19608,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -15235,7 +19689,61 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_DUST
+		-1,             // doomednum
+		S_DUST1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		3*FRACUNIT,     // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WOODDEBRIS
+		-1,             // doomednum
+		S_WOODDEBRIS,   // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_wbreak,     // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -15272,7 +19780,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_ambint,     // seesound
-		0,              // reactiontime
+		4,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		255,            // painchance
@@ -15286,7 +19794,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		0,              // mass
 		0,              // damage
 		sfx_rocks1,     // activesound
 		MF_PAIN|MF_BOUNCE,  // flags
@@ -15725,6 +20233,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BRICKDEBRIS
+		-1,             // doomednum
+		S_BRICKDEBRIS, // spawnstate
+		1,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,           // mass
+		0,              // damage
+		sfx_None,     // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY,  // flags
+		S_NULL          // raisestate
+	},
+
 #ifdef SEENAMES
 	{           // MT_NAMECHECK
 		-1,             // doomednum
@@ -15746,7 +20281,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		30*FRACUNIT,    // radius
 		40*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_NOSECTOR, // flags
diff --git a/src/info.h b/src/info.h
index 25a9e15629208187d4d8333d6194713c94d38f72..d5174404451e5e638a453df8a372b883dbd60dec 100644
--- a/src/info.h
+++ b/src/info.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -34,6 +34,10 @@ void A_GoldMonitorSparkle();
 void A_Look();
 void A_Chase();
 void A_FaceStabChase();
+void A_FaceStabRev();
+void A_FaceStabHurl();
+void A_FaceStabMiss();
+void A_StatueBurst();
 void A_FaceTarget();
 void A_FaceTracer();
 void A_Scream();
@@ -53,7 +57,6 @@ void A_ExtraLife(); // Extra Life
 void A_GiveShield(); // Obtained Shield
 void A_GravityBox();
 void A_ScoreRise(); // Rise the score logo
-void A_ParticleSpawn();
 void A_AttractChase(); // Ring Chase
 void A_DropMine(); // Drop Mine from Skim or Jetty-Syn Bomber
 void A_FishJump(); // Fish Jump
@@ -98,13 +101,23 @@ void A_JetJawRoam();
 void A_JetJawChomp();
 void A_PointyThink();
 void A_CheckBuddy();
+void A_HoodFire();
 void A_HoodThink();
-void A_ArrowCheck();
+void A_HoodFall();
+void A_ArrowBonks();
 void A_SnailerThink();
 void A_SharpChase();
 void A_SharpSpin();
+void A_SharpDecel();
+void A_CrushstaceanWalk();
+void A_CrushstaceanPunch();
+void A_CrushclawAim();
+void A_CrushclawLaunch();
 void A_VultureVtol();
 void A_VultureCheck();
+void A_VultureHover();
+void A_VultureBlast();
+void A_VultureFly();
 void A_SkimChase();
 void A_SkullAttack();
 void A_LobShot();
@@ -206,6 +219,7 @@ void A_BrakLobShot();
 void A_NapalmScatter();
 void A_SpawnFreshCopy();
 void A_FlickySpawn();
+void A_FlickyCenter();
 void A_FlickyAim();
 void A_FlickyFly();
 void A_FlickySoar();
@@ -218,9 +232,43 @@ void A_FlickyFlutter();
 void A_FlameParticle();
 void A_FadeOverlay();
 void A_Boss5Jump();
+void A_LightBeamReset();
+void A_MineExplode();
+void A_MineRange();
+void A_ConnectToGround();
+void A_SpawnParticleRelative();
+void A_MultiShotDist();
+void A_WhoCaresIfYourSonIsABee();
+void A_ParentTriesToSleep();
+void A_CryingToMomma();
+void A_CheckFlags2();
+void A_Boss5FindWaypoint();
+void A_DoNPCSkid();
+void A_DoNPCPain();
+void A_PrepareRepeat();
+void A_Boss5ExtraRepeat();
+void A_Boss5Calm();
+void A_Boss5CheckOnGround();
+void A_Boss5CheckFalling();
+void A_Boss5PinchShot();
+void A_Boss5MakeItRain();
+void A_LookForBetter();
+void A_Boss5BombExplode();
+void A_DustDevilThink();
+void A_TNTExplode();
+void A_DebrisRandom();
+void A_TrainCameo();
+void A_TrainCameo2();
+void A_CanarivoreGas();
+void A_KillSegments();
+void A_SnapperSpawn();
+void A_SnapperThinker();
+void A_SaloonDoorSpawn();
+void A_MinecartSparkThink();
+void A_ModuloToState();
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
-#define NUMMOBJFREESLOTS 256
+#define NUMMOBJFREESLOTS 512
 #define NUMSPRITEFREESLOTS NUMMOBJFREESLOTS
 #define NUMSTATEFREESLOTS (NUMMOBJFREESLOTS*8)
 
@@ -234,33 +282,38 @@ typedef enum sprite
 	SPR_PLAY,
 
 	// Enemies
-	SPR_POSS,
-	SPR_SPOS,
-	SPR_FISH, // Greenflower Fish
+	SPR_POSS, // Crawla (Blue)
+	SPR_SPOS, // Crawla (Red)
+	SPR_FISH, // SDURF
 	SPR_BUZZ, // Buzz (Gold)
 	SPR_RBUZ, // Buzz (Red)
 	SPR_JETB, // Jetty-Syn Bomber
-	SPR_JETW, // Jetty-Syn Water Bomber
 	SPR_JETG, // Jetty-Syn Gunner
 	SPR_CCOM, // Crawla Commander
 	SPR_DETN, // Deton
 	SPR_SKIM, // Skim mine dropper
-	SPR_TRET,
+	SPR_TRET, // Industrial Turret
 	SPR_TURR, // Pop-Up Turret
 	SPR_SHRP, // Sharp
+	SPR_CRAB, // Crushstacean
 	SPR_JJAW, // Jet Jaw
 	SPR_SNLR, // Snailer
-	SPR_VLTR, // Vulture
+	SPR_VLTR, // BASH
 	SPR_PNTY, // Pointy
 	SPR_ARCH, // Robo-Hood
-	SPR_CBFS, // CastleBot FaceStabber (Egg Knight?)
+	SPR_CBFS, // Castlebot Facestabber
+	SPR_STAB, // Castlebot Facestabber spear aura
 	SPR_SPSH, // Egg Guard
-	SPR_ESHI, // Egg Shield for Egg Guard
+	SPR_ESHI, // Egg Guard's shield
 	SPR_GSNP, // Green Snapper
+	SPR_GSNL, // Green Snapper leg
+	SPR_GSNH, // Green Snapper head
 	SPR_MNUS, // Minus
+	SPR_MNUD, // Minus dirt
 	SPR_SSHL, // Spring Shell
 	SPR_UNID, // Unidus
-	SPR_BBUZ, // AquaBuzz, for Azure Temple
+	SPR_CANA, // Canarivore
+	SPR_CANG, // Canarivore gas
 
 	// Generic Boss Items
 	SPR_JETF, // Boss jet fumes
@@ -283,9 +336,14 @@ typedef enum sprite
 	// Boss 4 (Castle Eggman)
 	SPR_EGGP,
 	SPR_EFIR, // Boss 4 jet flame
+	SPR_EGR1, // Boss 4 Spectator Eggrobo
 
 	// Boss 5 (Arid Canyon)
-	SPR_EGGQ,
+	SPR_FANG, // replaces EGGQ
+	SPR_FBOM,
+	SPR_FSGN,
+	SPR_BARX, // bomb explosion (also used by barrel)
+	SPR_BARD, // bomb dust (also used by barrel)
 
 	// Boss 6 (Red Volcano)
 	SPR_EGGR,
@@ -316,16 +374,16 @@ typedef enum sprite
 	SPR_TOKE, // Special Stage Token
 	SPR_RFLG, // Red CTF Flag
 	SPR_BFLG, // Blue CTF Flag
-	SPR_NWNG, // NiGHTS Wing collectable item.
+	SPR_SPHR, // Sphere
+	SPR_NCHP, // NiGHTS chip
+	SPR_NSTR, // NiGHTS star
 	SPR_EMBM, // Emblem
 	SPR_CEMG, // Chaos Emeralds
-	SPR_EMER, // Emerald Hunt
+	SPR_SHRD, // Emerald Hunt
 
 	// Interactive Objects
-	SPR_FANS,
 	SPR_BBLS, // water bubble source
 	SPR_SIGN, // Level end sign
-	SPR_STEM, // Steam riser
 	SPR_SPIK, // Spike Ball
 	SPR_SFLM, // Spin fire
 	SPR_USPK, // Floor spike
@@ -381,16 +439,20 @@ typedef enum sprite
 	SPR_FWR4,
 	SPR_BUS1, // GFZ Bush w/ berries
 	SPR_BUS2, // GFZ Bush w/o berries
+	SPR_BUS3, // GFZ Bush w/ BLUE berries
 	// Trees (both GFZ and misc)
 	SPR_TRE1, // GFZ
 	SPR_TRE2, // Checker
 	SPR_TRE3, // Frozen Hillside
 	SPR_TRE4, // Polygon
 	SPR_TRE5, // Bush tree
+	SPR_TRE6, // Spring tree
 
 	// Techno Hill Scenery
-	SPR_THZP, // THZ1 Flower
-	SPR_FWR5, // Another flower
+	SPR_THZP, // THZ1 Steam Flower
+	SPR_FWR5, // THZ1 Spin flower (red)
+	SPR_FWR6, // THZ1 Spin flower (yellow)
+	SPR_THZT, // Steam Whistle tree/bush
 	SPR_ALRM, // THZ2 Alarm
 
 	// Deep Sea Scenery
@@ -401,6 +463,9 @@ typedef enum sprite
 	SPR_CRL2, // Coral 2
 	SPR_CRL3, // Coral 3
 	SPR_BCRY, // Blue Crystal
+	SPR_KELP, // Kelp
+	SPR_DSTG, // DSZ Stalagmites
+	SPR_LIBE, // DSZ Light beam
 
 	// Castle Eggman Scenery
 	SPR_CHAN, // CEZ Chain
@@ -414,11 +479,37 @@ typedef enum sprite
 	SPR_RSPB, // Red spring on a ball
 	SPR_SFBR, // Small Firebar
 	SPR_BFBR, // Big Firebar
+	SPR_BANR, // Banner/pole
+	SPR_PINE, // Pine Tree
+	SPR_CEZB, // Bush
+	SPR_CNDL, // Candle/pricket
+	SPR_FLMH, // Flame holder
+	SPR_CTRC, // Fire torch
+	SPR_CFLG, // Waving flag/segment
+	SPR_CSTA, // Crawla statue
+	SPR_CBBS, // Facestabber statue
+	SPR_CABR, // Brambles
 
 	// Arid Canyon Scenery
 	SPR_BTBL, // Big tumbleweed
 	SPR_STBL, // Small tumbleweed
 	SPR_CACT, // Cacti sprites
+	SPR_WWSG, // Caution Sign
+	SPR_WWS2, // Cacti Sign
+	SPR_WWS3, // Sharp Turn Sign
+	SPR_OILL, // Oil lamp
+	SPR_OILF, // Oil lamp flare
+	SPR_BARR, // TNT barrel
+	SPR_REMT, // TNT proximity shell
+	SPR_TAZD, // Dust devil
+	SPR_ADST, // Arid dust
+	SPR_MCRT, // Minecart
+	SPR_MCSP, // Minecart spark
+	SPR_NON2, // Saloon door thinker
+	SPR_SALD, // Saloon door
+	SPR_TRAE, // Train cameo locomotive
+	SPR_TRAI, // Train cameo wagon
+	SPR_STEA, // Train steam
 
 	// Red Volcano Scenery
 	SPR_FLME, // Flame jet
@@ -434,12 +525,25 @@ typedef enum sprite
 	SPR_XMS3, // Snowman
 	SPR_XMS4, // Lamppost
 	SPR_XMS5, // Hanging Star
+	SPR_FHZI, // FHZ Ice
+
+	// Halloween Scenery
+	SPR_PUMK, // Pumpkins
+	SPR_HHPL, // Dr Seuss Trees
+	SPR_SHRM, // Mushroom
+	SPR_HHZM, // Misc
 
 	// Botanic Serenity Scenery
 	SPR_BSZ1, // Tall flowers
 	SPR_BSZ2, // Medium flowers
 	SPR_BSZ3, // Small flowers
-	SPR_BSZ4, // Tulip
+	//SPR_BSZ4, -- Tulips
+	SPR_BST1, // Red tulip
+	SPR_BST2, // Purple tulip
+	SPR_BST3, // Blue tulip
+	SPR_BST4, // Cyan tulip
+	SPR_BST5, // Yellow tulip
+	SPR_BST6, // Orange tulip
 	SPR_BSZ5, // Cluster of Tulips
 	SPR_BSZ6, // Bush
 	SPR_BSZ7, // Vine
@@ -485,13 +589,20 @@ typedef enum sprite
 	SPR_FL14, // Dove
 	SPR_FL15, // Cat
 	SPR_FL16, // Canary
+	SPR_FS01, // Spider
+	SPR_FS02, // Bat
 
 	// Springs
-	SPR_SPRY, // yellow spring
-	SPR_SPRR, // red spring
-	SPR_SPRB, // Blue springs
+	SPR_FANS, // Fan
+	SPR_STEM, // Steam riser
+	SPR_BUMP, // Bumpers
+	SPR_BLON, // Balloons
+	SPR_SPRY, // Yellow spring
+	SPR_SPRR, // Red spring
+	SPR_SPRB, // Blue spring
 	SPR_YSPR, // Yellow Diagonal Spring
 	SPR_RSPR, // Red Diagonal Spring
+	SPR_BSPR, // Blue Diagonal Spring
 	SPR_SSWY, // Yellow Side Spring
 	SPR_SSWR, // Red Side Spring
 	SPR_SSWB, // Blue Side Spring
@@ -518,6 +629,7 @@ typedef enum sprite
 	SPR_GFLG, // Got Flag sign
 
 	SPR_CORK,
+	SPR_LHRT,
 
 	// Ring Weapons
 	SPR_RRNG, // Red Ring
@@ -565,13 +677,33 @@ typedef enum sprite
 	SPR_NSCR, // NiGHTS score sprite
 	SPR_NPRU, // Nights Powerups
 	SPR_CAPS, // Capsule thingy for NiGHTS
+	SPR_IDYA, // Ideya
+	SPR_NTPN, // Nightopian
+	SPR_SHLP, // Shleep
+
+	// Secret badniks and hazards, shhhh
+	SPR_PENG,
+	SPR_POPH,
+	SPR_HIVE,
+	SPR_BUMB,
+	SPR_BBUZ,
+	SPR_FMCE,
+	SPR_HMCE,
+	SPR_CACO,
+	SPR_BAL2,
+	SPR_SBOB,
+	SPR_SBFL,
+	SPR_SBSK,
+	SPR_HBAT,
 
 	// Debris
-	SPR_SPRK, // spark
+	SPR_SPRK, // Sparkle
 	SPR_BOM1, // Robot Explosion
 	SPR_BOM2, // Boss Explosion 1
 	SPR_BOM3, // Boss Explosion 2
 	SPR_BOM4, // Underwater Explosion
+	SPR_BMNB, // Mine Explosion
+	SPR_WDDB, // Wood Debris
 
 	// Crumbly rocks
 	SPR_ROIA,
@@ -591,8 +723,8 @@ typedef enum sprite
 	SPR_ROIO,
 	SPR_ROIP,
 
-	// Blue Spheres
-	SPR_BBAL,
+	// Bricks
+	SPR_BRIC,
 
 	// Gravity Well Objects
 	SPR_GWLG,
@@ -703,6 +835,7 @@ typedef enum playersprite
 
 	SPR2_SIGN, // end sign head
 	SPR2_LIFE, // life monitor icon
+	SPR2_XTRA, // stuff that isn't in-game - keep this last in the list
 
 	SPR2_FIRSTFREESLOT,
 	SPR2_LASTFREESLOT = 0x7f,
@@ -890,10 +1023,6 @@ typedef enum state
 	S_RBUZZFLY1,
 	S_RBUZZFLY2,
 
-	// AquaBuzz
-	S_BBUZZFLY1,
-	S_BBUZZFLY2,
-
 	// Jetty-Syn Bomber
 	S_JETBLOOK1,
 	S_JETBLOOK2,
@@ -930,7 +1059,6 @@ typedef enum state
 	S_DETON13,
 	S_DETON14,
 	S_DETON15,
-	S_DETON16,
 
 	// Skim Mine Dropper
 	S_SKIM1,
@@ -972,14 +1100,40 @@ typedef enum state
 	S_TURRETPOPDOWN7,
 	S_TURRETPOPDOWN8,
 
-	// Sharp
-	S_SHARP_ROAM1,
-	S_SHARP_ROAM2,
-	S_SHARP_AIM1,
-	S_SHARP_AIM2,
-	S_SHARP_AIM3,
-	S_SHARP_AIM4,
-	S_SHARP_SPIN,
+	// Spincushion
+	S_SPINCUSHION_LOOK,
+	S_SPINCUSHION_CHASE1,
+	S_SPINCUSHION_CHASE2,
+	S_SPINCUSHION_CHASE3,
+	S_SPINCUSHION_CHASE4,
+	S_SPINCUSHION_AIM1,
+	S_SPINCUSHION_AIM2,
+	S_SPINCUSHION_AIM3,
+	S_SPINCUSHION_AIM4,
+	S_SPINCUSHION_AIM5,
+	S_SPINCUSHION_SPIN1,
+	S_SPINCUSHION_SPIN2,
+	S_SPINCUSHION_SPIN3,
+	S_SPINCUSHION_SPIN4,
+	S_SPINCUSHION_STOP1,
+	S_SPINCUSHION_STOP2,
+	S_SPINCUSHION_STOP3,
+	S_SPINCUSHION_STOP4,
+
+	// Crushstacean
+	S_CRUSHSTACEAN_ROAM1,
+	S_CRUSHSTACEAN_ROAM2,
+	S_CRUSHSTACEAN_ROAM3,
+	S_CRUSHSTACEAN_ROAM4,
+	S_CRUSHSTACEAN_ROAMPAUSE,
+	S_CRUSHSTACEAN_PUNCH1,
+	S_CRUSHSTACEAN_PUNCH2,
+	S_CRUSHCLAW_AIM,
+	S_CRUSHCLAW_OUT,
+	S_CRUSHCLAW_STAY,
+	S_CRUSHCLAW_IN,
+	S_CRUSHCLAW_WAIT,
+	S_CRUSHCHAIN,
 
 	// Jet Jaw
 	S_JETJAW_ROAM1,
@@ -1012,15 +1166,10 @@ typedef enum state
 
 	// Vulture
 	S_VULTURE_STND,
-	S_VULTURE_VTOL1,
-	S_VULTURE_VTOL2,
-	S_VULTURE_VTOL3,
-	S_VULTURE_VTOL4,
+	S_VULTURE_DRIFT,
 	S_VULTURE_ZOOM1,
 	S_VULTURE_ZOOM2,
-	S_VULTURE_ZOOM3,
-	S_VULTURE_ZOOM4,
-	S_VULTURE_ZOOM5,
+	S_VULTURE_STUNNED,
 
 	// Pointy
 	S_POINTY1,
@@ -1028,13 +1177,14 @@ typedef enum state
 
 	// Robo-Hood
 	S_ROBOHOOD_LOOK,
-	S_ROBOHOOD_STND,
-	S_ROBOHOOD_SHOOT,
-	S_ROBOHOOD_JUMP,
+	S_ROBOHOOD_STAND,
+	S_ROBOHOOD_FIRE1,
+	S_ROBOHOOD_FIRE2,
+	S_ROBOHOOD_JUMP1,
 	S_ROBOHOOD_JUMP2,
-	S_ROBOHOOD_FALL,
+	S_ROBOHOOD_JUMP3,
 
-	// CastleBot FaceStabber
+	// Castlebot Facestabber
 	S_FACESTABBER_STND1,
 	S_FACESTABBER_STND2,
 	S_FACESTABBER_STND3,
@@ -1045,6 +1195,11 @@ typedef enum state
 	S_FACESTABBER_CHARGE2,
 	S_FACESTABBER_CHARGE3,
 	S_FACESTABBER_CHARGE4,
+	S_FACESTABBER_PAIN,
+	S_FACESTABBER_DIE1,
+	S_FACESTABBER_DIE2,
+	S_FACESTABBER_DIE3,
+	S_FACESTABBERSPEAR,
 
 	// Egg Guard
 	S_EGGGUARD_STND,
@@ -1062,17 +1217,34 @@ typedef enum state
 
 	// Egg Shield for Egg Guard
 	S_EGGSHIELD,
+	S_EGGSHIELDBREAK,
 
 	// Green Snapper
+	S_SNAPPER_SPAWN,
+	S_SNAPPER_SPAWN2,
 	S_GSNAPPER_STND,
 	S_GSNAPPER1,
 	S_GSNAPPER2,
 	S_GSNAPPER3,
 	S_GSNAPPER4,
+	S_SNAPPER_XPLD,
+	S_SNAPPER_LEG,
+	S_SNAPPER_LEGRAISE,
+	S_SNAPPER_HEAD,
 
 	// Minus
+	S_MINUS_INIT,
 	S_MINUS_STND,
-	S_MINUS_DIGGING,
+	S_MINUS_DIGGING1,
+	S_MINUS_DIGGING2,
+	S_MINUS_DIGGING3,
+	S_MINUS_DIGGING4,
+	S_MINUS_BURST0,
+	S_MINUS_BURST1,
+	S_MINUS_BURST2,
+	S_MINUS_BURST3,
+	S_MINUS_BURST4,
+	S_MINUS_BURST5,
 	S_MINUS_POPUP,
 	S_MINUS_UPWARD1,
 	S_MINUS_UPWARD2,
@@ -1091,6 +1263,15 @@ typedef enum state
 	S_MINUS_DOWNWARD7,
 	S_MINUS_DOWNWARD8,
 
+	// Minus dirt
+	S_MINUSDIRT1,
+	S_MINUSDIRT2,
+	S_MINUSDIRT3,
+	S_MINUSDIRT4,
+	S_MINUSDIRT5,
+	S_MINUSDIRT6,
+	S_MINUSDIRT7,
+
 	// Spring Shell
 	S_SSHELL_STND,
 	S_SSHELL_RUN1,
@@ -1118,14 +1299,30 @@ typedef enum state
 	S_UNIDUS_RUN,
 	S_UNIDUS_BALL,
 
+	// Canarivore
+	S_CANARIVORE_LOOK,
+	S_CANARIVORE_AWAKEN1,
+	S_CANARIVORE_AWAKEN2,
+	S_CANARIVORE_AWAKEN3,
+	S_CANARIVORE_GAS1,
+	S_CANARIVORE_GAS2,
+	S_CANARIVORE_GAS3,
+	S_CANARIVORE_GAS4,
+	S_CANARIVORE_GAS5,
+	S_CANARIVORE_GASREPEAT,
+	S_CANARIVORE_CLOSE1,
+	S_CANARIVORE_CLOSE2,
+	S_CANARIVOREGAS_1,
+	S_CANARIVOREGAS_2,
+	S_CANARIVOREGAS_3,
+	S_CANARIVOREGAS_4,
+	S_CANARIVOREGAS_5,
+	S_CANARIVOREGAS_6,
+	S_CANARIVOREGAS_7,
+	S_CANARIVOREGAS_8,
+
 	// Boss Explosion
-	S_BPLD1,
-	S_BPLD2,
-	S_BPLD3,
-	S_BPLD4,
-	S_BPLD5,
-	S_BPLD6,
-	S_BPLD7,
+	S_BOSSEXPLODE,
 
 	// S3&K Boss Explosion
 	S_SONIC3KBOSSEXPLOSION1,
@@ -1228,6 +1425,11 @@ typedef enum state
 
 	// Boss 3
 	S_EGGMOBILE3_STND,
+	S_EGGMOBILE3_LAUGH1,
+	S_EGGMOBILE3_LAUGH2,
+	S_EGGMOBILE3_LAUGH3,
+	S_EGGMOBILE3_LAUGH4,
+	S_EGGMOBILE3_LAUGH5,
 	S_EGGMOBILE3_ATK1,
 	S_EGGMOBILE3_ATK2,
 	S_EGGMOBILE3_ATK3A,
@@ -1236,11 +1438,6 @@ typedef enum state
 	S_EGGMOBILE3_ATK3D,
 	S_EGGMOBILE3_ATK4,
 	S_EGGMOBILE3_ATK5,
-	S_EGGMOBILE3_LAUGH1,
-	S_EGGMOBILE3_LAUGH2,
-	S_EGGMOBILE3_LAUGH3,
-	S_EGGMOBILE3_LAUGH4,
-	S_EGGMOBILE3_LAUGH5,
 	S_EGGMOBILE3_LAUGH6,
 	S_EGGMOBILE3_LAUGH7,
 	S_EGGMOBILE3_LAUGH8,
@@ -1293,8 +1490,8 @@ typedef enum state
 	S_FAKEMOBILE_ATK3B,
 	S_FAKEMOBILE_ATK3C,
 	S_FAKEMOBILE_ATK3D,
-	S_FAKEMOBILE_ATK4,
-	S_FAKEMOBILE_ATK5,
+	S_FAKEMOBILE_DIE1,
+	S_FAKEMOBILE_DIE2,
 
 	// Boss 4
 	S_EGGMOBILE4_STND,
@@ -1312,15 +1509,8 @@ typedef enum state
 	S_EGGMOBILE4_RATK6,
 	S_EGGMOBILE4_RAISE1,
 	S_EGGMOBILE4_RAISE2,
-	S_EGGMOBILE4_RAISE3,
-	S_EGGMOBILE4_RAISE4,
-	S_EGGMOBILE4_RAISE5,
-	S_EGGMOBILE4_RAISE6,
-	S_EGGMOBILE4_RAISE7,
-	S_EGGMOBILE4_RAISE8,
-	S_EGGMOBILE4_RAISE9,
-	S_EGGMOBILE4_RAISE10,
-	S_EGGMOBILE4_PAIN,
+	S_EGGMOBILE4_PAIN1,
+	S_EGGMOBILE4_PAIN2,
 	S_EGGMOBILE4_DIE1,
 	S_EGGMOBILE4_DIE2,
 	S_EGGMOBILE4_DIE3,
@@ -1338,10 +1528,111 @@ typedef enum state
 	S_EGGMOBILE4_FLEE1,
 	S_EGGMOBILE4_FLEE2,
 	S_EGGMOBILE4_MACE,
+	S_EGGMOBILE4_MACE_DIE1,
+	S_EGGMOBILE4_MACE_DIE2,
+	S_EGGMOBILE4_MACE_DIE3,
 
 	// Boss 4 jet flame
-	S_JETFLAME1,
-	S_JETFLAME2,
+	S_JETFLAME,
+
+	// Boss 4 Spectator Eggrobo
+	S_EGGROBO1_STND,
+	S_EGGROBO1_BSLAP1,
+	S_EGGROBO1_BSLAP2,
+	S_EGGROBO1_PISSED,
+
+	// Boss 4 Spectator Eggrobo jet flame
+	S_EGGROBOJET,
+
+	// Boss 5
+	S_FANG_IDLE1,
+	S_FANG_IDLE2,
+	S_FANG_IDLE3,
+	S_FANG_IDLE4,
+	S_FANG_IDLE5,
+	S_FANG_IDLE6,
+	S_FANG_IDLE7,
+	S_FANG_IDLE8,
+	S_FANG_PAIN1,
+	S_FANG_PAIN2,
+	S_FANG_PATHINGSTART1,
+	S_FANG_PATHINGSTART2,
+	S_FANG_PATHING,
+	S_FANG_BOUNCE1,
+	S_FANG_BOUNCE2,
+	S_FANG_BOUNCE3,
+	S_FANG_BOUNCE4,
+	S_FANG_FALL1,
+	S_FANG_FALL2,
+	S_FANG_CHECKPATH1,
+	S_FANG_CHECKPATH2,
+	S_FANG_PATHINGCONT1,
+	S_FANG_PATHINGCONT2,
+	S_FANG_PATHINGCONT3,
+	S_FANG_SKID1,
+	S_FANG_SKID2,
+	S_FANG_SKID3,
+	S_FANG_CHOOSEATTACK,
+	S_FANG_FIRESTART1,
+	S_FANG_FIRESTART2,
+	S_FANG_FIRE1,
+	S_FANG_FIRE2,
+	S_FANG_FIRE3,
+	S_FANG_FIRE4,
+	S_FANG_FIREREPEAT,
+	S_FANG_LOBSHOT1,
+	S_FANG_LOBSHOT2,
+	S_FANG_WAIT1,
+	S_FANG_WAIT2,
+	S_FANG_WALLHIT,
+	S_FANG_PINCHPATHINGSTART1,
+	S_FANG_PINCHPATHINGSTART2,
+	S_FANG_PINCHPATHING,
+	S_FANG_PINCHBOUNCE1,
+	S_FANG_PINCHBOUNCE2,
+	S_FANG_PINCHBOUNCE3,
+	S_FANG_PINCHBOUNCE4,
+	S_FANG_PINCHFALL1,
+	S_FANG_PINCHFALL2,
+	S_FANG_PINCHSKID1,
+	S_FANG_PINCHSKID2,
+	S_FANG_PINCHLOBSHOT1,
+	S_FANG_PINCHLOBSHOT2,
+	S_FANG_PINCHLOBSHOT3,
+	S_FANG_PINCHLOBSHOT4,
+	S_FANG_DIE1,
+	S_FANG_DIE2,
+	S_FANG_DIE3,
+	S_FANG_DIE4,
+	S_FANG_DIE5,
+	S_FANG_DIE6,
+	S_FANG_DIE7,
+	S_FANG_DIE8,
+	S_FANG_FLEEPATHING1,
+	S_FANG_FLEEPATHING2,
+	S_FANG_FLEEBOUNCE1,
+	S_FANG_FLEEBOUNCE2,
+	S_FANG_KO,
+
+	S_FBOMB1,
+	S_FBOMB2,
+	S_FBOMB_EXPL1,
+	S_FBOMB_EXPL2,
+	S_FBOMB_EXPL3,
+	S_FBOMB_EXPL4,
+	S_FBOMB_EXPL5,
+	S_FBOMB_EXPL6,
+	S_TNTDUST_1,
+	S_TNTDUST_2,
+	S_TNTDUST_3,
+	S_TNTDUST_4,
+	S_TNTDUST_5,
+	S_TNTDUST_6,
+	S_TNTDUST_7,
+	S_TNTDUST_8,
+	S_FSGNA,
+	S_FSGNB,
+	S_FSGNC,
 
 	// Black Eggman (Boss 7)
 	S_BLACKEGG_STND,
@@ -1604,7 +1895,10 @@ typedef enum state
 	S_METALSONIC_BADBOUNCE,
 	S_METALSONIC_SHOOT,
 	S_METALSONIC_PAIN,
-	S_METALSONIC_DEATH,
+	S_METALSONIC_DEATH1,
+	S_METALSONIC_DEATH2,
+	S_METALSONIC_DEATH3,
+	S_METALSONIC_DEATH4,
 	S_METALSONIC_FLEE1,
 	S_METALSONIC_FLEE2,
 	S_METALSONIC_FLEE3,
@@ -1627,17 +1921,27 @@ typedef enum state
 	S_RING,
 
 	// Blue Sphere for special stages
-	S_BLUEBALL,
-	S_BLUEBALLSPARK,
+	S_BLUESPHERE,
+	S_BLUESPHEREBONUS,
+	S_BLUESPHERESPARK,
+
+	// Bomb Sphere
+	S_BOMBSPHERE1,
+	S_BOMBSPHERE2,
+	S_BOMBSPHERE3,
+	S_BOMBSPHERE4,
+
+	// NiGHTS Chip
+	S_NIGHTSCHIP,
+	S_NIGHTSCHIPBONUS,
+
+	// NiGHTS Star
+	S_NIGHTSSTAR,
+	S_NIGHTSSTARXMAS,
 
 	// Gravity Wells for special stages
 	S_GRAVWELLGREEN,
-	S_GRAVWELLGREEN2,
-	S_GRAVWELLGREEN3,
-
 	S_GRAVWELLRED,
-	S_GRAVWELLRED2,
-	S_GRAVWELLRED3,
 
 	// Individual Team Rings
 	S_TEAMRING,
@@ -1686,14 +1990,10 @@ typedef enum state
 	S_CEMG6,
 	S_CEMG7,
 
-	// Emeralds (for hunt)
-	S_EMER1,
-
-	S_FAN,
-	S_FAN2,
-	S_FAN3,
-	S_FAN4,
-	S_FAN5,
+	// Emerald hunt shards
+	S_SHRD1,
+	S_SHRD2,
+	S_SHRD3,
 
 	// Bubble Source
 	S_BUBBLES1,
@@ -1756,16 +2056,6 @@ typedef enum state
 	S_SIGN52, // Eggman
 	S_SIGN53,
 
-	// Steam Riser
-	S_STEAM1,
-	S_STEAM2,
-	S_STEAM3,
-	S_STEAM4,
-	S_STEAM5,
-	S_STEAM6,
-	S_STEAM7,
-	S_STEAM8,
-
 	// Spike Ball
 	S_SPIKEBALL1,
 	S_SPIKEBALL2,
@@ -1813,14 +2103,18 @@ typedef enum state
 	S_STARPOST_ENDSPIN,
 
 	// Big floating mine
-	S_BIGMINE1,
-	S_BIGMINE2,
-	S_BIGMINE3,
-	S_BIGMINE4,
-	S_BIGMINE5,
-	S_BIGMINE6,
-	S_BIGMINE7,
-	S_BIGMINE8,
+	S_BIGMINE_IDLE,
+	S_BIGMINE_ALERT1,
+	S_BIGMINE_ALERT2,
+	S_BIGMINE_ALERT3,
+	S_BIGMINE_SET1,
+	S_BIGMINE_SET2,
+	S_BIGMINE_SET3,
+	S_BIGMINE_BLAST1,
+	S_BIGMINE_BLAST2,
+	S_BIGMINE_BLAST3,
+	S_BIGMINE_BLAST4,
+	S_BIGMINE_BLAST5,
 
 	// Cannon Launcher
 	S_CANNONLAUNCHER1,
@@ -1831,6 +2125,7 @@ typedef enum state
 	S_BOXSPARKLE1,
 	S_BOXSPARKLE2,
 	S_BOXSPARKLE3,
+	S_BOXSPARKLE4,
 
 	S_BOX_FLICKER,
 	S_BOX_POP1,
@@ -1982,22 +2277,17 @@ typedef enum state
 
 	// Arrow
 	S_ARROW,
-	S_ARROWUP,
-	S_ARROWDOWN,
+	S_ARROWBONK,
 
 	// Trapgoyle Demon fire
-	S_DEMONFIRE1,
-	S_DEMONFIRE2,
-	S_DEMONFIRE3,
-	S_DEMONFIRE4,
-	S_DEMONFIRE5,
-	S_DEMONFIRE6,
+	S_DEMONFIRE,
 
 	// GFZ flowers
 	S_GFZFLOWERA,
 	S_GFZFLOWERB,
 	S_GFZFLOWERC,
 
+	S_BLUEBERRYBUSH,
 	S_BERRYBUSH,
 	S_BUSH,
 
@@ -2012,10 +2302,28 @@ typedef enum state
 	S_POLYGONTREE,
 	S_BUSHTREE,
 	S_BUSHREDTREE,
-
-	// THZ Plant
-	S_THZFLOWERA,
-	S_THZFLOWERB,
+	S_SPRINGTREE,
+
+	// THZ flowers
+	S_THZFLOWERA, // THZ1 Steam flower
+	S_THZFLOWERB, // THZ1 Spin flower (red)
+	S_THZFLOWERC, // THZ1 Spin flower (yellow)
+
+	// THZ Steam Whistle tree/bush
+	S_THZTREE,
+	S_THZTREEBRANCH1,
+	S_THZTREEBRANCH2,
+	S_THZTREEBRANCH3,
+	S_THZTREEBRANCH4,
+	S_THZTREEBRANCH5,
+	S_THZTREEBRANCH6,
+	S_THZTREEBRANCH7,
+	S_THZTREEBRANCH8,
+	S_THZTREEBRANCH9,
+	S_THZTREEBRANCH10,
+	S_THZTREEBRANCH11,
+	S_THZTREEBRANCH12,
+	S_THZTREEBRANCH13,
 
 	// THZ Alarm
 	S_ALARM1,
@@ -2053,18 +2361,33 @@ typedef enum state
 	// Blue Crystal
 	S_BLUECRYSTAL1,
 
+	// Kelp,
+	S_KELP,
+
+	// DSZ Stalagmites
+	S_DSZSTALAGMITE,
+	S_DSZ2STALAGMITE,
+
+	// DSZ Light beam
+	S_LIGHTBEAM1,
+	S_LIGHTBEAM2,
+	S_LIGHTBEAM3,
+	S_LIGHTBEAM4,
+	S_LIGHTBEAM5,
+	S_LIGHTBEAM6,
+	S_LIGHTBEAM7,
+	S_LIGHTBEAM8,
+	S_LIGHTBEAM9,
+	S_LIGHTBEAM10,
+	S_LIGHTBEAM11,
+	S_LIGHTBEAM12,
+
 	// CEZ Chain
 	S_CEZCHAIN,
 
 	// Flame
-	S_FLAME1,
-	S_FLAME2,
-	S_FLAME3,
-	S_FLAME4,
-	S_FLAME5,
-	S_FLAME6,
+	S_FLAME,
 	S_FLAMEPARTICLE,
-
 	S_FLAMEREST,
 
 	// Eggman Statue
@@ -2079,6 +2402,8 @@ typedef enum state
 	S_BIGMACECHAIN,
 	S_SMALLMACE,
 	S_BIGMACE,
+	S_SMALLGRABCHAIN,
+	S_BIGGRABCHAIN,
 
 	// Yellow spring on a ball
 	S_YELLOWSPRINGBALL,
@@ -2130,7 +2455,26 @@ typedef enum state
 	S_BIGFIREBAR15,
 	S_BIGFIREBAR16,
 
-	S_CEZFLOWER1,
+	S_CEZFLOWER,
+	S_CEZPOLE,
+	S_CEZBANNER1,
+	S_CEZBANNER2,
+	S_PINETREE,
+	S_CEZBUSH1,
+	S_CEZBUSH2,
+	S_CANDLE,
+	S_CANDLEPRICKET,
+	S_FLAMEHOLDER,
+	S_FIRETORCH,
+	S_WAVINGFLAG,
+	S_WAVINGFLAGSEG1,
+	S_WAVINGFLAGSEG2,
+	S_CRAWLASTATUE,
+	S_FACESTABBERSTATUE,
+	S_SUSPICIOUSFACESTABBERSTATUE_WAIT,
+	S_SUSPICIOUSFACESTABBERSTATUE_BURST1,
+	S_SUSPICIOUSFACESTABBERSTATUE_BURST2,
+	S_BRAMBLES,
 
 	// Big Tumbleweed
 	S_BIGTUMBLEWEED,
@@ -2159,6 +2503,95 @@ typedef enum state
 	S_CACTI2,
 	S_CACTI3,
 	S_CACTI4,
+	S_CACTI5,
+	S_CACTI6,
+	S_CACTI7,
+	S_CACTI8,
+	S_CACTI9,
+
+	// Warning signs sprites
+	S_ARIDSIGN_CAUTION,
+	S_ARIDSIGN_CACTI,
+	S_ARIDSIGN_SHARPTURN,
+
+	// Oil lamp
+	S_OILLAMP,
+	S_OILLAMPFLARE,
+
+	// TNT barrel
+	S_TNTBARREL_STND1,
+	S_TNTBARREL_EXPL1,
+	S_TNTBARREL_EXPL2,
+	S_TNTBARREL_EXPL3,
+	S_TNTBARREL_EXPL4,
+	S_TNTBARREL_EXPL5,
+	S_TNTBARREL_EXPL6,
+	S_TNTBARREL_FLYING,
+
+	// TNT proximity shell
+	S_PROXIMITY_TNT,
+	S_PROXIMITY_TNT_TRIGGER1,
+	S_PROXIMITY_TNT_TRIGGER2,
+	S_PROXIMITY_TNT_TRIGGER3,
+	S_PROXIMITY_TNT_TRIGGER4,
+	S_PROXIMITY_TNT_TRIGGER5,
+	S_PROXIMITY_TNT_TRIGGER6,
+	S_PROXIMITY_TNT_TRIGGER7,
+	S_PROXIMITY_TNT_TRIGGER8,
+	S_PROXIMITY_TNT_TRIGGER9,
+	S_PROXIMITY_TNT_TRIGGER10,
+	S_PROXIMITY_TNT_TRIGGER11,
+	S_PROXIMITY_TNT_TRIGGER12,
+	S_PROXIMITY_TNT_TRIGGER13,
+	S_PROXIMITY_TNT_TRIGGER14,
+	S_PROXIMITY_TNT_TRIGGER15,
+	S_PROXIMITY_TNT_TRIGGER16,
+	S_PROXIMITY_TNT_TRIGGER17,
+	S_PROXIMITY_TNT_TRIGGER18,
+	S_PROXIMITY_TNT_TRIGGER19,
+	S_PROXIMITY_TNT_TRIGGER20,
+	S_PROXIMITY_TNT_TRIGGER21,
+	S_PROXIMITY_TNT_TRIGGER22,
+	S_PROXIMITY_TNT_TRIGGER23,
+
+	// Dust devil
+	S_DUSTDEVIL,
+	S_DUSTLAYER1,
+	S_DUSTLAYER2,
+	S_DUSTLAYER3,
+	S_DUSTLAYER4,
+	S_DUSTLAYER5,
+	S_ARIDDUST1,
+	S_ARIDDUST2,
+	S_ARIDDUST3,
+
+	// Minecart
+	S_MINECART_IDLE,
+	S_MINECART_DTH1,
+	S_MINECARTEND,
+	S_MINECARTSEG_FRONT,
+	S_MINECARTSEG_BACK,
+	S_MINECARTSEG_LEFT,
+	S_MINECARTSEG_RIGHT,
+	S_MINECARTSIDEMARK1,
+	S_MINECARTSIDEMARK2,
+	S_MINECARTSPARK,
+
+	// Saloon door
+	S_SALOONDOOR,
+	S_SALOONDOORTHINKER,
+
+	// Train cameo
+	S_TRAINCAMEOSPAWNER_1,
+	S_TRAINCAMEOSPAWNER_2,
+	S_TRAINCAMEOSPAWNER_3,
+	S_TRAINCAMEOSPAWNER_4,
+	S_TRAINCAMEOSPAWNER_5,
+	S_TRAINPUFFMAKER,
+
+	// Train
+	S_TRAINDUST,
+	S_TRAINSTEAM,
 
 	// Flame jet
 	S_FLAMEJETSTND,
@@ -2231,8 +2664,57 @@ typedef enum state
 	S_LAMPPOST2,  // with snow
 	S_HANGSTAR,
 	// Xmas GFZ bushes
+	S_XMASBLUEBERRYBUSH,
 	S_XMASBERRYBUSH,
 	S_XMASBUSH,
+	// FHZ
+	S_FHZICE1,
+	S_FHZICE2,
+
+	// Halloween Scenery
+	// Pumpkins
+	S_JACKO1,
+	S_JACKO1OVERLAY_1,
+	S_JACKO1OVERLAY_2,
+	S_JACKO1OVERLAY_3,
+	S_JACKO1OVERLAY_4,
+	S_JACKO2,
+	S_JACKO2OVERLAY_1,
+	S_JACKO2OVERLAY_2,
+	S_JACKO2OVERLAY_3,
+	S_JACKO2OVERLAY_4,
+	S_JACKO3,
+	S_JACKO3OVERLAY_1,
+	S_JACKO3OVERLAY_2,
+	S_JACKO3OVERLAY_3,
+	S_JACKO3OVERLAY_4,
+	// Dr Seuss Trees
+	S_HHZTREE_TOP,
+	S_HHZTREE_TRUNK,
+	S_HHZTREE_LEAF,
+	// Mushroom
+	S_HHZSHROOM_1,
+	S_HHZSHROOM_2,
+	S_HHZSHROOM_3,
+	S_HHZSHROOM_4,
+	S_HHZSHROOM_5,
+	S_HHZSHROOM_6,
+	S_HHZSHROOM_7,
+	S_HHZSHROOM_8,
+	S_HHZSHROOM_9,
+	S_HHZSHROOM_10,
+	S_HHZSHROOM_11,
+	S_HHZSHROOM_12,
+	S_HHZSHROOM_13,
+	S_HHZSHROOM_14,
+	S_HHZSHROOM_15,
+	S_HHZSHROOM_16,
+	// Misc
+	S_HHZGRASS,
+	S_HHZTENT1,
+	S_HHZTENT2,
+	S_HHZSTALAGMITE_TALL,
+	S_HHZSTALAGMITE_SHORT,
 
 	// Botanic Serenity's loads of scenery states
 	S_BSZTALLFLOWER_RED,
@@ -2326,6 +2808,22 @@ typedef enum state
 	S_ARMF14,
 	S_ARMF15,
 	S_ARMF16,
+	S_ARMF17,
+	S_ARMF18,
+	S_ARMF19,
+	S_ARMF20,
+	S_ARMF21,
+	S_ARMF22,
+	S_ARMF23,
+	S_ARMF24,
+	S_ARMF25,
+	S_ARMF26,
+	S_ARMF27,
+	S_ARMF28,
+	S_ARMF29,
+	S_ARMF30,
+	S_ARMF31,
+	S_ARMF32,
 
 	S_ARMB1,
 	S_ARMB2,
@@ -2343,6 +2841,22 @@ typedef enum state
 	S_ARMB14,
 	S_ARMB15,
 	S_ARMB16,
+	S_ARMB17,
+	S_ARMB18,
+	S_ARMB19,
+	S_ARMB20,
+	S_ARMB21,
+	S_ARMB22,
+	S_ARMB23,
+	S_ARMB24,
+	S_ARMB25,
+	S_ARMB26,
+	S_ARMB27,
+	S_ARMB28,
+	S_ARMB29,
+	S_ARMB30,
+	S_ARMB31,
+	S_ARMB32,
 
 	S_WIND1,
 	S_WIND2,
@@ -2424,6 +2938,12 @@ typedef enum state
 	S_PITY4,
 	S_PITY5,
 	S_PITY6,
+	S_PITY7,
+	S_PITY8,
+	S_PITY9,
+	S_PITY10,
+	S_PITY11,
+	S_PITY12,
 
 	S_FIRS1,
 	S_FIRS2,
@@ -2521,6 +3041,8 @@ typedef enum state
 	S_FLICKY_01_FLAP1,
 	S_FLICKY_01_FLAP2,
 	S_FLICKY_01_FLAP3,
+	S_FLICKY_01_STAND,
+	S_FLICKY_01_CENTER,
 
 	// Rabbit
 	S_FLICKY_02_OUT,
@@ -2528,6 +3050,8 @@ typedef enum state
 	S_FLICKY_02_HOP,
 	S_FLICKY_02_UP,
 	S_FLICKY_02_DOWN,
+	S_FLICKY_02_STAND,
+	S_FLICKY_02_CENTER,
 
 	// Chicken
 	S_FLICKY_03_OUT,
@@ -2536,6 +3060,8 @@ typedef enum state
 	S_FLICKY_03_UP,
 	S_FLICKY_03_FLAP1,
 	S_FLICKY_03_FLAP2,
+	S_FLICKY_03_STAND,
+	S_FLICKY_03_CENTER,
 
 	// Seal
 	S_FLICKY_04_OUT,
@@ -2547,6 +3073,8 @@ typedef enum state
 	S_FLICKY_04_SWIM2,
 	S_FLICKY_04_SWIM3,
 	S_FLICKY_04_SWIM4,
+	S_FLICKY_04_STAND,
+	S_FLICKY_04_CENTER,
 
 	// Pig
 	S_FLICKY_05_OUT,
@@ -2554,6 +3082,8 @@ typedef enum state
 	S_FLICKY_05_HOP,
 	S_FLICKY_05_UP,
 	S_FLICKY_05_DOWN,
+	S_FLICKY_05_STAND,
+	S_FLICKY_05_CENTER,
 
 	// Chipmunk
 	S_FLICKY_06_OUT,
@@ -2561,6 +3091,8 @@ typedef enum state
 	S_FLICKY_06_HOP,
 	S_FLICKY_06_UP,
 	S_FLICKY_06_DOWN,
+	S_FLICKY_06_STAND,
+	S_FLICKY_06_CENTER,
 
 	// Penguin
 	S_FLICKY_07_OUT,
@@ -2575,6 +3107,8 @@ typedef enum state
 	S_FLICKY_07_SWIM1,
 	S_FLICKY_07_SWIM2,
 	S_FLICKY_07_SWIM3,
+	S_FLICKY_07_STAND,
+	S_FLICKY_07_CENTER,
 
 	// Fish
 	S_FLICKY_08_OUT,
@@ -2588,6 +3122,8 @@ typedef enum state
 	S_FLICKY_08_SWIM2,
 	S_FLICKY_08_SWIM3,
 	S_FLICKY_08_SWIM4,
+	S_FLICKY_08_STAND,
+	S_FLICKY_08_CENTER,
 
 	// Ram
 	S_FLICKY_09_OUT,
@@ -2595,11 +3131,15 @@ typedef enum state
 	S_FLICKY_09_HOP,
 	S_FLICKY_09_UP,
 	S_FLICKY_09_DOWN,
+	S_FLICKY_09_STAND,
+	S_FLICKY_09_CENTER,
 
 	// Puffin
 	S_FLICKY_10_OUT,
 	S_FLICKY_10_FLAP1,
 	S_FLICKY_10_FLAP2,
+	S_FLICKY_10_STAND,
+	S_FLICKY_10_CENTER,
 
 	// Cow
 	S_FLICKY_11_OUT,
@@ -2607,6 +3147,8 @@ typedef enum state
 	S_FLICKY_11_RUN1,
 	S_FLICKY_11_RUN2,
 	S_FLICKY_11_RUN3,
+	S_FLICKY_11_STAND,
+	S_FLICKY_11_CENTER,
 
 	// Rat
 	S_FLICKY_12_OUT,
@@ -2614,6 +3156,8 @@ typedef enum state
 	S_FLICKY_12_RUN1,
 	S_FLICKY_12_RUN2,
 	S_FLICKY_12_RUN3,
+	S_FLICKY_12_STAND,
+	S_FLICKY_12_CENTER,
 
 	// Bear
 	S_FLICKY_13_OUT,
@@ -2621,12 +3165,16 @@ typedef enum state
 	S_FLICKY_13_HOP,
 	S_FLICKY_13_UP,
 	S_FLICKY_13_DOWN,
+	S_FLICKY_13_STAND,
+	S_FLICKY_13_CENTER,
 
 	// Dove
 	S_FLICKY_14_OUT,
 	S_FLICKY_14_FLAP1,
 	S_FLICKY_14_FLAP2,
 	S_FLICKY_14_FLAP3,
+	S_FLICKY_14_STAND,
+	S_FLICKY_14_CENTER,
 
 	// Cat
 	S_FLICKY_15_OUT,
@@ -2634,26 +3182,79 @@ typedef enum state
 	S_FLICKY_15_HOP,
 	S_FLICKY_15_UP,
 	S_FLICKY_15_DOWN,
+	S_FLICKY_15_STAND,
+	S_FLICKY_15_CENTER,
 
 	// Canary
 	S_FLICKY_16_OUT,
 	S_FLICKY_16_FLAP1,
 	S_FLICKY_16_FLAP2,
 	S_FLICKY_16_FLAP3,
+	S_FLICKY_16_STAND,
+	S_FLICKY_16_CENTER,
+
+	// Spider
+	S_SECRETFLICKY_01_OUT,
+	S_SECRETFLICKY_01_AIM,
+	S_SECRETFLICKY_01_HOP,
+	S_SECRETFLICKY_01_UP,
+	S_SECRETFLICKY_01_DOWN,
+	S_SECRETFLICKY_01_STAND,
+	S_SECRETFLICKY_01_CENTER,
+
+	// Bat
+	S_SECRETFLICKY_02_OUT,
+	S_SECRETFLICKY_02_FLAP1,
+	S_SECRETFLICKY_02_FLAP2,
+	S_SECRETFLICKY_02_FLAP3,
+	S_SECRETFLICKY_02_STAND,
+	S_SECRETFLICKY_02_CENTER,
+
+	// Fan
+	S_FAN,
+	S_FAN2,
+	S_FAN3,
+	S_FAN4,
+	S_FAN5,
+
+	// Steam Riser
+	S_STEAM1,
+	S_STEAM2,
+	S_STEAM3,
+	S_STEAM4,
+	S_STEAM5,
+	S_STEAM6,
+	S_STEAM7,
+	S_STEAM8,
 
+	// Bumpers
+	S_BUMPER,
+	S_BUMPERHIT,
+
+	// Balloons
+	S_BALLOON,
+	S_BALLOONPOP1,
+	S_BALLOONPOP2,
+	S_BALLOONPOP3,
+	S_BALLOONPOP4,
+	S_BALLOONPOP5,
+	S_BALLOONPOP6,
+
+	// Yellow Spring
 	S_YELLOWSPRING,
 	S_YELLOWSPRING2,
 	S_YELLOWSPRING3,
 	S_YELLOWSPRING4,
 	S_YELLOWSPRING5,
 
+	// Red Spring
 	S_REDSPRING,
 	S_REDSPRING2,
 	S_REDSPRING3,
 	S_REDSPRING4,
 	S_REDSPRING5,
 
-	// Blue Springs
+	// Blue Spring
 	S_BLUESPRING,
 	S_BLUESPRING2,
 	S_BLUESPRING3,
@@ -2680,6 +3281,16 @@ typedef enum state
 	S_RDIAG7,
 	S_RDIAG8,
 
+	// Blue Diagonal Spring
+	S_BDIAG1,
+	S_BDIAG2,
+	S_BDIAG3,
+	S_BDIAG4,
+	S_BDIAG5,
+	S_BDIAG6,
+	S_BDIAG7,
+	S_BDIAG8,
+
 	// Yellow Side Spring
 	S_YHORIZ1,
 	S_YHORIZ2,
@@ -2785,7 +3396,6 @@ typedef enum state
 	S_SEED,
 
 	S_PARTICLE,
-	S_PARTICLEGEN,
 
 	// Score Logos
 	S_SCRA, // 100
@@ -2799,6 +3409,7 @@ typedef enum state
 	S_SCRI, // 4000 (mario)
 	S_SCRJ, // 8000 (mario)
 	S_SCRK, // 1UP (mario)
+	S_SCRL, // 10
 
 	// Drowning Timer Numbers
 	S_ZERO1,
@@ -2817,16 +3428,21 @@ typedef enum state
 
 	S_LOCKON1,
 	S_LOCKON2,
+	S_LOCKON3,
+	S_LOCKON4,
+	S_LOCKONINF1,
+	S_LOCKONINF2,
+	S_LOCKONINF3,
+	S_LOCKONINF4,
 
 	// Tag Sign
 	S_TTAG,
 
 	// Got Flag Sign
 	S_GOTFLAG,
-	S_GOTREDFLAG,
-	S_GOTBLUEFLAG,
 
 	S_CORK,
+	S_LHRT,
 
 	// Red Ring
 	S_RRNG1,
@@ -3027,8 +3643,8 @@ typedef enum state
 	S_TOAD,
 
 	// Nights-specific stuff
-	S_NIGHTSDRONE1,
-	S_NIGHTSDRONE2,
+	S_NIGHTSDRONE_MAN1,
+	S_NIGHTSDRONE_MAN2,
 	S_NIGHTSDRONE_SPARKLING1,
 	S_NIGHTSDRONE_SPARKLING2,
 	S_NIGHTSDRONE_SPARKLING3,
@@ -3045,10 +3661,10 @@ typedef enum state
 	S_NIGHTSDRONE_SPARKLING14,
 	S_NIGHTSDRONE_SPARKLING15,
 	S_NIGHTSDRONE_SPARKLING16,
-	S_NIGHTSGOAL1,
-	S_NIGHTSGOAL2,
-	S_NIGHTSGOAL3,
-	S_NIGHTSGOAL4,
+	S_NIGHTSDRONE_GOAL1,
+	S_NIGHTSDRONE_GOAL2,
+	S_NIGHTSDRONE_GOAL3,
+	S_NIGHTSDRONE_GOAL4,
 
 	S_NIGHTSPARKLE1,
 	S_NIGHTSPARKLE2,
@@ -3099,9 +3715,6 @@ typedef enum state
 	S_NIGHTSCORE90_2,
 	S_NIGHTSCORE100_2,
 
-	S_NIGHTSWING,
-	S_NIGHTSWING_XMAS,
-
 	// NiGHTS Paraloop Powerups
 	S_NIGHTSSUPERLOOP,
 	S_NIGHTSDRILLREFILL,
@@ -3119,14 +3732,11 @@ typedef enum state
 	S_ORBITEM6,
 	S_ORBITEM7,
 	S_ORBITEM8,
-	S_ORBITEM9,
-	S_ORBITEM10,
-	S_ORBITEM11,
-	S_ORBITEM12,
-	S_ORBITEM13,
-	S_ORBITEM14,
-	S_ORBITEM15,
-	S_ORBITEM16,
+	S_ORBIDYA1,
+	S_ORBIDYA2,
+	S_ORBIDYA3,
+	S_ORBIDYA4,
+	S_ORBIDYA5,
 
 	// "Flicky" helper
 	S_NIGHTOPIANHELPER1,
@@ -3139,6 +3749,141 @@ typedef enum state
 	S_NIGHTOPIANHELPER8,
 	S_NIGHTOPIANHELPER9,
 
+	// Nightopian
+	S_PIAN0,
+	S_PIAN1,
+	S_PIAN2,
+	S_PIAN3,
+	S_PIAN4,
+	S_PIAN5,
+	S_PIAN6,
+	S_PIANSING,
+
+	// Shleep
+	S_SHLEEP1,
+	S_SHLEEP2,
+	S_SHLEEP3,
+	S_SHLEEP4,
+	S_SHLEEPBOUNCE1,
+	S_SHLEEPBOUNCE2,
+	S_SHLEEPBOUNCE3,
+
+	// Secret badniks and hazards, shhhh
+	S_PENGUINATOR_LOOK,
+	S_PENGUINATOR_WADDLE1,
+	S_PENGUINATOR_WADDLE2,
+	S_PENGUINATOR_WADDLE3,
+	S_PENGUINATOR_WADDLE4,
+	S_PENGUINATOR_SLIDE1,
+	S_PENGUINATOR_SLIDE2,
+	S_PENGUINATOR_SLIDE3,
+	S_PENGUINATOR_SLIDE4,
+	S_PENGUINATOR_SLIDE5,
+
+	S_POPHAT_LOOK,
+	S_POPHAT_SHOOT1,
+	S_POPHAT_SHOOT2,
+	S_POPHAT_SHOOT3,
+
+	S_HIVEELEMENTAL_LOOK,
+	S_HIVEELEMENTAL_PREPARE1,
+	S_HIVEELEMENTAL_PREPARE2,
+	S_HIVEELEMENTAL_SHOOT1,
+	S_HIVEELEMENTAL_SHOOT2,
+	S_HIVEELEMENTAL_DORMANT,
+	S_HIVEELEMENTAL_PAIN,
+	S_HIVEELEMENTAL_DIE1,
+	S_HIVEELEMENTAL_DIE2,
+	S_HIVEELEMENTAL_DIE3,
+
+	S_BUMBLEBORE_SPAWN,
+	S_BUMBLEBORE_LOOK1,
+	S_BUMBLEBORE_LOOK2,
+	S_BUMBLEBORE_FLY1,
+	S_BUMBLEBORE_FLY2,
+	S_BUMBLEBORE_RAISE,
+	S_BUMBLEBORE_FALL1,
+	S_BUMBLEBORE_FALL2,
+	S_BUMBLEBORE_STUCK1,
+	S_BUMBLEBORE_STUCK2,
+	S_BUMBLEBORE_DIE,
+
+	S_BBUZZFLY1,
+	S_BBUZZFLY2,
+
+	S_SMASHSPIKE_FLOAT,
+	S_SMASHSPIKE_EASE1,
+	S_SMASHSPIKE_EASE2,
+	S_SMASHSPIKE_FALL,
+	S_SMASHSPIKE_STOMP1,
+	S_SMASHSPIKE_STOMP2,
+	S_SMASHSPIKE_RISE1,
+	S_SMASHSPIKE_RISE2,
+
+	S_CACO_LOOK,
+	S_CACO_WAKE1,
+	S_CACO_WAKE2,
+	S_CACO_WAKE3,
+	S_CACO_WAKE4,
+	S_CACO_ROAR,
+	S_CACO_CHASE,
+	S_CACO_CHASE_REPEAT,
+	S_CACO_RANDOM,
+	S_CACO_PREPARE_SOUND,
+	S_CACO_PREPARE1,
+	S_CACO_PREPARE2,
+	S_CACO_PREPARE3,
+	S_CACO_SHOOT_SOUND,
+	S_CACO_SHOOT1,
+	S_CACO_SHOOT2,
+	S_CACO_CLOSE,
+	S_CACO_DIE_FLAGS,
+	S_CACO_DIE_GIB1,
+	S_CACO_DIE_GIB2,
+	S_CACO_DIE_SCREAM,
+	S_CACO_DIE_SHATTER,
+	S_CACO_DIE_FALL,
+	S_CACOSHARD_RANDOMIZE,
+	S_CACOSHARD1_1,
+	S_CACOSHARD1_2,
+	S_CACOSHARD2_1,
+	S_CACOSHARD2_2,
+	S_CACOFIRE1,
+	S_CACOFIRE2,
+	S_CACOFIRE3,
+	S_CACOFIRE_EXPLODE1,
+	S_CACOFIRE_EXPLODE2,
+	S_CACOFIRE_EXPLODE3,
+	S_CACOFIRE_EXPLODE4,
+
+	S_SPINBOBERT_MOVE_FLIPUP,
+	S_SPINBOBERT_MOVE_UP,
+	S_SPINBOBERT_MOVE_FLIPDOWN,
+	S_SPINBOBERT_MOVE_DOWN,
+	S_SPINBOBERT_FIRE_MOVE,
+	S_SPINBOBERT_FIRE_GHOST,
+	S_SPINBOBERT_FIRE_TRAIL1,
+	S_SPINBOBERT_FIRE_TRAIL2,
+	S_SPINBOBERT_FIRE_TRAIL3,
+
+	S_HANGSTER_LOOK,
+	S_HANGSTER_SWOOP1,
+	S_HANGSTER_SWOOP2,
+	S_HANGSTER_ARC1,
+	S_HANGSTER_ARC2,
+	S_HANGSTER_ARC3,
+	S_HANGSTER_FLY1,
+	S_HANGSTER_FLY2,
+	S_HANGSTER_FLY3,
+	S_HANGSTER_FLY4,
+	S_HANGSTER_FLYREPEAT,
+	S_HANGSTER_ARCUP1,
+	S_HANGSTER_ARCUP2,
+	S_HANGSTER_ARCUP3,
+	S_HANGSTER_RETURN1,
+	S_HANGSTER_RETURN2,
+	S_HANGSTER_RETURN3,
+
 	S_CRUMBLE1,
 	S_CRUMBLE2,
 
@@ -3146,24 +3891,15 @@ typedef enum state
 	S_SPRK1,
 	S_SPRK2,
 	S_SPRK3,
-	S_SPRK4,
-	S_SPRK5,
-	S_SPRK6,
-	S_SPRK7,
-	S_SPRK8,
-	S_SPRK9,
-	S_SPRK10,
-	S_SPRK11,
-	S_SPRK12,
-	S_SPRK13,
-	S_SPRK14,
-	S_SPRK15,
-	S_SPRK16,
 
 	// Robot Explosion
 	S_XPLD_FLICKY,
 	S_XPLD1,
 	S_XPLD2,
+	S_XPLD3,
+	S_XPLD4,
+	S_XPLD5,
+	S_XPLD6,
 	S_XPLD_EGGTRAP,
 
 	// Underwater Explosion
@@ -3174,6 +3910,13 @@ typedef enum state
 	S_WPLD5,
 	S_WPLD6,
 
+	S_DUST1,
+	S_DUST2,
+	S_DUST3,
+	S_DUST4,
+
+	S_WOODDEBRIS,
+
 	S_ROCKSPAWN,
 
 	S_ROCKCRUMBLEA,
@@ -3193,6 +3936,9 @@ typedef enum state
 	S_ROCKCRUMBLEO,
 	S_ROCKCRUMBLEP,
 
+	// Bricks
+	S_BRICKDEBRIS,
+
 #ifdef SEENAMES
 	S_NAMECHECK,
 #endif
@@ -3230,35 +3976,43 @@ typedef enum mobj_type
 	MT_TAILSOVERLAY, // c:
 
 	// Enemies
-	MT_BLUECRAWLA,
-	MT_REDCRAWLA,
-	MT_GFZFISH, // Greenflower Fish
-	MT_GOLDBUZZ,
-	MT_REDBUZZ,
-	MT_AQUABUZZ, // AquaBuzz for ATZ
+	MT_BLUECRAWLA, // Crawla (Blue)
+	MT_REDCRAWLA, // Crawla (Red)
+	MT_GFZFISH, // SDURF
+	MT_GOLDBUZZ, // Buzz (Gold)
+	MT_REDBUZZ, // Buzz (Red)
 	MT_JETTBOMBER, // Jetty-Syn Bomber
 	MT_JETTGUNNER, // Jetty-Syn Gunner
 	MT_CRAWLACOMMANDER, // Crawla Commander
 	MT_DETON, // Deton
 	MT_SKIM, // Skim mine dropper
-	MT_TURRET,
-	MT_POPUPTURRET,
-	MT_SHARP, // Sharp
+	MT_TURRET, // Industrial Turret
+	MT_POPUPTURRET, // Pop-Up Turret
+	MT_SPINCUSHION, // Spincushion
+	MT_CRUSHSTACEAN, // Crushstacean
+	MT_CRUSHCLAW, // Big meaty claw
+	MT_CRUSHCHAIN, // Chain
 	MT_JETJAW, // Jet Jaw
 	MT_SNAILER, // Snailer
-	MT_VULTURE, // Vulture
+	MT_VULTURE, // BASH
 	MT_POINTY, // Pointy
 	MT_POINTYBALL, // Pointy Ball
 	MT_ROBOHOOD, // Robo-Hood
-	MT_FACESTABBER, // CastleBot FaceStabber
+	MT_FACESTABBER, // Castlebot Facestabber
+	MT_FACESTABBERSPEAR, // Castlebot Facestabber spear aura
 	MT_EGGGUARD, // Egg Guard
-	MT_EGGSHIELD, // Egg Shield for Egg Guard
+	MT_EGGSHIELD, // Egg Guard's shield
 	MT_GSNAPPER, // Green Snapper
+	MT_SNAPPER_LEG, // Green Snapper leg
+	MT_SNAPPER_HEAD, // Green Snapper head
 	MT_MINUS, // Minus
+	MT_MINUSDIRT, // Minus dirt
 	MT_SPRINGSHELL, // Spring Shell
 	MT_YELLOWSHELL, // Spring Shell (yellow)
 	MT_UNIDUS, // Unidus
 	MT_UNIBALL, // Unidus Ball
+	MT_CANARIVORE, // Canarivore
+	MT_CANARIVORE_GAS, // Canarivore gas
 
 	// Generic Boss Items
 	MT_BOSSEXPLODE,
@@ -3288,11 +4042,22 @@ typedef enum mobj_type
 	MT_EGGMOBILE3,
 	MT_PROPELLER,
 	MT_FAKEMOBILE,
+	MT_SHOCK,
 
 	// Boss 4
 	MT_EGGMOBILE4,
 	MT_EGGMOBILE4_MACE,
 	MT_JETFLAME,
+	MT_EGGROBO1,
+	MT_EGGROBO1JET,
+
+	// Boss 5
+	MT_FANG,
+	MT_FBOMB,
+	MT_TNTDUST, // also used by barrel
+	MT_FSGNA,
+	MT_FSGNB,
+	MT_FANGWAYPOINT,
 
 	// Black Eggman (Boss 7)
 	MT_BLACKEGGMAN,
@@ -3322,7 +4087,9 @@ typedef enum mobj_type
 	// Collectible Items
 	MT_RING,
 	MT_FLINGRING, // Lost ring
-	MT_BLUEBALL,  // Blue sphere replacement for special stages
+	MT_BLUESPHERE,  // Blue sphere for special stages
+	MT_FLINGBLUESPHERE, // Lost blue sphere
+	MT_BOMBSPHERE,
 	MT_REDTEAMRING,  //Rings collectable by red team.
 	MT_BLUETEAMRING, //Rings collectable by blue team.
 	MT_TOKEN, // Special Stage token for special stage
@@ -3342,28 +4109,31 @@ typedef enum mobj_type
 
 	// Springs and others
 	MT_FAN,
-	MT_STEAM, // Steam riser
-	MT_BLUESPRING,
+	MT_STEAM,
+	MT_BUMPER,
+	MT_BALLOON,
+
 	MT_YELLOWSPRING,
 	MT_REDSPRING,
-	MT_YELLOWDIAG, // Yellow Diagonal Spring
-	MT_REDDIAG, // Red Diagonal Spring
-	MT_YELLOWHORIZ, // Yellow Side Spring
-	MT_REDHORIZ, // Red Side Spring
-	MT_BLUEHORIZ, // Blue Side Spring
+	MT_BLUESPRING,
+	MT_YELLOWDIAG,
+	MT_REDDIAG,
+	MT_BLUEDIAG,
+	MT_YELLOWHORIZ,
+	MT_REDHORIZ,
+	MT_BLUEHORIZ,
 
 	// Interactive Objects
 	MT_BUBBLES, // Bubble source
 	MT_SIGN, // Level end sign
 	MT_SPIKEBALL, // Spike Ball
-	MT_SPECIALSPIKEBALL,
 	MT_SPINFIRE,
 	MT_SPIKE,
 	MT_WALLSPIKE,
 	MT_WALLSPIKEBASE,
 	MT_STARPOST,
 	MT_BIGMINE,
-	MT_BIGAIRMINE,
+	MT_BLASTEXECUTOR,
 	MT_CANNONLAUNCHER,
 
 	// Monitor miscellany
@@ -3449,8 +4219,11 @@ typedef enum mobj_type
 	MT_GFZFLOWER1,
 	MT_GFZFLOWER2,
 	MT_GFZFLOWER3,
+
+	MT_BLUEBERRYBUSH,
 	MT_BERRYBUSH,
 	MT_BUSH,
+
 	// Trees (both GFZ and misc)
 	MT_GFZTREE,
 	MT_GFZBERRYTREE,
@@ -3462,10 +4235,14 @@ typedef enum mobj_type
 	MT_POLYGONTREE,
 	MT_BUSHTREE,
 	MT_BUSHREDTREE,
+	MT_SPRINGTREE,
 
 	// Techno Hill Scenery
 	MT_THZFLOWER1,
 	MT_THZFLOWER2,
+	MT_THZFLOWER3,
+	MT_THZTREE, // Steam whistle tree/bush
+	MT_THZTREEBRANCH, // branch of said tree
 	MT_ALARM,
 
 	// Deep Sea Scenery
@@ -3478,6 +4255,10 @@ typedef enum mobj_type
 	MT_CORAL2, // Coral 2
 	MT_CORAL3, // Coral 3
 	MT_BLUECRYSTAL, // Blue Crystal
+	MT_KELP, // Kelp
+	MT_DSZSTALAGMITE, // Deep Sea 1 Stalagmite
+	MT_DSZ2STALAGMITE, // Deep Sea 2 Stalagmite
+	MT_LIGHTBEAM, // DSZ Light beam
 
 	// Castle Eggman Scenery
 	MT_CHAIN, // CEZ Chain
@@ -3495,11 +4276,32 @@ typedef enum mobj_type
 	MT_BIGMACECHAIN, // Big Mace Chain
 	MT_SMALLMACE, // Small Mace
 	MT_BIGMACE, // Big Mace
+	MT_SMALLGRABCHAIN, // Small Grab Chain
+	MT_BIGGRABCHAIN, // Big Grab Chain
 	MT_YELLOWSPRINGBALL, // Yellow spring on a ball
 	MT_REDSPRINGBALL, // Red spring on a ball
 	MT_SMALLFIREBAR, // Small Firebar
 	MT_BIGFIREBAR, // Big Firebar
-	MT_CEZFLOWER,
+	MT_CEZFLOWER, // Flower
+	MT_CEZPOLE1, // Pole (with red banner)
+	MT_CEZPOLE2, // Pole (with blue banner)
+	MT_CEZBANNER1, // Banner (red)
+	MT_CEZBANNER2, // Banner (blue)
+	MT_PINETREE, // Pine Tree
+	MT_CEZBUSH1, // Bush 1
+	MT_CEZBUSH2, // Bush 2
+	MT_CANDLE, // Candle
+	MT_CANDLEPRICKET, // Candle pricket
+	MT_FLAMEHOLDER, // Flame holder
+	MT_FIRETORCH, // Fire torch
+	MT_WAVINGFLAG1, // Waving flag (red)
+	MT_WAVINGFLAG2, // Waving flag (blue)
+	MT_WAVINGFLAGSEG1, // Waving flag segment (red)
+	MT_WAVINGFLAGSEG2, // Waving flag segment (blue)
+	MT_CRAWLASTATUE, // Crawla statue
+	MT_FACESTABBERSTATUE, // Facestabber statue
+	MT_SUSPICIOUSFACESTABBERSTATUE, // :eggthinking:
+	MT_BRAMBLES, // Brambles
 
 	// Arid Canyon Scenery
 	MT_BIGTUMBLEWEED,
@@ -3508,6 +4310,34 @@ typedef enum mobj_type
 	MT_CACTI2,
 	MT_CACTI3,
 	MT_CACTI4,
+	MT_CACTI5, // Harmful Cactus 1
+	MT_CACTI6, // Harmful Cactus 2
+	MT_CACTI7, // Harmful Cactus 3
+	MT_CACTI8, // Harmful Cactus 4
+	MT_CACTI9, // Harmful Cactus 5
+	MT_ARIDSIGN_CAUTION, // Caution Sign
+	MT_ARIDSIGN_CACTI, // Cacti Sign
+	MT_ARIDSIGN_SHARPTURN, // Sharp Turn Sign
+	MT_OILLAMP,
+	MT_TNTBARREL,
+	MT_PROXIMITYTNT,
+	MT_DUSTDEVIL,
+	MT_DUSTLAYER,
+	MT_ARIDDUST,
+	MT_MINECART,
+	MT_MINECARTSEG,
+	MT_MINECARTSPAWNER,
+	MT_MINECARTEND,
+	MT_MINECARTENDSOLID,
+	MT_MINECARTSIDEMARK,
+	MT_MINECARTSPARK,
+	MT_SALOONDOOR,
+	MT_SALOONDOORTHINKER,
+	MT_TRAINCAMEOSPAWNER,
+	MT_TRAINSEG,
+	MT_TRAINDUSTSPAWNER,
+	MT_TRAINSTEAMSPAWNER,
+	MT_MINECARTSWITCHPOINT,
 
 	// Red Volcano Scenery
 	MT_FLAMEJET,
@@ -3551,8 +4381,28 @@ typedef enum mobj_type
 	MT_LAMPPOST2,  // with snow
 	MT_HANGSTAR,
 	// Xmas GFZ bushes
+	MT_XMASBLUEBERRYBUSH,
 	MT_XMASBERRYBUSH,
 	MT_XMASBUSH,
+	// FHZ
+	MT_FHZICE1,
+	MT_FHZICE2,
+
+	// Halloween Scenery
+	// Pumpkins
+	MT_JACKO1,
+	MT_JACKO2,
+	MT_JACKO3,
+	// Dr Seuss Trees
+	MT_HHZTREE_TOP,
+	MT_HHZTREE_PART,
+	// Misc
+	MT_HHZSHROOM,
+	MT_HHZGRASS,
+	MT_HHZTENTACLE1,
+	MT_HHZTENTACLE2,
+	MT_HHZSTALAGMITE_TALL,
+	MT_HHZSTALAGMITE_SHORT,
 
 	// Botanic Serenity scenery
 	MT_BSZTALLFLOWER_RED,
@@ -3624,21 +4474,42 @@ typedef enum mobj_type
 
 	// Flickies
 	MT_FLICKY_01, // Bluebird
+	MT_FLICKY_01_CENTER,
 	MT_FLICKY_02, // Rabbit
+	MT_FLICKY_02_CENTER,
 	MT_FLICKY_03, // Chicken
+	MT_FLICKY_03_CENTER,
 	MT_FLICKY_04, // Seal
+	MT_FLICKY_04_CENTER,
 	MT_FLICKY_05, // Pig
+	MT_FLICKY_05_CENTER,
 	MT_FLICKY_06, // Chipmunk
+	MT_FLICKY_06_CENTER,
 	MT_FLICKY_07, // Penguin
+	MT_FLICKY_07_CENTER,
 	MT_FLICKY_08, // Fish
+	MT_FLICKY_08_CENTER,
 	MT_FLICKY_09, // Ram
+	MT_FLICKY_09_CENTER,
 	MT_FLICKY_10, // Puffin
+	MT_FLICKY_10_CENTER,
 	MT_FLICKY_11, // Cow
+	MT_FLICKY_11_CENTER,
 	MT_FLICKY_12, // Rat
+	MT_FLICKY_12_CENTER,
 	MT_FLICKY_13, // Bear
+	MT_FLICKY_13_CENTER,
 	MT_FLICKY_14, // Dove
+	MT_FLICKY_14_CENTER,
 	MT_FLICKY_15, // Cat
+	MT_FLICKY_15_CENTER,
 	MT_FLICKY_16, // Canary
+	MT_FLICKY_16_CENTER,
+	MT_SECRETFLICKY_01, // Spider
+	MT_SECRETFLICKY_01_CENTER,
+	MT_SECRETFLICKY_02, // Bat
+	MT_SECRETFLICKY_02_CENTER,
+	MT_SEED,
 
 	// Environmental Effects
 	MT_RAIN, // Rain
@@ -3651,7 +4522,6 @@ typedef enum mobj_type
 	MT_WATERZAP,
 	MT_SPINDUST, // Spindash dust
 	MT_TFOG,
-	MT_SEED,
 	MT_PARTICLE,
 	MT_PARTICLEGEN, // For fans, etc.
 
@@ -3660,6 +4530,7 @@ typedef enum mobj_type
 	MT_DROWNNUMBERS, // Drowning Timer
 	MT_GOTEMERALD, // Chaos Emerald (intangible)
 	MT_LOCKON, // Target
+	MT_LOCKONINF, // In-level Target
 	MT_TAG, // Tag Sign
 	MT_GOTFLAG, // Got Flag sign
 
@@ -3674,8 +4545,10 @@ typedef enum mobj_type
 	MT_AWATERH, // Ambient Water Sound 8
 	MT_RANDOMAMBIENT,
 	MT_RANDOMAMBIENT2,
+	MT_MACHINEAMBIENCE,
 
 	MT_CORK,
+	MT_LHRT,
 
 	// Ring Weapons
 	MT_REDRING,
@@ -3724,7 +4597,9 @@ typedef enum mobj_type
 	MT_AXISTRANSFER,
 	MT_AXISTRANSFERLINE,
 	MT_NIGHTSDRONE,
-	MT_NIGHTSGOAL,
+	MT_NIGHTSDRONE_MAN,
+	MT_NIGHTSDRONE_SPARKLING,
+	MT_NIGHTSDRONE_GOAL,
 	MT_NIGHTSPARKLE,
 	MT_NIGHTSLOOPHELPER,
 	MT_NIGHTSBUMPER, // NiGHTS Bumper
@@ -3732,14 +4607,38 @@ typedef enum mobj_type
 	MT_HOOPCOLLIDE, // Collision detection for NiGHTS hoops
 	MT_HOOPCENTER, // Center of a hoop
 	MT_NIGHTSCORE,
-	MT_NIGHTSWING,
+	MT_NIGHTSCHIP, // NiGHTS Chip
+	MT_FLINGNIGHTSCHIP, // Lost NiGHTS Chip
+	MT_NIGHTSSTAR, // NiGHTS Star
 	MT_NIGHTSSUPERLOOP,
 	MT_NIGHTSDRILLREFILL,
 	MT_NIGHTSHELPER,
 	MT_NIGHTSEXTRATIME,
 	MT_NIGHTSLINKFREEZE,
 	MT_EGGCAPSULE,
+	MT_IDEYAANCHOR,
 	MT_NIGHTOPIANHELPER, // the actual helper object that orbits you
+	MT_PIAN, // decorative singing friend
+	MT_SHLEEP, // almost-decorative sleeping enemy
+
+	// Secret badniks and hazards, shhhh
+	MT_PENGUINATOR,
+	MT_POPHAT,
+	MT_POPSHOT,
+
+	MT_HIVEELEMENTAL,
+	MT_BUMBLEBORE,
+
+	MT_BUBBLEBUZZ,
+
+	MT_SMASHINGSPIKEBALL,
+	MT_CACOLANTERN,
+	MT_CACOSHARD,
+	MT_CACOFIRE,
+	MT_SPINBOBERT,
+	MT_SPINBOBERT_FIRE1,
+	MT_SPINBOBERT_FIRE2,
+	MT_HANGSTER,
 
 	// Utility Objects
 	MT_TELEPORTMAN,
@@ -3750,6 +4649,7 @@ typedef enum mobj_type
 	MT_PULL,
 	MT_GHOST,
 	MT_OVERLAY,
+	MT_ANGLEMAN,
 	MT_POLYANCHOR,
 	MT_POLYSPAWN,
 	MT_POLYSPAWNCRUSH,
@@ -3761,6 +4661,8 @@ typedef enum mobj_type
 	MT_SPARK, //spark
 	MT_EXPLODE, // Robot Explosion
 	MT_UWEXPLODE, // Underwater Explosion
+	MT_DUST,
+	MT_WOODDEBRIS,
 	MT_ROCKSPAWNER,
 	MT_FALLINGROCK,
 	MT_ROCKCRUMBLE1,
@@ -3780,6 +4682,9 @@ typedef enum mobj_type
 	MT_ROCKCRUMBLE15,
 	MT_ROCKCRUMBLE16,
 
+	// Bricks
+	MT_BRICKDEBRIS,
+
 #ifdef SEENAMES
 	MT_NAMECHECK,
 #endif
diff --git a/src/keys.h b/src/keys.h
index 654aa1d9dfb71881cafebd395da48392d34915f4..cf3a0d060c539483e61026e175e1365da3f5eb3e 100644
--- a/src/keys.h
+++ b/src/keys.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/locale/en.po b/src/locale/en.po
index 069930b8144c7576fe6e371b3f6af5b28e9540ff..83e1187721e682399e092b58c78071ff9e6fe530 100644
--- a/src/locale/en.po
+++ b/src/locale/en.po
@@ -3624,7 +3624,7 @@ msgid "another castle!"
 msgstr ""
 
 #: st_stuff.c:2328 st_stuff.c:2334 st_stuff.c:2356
-msgid "Press F12 to watch another player."
+msgid "Press Viewpoint Key to watch a player."
 msgstr ""
 
 #: st_stuff.c:2333
@@ -3825,7 +3825,7 @@ msgid "Music lump is not MID music format\n"
 msgstr ""
 
 #: win32/win_snd.c:2128
-msgid "I_RegisterSong: StreamBufferSetup FAILED"
+msgid "I_LoadSong: StreamBufferSetup FAILED"
 msgstr ""
 
 #: win32/win_sys.c:892
diff --git a/src/locale/srb2.pot b/src/locale/srb2.pot
index ced13bbe655569d00a616ec396c11a0b6abeba89..087c8720c82397ce6c7376cd87beac40cdeda42f 100644
--- a/src/locale/srb2.pot
+++ b/src/locale/srb2.pot
@@ -3815,7 +3815,7 @@ msgid "another castle!"
 msgstr ""
 
 #: st_stuff.c:2092 st_stuff.c:2098 st_stuff.c:2120
-msgid "Press F12 to watch another player."
+msgid "Press Viewpoint Key to watch a player."
 msgstr ""
 
 #: st_stuff.c:2097
@@ -4021,7 +4021,7 @@ msgid "Music lump is not MID music format\n"
 msgstr ""
 
 #: win32/win_snd.c:2126
-msgid "I_RegisterSong: StreamBufferSetup FAILED"
+msgid "I_LoadSong: StreamBufferSetup FAILED"
 msgstr ""
 
 #: win32/win_sys.c:894
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 9d65a583207055b35349dc64854888d845d9013b..3c136a43695b66fdafb3ae2cb29f8ef3ff18dbe5 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -14,12 +14,18 @@
 #ifdef HAVE_BLUA
 #include "p_local.h"
 #include "p_setup.h" // So we can have P_SetupLevelSky
+#ifdef ESLOPE
+#include "p_slopes.h" // P_GetZAt
+#endif
 #include "z_zone.h"
 #include "r_main.h"
 #include "r_things.h"
 #include "m_random.h"
 #include "s_sound.h"
 #include "g_game.h"
+#include "hu_stuff.h"	// HU_AddChatText
+#include "console.h"
+#include "d_netcmd.h" // IsPlayerAdmin
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -27,8 +33,6 @@
 
 #define NOHUD if (hud_running)\
 return luaL_error(L, "HUD rendering code should not call this function!");
-#define INLEVEL if (gamestate != GS_LEVEL)\
-return luaL_error(L, "This function can only be used in a level!");
 
 boolean luaL_checkboolean(lua_State *L, int narg) {
 	luaL_checktype(L, narg, LUA_TBOOLEAN);
@@ -87,6 +91,51 @@ static int lib_print(lua_State *L)
 	return 0;
 }
 
+// Print stuff in the chat, or in the console if we can't.
+static int lib_chatprint(lua_State *L)
+{
+	const char *str = luaL_checkstring(L, 1);	// retrieve string
+	boolean sound = lua_optboolean(L, 2);	// retrieve sound boolean
+	int len = strlen(str);
+
+	if (str == NULL)	// error if we don't have a string!
+		return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprint"));
+
+	if (len > 255)	// string is too long!!!
+		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
+
+	HU_AddChatText(str, sound);
+	return 0;
+}
+
+// Same as above, but do it for only one player.
+static int lib_chatprintf(lua_State *L)
+{
+	int n = lua_gettop(L);  /* number of arguments */
+	const char *str = luaL_checkstring(L, 2);	// retrieve string
+	boolean sound = lua_optboolean(L, 3);	// sound?
+	int len = strlen(str);
+	player_t *plr;
+
+	if (n < 2)
+		return luaL_error(L, "chatprintf requires at least two arguments: player and text.");
+
+	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));	// retrieve player
+	if (!plr)
+		return LUA_ErrInvalid(L, "player_t");
+	if (plr != &players[consoleplayer])
+		return 0;
+
+	if (str == NULL)	// error if we don't have a string!
+		return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprintf"));
+
+	if (len > 255)	// string is too long!!!
+		return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer.");
+
+	HU_AddChatText(str, sound);
+	return 0;
+}
+
 static const struct {
 	const char *meta;
 	const char *utype;
@@ -169,6 +218,16 @@ static int lib_userdataType(lua_State *L)
 	return 1;
 }
 
+static int lib_isPlayerAdmin(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	//HUDSAFE
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, IsPlayerAdmin(player-players));
+	return 1;
+}
+
 // M_RANDOM
 //////////////
 
@@ -214,15 +273,7 @@ static int lib_pRandomRange(lua_State *L)
 	return 1;
 }
 
-// Deprecated, macros, etc.
-static int lib_pRandom(lua_State *L)
-{
-	NOHUD
-	LUA_Deprecated(L, "P_Random", "P_RandomByte");
-	lua_pushinteger(L, P_RandomByte());
-	return 1;
-}
-
+// Macros.
 static int lib_pSignedRandom(lua_State *L)
 {
 	NOHUD
@@ -486,7 +537,8 @@ static int lib_pSpawnLockOn(lua_State *L)
 	if (P_IsLocalPlayer(player)) // Only display it on your own view.
 	{
 		mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-		visual->target = lockon;
+		P_SetTarget(&visual->target, lockon);
+		visual->flags2 |= MF2_DONTDRAW;
 		P_SetMobjStateNF(visual, state);
 	}
 	return 0;
@@ -776,6 +828,19 @@ static int lib_pCanRunOnWater(lua_State *L)
 	return 1;
 }
 
+static int lib_pMaceRotate(lua_State *L)
+{
+	mobj_t *center = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	INT32 baserot = luaL_checkinteger(L, 2);
+	INT32 baseprevrot = luaL_checkinteger(L, 3);
+	NOHUD
+	INLEVEL
+	if (!center)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_MaceRotate(center, baserot, baseprevrot);
+	return 0;
+}
+
 // P_USER
 ////////////
 
@@ -885,6 +950,21 @@ static int lib_pResetPlayer(lua_State *L)
 	return 0;
 }
 
+static int lib_pPlayerCanDamage(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	mobj_t *thing = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	NOHUD // was hud safe but then i added a lua hook
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	if (!thing)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_PlayerCanDamage(player, thing));
+	return 1;
+}
+
+
 static int lib_pIsObjectInGoop(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -949,7 +1029,8 @@ static int lib_pRestoreMusic(lua_State *L)
 	INLEVEL
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	P_RestoreMusic(player);
+	if (P_IsLocalPlayer(player))
+		P_RestoreMusic(player);
 	return 0;
 }
 
@@ -1152,8 +1233,8 @@ static int lib_pHomingAttack(lua_State *L)
 	INLEVEL
 	if (!source || !enemy)
 		return LUA_ErrInvalid(L, "mobj_t");
-	P_HomingAttack(source, enemy);
-	return 0;
+	lua_pushboolean(L, P_HomingAttack(source, enemy));
+	return 1;
 }
 
 static int lib_pSuperReady(lua_State *L)
@@ -1351,11 +1432,12 @@ static int lib_pRadiusAttack(lua_State *L)
 	mobj_t *spot = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
 	mobj_t *source = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
 	fixed_t damagedist = luaL_checkfixed(L, 3);
+	UINT8 damagetype = luaL_optinteger(L, 4, 0);
 	NOHUD
 	INLEVEL
 	if (!spot || !source)
 		return LUA_ErrInvalid(L, "mobj_t");
-	P_RadiusAttack(spot, source, damagedist);
+	P_RadiusAttack(spot, source, damagedist, damagetype);
 	return 0;
 }
 
@@ -1803,9 +1885,11 @@ static int lib_pFadeLight(lua_State *L)
 	INT16 tag = (INT16)luaL_checkinteger(L, 1);
 	INT32 destvalue = (INT32)luaL_checkinteger(L, 2);
 	INT32 speed = (INT32)luaL_checkinteger(L, 3);
+	boolean ticbased = lua_optboolean(L, 4);
+	boolean force = lua_optboolean(L, 5);
 	NOHUD
 	INLEVEL
-	P_FadeLight(tag, destvalue, speed);
+	P_FadeLight(tag, destvalue, speed, ticbased, force);
 	return 0;
 }
 
@@ -1961,12 +2045,22 @@ static int lib_pStartQuake(lua_State *L)
 
 static int lib_evCrumbleChain(lua_State *L)
 {
-	sector_t *sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
+	sector_t *sec = NULL;
+	ffloor_t *rover = NULL;
 	NOHUD
 	INLEVEL
-	if (!sec)
-		return LUA_ErrInvalid(L, "sector_t");
+	if (!lua_isnone(L, 2))
+	{
+		if (!lua_isnil(L, 1))
+		{
+			sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
+			if (!sec)
+				return LUA_ErrInvalid(L, "sector_t");
+		}
+		rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
+	}
+	else
+		rover = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
 	if (!rover)
 		return LUA_ErrInvalid(L, "ffloor_t");
 	EV_CrumbleChain(sec, rover);
@@ -2000,6 +2094,24 @@ static int lib_evStartCrumble(lua_State *L)
 	return 0;
 }
 
+#ifdef ESLOPE
+// P_SLOPES
+////////////
+
+static int lib_pGetZAt(lua_State *L)
+{
+	pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
+	fixed_t x = luaL_checkfixed(L, 2);
+	fixed_t y = luaL_checkfixed(L, 3);
+	//HUDSAFE
+	if (!slope)
+		return LUA_ErrInvalid(L, "pslope_t");
+
+	lua_pushfixed(L, P_GetZAt(slope, x, y));
+	return 1;
+}
+#endif
+
 // R_DEFS
 ////////////
 
@@ -2103,6 +2215,25 @@ static int lib_rSetPlayerSkin(lua_State *L)
 	return 0;
 }
 
+// R_DATA
+////////////
+
+static int lib_rCheckTextureNumForName(lua_State *L)
+{
+	const char *name = luaL_checkstring(L, 1);
+	//HUDSAFE
+	lua_pushinteger(L, R_CheckTextureNumForName(name));
+	return 1;
+}
+
+static int lib_rTextureNumForName(lua_State *L)
+{
+	const char *name = luaL_checkstring(L, 1);
+	//HUDSAFE
+	lua_pushinteger(L, R_TextureNumForName(name));
+	return 1;
+}
+
 // S_SOUND
 ////////////
 
@@ -2111,7 +2242,7 @@ static int lib_sStartSound(lua_State *L)
 	const void *origin = NULL;
 	sfxenum_t sound_id = luaL_checkinteger(L, 2);
 	player_t *player = NULL;
-	NOHUD
+	//NOHUD // kys @whoever did this.
 	if (sound_id >= NUMSFX)
 		return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1);
 	if (!lua_isnil(L, 1))
@@ -2127,7 +2258,12 @@ static int lib_sStartSound(lua_State *L)
 			return LUA_ErrInvalid(L, "player_t");
 	}
 	if (!player || P_IsLocalPlayer(player))
+	{
+		if (hud_running)
+			origin = NULL;	// HUD rendering startsound shouldn't have an origin, just remove it instead of having a retarded error.
+
 		S_StartSound(origin, sound_id);
+	}
 	return 0;
 }
 
@@ -2172,7 +2308,7 @@ static int lib_sChangeMusic(lua_State *L)
 {
 #ifdef MUSICSLOT_COMPATIBILITY
 	const char *music_name;
-	UINT32 music_num;
+	UINT32 music_num, position, prefadems, fadeinms;
 	char music_compat_name[7];
 
 	boolean looping;
@@ -2200,7 +2336,6 @@ static int lib_sChangeMusic(lua_State *L)
 		music_name = luaL_checkstring(L, 1);
 	}
 
-
 	looping = (boolean)lua_opttrueboolean(L, 2);
 
 #else
@@ -2225,8 +2360,12 @@ static int lib_sChangeMusic(lua_State *L)
 #endif
 	music_flags = (UINT16)luaL_optinteger(L, 4, 0);
 
+	position = (UINT32)luaL_optinteger(L, 5, 0);
+	prefadems = (UINT32)luaL_optinteger(L, 6, 0);
+	fadeinms = (UINT32)luaL_optinteger(L, 7, 0);
+
 	if (!player || P_IsLocalPlayer(player))
-		S_ChangeMusic(music_name, music_flags, looping);
+		S_ChangeMusicEx(music_name, music_flags, looping, position, prefadems, fadeinms);
 	return 0;
 }
 
@@ -2243,10 +2382,8 @@ static int lib_sSpeedMusic(lua_State *L)
 			return LUA_ErrInvalid(L, "player_t");
 	}
 	if (!player || P_IsLocalPlayer(player))
-		lua_pushboolean(L, S_SpeedMusic(speed));
-	else
-		lua_pushboolean(L, false);
-	return 1;
+		S_SpeedMusic(speed);
+	return 0;
 }
 
 static int lib_sStopMusic(lua_State *L)
@@ -2264,6 +2401,110 @@ static int lib_sStopMusic(lua_State *L)
 	return 0;
 }
 
+static int lib_sSetInternalMusicVolume(lua_State *L)
+{
+	UINT32 volume = (UINT32)luaL_checkinteger(L, 1);
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
+	{
+		player = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_SetInternalMusicVolume(volume);
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sStopFadingMusic(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_StopFadingMusic();
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sFadeMusic(lua_State *L)
+{
+	UINT32 target_volume = (UINT32)luaL_checkinteger(L, 1);
+	UINT32 ms;
+	INT32 source_volume;
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 3) && lua_isuserdata(L, 3))
+	{
+		player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+		ms = (UINT32)luaL_checkinteger(L, 2);
+		source_volume = -1;
+	}
+	else if (!lua_isnone(L, 4) && lua_isuserdata(L, 4))
+	{
+		player = *((player_t **)luaL_checkudata(L, 4, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+		source_volume = (INT32)luaL_checkinteger(L, 2);
+		ms = (UINT32)luaL_checkinteger(L, 3);
+	}
+	else if (luaL_optinteger(L, 3, INT32_MAX) == INT32_MAX)
+	{
+		ms = (UINT32)luaL_checkinteger(L, 2);
+		source_volume = -1;
+	}
+	else
+	{
+		source_volume = (INT32)luaL_checkinteger(L, 2);
+		ms = (UINT32)luaL_checkinteger(L, 3);
+	}
+
+	NOHUD
+
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_FadeMusicFromVolume(target_volume, source_volume, ms));
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sFadeOutStopMusic(lua_State *L)
+{
+	UINT32 ms = (UINT32)luaL_checkinteger(L, 1);
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
+	{
+		player = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		lua_pushboolean(L, S_FadeOutStopMusic(ms));
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
 static int lib_sOriginPlaying(lua_State *L)
 {
 	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -2347,7 +2588,9 @@ static int lib_gDoReborn(lua_State *L)
 	return 0;
 }
 
-static int lib_gExitLevel(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.
+static int lib_gSetCustomExitVars(lua_State *L)
 {
 	int n = lua_gettop(L); // Num arguments
 	NOHUD
@@ -2355,21 +2598,36 @@ static int lib_gExitLevel(lua_State *L)
 
 	// LUA EXTENSION: Custom exit like support
 	// Supported:
-	//	G_ExitLevel();			[no modifications]
-	//	G_ExitLevel(int)		[nextmap override only]
-	//	G_ExitLevel(bool)		[skipstats only]
-	//	G_ExitLevel(int, bool)	[both of the above]
+	//	G_SetCustomExitVars();			[reset to defaults]
+	//	G_SetCustomExitVars(int)		[nextmap override only]
+	//	G_SetCustomExitVars(bool)		[skipstats only]
+	//	G_SetCustomExitVars(int, bool)	[both of the above]
 	if (n >= 1)
 	{
 		if (lua_isnumber(L, 1) || n >= 2)
 		{
 			nextmapoverride = (INT16)luaL_checknumber(L, 1);
-			lua_pop(L, 1); // pop nextmapoverride; skipstats now 1 if available
+			lua_remove(L, 1); // remove nextmapoverride; skipstats now 1 if available
 		}
-		skipstats = lua_optboolean(L, 1);
+		skipstats = luaL_optinteger(L, 2, 0);
+	}
+	else
+	{
+		nextmapoverride = 0;
+		skipstats = 0;
 	}
 	// ---
 
+	return 0;
+}
+
+static int lib_gExitLevel(lua_State *L)
+{
+	int n = lua_gettop(L); // Num arguments
+	NOHUD
+	// Moved this bit to G_SetCustomExitVars
+	if (n >= 1) // Don't run the reset to defaults option
+		lib_gSetCustomExitVars(L);
 	G_ExitLevel();
 	return 0;
 }
@@ -2474,14 +2732,16 @@ static int lib_gTicsToMilliseconds(lua_State *L)
 
 static luaL_Reg lib[] = {
 	{"print", lib_print},
+	{"chatprint", lib_chatprint},
+	{"chatprintf", lib_chatprintf},
 	{"userdataType", lib_userdataType},
+	{"IsPlayerAdmin", lib_isPlayerAdmin},
 
 	// m_random
 	{"P_RandomFixed",lib_pRandomFixed},
 	{"P_RandomByte",lib_pRandomByte},
 	{"P_RandomKey",lib_pRandomKey},
 	{"P_RandomRange",lib_pRandomRange},
-	{"P_Random",lib_pRandom}, // DEPRECATED
 	{"P_SignedRandom",lib_pSignedRandom}, // MACRO
 	{"P_RandomChance",lib_pRandomChance}, // MACRO
 
@@ -2526,6 +2786,7 @@ static luaL_Reg lib[] = {
 	{"P_CheckDeathPitCollide",lib_pCheckDeathPitCollide},
 	{"P_CheckSolidLava",lib_pCheckSolidLava},
 	{"P_CanRunOnWater",lib_pCanRunOnWater},
+	{"P_MaceRotate",lib_pMaceRotate},
 
 	// p_user
 	{"P_GetPlayerHeight",lib_pGetPlayerHeight},
@@ -2537,6 +2798,7 @@ static luaL_Reg lib[] = {
 	{"P_PlayerInPain",lib_pPlayerInPain},
 	{"P_DoPlayerPain",lib_pDoPlayerPain},
 	{"P_ResetPlayer",lib_pResetPlayer},
+	{"P_PlayerCanDamage",lib_pPlayerCanDamage},
 	{"P_IsObjectInGoop",lib_pIsObjectInGoop},
 	{"P_IsObjectOnGround",lib_pIsObjectOnGround},
 	{"P_InSpaceSector",lib_pInSpaceSector},
@@ -2623,6 +2885,11 @@ static luaL_Reg lib[] = {
 	{"EV_CrumbleChain",lib_evCrumbleChain},
 	{"EV_StartCrumble",lib_evStartCrumble},
 
+#ifdef ESLOPE
+	// p_slopes
+	{"P_GetZAt",lib_pGetZAt},
+#endif
+
 	// r_defs
 	{"R_PointToAngle",lib_rPointToAngle},
 	{"R_PointToAngle2",lib_rPointToAngle2},
@@ -2635,6 +2902,10 @@ static luaL_Reg lib[] = {
 	{"R_Frame2Char",lib_rFrame2Char},
 	{"R_SetPlayerSkin",lib_rSetPlayerSkin},
 
+	// r_data
+	{"R_CheckTextureNumForName",lib_rCheckTextureNumForName},
+	{"R_TextureNumForName",lib_rTextureNumForName},
+
 	// s_sound
 	{"S_StartSound",lib_sStartSound},
 	{"S_StartSoundAtVolume",lib_sStartSoundAtVolume},
@@ -2642,6 +2913,10 @@ static luaL_Reg lib[] = {
 	{"S_ChangeMusic",lib_sChangeMusic},
 	{"S_SpeedMusic",lib_sSpeedMusic},
 	{"S_StopMusic",lib_sStopMusic},
+	{"S_SetInternalMusicVolume", lib_sSetInternalMusicVolume},
+	{"S_StopFadingMusic",lib_sStopFadingMusic},
+	{"S_FadeMusic",lib_sFadeMusic},
+	{"S_FadeOutStopMusic",lib_sFadeOutStopMusic},
 	{"S_OriginPlaying",lib_sOriginPlaying},
 	{"S_IdPlaying",lib_sIdPlaying},
 	{"S_SoundPlaying",lib_sSoundPlaying},
@@ -2650,6 +2925,7 @@ static luaL_Reg lib[] = {
 	// g_game
 	{"G_BuildMapName",lib_gBuildMapName},
 	{"G_DoReborn",lib_gDoReborn},
+	{"G_SetCustomExitVars",lib_gSetCustomExitVars},
 	{"G_ExitLevel",lib_gExitLevel},
 	{"G_IsSpecialStage",lib_gIsSpecialStage},
 	{"G_GametypeUsesLives",lib_gGametypeUsesLives},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index ebfd6a2dfd31dabde12a50688ab39842e7e26749..2383bb32e1f77812b38f8602f7bccb7765a8cf4f 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -54,10 +54,12 @@ static UINT8 lib_searchBlockmap_Objects(lua_State *L, INT32 x, INT32 y, mobj_t *
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
 			blockfuncerror = true;
+			P_SetTarget(&bnext, NULL);
 			return 0; // *shrugs*
 		}
 		if (!lua_isnil(gL, -1))
 		{ // if nil, continue
+			P_SetTarget(&bnext, NULL);
 			if (lua_toboolean(gL, -1))
 				return 2; // stop whole search
 			else
@@ -186,9 +188,6 @@ static int lib_searchBlockmap(lua_State *L)
 	UINT8 funcret = 0;
 	blockmap_func searchFunc;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
-
 	lua_remove(L, 1); // remove searchtype, stack is now function, mobj, [x1, x2, y1, y2]
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index c82c39b4606c4425e2b425d7deece121c568ed7c..c6856b426224b17f29f7fd26b122865ad0d51b51 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,9 +28,6 @@ return luaL_error(L, "HUD rendering code should not call this function!");
 // for functions not allowed in hooks or coroutines (supercedes above)
 #define NOHOOK if (!lua_lumploading)\
 		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
-// for functions only allowed within a level
-#define INLEVEL if (gamestate != GS_LEVEL)\
-return luaL_error(L, "This function can only be used in a level!");
 
 static const char *cvname = NULL;
 
@@ -63,7 +60,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum)
 	lua_pop(gL, 1); // pop flags
 
 	// requires server/admin and the player is not one of them
-	if ((flags & 1) && playernum != serverplayer && playernum != adminplayer)
+	if ((flags & 1) && playernum != serverplayer && !IsPlayerAdmin(playernum))
 		goto deny;
 
 	lua_rawgeti(gL, -1, 1); // push function from command info table
@@ -85,7 +82,9 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum)
 
 deny:
 	//must be hacked/buggy client
-	lua_settop(gL, 0); // clear stack
+	if (gL) // check if Lua is actually turned on first, you dummmy -- Monster Iestyn 04/07/18
+		lua_settop(gL, 0); // clear stack
+
 	CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
 	if (server)
 	{
@@ -139,7 +138,7 @@ void COM_Lua_f(void)
 		UINT8 argc;
 		lua_pop(gL, 1); // pop command info table
 
-		if (flags & 1 && !server && adminplayer != playernum) // flag 1: only server/admin can use this command.
+		if (flags & 1 && !server && !IsPlayerAdmin(playernum)) // flag 1: only server/admin can use this command.
 		{
 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 			return;
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 822edf79fe127224e50ee10a4f7330ce68ae63e4..45e116c344243ec23a78f05eb1d0581160747d52 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -48,12 +48,14 @@ enum hook {
 	hook_MobjMoveBlocked,
 	hook_MapThingSpawn,
 	hook_FollowMobj,
+	hook_PlayerCanDamage,
+	hook_PlayerQuit,
 
 	hook_MAX // last hook
 };
 extern const char *const hookNames[];
 
-void LUAh_MapChange(void); // Hook for map change (before load)
+void LUAh_MapChange(INT16 mapnumber); // Hook for map change (before load)
 void LUAh_MapLoad(void); // Hook for map load
 void LUAh_PlayerJoin(int playernum); // Hook for Got_AddPlayer
 void LUAh_ThinkFrame(void); // Hook for frame (after mobj and player thinkers)
@@ -86,6 +88,8 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 #define LUAh_ShieldSpecial(player) LUAh_PlayerHook(player, hook_ShieldSpecial) // Hook for shield abilities
 #define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked)
 boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
-boolean LUAh_FollowMobj(player_t *player, mobj_t *mo); // Hook for P_PlayerAfterThink Smiles mobj-following
+boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
+UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
+void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 53886f7bada25666c4d2efc6c53e8b153e9cd721..7f7e8adc672d19dcb6276bcff0075bc51f56bfb8 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -59,6 +59,8 @@ const char *const hookNames[hook_MAX+1] = {
 	"MobjMoveBlocked",
 	"MapThingSpawn",
 	"FollowMobj",
+	"PlayerCanDamage",
+	"PlayerQuit",
 	NULL
 };
 
@@ -199,6 +201,9 @@ static int lib_addHook(lua_State *L)
 	case hook_JumpSpinSpecial:
 	case hook_PlayerSpawn:
 	case hook_FollowMobj:
+	case hook_PlayerCanDamage:
+	case hook_ShieldSpawn:
+	case hook_ShieldSpecial:
 		lastp = &playerhooks;
 		break;
 	case hook_LinedefExecute:
@@ -247,44 +252,48 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 
 	// Look for all generic mobj hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -300,47 +309,51 @@ boolean LUAh_PlayerHook(player_t *plr, enum hook which)
 	lua_settop(gL, 0);
 
 	for (hookp = playerhooks; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, plr, META_PLAYER);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, plr, META_PLAYER);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
 }
 
 // Hook for map change (before load)
-void LUAh_MapChange(void)
+void LUAh_MapChange(INT16 mapnumber)
 {
 	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8))))
 		return;
 
 	lua_settop(gL, 0);
-	lua_pushinteger(gL, gamemap);
+	lua_pushinteger(gL, mapnumber);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapChange)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_MapChange)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -356,13 +369,15 @@ void LUAh_MapLoad(void)
 	lua_pushinteger(gL, gamemap);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapLoad)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_MapLoad)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -378,13 +393,15 @@ void LUAh_PlayerJoin(int playernum)
 	lua_pushinteger(gL, playernum);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_PlayerJoin)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_PlayerJoin)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -397,17 +414,19 @@ void LUAh_ThinkFrame(void)
 		return;
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ThinkFrame)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			if (lua_pcall(gL, 0, 0, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-			}
+	{
+		if (hookp->type != hook_ThinkFrame)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		if (lua_pcall(gL, 0, 0, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
 		}
+	}
 }
 
 // Hook for mobj collisions
@@ -424,62 +443,66 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 
 	// Look for all generic mobj collision hooks
 	for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, thing1, META_MOBJ);
-				LUA_PushUserdata(gL, thing2, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{ // if nil, leave shouldCollide = 0.
-				if (lua_toboolean(gL, -1))
-					shouldCollide = 1; // Force yes
-				else
-					shouldCollide = 2; // Force no
-			}
+			LUA_PushUserdata(gL, thing1, META_MOBJ);
+			LUA_PushUserdata(gL, thing2, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjcollidehooks[thing1->type]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, thing1, META_MOBJ);
-				LUA_PushUserdata(gL, thing2, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{ // if nil, leave shouldCollide = 0.
-				if (lua_toboolean(gL, -1))
-					shouldCollide = 1; // Force yes
-				else
-					shouldCollide = 2; // Force no
-			}
+			LUA_PushUserdata(gL, thing1, META_MOBJ);
+			LUA_PushUserdata(gL, thing2, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return shouldCollide;
@@ -554,52 +577,56 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 
 	// Look for all generic touch special hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_TouchSpecial)
+	{
+		if (hookp->type != hook_TouchSpecial)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, special, META_MOBJ);
-				LUA_PushUserdata(gL, toucher, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, special, META_MOBJ);
+			LUA_PushUserdata(gL, toucher, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[special->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_TouchSpecial)
+	{
+		if (hookp->type != hook_TouchSpecial)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, special, META_MOBJ);
-				LUA_PushUserdata(gL, toucher, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, special, META_MOBJ);
+			LUA_PushUserdata(gL, toucher, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -619,72 +646,75 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 
 	// Look for all generic should damage hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ShouldDamage)
+	{
+		if (hookp->type != hook_ShouldDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{
-				if (lua_toboolean(gL, -1))
-					shouldDamage = 1; // Force yes
-				else
-					shouldDamage = 2; // Force no
-			}
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{
+			if (lua_toboolean(gL, -1))
+				shouldDamage = 1; // Force yes
+			else
+				shouldDamage = 2; // Force no
 		}
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ShouldDamage)
+	{
+		if (hookp->type != hook_ShouldDamage)
+			continue;
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			if (lua_pcall(gL, 5, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{
-				if (lua_toboolean(gL, -1))
-					shouldDamage = 1; // Force yes
-				else
-					shouldDamage = 2; // Force no
-			}
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (!lua_isnil(gL, -1))
+		{
+			if (lua_toboolean(gL, -1))
+				shouldDamage = 1; // Force yes
+			else
+				shouldDamage = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return shouldDamage;
@@ -704,62 +734,66 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 
 	// Look for all generic mobj damage hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDamage)
+	{
+		if (hookp->type != hook_MobjDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDamage)
+	{
+		if (hookp->type != hook_MobjDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			if (lua_pcall(gL, 5, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -779,58 +813,62 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 
 	// Look for all generic mobj death hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDeath)
+	{
+		if (hookp->type != hook_MobjDeath)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			if (lua_pcall(gL, 3, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		if (lua_pcall(gL, 3, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDeath)
+	{
+		if (hookp->type != hook_MobjDeath)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -847,28 +885,30 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_BotTiccmd)
+	{
+		if (hookp->type != hook_BotTiccmd)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, bot, META_PLAYER);
-				LUA_PushUserdata(gL, cmd, META_TICCMD);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, bot, META_PLAYER);
+			LUA_PushUserdata(gL, cmd, META_TICCMD);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -885,51 +925,53 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_BotAI
-		&& (hookp->s.skinname == NULL || !strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+	{
+		if (hookp->type != hook_BotAI
+		|| (hookp->s.skinname && strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, sonic, META_MOBJ);
-				LUA_PushUserdata(gL, tails, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 8, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
+			LUA_PushUserdata(gL, sonic, META_MOBJ);
+			LUA_PushUserdata(gL, tails, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 8, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
 
-			// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
-			if (lua_istable(gL, 2+1)) {
-				boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
+		// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
+		if (lua_istable(gL, 2+1)) {
+			boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
 #define CHECKFIELD(field) \
-				lua_getfield(gL, 2+1, #field);\
-				if (lua_toboolean(gL, -1))\
-					field = true;\
-				lua_pop(gL, 1);
-
-				CHECKFIELD(forward)
-				CHECKFIELD(backward)
-				CHECKFIELD(left)
-				CHECKFIELD(right)
-				CHECKFIELD(strafeleft)
-				CHECKFIELD(straferight)
-				CHECKFIELD(jump)
-				CHECKFIELD(spin)
+			lua_getfield(gL, 2+1, #field);\
+			if (lua_toboolean(gL, -1))\
+				field = true;\
+			lua_pop(gL, 1);
+
+			CHECKFIELD(forward)
+			CHECKFIELD(backward)
+			CHECKFIELD(left)
+			CHECKFIELD(right)
+			CHECKFIELD(strafeleft)
+			CHECKFIELD(straferight)
+			CHECKFIELD(jump)
+			CHECKFIELD(spin)
 #undef CHECKFIELD
-				B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
-			} else
-				B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8));
+			B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
+		} else
+			B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8));
 
-			lua_pop(gL, 8);
-			hooked = true;
-		}
+		lua_pop(gL, 8);
+		hooked = true;
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -946,28 +988,30 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 	lua_settop(gL, 0);
 
 	for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
-		if (!strcmp(hookp->s.funcname, line->text))
+	{
+		if (strcmp(hookp->s.funcname, line->text))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, line, META_LINE);
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, sector, META_SECTOR);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			LUA_Call(gL, 3);
-			hooked = true;
+			LUA_PushUserdata(gL, line, META_LINE);
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, sector, META_SECTOR);
 		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		LUA_Call(gL, 3);
+		hooked = true;
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
 }
 
-// Hook for player chat
+
 boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 {
 	hook_p hookp;
@@ -978,48 +1022,51 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_PlayerMsg)
+	{
+		if (hookp->type != hook_PlayerMsg)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player
-				if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c
-					lua_pushinteger(gL, 3); // type
-					lua_pushnil(gL); // target
-				} else if (target == -1) { // sayteam
-					lua_pushinteger(gL, 1); // type
-					lua_pushnil(gL); // target
-				} else if (target == 0) { // say
-					lua_pushinteger(gL, 0); // type
-					lua_pushnil(gL); // target
-				} else { // sayto
-					lua_pushinteger(gL, 2); // type
-					LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
-				}
-				lua_pushstring(gL, msg); // msg
+			LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player
+			if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c
+				lua_pushinteger(gL, 3); // type
+				lua_pushnil(gL); // target
+			} else if (target == -1) { // sayteam
+				lua_pushinteger(gL, 1); // type
+				lua_pushnil(gL); // target
+			} else if (target == 0) { // say
+				lua_pushinteger(gL, 0); // type
+				lua_pushnil(gL); // target
+			} else { // sayto
+				lua_pushinteger(gL, 2); // type
+				LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
 			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			lua_pushstring(gL, msg); // msg
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
 }
 
+
 // Hook for hurt messages
 boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
 {
@@ -1031,33 +1078,35 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_HurtMsg
-		&& (hookp->s.mt == MT_NULL || (inflictor && hookp->s.mt == inflictor->type)))
+	{
+		if (hookp->type != hook_HurtMsg
+		|| (hookp->s.mt && !(inflictor && hookp->s.mt == inflictor->type)))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, player, META_PLAYER);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -1080,13 +1129,15 @@ void LUAh_NetArchiveHook(lua_CFunction archFunc)
 	// stack: tables, archFunc
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_NetVars)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2); // archFunc
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_NetVars)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2); // archFunc
+		LUA_Call(gL, 1);
+	}
 
 	lua_pop(gL, 1); // pop archFunc
 	// stack: tables
@@ -1103,52 +1154,56 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
 
 	// Look for all generic mobj map thing spawn hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapThingSpawn)
+	{
+		if (hookp->type != hook_MapThingSpawn)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, mthing, META_MAPTHING);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, mthing, META_MAPTHING);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapThingSpawn)
+	{
+		if (hookp->type != hook_MapThingSpawn)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, mthing, META_MAPTHING);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, mthing, META_MAPTHING);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -1165,31 +1220,106 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
 	lua_settop(gL, 0);
 
 	for (hookp = playerhooks; hookp; hookp = hookp->next)
-		if (hookp->type == hook_FollowMobj)
+	{
+		if (hookp->type != hook_FollowMobj)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, player, META_PLAYER);
-				LUA_PushUserdata(gL, mobj, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, mobj, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
 }
 
+// Hook for P_PlayerCanDamage
+UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
+{
+	hook_p hookp;
+	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
+	if (!gL || !(hooksAvailable[hook_PlayerCanDamage/8] & (1<<(hook_PlayerCanDamage%8))))
+		return 0;
+
+	lua_settop(gL, 0);
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerCanDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, mobj, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return shouldCollide;
+}
+
+void LUAh_PlayerQuit(player_t *plr, int reason)
+{
+	hook_p hookp;
+	if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8))))
+		return;
+
+	lua_settop(gL, 0);
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerQuit)
+			continue;
+
+	    if (lua_gettop(gL) == 0)
+	    {
+	        LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit
+	        lua_pushinteger(gL, reason); // Reason for quitting
+	    }
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		LUA_Call(gL, 2);
+	}
+
+	lua_settop(gL, 0);
+}
+
 #endif
diff --git a/src/lua_hud.h b/src/lua_hud.h
index beaca7883e4425818539cbd9d0866e4e6cf3b8cc..7f928f7c4a1c82de246a417173166bfd4e81cb58 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2016 by Sonic Team Junior.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -24,7 +24,7 @@ enum hud {
 	// NiGHTS mode
 	hud_nightslink,
 	hud_nightsdrill,
-	hud_nightsrings,
+	hud_nightsspheres,
 	hud_nightsscore,
 	hud_nightstime,
 	hud_nightsrecords,
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 68a69cd1d2934e3af83f24c804aad7368b6f7359..8c1134bca7c942ed4fe18398570e0713ec9fe686 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2016 by Sonic Team Junior.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -20,6 +20,7 @@
 #include "i_video.h" // rendermode
 #include "p_local.h" // camera_t
 #include "screen.h" // screen width/height
+#include "m_random.h" // m_random
 #include "v_video.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -63,12 +64,14 @@ static const char *const hud_disable_options[] = {
 
 enum hudinfo {
 	hudinfo_x = 0,
-	hudinfo_y
+	hudinfo_y,
+	hudinfo_f
 };
 
 static const char *const hudinfo_opt[] = {
 	"x",
 	"y",
+	"f",
 	NULL};
 
 enum patch {
@@ -198,6 +201,9 @@ static int hudinfo_get(lua_State *L)
 	case hudinfo_y:
 		lua_pushinteger(L, info->y);
 		break;
+	case hudinfo_f:
+		lua_pushinteger(L, info->f);
+		break;
 	}
 	return 1;
 }
@@ -216,6 +222,9 @@ static int hudinfo_set(lua_State *L)
 	case hudinfo_y:
 		info->y = (INT32)luaL_checkinteger(L, 3);
 		break;
+	case hudinfo_f:
+		info->f = (INT32)luaL_checkinteger(L, 3);
+		break;
 	}
 	return 0;
 }
@@ -407,7 +416,7 @@ static int libd_getSpritePatch(lua_State *L)
 static int libd_getSprite2Patch(lua_State *L)
 {
 	INT32 i; // skin number
-	UINT32 j; // sprite2 prefix
+	playersprite_t j; // sprite2 prefix
 	UINT32 frame = 0; // 'A'
 	UINT8 angle = 0;
 	spritedef_t *sprdef;
@@ -661,8 +670,8 @@ static int libd_getColormap(lua_State *L)
 	else if (lua_type(L, 1) == LUA_TNUMBER) // skin number
 	{
 		skinnum = (INT32)luaL_checkinteger(L, 1);
-		if (skinnum < TC_ALLWHITE || skinnum >= MAXSKINS)
-			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_ALLWHITE, MAXSKINS-1);
+		if (skinnum < TC_BLINK || skinnum >= MAXSKINS)
+			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_BLINK, MAXSKINS-1);
 	}
 	else // skin name
 	{
@@ -679,6 +688,30 @@ static int libd_getColormap(lua_State *L)
 	return 1;
 }
 
+static int libd_fadeScreen(lua_State *L)
+{
+	UINT16 color = luaL_checkinteger(L, 1);
+	UINT8 strength = luaL_checkinteger(L, 2);
+	const UINT8 maxstrength = ((color & 0xFF00) ? 32 : 10);
+
+	HUDONLY
+
+	if (!strength)
+		return 0;
+
+	if (strength > maxstrength)
+		return luaL_error(L, "%s fade strength %d out of range (0 - %d)", ((color & 0xFF00) ? "COLORMAP" : "TRANSMAP"), strength, maxstrength);
+
+	if (strength == maxstrength) // Allow as a shortcut for drawfill...
+	{
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, ((color & 0xFF00) ? 31 : color));
+		return 0;
+	}
+
+	V_DrawFadeScreen(color, strength);
+	return 0;
+}
+
 static int libd_width(lua_State *L)
 {
 	HUDONLY
@@ -720,24 +753,107 @@ static int libd_renderer(lua_State *L)
 	return 1;
 }
 
+// M_RANDOM
+//////////////
+
+static int libd_RandomFixed(lua_State *L)
+{
+	HUDONLY
+	lua_pushfixed(L, M_RandomFixed());
+	return 1;
+}
+
+static int libd_RandomByte(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, M_RandomByte());
+	return 1;
+}
+
+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;
+}
+
+static int libd_RandomRange(lua_State *L)
+{
+	INT32 a = (INT32)luaL_checkinteger(L, 1);
+	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;
+}
+
+// Macros.
+static int libd_SignedRandom(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, M_SignedRandom());
+	return 1;
+}
+
+static int libd_RandomChance(lua_State *L)
+{
+	fixed_t p = luaL_checkfixed(L, 1);
+	HUDONLY
+	lua_pushboolean(L, M_RandomChance(p));
+	return 1;
+}
+
+// 30/10/18 Lat': Get cv_translucenthud's value for HUD rendering as a normal V_xxTRANS int
+// Could as well be thrown in global vars for ease of access but I guess it makes sense for it to be a HUD fn
+static int libd_getlocaltransflag(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, (10-cv_translucenthud.value)*V_10TRANS);	// A bit weird that it's called "translucenthud" yet 10 is fully opaque :V
+	return 1;
+}
+
 static luaL_Reg lib_draw[] = {
+	// cache
 	{"patchExists", libd_patchExists},
 	{"cachePatch", libd_cachePatch},
 	{"getSpritePatch", libd_getSpritePatch},
 	{"getSprite2Patch", libd_getSprite2Patch},
+	{"getColormap", libd_getColormap},
+	// drawing
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
 	{"drawNum", libd_drawNum},
 	{"drawPaddedNum", libd_drawPaddedNum},
 	{"drawFill", libd_drawFill},
 	{"drawString", libd_drawString},
+	{"fadeScreen", libd_fadeScreen},
+	// misc
 	{"stringWidth", libd_stringWidth},
-	{"getColormap", libd_getColormap},
+	// m_random
+	{"RandomFixed",libd_RandomFixed},
+	{"RandomByte",libd_RandomByte},
+	{"RandomKey",libd_RandomKey},
+	{"RandomRange",libd_RandomRange},
+	{"SignedRandom",libd_SignedRandom}, // MACRO
+	{"RandomChance",libd_RandomChance}, // MACRO
+	// properties
 	{"width", libd_width},
 	{"height", libd_height},
 	{"dupx", libd_dupx},
 	{"dupy", libd_dupy},
 	{"renderer", libd_renderer},
+	{"localTransFlag", libd_getlocaltransflag},
 	{NULL, NULL}
 };
 
@@ -761,6 +877,19 @@ static int lib_huddisable(lua_State *L)
 	return 0;
 }
 
+// 30/10/18: Lat': How come this wasn't here before?
+static int lib_hudenabled(lua_State *L)
+{
+	enum hud option = luaL_checkoption(L, 1, NULL, hud_disable_options);
+	if (hud_enabled[option/8] & (1<<(option%8)))
+		lua_pushboolean(L, true);
+	else
+		lua_pushboolean(L, false);
+
+	return 1;
+}
+
+
 // add a HUD element for rendering
 static int lib_hudadd(lua_State *L)
 {
@@ -788,6 +917,7 @@ static int lib_hudadd(lua_State *L)
 static luaL_Reg lib_hud[] = {
 	{"enable", lib_hudenable},
 	{"disable", lib_huddisable},
+	{"enabled", lib_hudenabled},
 	{"add", lib_hudadd},
 	{NULL, NULL}
 };
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 8f2ce67cda3f72791c4cee416b422629f051d824..77f37f8ecc90b26b3f43894ac2f29a3e93f4f9ec 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -102,7 +102,7 @@ static int lib_sprnamelen(lua_State *L)
 // push sprite name
 static int lib_getSpr2name(lua_State *L)
 {
-	UINT32 i;
+	playersprite_t i;
 
 	lua_remove(L, 1); // don't care about spr2names[] dummy userdata.
 
@@ -129,7 +129,7 @@ static int lib_getSpr2name(lua_State *L)
 
 static int lib_getSpr2default(lua_State *L)
 {
-	UINT32 i;
+	playersprite_t i;
 
 	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
 
@@ -154,9 +154,21 @@ static int lib_getSpr2default(lua_State *L)
 
 static int lib_setSpr2default(lua_State *L)
 {
-	UINT32 i;
+	playersprite_t i;
 	UINT8 j = 0;
 
+	if (hud_running)
+		return luaL_error(L, "Do not alter spr2defaults[] in HUD rendering code!");
+
+// todo: maybe allow setting below first freeslot..? step 1 is toggling this, step 2 is testing to see whether it's net-safe
+#ifdef SETALLSPR2DEFAULTS
+#define FIRSTMODIFY 0
+#else
+#define FIRSTMODIFY SPR2_FIRSTFREESLOT
+	if (free_spr2 == SPR2_FIRSTFREESLOT)
+		return luaL_error(L, "You can only modify the spr2defaults[] entries of sprite2 freeslots, and none are currently added.");
+#endif
+
 	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
 
 	if (lua_isnumber(L, 1))
@@ -175,8 +187,9 @@ static int lib_setSpr2default(lua_State *L)
 	else
 		return luaL_error(L, "spr2defaults[] invalid index");
 
-	if (i < SPR2_FIRSTFREESLOT || i >= free_spr2)
-		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+	if (i < FIRSTMODIFY || i >= free_spr2)
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, FIRSTMODIFY, free_spr2-1);
+#undef FIRSTMODIFY
 
 	if (lua_isnumber(L, 2))
 		j = lua_tonumber(L, 2);
@@ -189,11 +202,13 @@ static int lib_setSpr2default(lua_State *L)
 				break;
 		}
 		if (j == free_spr2)
-			return luaL_error(L, "spr2defaults[] invalid index");
+			return luaL_error(L, "spr2defaults[] invalid set");
 	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid set");
 
 	if (j >= free_spr2)
-		j = 0; // return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
+		return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
 
 	spr2defaults[i] = j;
 	return 0;
diff --git a/src/lua_libs.h b/src/lua_libs.h
index b63a3ceed8421496335ef6f16a4bd417057914fe..827c1d7987713ae0440fb31665226c839748fa71 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -42,6 +42,11 @@ extern lua_State *gL;
 #define META_SEG "SEG_T*"
 #define META_NODE "NODE_T*"
 #endif
+#ifdef ESLOPE
+#define META_SLOPE "PSLOPE_T*"
+#define META_VECTOR2 "VECTOR2_T"
+#define META_VECTOR3 "VECTOR3_T"
+#endif
 #define META_MAPHEADER "MAPHEADER_T*"
 
 #define META_CVAR "CONSVAR_T*"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index f1bfcb8f1b845e0b3161df7084415badaa01d5d4..74b2599219bc203f39352632eb2c2985a04eb4e7 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -16,6 +16,10 @@
 #include "p_local.h"
 #include "p_setup.h"
 #include "z_zone.h"
+#ifdef ESLOPE
+#include "p_slopes.h"
+#endif
+#include "r_main.h"
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -38,7 +42,13 @@ enum sector_e {
 	sector_heightsec,
 	sector_camsec,
 	sector_lines,
+#ifdef ESLOPE
+	sector_ffloors,
+	sector_fslope,
+	sector_cslope
+#else
 	sector_ffloors
+#endif
 };
 
 static const char *const sector_opt[] = {
@@ -55,6 +65,10 @@ static const char *const sector_opt[] = {
 	"camsec",
 	"lines",
 	"ffloors",
+#ifdef ESLOPE
+	"f_slope",
+	"c_slope",
+#endif
 	NULL};
 
 enum subsector_e {
@@ -160,6 +174,10 @@ enum ffloor_e {
 	ffloor_toplightlevel,
 	ffloor_bottomheight,
 	ffloor_bottompic,
+#ifdef ESLOPE
+	ffloor_tslope,
+	ffloor_bslope,
+#endif
 	ffloor_sector,
 	ffloor_flags,
 	ffloor_master,
@@ -176,6 +194,10 @@ static const char *const ffloor_opt[] = {
 	"toplightlevel",
 	"bottomheight",
 	"bottompic",
+#ifdef ESLOPE
+	"t_slope",
+	"b_slope",
+#endif
 	"sector", // secnum pushed as control sector userdata
 	"flags",
 	"master", // control linedef
@@ -261,6 +283,43 @@ static const char *const bbox_opt[] = {
 	"right",
 	NULL};
 
+#ifdef ESLOPE
+enum slope_e {
+	slope_valid = 0,
+	slope_o,
+	slope_d,
+	slope_zdelta,
+	slope_normal,
+	slope_zangle,
+	slope_xydirection,
+	slope_flags
+};
+
+static const char *const slope_opt[] = {
+	"valid",
+	"o",
+	"d",
+	"zdelta",
+	"normal",
+	"zangle",
+	"xydirection",
+	"flags",
+	NULL};
+
+// shared by both vector2_t and vector3_t
+enum vector_e {
+	vector_x = 0,
+	vector_y,
+	vector_z
+};
+
+static const char *const vector_opt[] = {
+	"x",
+	"y",
+	"z",
+	NULL};
+#endif
+
 static const char *const array_opt[] ={"iterate",NULL};
 static const char *const valid_opt[] ={"valid",NULL};
 
@@ -274,8 +333,7 @@ static int lib_iterateSectorThinglist(lua_State *L)
 	mobj_t *state = NULL;
 	mobj_t *thing = NULL;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sector.thinglist() directly, use it as 'for rover in sector.thinglist do <block> end'.");
@@ -310,8 +368,7 @@ static int lib_iterateSectorFFloors(lua_State *L)
 	ffloor_t *state = NULL;
 	ffloor_t *ffloor = NULL;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sector.ffloors() directly, use it as 'for rover in sector.ffloors do <block> end'.");
@@ -421,6 +478,7 @@ 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);
+	INT16 i;
 
 	if (!sector)
 	{
@@ -443,11 +501,23 @@ static int sector_get(lua_State *L)
 		lua_pushfixed(L, sector->ceilingheight);
 		return 1;
 	case sector_floorpic: // floorpic
-		lua_pushlstring(L, levelflats[sector->floorpic].name, 8);
+	{
+		levelflat_t *levelflat = &levelflats[sector->floorpic];
+		for (i = 0; i < 8; i++)
+			if (!levelflat->name[i])
+				break;
+		lua_pushlstring(L, levelflat->name, i);
 		return 1;
+	}
 	case sector_ceilingpic: // ceilingpic
-		lua_pushlstring(L, levelflats[sector->ceilingpic].name, 8);
+	{
+		levelflat_t *levelflat = &levelflats[sector->ceilingpic];
+		for (i = 0; i < 8; i++)
+			if (!levelflat->name[i])
+				break;
+		lua_pushlstring(L, levelflat->name, i);
 		return 1;
+	}
 	case sector_lightlevel:
 		lua_pushinteger(L, sector->lightlevel);
 		return 1;
@@ -480,6 +550,14 @@ static int sector_get(lua_State *L)
 		LUA_PushUserdata(L, sector->ffloors, META_FFLOOR);
 		lua_pushcclosure(L, sector_iterate, 2); // push lib_iterateFFloors and sector->ffloors as upvalues for the function
 		return 1;
+#ifdef ESLOPE
+	case sector_fslope: // f_slope
+		LUA_PushUserdata(L, sector->f_slope, META_SLOPE);
+		return 1;
+	case sector_cslope: // c_slope
+		LUA_PushUserdata(L, sector->c_slope, META_SLOPE);
+		return 1;
+#endif
 	}
 	return 0;
 }
@@ -502,6 +580,10 @@ static int sector_set(lua_State *L)
 	case sector_heightsec: // heightsec
 	case sector_camsec: // camsec
 	case sector_ffloors: // ffloors
+#ifdef ESLOPE
+	case sector_fslope: // f_slope
+	case sector_cslope: // c_slope
+#endif
 	default:
 		return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]);
 	case sector_floorheight: { // floorheight
@@ -817,16 +899,16 @@ static int side_set(lua_State *L)
 		side->rowoffset = luaL_checkfixed(L, 3);
 		break;
 	case side_toptexture:
-        side->toptexture = luaL_checkinteger(L, 3);
+		side->toptexture = luaL_checkinteger(L, 3);
 		break;
 	case side_bottomtexture:
-        side->bottomtexture = luaL_checkinteger(L, 3);
+		side->bottomtexture = luaL_checkinteger(L, 3);
 		break;
 	case side_midtexture:
-        side->midtexture = luaL_checkinteger(L, 3);
+		side->midtexture = luaL_checkinteger(L, 3);
 		break;
 	case side_repeatcnt:
-        side->repeatcnt = luaL_checkinteger(L, 3);
+		side->repeatcnt = luaL_checkinteger(L, 3);
 		break;
 	}
 	return 0;
@@ -1167,8 +1249,7 @@ static int bbox_get(lua_State *L)
 static int lib_iterateSectors(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sectors.iterate() directly, use it as 'for sector in sectors.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1186,8 +1267,7 @@ static int lib_iterateSectors(lua_State *L)
 static int lib_getSector(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1221,8 +1301,7 @@ static int lib_numsectors(lua_State *L)
 static int lib_iterateSubsectors(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call subsectors.iterate() directly, use it as 'for subsector in subsectors.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1240,8 +1319,7 @@ static int lib_iterateSubsectors(lua_State *L)
 static int lib_getSubsector(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1275,8 +1353,7 @@ static int lib_numsubsectors(lua_State *L)
 static int lib_iterateLines(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call lines.iterate() directly, use it as 'for line in lines.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1294,8 +1371,7 @@ static int lib_iterateLines(lua_State *L)
 static int lib_getLine(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1329,8 +1405,7 @@ static int lib_numlines(lua_State *L)
 static int lib_iterateSides(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sides.iterate() directly, use it as 'for side in sides.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1348,8 +1423,7 @@ static int lib_iterateSides(lua_State *L)
 static int lib_getSide(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1383,8 +1457,7 @@ static int lib_numsides(lua_State *L)
 static int lib_iterateVertexes(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call vertexes.iterate() directly, use it as 'for vertex in vertexes.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1402,8 +1475,7 @@ static int lib_iterateVertexes(lua_State *L)
 static int lib_getVertex(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1439,8 +1511,7 @@ static int lib_numvertexes(lua_State *L)
 static int lib_iterateSegs(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call segs.iterate() directly, use it as 'for seg in segs.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1458,8 +1529,7 @@ static int lib_iterateSegs(lua_State *L)
 static int lib_getSeg(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1493,8 +1563,7 @@ static int lib_numsegs(lua_State *L)
 static int lib_iterateNodes(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call nodes.iterate() directly, use it as 'for node in nodes.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1512,8 +1581,7 @@ static int lib_iterateNodes(lua_State *L)
 static int lib_getNode(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1549,6 +1617,7 @@ 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);
+	INT16 i;
 
 	if (!ffloor)
 	{
@@ -1568,11 +1637,11 @@ static int ffloor_get(lua_State *L)
 		lua_pushfixed(L, *ffloor->topheight);
 		return 1;
 	case ffloor_toppic: { // toppic
-		levelflat_t *levelflat;
-		INT16 i;
-		for (i = 0, levelflat = levelflats; i != *ffloor->toppic; i++, levelflat++)
-			;
-		lua_pushlstring(L, levelflat->name, 8);
+		levelflat_t *levelflat = &levelflats[*ffloor->toppic];
+		for (i = 0; i < 8; i++)
+			if (!levelflat->name[i])
+				break;
+		lua_pushlstring(L, levelflat->name, i);
 		return 1;
 	}
 	case ffloor_toplightlevel:
@@ -1582,13 +1651,21 @@ static int ffloor_get(lua_State *L)
 		lua_pushfixed(L, *ffloor->bottomheight);
 		return 1;
 	case ffloor_bottompic: { // bottompic
-		levelflat_t *levelflat;
-		INT16 i;
-		for (i = 0, levelflat = levelflats; i != *ffloor->bottompic; i++, levelflat++)
-			;
-		lua_pushlstring(L, levelflat->name, 8);
+		levelflat_t *levelflat = &levelflats[*ffloor->bottompic];
+		for (i = 0; i < 8; i++)
+			if (!levelflat->name[i])
+				break;
+		lua_pushlstring(L, levelflat->name, i);
 		return 1;
 	}
+#ifdef ESLOPE
+	case ffloor_tslope:
+		LUA_PushUserdata(L, *ffloor->t_slope, META_SLOPE);
+		return 1;
+	case ffloor_bslope:
+		LUA_PushUserdata(L, *ffloor->b_slope, META_SLOPE);
+		return 1;
+#endif
 	case ffloor_sector:
 		LUA_PushUserdata(L, &sectors[ffloor->secnum], META_SECTOR);
 		return 1;
@@ -1628,6 +1705,10 @@ static int ffloor_set(lua_State *L)
 	switch(field)
 	{
 	case ffloor_valid: // valid
+#ifdef ESLOPE
+	case ffloor_tslope: // t_slope
+	case ffloor_bslope: // b_slope
+#endif
 	case ffloor_sector: // sector
 	case ffloor_master: // master
 	case ffloor_target: // target
@@ -1688,6 +1769,181 @@ static int ffloor_set(lua_State *L)
 	return 0;
 }
 
+#ifdef ESLOPE
+//////////////
+// pslope_t //
+//////////////
+
+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);
+
+	if (!slope)
+	{
+		if (field == slope_valid) {
+			lua_pushboolean(L, 0);
+			return 1;
+		}
+		return luaL_error(L, "accessed pslope_t doesn't exist anymore.");
+	}
+
+	switch(field)
+	{
+	case slope_valid: // valid
+		lua_pushboolean(L, 1);
+		return 1;
+	case slope_o: // o
+		LUA_PushUserdata(L, &slope->o, META_VECTOR3);
+		return 1;
+	case slope_d: // d
+		LUA_PushUserdata(L, &slope->d, META_VECTOR2);
+		return 1;
+	case slope_zdelta: // zdelta
+		lua_pushfixed(L, slope->zdelta);
+		return 1;
+	case slope_normal: // normal
+		LUA_PushUserdata(L, &slope->normal, META_VECTOR3);
+		return 1;
+	case slope_zangle: // zangle
+		lua_pushangle(L, slope->zangle);
+		return 1;
+	case slope_xydirection: // xydirection
+		lua_pushangle(L, slope->xydirection);
+		return 1;
+	case slope_flags: // flags
+		lua_pushinteger(L, slope->flags);
+		return 1;
+	}
+	return 0;
+}
+
+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);
+
+	if (!slope)
+		return luaL_error(L, "accessed pslope_t doesn't exist anymore.");
+
+	if (hud_running)
+		return luaL_error(L, "Do not alter pslope_t in HUD rendering code!");
+
+	switch(field) // todo: reorganize this shit
+	{
+	case slope_valid: // valid
+	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]);
+	case slope_o: { // o
+		luaL_checktype(L, 3, LUA_TTABLE);
+
+		lua_getfield(L, 3, "x");
+		if (lua_isnil(L, -1))
+		{
+			lua_pop(L, 1);
+			lua_rawgeti(L, 3, 1);
+		}
+		if (!lua_isnil(L, -1))
+			slope->o.x = luaL_checkfixed(L, -1);
+		else
+			slope->o.x = 0;
+		lua_pop(L, 1);
+
+		lua_getfield(L, 3, "y");
+		if (lua_isnil(L, -1))
+		{
+			lua_pop(L, 1);
+			lua_rawgeti(L, 3, 2);
+		}
+		if (!lua_isnil(L, -1))
+			slope->o.y = luaL_checkfixed(L, -1);
+		else
+			slope->o.y = 0;
+		lua_pop(L, 1);
+
+		lua_getfield(L, 3, "z");
+		if (lua_isnil(L, -1))
+		{
+			lua_pop(L, 1);
+			lua_rawgeti(L, 3, 3);
+		}
+		if (!lua_isnil(L, -1))
+			slope->o.z = luaL_checkfixed(L, -1);
+		else
+			slope->o.z = 0;
+		lua_pop(L, 1);
+		break;
+	}
+	case slope_zdelta: { // zdelta, this is temp until i figure out wtf to do
+		slope->zdelta = luaL_checkfixed(L, 3);
+		slope->zangle = R_PointToAngle2(0, 0, FRACUNIT, -slope->zdelta);
+		P_CalculateSlopeNormal(slope);
+		break;
+	}
+	case slope_zangle: { // zangle
+		angle_t zangle = luaL_checkangle(L, 3);
+		if (zangle == ANGLE_90 || zangle == ANGLE_270)
+			return luaL_error(L, "invalid zangle for slope!");
+		slope->zangle = zangle;
+		slope->zdelta = -FINETANGENT(((slope->zangle+ANGLE_90)>>ANGLETOFINESHIFT) & 4095);
+		P_CalculateSlopeNormal(slope);
+		break;
+	}
+	case slope_xydirection: // xydirection
+		slope->xydirection = luaL_checkangle(L, 3);
+		slope->d.x = -FINECOSINE((slope->xydirection>>ANGLETOFINESHIFT) & FINEMASK);
+		slope->d.y = -FINESINE((slope->xydirection>>ANGLETOFINESHIFT) & FINEMASK);
+		P_CalculateSlopeNormal(slope);
+		break;
+	}
+	return 0;
+}
+
+///////////////
+// vector*_t //
+///////////////
+
+static int vector2_get(lua_State *L)
+{
+	vector2_t *vec = *((vector2_t **)luaL_checkudata(L, 1, META_VECTOR2));
+	enum vector_e field = luaL_checkoption(L, 2, vector_opt[0], vector_opt);
+
+	if (!vec)
+		return luaL_error(L, "accessed vector2_t doesn't exist anymore.");
+
+	switch(field)
+	{
+		case vector_x: lua_pushfixed(L, vec->x); return 1;
+		case vector_y: lua_pushfixed(L, vec->y); return 1;
+		default: break;
+	}
+
+	return 0;
+}
+
+static int vector3_get(lua_State *L)
+{
+	vector3_t *vec = *((vector3_t **)luaL_checkudata(L, 1, META_VECTOR3));
+	enum vector_e field = luaL_checkoption(L, 2, vector_opt[0], vector_opt);
+
+	if (!vec)
+		return luaL_error(L, "accessed vector3_t doesn't exist anymore.");
+
+	switch(field)
+	{
+		case vector_x: lua_pushfixed(L, vec->x); return 1;
+		case vector_y: lua_pushfixed(L, vec->y); return 1;
+		case vector_z: lua_pushfixed(L, vec->z); return 1;
+		default: break;
+	}
+
+	return 0;
+}
+#endif
+
 /////////////////////
 // mapheaderinfo[] //
 /////////////////////
@@ -1747,6 +2003,20 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushstring(L, header->musname);
 	else if (fastcmp(field,"mustrack"))
 		lua_pushinteger(L, header->mustrack);
+	else if (fastcmp(field,"muspos"))
+		lua_pushinteger(L, header->muspos);
+	else if (fastcmp(field,"musinterfadeout"))
+		lua_pushinteger(L, header->musinterfadeout);
+	else if (fastcmp(field,"musintername"))
+		lua_pushstring(L, header->musintername);
+	else if (fastcmp(field,"muspostbossname"))
+		lua_pushstring(L, header->muspostbossname);
+	else if (fastcmp(field,"muspostbosstrack"))
+		lua_pushinteger(L, header->muspostbosstrack);
+	else if (fastcmp(field,"muspostbosspos"))
+		lua_pushinteger(L, header->muspostbosspos);
+	else if (fastcmp(field,"muspostbossfadein"))
+		lua_pushinteger(L, header->muspostbossfadein);
 	else if (fastcmp(field,"forcecharacter"))
 		lua_pushstring(L, header->forcecharacter);
 	else if (fastcmp(field,"weather"))
@@ -1784,10 +2054,14 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->levelselect);
 	else if (fastcmp(field,"bonustype"))
 		lua_pushinteger(L, header->bonustype);
+	else if (fastcmp(field,"maxbonuslives"))
+		lua_pushinteger(L, header->maxbonuslives);
 	else if (fastcmp(field,"levelflags"))
 		lua_pushinteger(L, header->levelflags);
 	else if (fastcmp(field,"menuflags"))
 		lua_pushinteger(L, header->menuflags);
+	else if (fastcmp(field,"startrings"))
+		lua_pushinteger(L, header->startrings);
 	// TODO add support for reading numGradedMares and grades
 	else {
 		// Read custom vars now
@@ -1907,6 +2181,26 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__index");
 	lua_pop(L, 1);
 
+#ifdef ESLOPE
+	luaL_newmetatable(L, META_SLOPE);
+		lua_pushcfunction(L, slope_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, slope_set);
+		lua_setfield(L, -2, "__newindex");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_VECTOR2);
+		lua_pushcfunction(L, vector2_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_VECTOR3);
+		lua_pushcfunction(L, vector3_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L, 1);
+#endif
+
 	luaL_newmetatable(L, META_MAPHEADER);
 		lua_pushcfunction(L, mapheaderinfo_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c
index dd526a1698f6e1c93ed70cd7debc28d0e8f120c7..1f0d6e2872d99cbec85b8aebfa6dbf13f0848ad4 100644
--- a/src/lua_mathlib.c
+++ b/src/lua_mathlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index d384b75d1b5163e7f24ffb83a3177f0e84585fd4..063158b263b58b4f4a6b7764a6c3075a4e72867c 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -40,6 +40,8 @@ enum mobj_e {
 	mobj_subsector,
 	mobj_floorz,
 	mobj_ceilingz,
+	mobj_floorrover,
+	mobj_ceilingrover,
 	mobj_radius,
 	mobj_height,
 	mobj_momx,
@@ -81,7 +83,11 @@ enum mobj_e {
 	mobj_extravalue1,
 	mobj_extravalue2,
 	mobj_cusval,
-	mobj_cvmem
+	mobj_cvmem,
+#ifdef ESLOPE
+	mobj_standingslope,
+#endif
+	mobj_colorized
 };
 
 static const char *const mobj_opt[] = {
@@ -100,6 +106,8 @@ static const char *const mobj_opt[] = {
 	"subsector",
 	"floorz",
 	"ceilingz",
+	"floorrover",
+	"ceilingrover",
 	"radius",
 	"height",
 	"momx",
@@ -142,6 +150,10 @@ static const char *const mobj_opt[] = {
 	"extravalue2",
 	"cusval",
 	"cvmem",
+#ifdef ESLOPE
+	"standingslope",
+#endif
+	"colorized",
 	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])
@@ -208,6 +220,12 @@ static int mobj_get(lua_State *L)
 	case mobj_ceilingz:
 		lua_pushfixed(L, mo->ceilingz);
 		break;
+	case mobj_floorrover:
+		LUA_PushUserdata(L, mo->floorrover, META_FFLOOR);
+		break;
+	case mobj_ceilingrover:
+		LUA_PushUserdata(L, mo->ceilingrover, META_FFLOOR);
+		break;
 	case mobj_radius:
 		lua_pushfixed(L, mo->radius);
 		break;
@@ -256,10 +274,19 @@ static int mobj_get(lua_State *L)
 		// bprev -- same deal as sprev above, but for the blockmap.
 		return UNIMPLEMENTED;
 	case mobj_hnext:
+		if (mo->hnext && P_MobjWasRemoved(mo->hnext))
+		{ // don't put invalid mobj back into Lua.
+			P_SetTarget(&mo->hnext, NULL);
+			return 0;
+		}
 		LUA_PushUserdata(L, mo->hnext, META_MOBJ);
 		break;
 	case mobj_hprev:
-		// implimented differently from sprev and bprev because SSNTails.
+		if (mo->hprev && P_MobjWasRemoved(mo->hprev))
+		{ // don't put invalid mobj back into Lua.
+			P_SetTarget(&mo->hprev, NULL);
+			return 0;
+		}
 		LUA_PushUserdata(L, mo->hprev, META_MOBJ);
 		break;
 	case mobj_type:
@@ -348,6 +375,14 @@ static int mobj_get(lua_State *L)
 	case mobj_cvmem:
 		lua_pushinteger(L, mo->cvmem);
 		break;
+#ifdef ESLOPE
+	case mobj_standingslope:
+		LUA_PushUserdata(L, mo->standingslope, META_SLOPE);
+		break;
+#endif
+	case mobj_colorized:
+		lua_pushboolean(L, mo->colorized);
+		break;
 	default: // extra custom variables in Lua memory
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -396,6 +431,8 @@ static int mobj_set(lua_State *L)
 		P_CheckPosition(mo, mo->x, mo->y);
 		mo->floorz = tmfloorz;
 		mo->ceilingz = tmceilingz;
+		mo->floorrover = tmfloorrover;
+		mo->ceilingrover = tmceilingrover;
 		P_SetTarget(&tmthing, ptmthing);
 		break;
 	}
@@ -430,6 +467,10 @@ static int mobj_set(lua_State *L)
 		return NOSETPOS;
 	case mobj_ceilingz:
 		return NOSETPOS;
+	case mobj_floorrover:
+		return NOSET;
+	case mobj_ceilingrover:
+		return NOSET;
 	case mobj_radius:
 	{
 		mobj_t *ptmthing = tmthing;
@@ -439,6 +480,8 @@ static int mobj_set(lua_State *L)
 		P_CheckPosition(mo, mo->x, mo->y);
 		mo->floorz = tmfloorz;
 		mo->ceilingz = tmceilingz;
+		mo->floorrover = tmfloorrover;
+		mo->ceilingrover = tmceilingrover;
 		P_SetTarget(&tmthing, ptmthing);
 		break;
 	}
@@ -451,6 +494,8 @@ static int mobj_set(lua_State *L)
 		P_CheckPosition(mo, mo->x, mo->y);
 		mo->floorz = tmfloorz;
 		mo->ceilingz = tmceilingz;
+		mo->floorrover = tmfloorrover;
+		mo->ceilingrover = tmceilingrover;
 		P_SetTarget(&tmthing, ptmthing);
 		break;
 	}
@@ -530,10 +575,22 @@ static int mobj_set(lua_State *L)
 	case mobj_bprev:
 		return UNIMPLEMENTED;
 	case mobj_hnext:
-		mo->hnext = luaL_checkudata(L, 3, META_MOBJ);
+		if (lua_isnil(L, 3))
+			P_SetTarget(&mo->hnext, NULL);
+		else
+		{
+			mobj_t *hnext = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			P_SetTarget(&mo->hnext, hnext);
+		}
 		break;
 	case mobj_hprev:
-		mo->hprev = luaL_checkudata(L, 3, META_MOBJ);
+		if (lua_isnil(L, 3))
+			P_SetTarget(&mo->hprev, NULL);
+		else
+		{
+			mobj_t *hprev = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			P_SetTarget(&mo->hprev, hprev);
+		}
 		break;
 	case mobj_type: // yeah sure, we'll let you change the mobj's type.
 	{
@@ -643,6 +700,13 @@ static int mobj_set(lua_State *L)
 	case mobj_cvmem:
 		mo->cvmem = luaL_checkinteger(L, 3);
 		break;
+#ifdef ESLOPE
+	case mobj_standingslope:
+		return NOSET;
+#endif
+	case mobj_colorized:
+		mo->colorized = luaL_checkboolean(L, 3);
+		break;
 	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -740,7 +804,12 @@ static int mapthing_set(lua_State *L)
 	else if(fastcmp(field,"z"))
 		mt->z = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"extrainfo"))
-		mt->extrainfo = (UINT8)luaL_checkinteger(L, 3);
+	{
+		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;
+	}
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
@@ -752,8 +821,7 @@ static int mapthing_set(lua_State *L)
 static int lib_iterateMapthings(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call mapthings.iterate() directly, use it as 'for mapthing in mapthings.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -771,8 +839,7 @@ static int lib_iterateMapthings(lua_State *L)
 static int lib_getMapthing(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 12b2646d04df53fa69f3b0526435da84ca4df1ff..b7bdaa1be87a078974490fe569e171c15906b319 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -25,8 +25,7 @@
 static int lib_iteratePlayers(lua_State *L)
 {
 	INT32 i = -1;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 	{
 		//return luaL_error(L, "Don't call players.iterate() directly, use it as 'for player in players.iterate do <block> end'.");
@@ -53,8 +52,7 @@ static int lib_getPlayer(lua_State *L)
 {
 	const char *field;
 	// i -> players[i]
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	if (lua_type(L, 2) == LUA_TNUMBER)
 	{
 		lua_Integer i = luaL_checkinteger(L, 2);
@@ -130,6 +128,8 @@ static int player_get(lua_State *L)
 		lua_pushangle(L, plr->drawangle);
 	else if (fastcmp(field,"rings"))
 		lua_pushinteger(L, plr->rings);
+	else if (fastcmp(field,"spheres"))
+		lua_pushinteger(L, plr->spheres);
 	else if (fastcmp(field,"pity"))
 		lua_pushinteger(L, plr->pity);
 	else if (fastcmp(field,"currentweapon"))
@@ -286,22 +286,46 @@ static int player_get(lua_State *L)
 		lua_pushboolean(L, plr->bonustime);
 	else if (fastcmp(field,"capsule"))
 		LUA_PushUserdata(L, plr->capsule, META_MOBJ);
+	else if (fastcmp(field,"drone"))
+		LUA_PushUserdata(L, plr->drone, META_MOBJ);
+	else if (fastcmp(field,"oldscale"))
+		lua_pushfixed(L, plr->oldscale);
 	else if (fastcmp(field,"mare"))
 		lua_pushinteger(L, plr->mare);
+	else if (fastcmp(field,"marelap"))
+		lua_pushinteger(L, plr->marelap);
+	else if (fastcmp(field,"marebonuslap"))
+		lua_pushinteger(L, plr->marebonuslap);
 	else if (fastcmp(field,"marebegunat"))
 		lua_pushinteger(L, plr->marebegunat);
 	else if (fastcmp(field,"startedtime"))
 		lua_pushinteger(L, plr->startedtime);
 	else if (fastcmp(field,"finishedtime"))
 		lua_pushinteger(L, plr->finishedtime);
+	else if (fastcmp(field,"lapbegunat"))
+		lua_pushinteger(L, plr->lapbegunat);
+	else if (fastcmp(field,"lapstartedtime"))
+		lua_pushinteger(L, plr->lapstartedtime);
+	else if (fastcmp(field,"finishedspheres"))
+		lua_pushinteger(L, plr->finishedspheres);
 	else if (fastcmp(field,"finishedrings"))
 		lua_pushinteger(L, plr->finishedrings);
 	else if (fastcmp(field,"marescore"))
 		lua_pushinteger(L, plr->marescore);
 	else if (fastcmp(field,"lastmarescore"))
 		lua_pushinteger(L, plr->lastmarescore);
+	else if (fastcmp(field,"totalmarescore"))
+		lua_pushinteger(L, plr->totalmarescore);
 	else if (fastcmp(field,"lastmare"))
 		lua_pushinteger(L, plr->lastmare);
+	else if (fastcmp(field,"lastmarelap"))
+		lua_pushinteger(L, plr->lastmarelap);
+	else if (fastcmp(field,"lastmarebonuslap"))
+		lua_pushinteger(L, plr->lastmarebonuslap);
+	else if (fastcmp(field,"totalmarelap"))
+		lua_pushinteger(L, plr->totalmarelap);
+	else if (fastcmp(field,"totalmarebonuslap"))
+		lua_pushinteger(L, plr->totalmarebonuslap);
 	else if (fastcmp(field,"maxlink"))
 		lua_pushinteger(L, plr->maxlink);
 	else if (fastcmp(field,"texttimer"))
@@ -396,6 +420,8 @@ static int player_set(lua_State *L)
 		plr->drawangle = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"rings"))
 		plr->rings = (INT32)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"spheres"))
+		plr->spheres = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"pity"))
 		plr->pity = (SINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"currentweapon"))
@@ -448,7 +474,12 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"followitem"))
 		plr->followitem = luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"followmobj"))
-		plr->followmobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->followmobj, mo);
+	}
 	else if (fastcmp(field,"actionspd"))
 		plr->actionspd = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"mindash"))
@@ -532,9 +563,19 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"old_angle_pos"))
 		plr->old_angle_pos = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"axis1"))
-		P_SetTarget(&plr->axis1, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->axis1, mo);
+	}
 	else if (fastcmp(field,"axis2"))
-		P_SetTarget(&plr->axis2, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->axis2, mo);
+	}
 	else if (fastcmp(field,"bumpertime"))
 		plr->bumpertime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"flyangle"))
@@ -562,22 +603,51 @@ static int player_set(lua_State *L)
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->capsule, mo);
 	}
+	else if (fastcmp(field,"drone"))
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->drone, mo);
+	}
+	else if (fastcmp(field,"oldscale"))
+		plr->oldscale = luaL_checkfixed(L, 3);
 	else if (fastcmp(field,"mare"))
 		plr->mare = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"marelap"))
+		plr->marelap = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"marebonuslap"))
+		plr->marebonuslap = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"marebegunat"))
 		plr->marebegunat = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"startedtime"))
 		plr->startedtime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"finishedtime"))
 		plr->finishedtime = (tic_t)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"lapbegunat"))
+		plr->lapbegunat = (tic_t)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"lapstartedtime"))
+		plr->lapstartedtime = (tic_t)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"finishedspheres"))
+		plr->finishedspheres = (INT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"finishedrings"))
 		plr->finishedrings = (INT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"marescore"))
 		plr->marescore = (UINT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"lastmarescore"))
 		plr->lastmarescore = (UINT32)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"totalmarescore"))
+		plr->totalmarescore = (UINT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"lastmare"))
 		plr->lastmare = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"lastmarelap"))
+		plr->lastmarelap = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"lastmarebonuslap"))
+		plr->lastmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"totalmarelap"))
+		plr->totalmarelap = (UINT8)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"totalmarebonuslap"))
+		plr->totalmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"maxlink"))
 		plr->maxlink = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"texttimer"))
diff --git a/src/lua_script.c b/src/lua_script.c
index 853ed5a2fd7500c7788123c290f4952ae29b757c..deb644dc0351c169c60cb859952627278538241c 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -22,6 +22,9 @@
 #include "byteptr.h"
 #include "p_saveg.h"
 #include "p_local.h"
+#ifdef ESLOPE
+#include "p_slopes.h" // for P_SlopeById
+#endif
 #ifdef LUA_ALLOW_BYTECODE
 #include "d_netfil.h" // for LUA_DumpFile
 #endif
@@ -176,11 +179,16 @@ static inline void LUA_LoadFile(MYFILE *f, char *name)
 		LUA_ClearState();
 	lua_pushinteger(gL, f->wad);
 	lua_setfield(gL, LUA_REGISTRYINDEX, "WAD");
+
+	lua_lumploading = true; // turn on loading flag
+
 	if (luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)) || lua_pcall(gL, 0, 0, 0)) {
 		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
 		lua_pop(gL,1);
 	}
 	lua_gc(gL, LUA_GCCOLLECT, 0);
+
+	lua_lumploading = false; // turn off again
 }
 
 // Load a script from a lump
@@ -188,24 +196,30 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump)
 {
 	MYFILE f;
 	char *name;
+	size_t len;
 	f.wad = wad;
 	f.size = W_LumpLengthPwad(wad, lump);
 	f.data = Z_Malloc(f.size, PU_LUA, NULL);
 	W_ReadLumpPwad(wad, lump, f.data);
 	f.curpos = f.data;
 
-	name = malloc(strlen(wadfiles[wad]->filename)+10);
-	strcpy(name, wadfiles[wad]->filename);
-	if (!fasticmp(&name[strlen(name) - 4], ".lua")) {
-		// If it's not a .lua file, copy the lump name in too.
-		name[strlen(wadfiles[wad]->filename)] = '|';
-		M_Memcpy(name+strlen(wadfiles[wad]->filename)+1, wadfiles[wad]->lumpinfo[lump].name, 8);
-		name[strlen(wadfiles[wad]->filename)+9] = '\0';
+	len = strlen(wadfiles[wad]->filename); // length of file name
+
+	if (wadfiles[wad]->type == RET_LUA)
+	{
+		name = malloc(len+1);
+		strcpy(name, wadfiles[wad]->filename);
+	}
+	else // If it's not a .lua file, copy the lump name in too.
+	{
+		lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump];
+		len += 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+		name = malloc(len+1);
+		sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->name2);
+		name[len] = '\0';
 	}
 
-	lua_lumploading = true; // turn on loading flag
 	LUA_LoadFile(&f, name); // actually load file!
-	lua_lumploading = false; // turn off again
 
 	free(name);
 	Z_Free(f.data);
@@ -406,9 +420,9 @@ void LUA_InvalidateLevel(void)
 	ffloor_t *rover = NULL;
 	if (!gL)
 		return;
-
-	for (th = thinkercap.next; th && th != &thinkercap; th = th->next)
-		LUA_InvalidateUserdata(th);
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+		for (th = thlist[i].next; th && th != &thlist[i]; th = th->next)
+			LUA_InvalidateUserdata(th);
 
 	LUA_InvalidateMapthings();
 
@@ -487,6 +501,9 @@ enum
 	ARCH_NODE,
 #endif
 	ARCH_FFLOOR,
+#ifdef ESLOPE
+	ARCH_SLOPE,
+#endif
 	ARCH_MAPHEADER,
 
 	ARCH_TEND=0xFF,
@@ -511,14 +528,17 @@ static const struct {
 	{META_NODE,     ARCH_NODE},
 #endif
 	{META_FFLOOR,	ARCH_FFLOOR},
+#ifdef ESLOPE
+	{META_SLOPE,    ARCH_SLOPE},
+#endif
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{NULL,          ARCH_NULL}
 };
 
-static UINT8 GetUserdataArchType(void)
+static UINT8 GetUserdataArchType(int index)
 {
 	UINT8 i;
-	lua_getmetatable(gL, -1);
+	lua_getmetatable(gL, index);
 
 	for (i = 0; meta2arch[i].meta; i++)
 	{
@@ -563,9 +583,23 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		break;
 	}
 	case LUA_TSTRING:
+	{
+		UINT16 len = (UINT16)lua_objlen(gL, myindex); // get length of string, including embedded zeros
+		const char *s = lua_tostring(gL, myindex);
+		UINT16 i = 0;
 		WRITEUINT8(save_p, ARCH_STRING);
-		WRITESTRING(save_p, lua_tostring(gL, myindex));
+		// if you're wondering why we're writing a string to save_p this way,
+		// it turns out that Lua can have embedded zeros ('\0') in the strings,
+		// so we can't use WRITESTRING as that cuts off when it finds a '\0'.
+		// Saving the size of the string also allows us to get the size of the string on the other end,
+		// fixing the awful crashes previously encountered for reading strings longer than 1024
+		// (yes I know that's kind of a stupid thing to care about, but it'd be evil to trim or ignore them?)
+		// -- Monster Iestyn 05/08/18
+		WRITEUINT16(save_p, len); // save size of string
+		while (i < len)
+			WRITECHAR(save_p, s[i++]); // write chars individually, including the embedded zeros
 		break;
+	}
 	case LUA_TTABLE:
 	{
 		boolean found = false;
@@ -597,7 +631,7 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		break;
 	}
 	case LUA_TUSERDATA:
-		switch (GetUserdataArchType())
+		switch (GetUserdataArchType(myindex))
 		{
 		case ARCH_MOBJINFO:
 		{
@@ -751,6 +785,19 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			}
 			break;
 		}
+#ifdef ESLOPE
+		case ARCH_SLOPE:
+		{
+			pslope_t *slope = *((pslope_t **)lua_touserdata(gL, myindex));
+			if (!slope)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				WRITEUINT8(save_p, ARCH_SLOPE);
+				WRITEUINT16(save_p, slope->id);
+			}
+			break;
+		}
+#endif
 		case ARCH_MAPHEADER:
 		{
 			mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
@@ -854,16 +901,25 @@ static void ArchiveTables(void)
 		lua_pushnil(gL);
 		while (lua_next(gL, -2))
 		{
-			ArchiveValue(TABLESINDEX, -2); // key should be either a number or a string, ArchiveValue can handle this.
+			// Write key
+			e = ArchiveValue(TABLESINDEX, -2); // key should be either a number or a string, ArchiveValue can handle this.
+			if (e == 2) // invalid key type (function, thread, lightuserdata, or anything we don't recognise)
+			{
+				lua_pushvalue(gL, -2);
+				CONS_Alert(CONS_ERROR, "Index '%s' (%s) of table %d could not be archived!\n", lua_tostring(gL, -1), luaL_typename(gL, -1), i);
+				lua_pop(gL, 1);
+			}
+			// Write value
 			e = ArchiveValue(TABLESINDEX, -1);
 			if (e == 1)
 				n++; // the table contained a new table we'll have to archive. :(
-			else if (e == 2)
+			else if (e == 2) // invalid value type
 			{
 				lua_pushvalue(gL, -2);
 				CONS_Alert(CONS_ERROR, "Type of value for table %d entry '%s' (%s) could not be archived!\n", i, lua_tostring(gL, -1), luaL_typename(gL, -1));
 				lua_pop(gL, 1);
 			}
+
 			lua_pop(gL, 1);
 		}
 		lua_pop(gL, 1);
@@ -887,9 +943,19 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 		break;
 	case ARCH_STRING:
 	{
-		char value[1024];
-		READSTRING(save_p, value);
-		lua_pushstring(gL, value);
+		UINT16 len = READUINT16(save_p); // length of string, including embedded zeros
+		char *value;
+		UINT16 i = 0;
+		// See my comments in the ArchiveValue function;
+		// it's much the same for reading strings as writing them!
+		// (i.e. we can't use READSTRING either)
+		// -- Monster Iestyn 05/08/18
+		value = malloc(len); // make temp buffer of size len
+		// now read the actual string
+		while (i < len)
+			value[i++] = READCHAR(save_p); // read chars individually, including the embedded zeros
+		lua_pushlstring(gL, value, len); // push the string (note: this function supports embedded zeros)
+		free(value); // free the buffer
 		break;
 	}
 	case ARCH_TABLE:
@@ -953,8 +1019,13 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 			LUA_PushUserdata(gL, rover, META_FFLOOR);
 		break;
 	}
+#ifdef ESLOPE
+	case ARCH_SLOPE:
+		LUA_PushUserdata(gL, P_SlopeById(READUINT16(save_p)), META_SLOPE);
+		break;
+#endif
 	case ARCH_MAPHEADER:
-		LUA_PushUserdata(gL, &sectors[READUINT16(save_p)], META_MAPHEADER);
+		LUA_PushUserdata(gL, mapheaderinfo[READUINT16(save_p)], META_MAPHEADER);
 		break;
 	case ARCH_TEND:
 		return 1;
@@ -1016,11 +1087,17 @@ static void UnArchiveTables(void)
 		lua_rawgeti(gL, TABLESINDEX, i);
 		while (true)
 		{
-			if (UnArchiveValue(TABLESINDEX) == 1)
+			if (UnArchiveValue(TABLESINDEX) == 1) // read key
 				break;
-			if (UnArchiveValue(TABLESINDEX) == 2)
+			if (UnArchiveValue(TABLESINDEX) == 2) // read value
 				n++;
-			lua_rawset(gL, -3);
+			if (lua_isnil(gL, -2)) // if key is nil (if a function etc was accidentally saved)
+			{
+				CONS_Alert(CONS_ERROR, "A nil key in table %d was found! (Invalid key type or corrupted save?)\n", i);
+				lua_pop(gL, 2); // pop key and value instead of setting them in the table, to prevent Lua panic errors
+			}
+			else
+				lua_rawset(gL, -3);
 		}
 		lua_pop(gL, 1);
 	}
@@ -1050,13 +1127,16 @@ void LUA_Archive(void)
 		ArchiveExtVars(&players[i], "player");
 	}
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			// archive function will determine when to skip mobjs,
-			// and write mobjnum in otherwise.
-			ArchiveExtVars(th, "mobj");
-		}
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		// archive function will determine when to skip mobjs,
+		// and write mobjnum in otherwise.
+		ArchiveExtVars(th, "mobj");
+	}
+
 	WRITEUINT32(save_p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
 
 	LUAh_NetArchiveHook(NetArchive); // call the NetArchive hook in archive mode
@@ -1084,10 +1164,14 @@ void LUA_UnArchive(void)
 
 	do {
 		mobjnum = READUINT32(save_p); // read a mobjnum
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
-			if (th->function.acp1 == (actionf_p1)P_MobjThinker
-			&& ((mobj_t *)th)->mobjnum == mobjnum) // find matching mobj
-				UnArchiveExtVars(th); // apply variables
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+			if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj
+				continue;
+			UnArchiveExtVars(th); // apply variables
+		}
 	} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
 
 	LUAh_NetArchiveHook(NetUnArchive); // call the NetArchive hook in unarchive mode
diff --git a/src/lua_script.h b/src/lua_script.h
index 51f1eaeaaeb4d00b4c55721aa4ae8fad165cd185..fcbca29379e0acd50aeeab20250696ee16a3233d 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,7 @@
 #include "m_fixed.h"
 #include "doomtype.h"
 #include "d_player.h"
+#include "g_state.h"
 
 #include "blua/lua.h"
 #include "blua/lualib.h"
@@ -97,4 +98,7 @@ void COM_Lua_f(void);
 // uncomment if you want seg_t/node_t in Lua
 // #define HAVE_LUA_SEGS
 
+#define INLEVEL if (gamestate != GS_LEVEL && !titlemapinaction)\
+return luaL_error(L, "This can only be used in a level!");
+
 #endif
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 1a70a82d9fc27539124d1fc7e4104e6714d8ee3e..a28f6a359556da233d71c6b598264101e56bb4e8 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2016 by Sonic Team Junior.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,9 +27,6 @@ enum skin {
 	skin_flags,
 	skin_realname,
 	skin_hudname,
-	skin_charsel,
-	skin_face,
-	skin_superface,
 	skin_ability,
 	skin_ability2,
 	skin_thokitem,
@@ -66,9 +63,6 @@ static const char *const skin_opt[] = {
 	"flags",
 	"realname",
 	"hudname",
-	"charsel",
-	"face",
-	"superface",
 	"ability",
 	"ability2",
 	"thokitem",
@@ -104,7 +98,6 @@ 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);
-	INT32 i;
 
 	// skins are always valid, only added, never removed
 	I_Assert(skin != NULL);
@@ -131,24 +124,6 @@ static int skin_get(lua_State *L)
 	case skin_hudname:
 		lua_pushstring(L, skin->hudname);
 		break;
-	case skin_charsel:
-		for (i = 0; i < 8; i++)
-			if (!skin->charsel[i])
-				break;
-		lua_pushlstring(L, skin->charsel, i);
-		break;
-	case skin_face:
-		for (i = 0; i < 8; i++)
-			if (!skin->face[i])
-				break;
-		lua_pushlstring(L, skin->face, i);
-		break;
-	case skin_superface:
-		for (i = 0; i < 8; i++)
-			if (!skin->superface[i])
-				break;
-		lua_pushlstring(L, skin->superface, i);
-		break;
 	case skin_ability:
 		lua_pushinteger(L, skin->ability);
 		break;
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index a661aaf04f780ef95774e0606426efc03ffc4844..877294898e1483541b12d093adf70b9accb9cd35 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -18,7 +18,7 @@
 
 #define META_ITERATIONSTATE "iteration state"
 
-static const char *const iter_opt[] = {
+/*static const char *const iter_opt[] = {
 	"all",
 	"mobj",
 	NULL};
@@ -26,7 +26,7 @@ static const char *const iter_opt[] = {
 static const actionf_p1 iter_funcs[] = {
 	NULL,
 	(actionf_p1)P_MobjThinker
-};
+};*/
 
 struct iterationState {
 	actionf_p1 filter;
@@ -56,15 +56,14 @@ static int lib_iterateThinkers(lua_State *L)
 	thinker_t *th = NULL, *next = NULL;
 	struct iterationState *it;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
 
 	lua_settop(L, 2);
 
 	if (lua_isnil(L, 2))
-		th = &thinkercap;
+		th = &thlist[THINK_MOBJ];
 	else if (lua_isuserdata(L, 2))
 	{
 		if (lua_islightuserdata(L, 2))
@@ -94,11 +93,11 @@ static int lib_iterateThinkers(lua_State *L)
 	if (!next)
 		return luaL_error(L, "next thinker invalidated during iteration");
 
-	for (; next != &thinkercap; next = next->next)
+	for (; next != &thlist[THINK_MOBJ]; next = next->next)
 		if (!it->filter || next->function.acp1 == it->filter)
 		{
 			push_thinker(next);
-			if (next->next != &thinkercap)
+			if (next->next != &thlist[THINK_MOBJ])
 			{
 				push_thinker(next->next);
 				it->next = luaL_ref(L, LUA_REGISTRYINDEX);
@@ -112,15 +111,14 @@ static int lib_startIterate(lua_State *L)
 {
 	struct iterationState *it;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	lua_pushvalue(L, lua_upvalueindex(1));
 	it = lua_newuserdata(L, sizeof(struct iterationState));
 	luaL_getmetatable(L, META_ITERATIONSTATE);
 	lua_setmetatable(L, -2);
 
-	it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
+	it->filter = (actionf_p1)P_MobjThinker; //iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
 	it->next = LUA_REFNIL;
 	return 2;
 }
@@ -138,7 +136,7 @@ int LUA_ThinkerLib(lua_State *L)
 		lua_pushcfunction(L, lib_iterateThinkers);
 		lua_pushcclosure(L, lib_startIterate, 1);
 		lua_setfield(L, -2, "iterate");
-	lua_setglobal(L, "thinkers");
+	lua_setglobal(L, "mobjs");
 	return 0;
 }
 
diff --git a/src/m_aatree.c b/src/m_aatree.c
index 6cb3a32cb927aac773db7739b6a0dc6887e14294..f19fa1dc04dd37b3b26045d9c954e9572ba408eb 100644
--- a/src/m_aatree.c
+++ b/src/m_aatree.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_aatree.h b/src/m_aatree.h
index eeaebca3b94320fb71c5b85579caf2c355503e00..e7f1eba0505d72b342b250e324e45bd29be11716 100644
--- a/src/m_aatree.h
+++ b/src/m_aatree.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 6ae112ea8ee86af4dcf57574f0988ecb9abc97d1..901927fa1bd2059660ac18f87451994347842a6a 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
 // Copyright (C) 2013      by "Ninji".
-// Copyright (C) 2013-2016 by Sonic Team Junior.
+// Copyright (C) 2013-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -497,7 +497,9 @@ static void GIF_framewrite(void)
 
 	// screen regions are handled in GIF_lzw
 	{
-		UINT16 delay = 3; // todo
+		int d1 = (int)((100.0f/NEWTICRATE)*(gif_frames+1));
+		int d2 = (int)((100.0f/NEWTICRATE)*(gif_frames));
+		UINT16 delay = d1-d2;
 		INT32 startline;
 
 		WRITEMEM(p, gifframe_gchead, 4);
diff --git a/src/m_anigif.h b/src/m_anigif.h
index dbf7b10a29476210bdbf8a9c0984d4dafc244de6..6d94ecf8ceece4ab6ca8a04caca15a0a1c10e29e 100644
--- a/src/m_anigif.h
+++ b/src/m_anigif.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 2013-2016 by Sonic Team Junior.
+// Copyright (C) 2013-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_argv.c b/src/m_argv.c
index 213577e766ddcc495d054391ea1802e4ceb3cfa3..117ec78339cf9768cab2110da40d00d619178c37 100644
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -25,6 +25,10 @@ INT32 myargc;
 */
 char **myargv;
 
+/** \brief did we alloc myargv ourselves?
+*/
+boolean myargmalloc = false;
+
 /**	\brief founded the parm
 */
 static INT32 found;
@@ -171,6 +175,7 @@ void M_FindResponseFile(void)
 				free(file);
 				I_Error("Not enough memory to read response file");
 			}
+			myargmalloc = true; // mark as having been allocated by us
 			memset(myargv, 0, sizeof (char *) * MAXARGVS);
 			myargv[0] = firstargv;
 
@@ -193,14 +198,12 @@ void M_FindResponseFile(void)
 					k++;
 			} while (k < size);
 
-			free(file);
-
 			for (k = 0; k < pindex; k++)
 				myargv[indexinfile++] = moreargs[k];
 			myargc = indexinfile;
 
 			// display arguments
-			CONS_Printf(M_GetText("%d command-line args:\n"), myargc);
+			CONS_Printf(M_GetText("%d command-line args:\n"), myargc-1); // -1 so @ don't actually get counted for
 			for (k = 1; k < myargc; k++)
 				CONS_Printf("%s\n", myargv[k]);
 
diff --git a/src/m_argv.h b/src/m_argv.h
index 46ef9a2cf5d81da5ddee474f004d17f6286d7f30..d654dcc195778eddb6907adfeed55aac2116e100 100644
--- a/src/m_argv.h
+++ b/src/m_argv.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -19,6 +19,7 @@
 //
 extern INT32 myargc;
 extern char **myargv;
+extern boolean myargmalloc;
 
 // Returns the position of the given parameter in the arg list (0 if not found).
 INT32 M_CheckParm(const char *check);
diff --git a/src/m_bbox.c b/src/m_bbox.c
index fb44b853e56fdb42db46154f92fb8a7e9b53bd54..0c57de43e45f9315f56d50a022d0c22ad0282163 100644
--- a/src/m_bbox.c
+++ b/src/m_bbox.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_bbox.h b/src/m_bbox.h
index a11257b8aa584318945aefbe83d0edb77a0d4b51..db5c2b4a7b3e815a34280216216a2608d6eb6f8a 100644
--- a/src/m_bbox.h
+++ b/src/m_bbox.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_cheat.c b/src/m_cheat.c
index a4eaede3ace36eda033f9864663a2e06e6cff3cc..29e8c8a0231425ea2440227d28ee7377b898e514 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -499,56 +499,211 @@ void Command_Teleport_f(void)
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 
-	if (COM_Argc() < 3 || COM_Argc() > 7)
+	if (COM_Argc() < 3 || COM_Argc() > 11)
 	{
-		CONS_Printf(M_GetText("teleport -x <value> -y <value> -z <value>: teleport to a location\n"));
+		CONS_Printf(M_GetText("teleport -x <value> -y <value> -z <value> -ang <value> -aim <value>: teleport to a location\nteleport -sp <sequence> <placement>: teleport to specified checkpoint\n"));
 		return;
 	}
 
 	if (!p->mo)
 		return;
 
-	i = COM_CheckParm("-x");
+	i = COM_CheckParm("-sp");
 	if (i)
-		intx = atoi(COM_Argv(i + 1));
-	else
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified\n"), "X");
-		return;
-	}
+		INT32 starpostnum = atoi(COM_Argv(i + 1)); // starpost number
+		INT32 starpostpath = atoi(COM_Argv(i + 2)); // quick, dirty way to distinguish between paths
 
-	i = COM_CheckParm("-y");
-	if (i)
-		inty = atoi(COM_Argv(i + 1));
-	else
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified\n"), "Y");
-		return;
-	}
+		if (starpostnum < 0 || starpostpath < 0)
+		{
+			CONS_Alert(CONS_NOTICE, M_GetText("Negative starpost indexing is not valid.\n"));
+			return;
+		}
 
-	ss = R_PointInSubsector(intx*FRACUNIT, inty*FRACUNIT);
-	if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
-		return;
-	}
-	i = COM_CheckParm("-z");
-	if (i)
-	{
-		intz = atoi(COM_Argv(i + 1));
-		intz <<= FRACBITS;
-		if (intz < ss->sector->floorheight)
-			intz = ss->sector->floorheight;
-		if (intz > ss->sector->ceilingheight - p->mo->height)
-			intz = ss->sector->ceilingheight - p->mo->height;
+		if (!starpostnum) // spawnpoints...
+		{
+			mapthing_t *mt;
+
+			if (starpostpath >= numcoopstarts)
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("Player %d spawnpoint not found (%d max).\n"), starpostpath+1, numcoopstarts-1);
+				return;
+			}
+
+			mt = playerstarts[starpostpath]; // Given above check, should never be NULL.
+			intx = mt->x<<FRACBITS;
+			inty = mt->y<<FRACBITS;
+
+			ss = R_IsPointInSubsector(intx, inty);
+			if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("Spawnpoint not in a valid location.\n"));
+				return;
+			}
+
+			// Flagging a player's ambush will make them start on the ceiling
+			// Objectflip inverts
+			if (!!(mt->options & MTF_AMBUSH) ^ !!(mt->options & MTF_OBJECTFLIP))
+			{
+				intz = ss->sector->ceilingheight - p->mo->height;
+				if (mt->options >> ZSHIFT)
+					intz -= ((mt->options >> ZSHIFT) << FRACBITS);
+			}
+			else
+			{
+				intz = ss->sector->floorheight;
+				if (mt->options >> ZSHIFT)
+					intz += ((mt->options >> ZSHIFT) << FRACBITS);
+			}
+
+			if (mt->options & MTF_OBJECTFLIP) // flip the player!
+			{
+				p->mo->eflags |= MFE_VERTICALFLIP;
+				p->mo->flags2 |= MF2_OBJECTFLIP;
+			}
+			else
+			{
+				p->mo->eflags &= ~MFE_VERTICALFLIP;
+				p->mo->flags2 &= ~MF2_OBJECTFLIP;
+			}
+
+			localangle = p->mo->angle = p->drawangle = FixedAngle(mt->angle<<FRACBITS);
+		}
+		else // scan the thinkers to find starposts...
+		{
+			mobj_t *mo2 = NULL;
+			thinker_t *th;
+
+			INT32 starpostmax = 0;
+			intz = starpostpath; // variable reuse - counting down for selection purposes
+
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+			{
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+
+				if (mo2->type != MT_STARPOST)
+					continue;
+
+				if (mo2->health != starpostnum)
+				{
+					if (mo2->health > starpostmax)
+						starpostmax = mo2->health;
+					continue;
+				}
+
+				if (intz--)
+					continue;
+
+				break;
+			}
+
+			if (th == &thlist[THINK_MOBJ])
+			{
+				if (intz == starpostpath)
+					CONS_Alert(CONS_NOTICE, M_GetText("No starpost of position %d found (%d max).\n"), starpostnum, starpostmax);
+				else
+					CONS_Alert(CONS_NOTICE, M_GetText("Starpost of position %d, %d not found (%d, %d max).\n"), starpostnum, starpostpath, starpostmax, (starpostpath-intz)-1);
+				return;
+			}
+
+			ss = R_IsPointInSubsector(mo2->x, mo2->y);
+			if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("Starpost not in a valid location.\n"));
+				return;
+			}
+
+			intx = mo2->x;
+			inty = mo2->y;
+			intz = mo2->z;
+
+			if (mo2->flags2 & MF2_OBJECTFLIP) // flip the player!
+			{
+				p->mo->eflags |= MFE_VERTICALFLIP;
+				p->mo->flags2 |= MF2_OBJECTFLIP;
+			}
+			else
+			{
+				p->mo->eflags &= ~MFE_VERTICALFLIP;
+				p->mo->flags2 &= ~MF2_OBJECTFLIP;
+			}
+
+			localangle = p->mo->angle = p->drawangle = mo2->angle;
+		}
+
+		CONS_Printf(M_GetText("Teleporting to checkpoint %d, %d...\n"), starpostnum, starpostpath);
 	}
 	else
-		intz = ss->sector->floorheight;
+	{
+		i = COM_CheckParm("-nop"); // undocumented stupid addition to allow pivoting on the spot with -ang and -aim
+		if (i)
+		{
+			intx = p->mo->x;
+			inty = p->mo->y;
+		}
+		else
+		{
+			i = COM_CheckParm("-x");
+			if (i)
+				intx = atoi(COM_Argv(i + 1))<<FRACBITS;
+			else
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified.\n"), "X");
+				return;
+			}
+
+			i = COM_CheckParm("-y");
+			if (i)
+				inty = atoi(COM_Argv(i + 1))<<FRACBITS;
+			else
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified.\n"), "Y");
+				return;
+			}
+		}
+
+		ss = R_IsPointInSubsector(intx, inty);
+		if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
+		{
+			CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
+			return;
+		}
+		i = COM_CheckParm("-z");
+		if (i)
+		{
+			intz = atoi(COM_Argv(i + 1))<<FRACBITS;
+			if (intz < ss->sector->floorheight)
+				intz = ss->sector->floorheight;
+			if (intz > ss->sector->ceilingheight - p->mo->height)
+				intz = ss->sector->ceilingheight - p->mo->height;
+		}
+		else
+			intz = ((p->mo->eflags & MFE_VERTICALFLIP) ? ss->sector->ceilingheight : ss->sector->floorheight);
+
+		i = COM_CheckParm("-ang");
+		if (i)
+			localangle = p->drawangle = p->mo->angle = FixedAngle(atoi(COM_Argv(i + 1))<<FRACBITS);
 
-	CONS_Printf(M_GetText("Teleporting to %d, %d, %d...\n"), intx, inty, FixedInt(intz));
+		i = COM_CheckParm("-aim");
+		if (i)
+		{
+			angle_t aim = FixedAngle(atoi(COM_Argv(i + 1))<<FRACBITS);
+			if (aim >= ANGLE_90 && aim <= ANGLE_270)
+			{
+				CONS_Alert(CONS_NOTICE, M_GetText("Not a valid aiming angle (between +/-90).\n"));
+				return;
+			}
+			localaiming = p->aiming = aim;
+		}
+
+		CONS_Printf(M_GetText("Teleporting to %d, %d, %d...\n"), FixedInt(intx), FixedInt(inty), FixedInt(intz));
+	}
 
 	P_MapStart();
-	if (!P_TeleportMove(p->mo, intx*FRACUNIT, inty*FRACUNIT, intz))
+	if (!P_TeleportMove(p->mo, intx, inty, intz))
 		CONS_Alert(CONS_WARNING, M_GetText("Unable to teleport to that spot!\n"));
 	else
 		S_StartSound(p->mo, sfx_mixup);
@@ -725,11 +880,19 @@ void Command_Setrings_f(void)
 
 	if (COM_Argc() > 1)
 	{
-		// P_GivePlayerRings does value clamping
-		players[consoleplayer].rings = 0;
-		P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1)));
-		if (!G_IsSpecialStage(gamemap) || !useNightsSS)
+		if (!(maptol & TOL_NIGHTS))
+		{
+			// P_GivePlayerRings does value clamping
+			players[consoleplayer].rings = 0;
+			P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1)));
 			players[consoleplayer].totalring -= atoi(COM_Argv(1)); //undo totalring addition done in P_GivePlayerRings
+		}
+		else
+		{
+			players[consoleplayer].spheres = 0;
+			P_GivePlayerSpheres(&players[consoleplayer], atoi(COM_Argv(1)));
+			// no totalsphere addition to revert
+		}
 
 		G_SetGameModified(multiplayer);
 	}
@@ -744,9 +907,15 @@ void Command_Setlives_f(void)
 
 	if (COM_Argc() > 1)
 	{
-		// P_GivePlayerLives does value clamping
-		players[consoleplayer].lives = 0;
-		P_GivePlayerLives(&players[consoleplayer], atoi(COM_Argv(1)));
+		SINT8 lives = atoi(COM_Argv(1));
+		if (lives == -1)
+			players[consoleplayer].lives = INFLIVES; // infinity!
+		else
+		{
+			// P_GivePlayerLives does value clamping
+			players[consoleplayer].lives = 0;
+			P_GivePlayerLives(&players[consoleplayer], atoi(COM_Argv(1)));
+		}
 
 		G_SetGameModified(multiplayer);
 	}
@@ -779,10 +948,12 @@ void Command_Setcontinues_f(void)
 static CV_PossibleValue_t op_mapthing_t[] = {{0, "MIN"}, {4095, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t op_speed_t[] = {{1, "MIN"}, {128, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t op_flags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t op_hoopflags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
 
 consvar_t cv_mapthingnum = {"op_mapthingnum", "0", CV_NOTINNET, op_mapthing_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_speed = {"op_speed", "16", CV_NOTINNET, op_speed_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_opflags = {"op_flags", "0", CV_NOTINNET, op_flags_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ophoopflags = {"op_hoopflags", "4", CV_NOTINNET, op_hoopflags_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 boolean objectplacing = false;
 mobjtype_t op_currentthing = 0; // For the object placement mode
@@ -898,15 +1069,16 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 		thinker_t *th;
 		mobj_t *mo;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo = (mobj_t *)th;
 			// get offset from mt, which points to old mapthings, then add new location
-			if (mo->spawnpoint)
-				mo->spawnpoint = (mo->spawnpoint - mt) + mapthings;
+			if (!mo->spawnpoint)
+				continue;
+			mo->spawnpoint = (mo->spawnpoint - mt) + mapthings;
 		}
 	}
 
@@ -986,17 +1158,10 @@ void OP_NightsObjectplace(player_t *player)
 	{
 		UINT16 angle = (UINT16)(player->anotherflyangle % 360);
 		INT16 temp = (INT16)FixedInt(AngleFixed(player->mo->angle)); // Traditional 2D Angle
-		sector_t *sec = player->mo->subsector->sector;
-#ifdef ESLOPE
-		fixed_t fheight = sec->f_slope ? P_GetZAt(sec->f_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->floorheight;
-#else
-		fixed_t fheight = sec->floorheight;
-#endif
-
 
 		player->pflags |= PF_ATTACKDOWN;
 
-		mt = OP_CreateNewMapThing(player, 1705, false);
+		mt = OP_CreateNewMapThing(player, 1713, false);
 
 		// Tilt
 		mt->angle = (INT16)FixedInt(FixedDiv(angle*FRACUNIT, 360*(FRACUNIT/256)));
@@ -1007,43 +1172,84 @@ void OP_NightsObjectplace(player_t *player)
 			temp += 90;
 		temp %= 360;
 
-		mt->options = (UINT16)((player->mo->z - fheight)>>FRACBITS);
+		mt->options = (mt->options & ~(UINT16)cv_opflags.value) | (UINT16)cv_ophoopflags.value;
 		mt->angle = (INT16)(mt->angle+(INT16)((FixedInt(FixedDiv(temp*FRACUNIT, 360*(FRACUNIT/256))))<<8));
 
-		P_SpawnHoopsAndRings(mt);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
 	// This places a bumper!
 	if (cmd->buttons & BT_TOSSFLAG)
 	{
+		UINT16 vertangle = (UINT16)(player->anotherflyangle % 360);
+		UINT16 newflags, newz;
+
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
 			return;
 
 		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_NIGHTSBUMPER].doomednum, false);
+		newz = min((mt->options >> ZSHIFT) - (mobjinfo[MT_NIGHTSBUMPER].height/4), 0);
+			// height offset: from P_TouchSpecialThing case MT_NIGHTSBUMPER
+
+		// clockwise
+		if (vertangle >= 75 && vertangle < 105) // up
+			newflags = 3;
+		else if (vertangle >= 105 && vertangle < 135) // 60 upward tilt
+			newflags = 2;
+		else if (vertangle >= 135 && vertangle < 165) // 30 upward tilt
+			newflags = 1;
+		//else if (vertangle >= 165 && vertangle < 195) // forward, see else case
+		//	newflags = 0;
+		else if (vertangle >= 195 && vertangle < 225) // 30 downward tilt
+			newflags = 11;
+		else if (vertangle >= 225 && vertangle < 255) // 60 downward tilt
+			newflags = 10;
+		else if (vertangle >= 255 && vertangle < 285) // down
+			newflags = 9;
+		else if (vertangle >= 285 && vertangle < 315) // 60 downward tilt backwards
+			newflags = 8;
+		else if (vertangle >= 315 && vertangle < 345) // 30 downward tilt backwards
+			newflags = 7;
+		else if (vertangle >= 345 || vertangle < 15) // backwards
+			newflags = 6;
+		else if (vertangle >= 15 && vertangle < 45) // 30 upward tilt backwards
+			newflags = 5;
+		else if (vertangle >= 45 && vertangle < 75) // 60 upward tilt backwards
+			newflags = 4;
+		else // forward
+			newflags = 0;
+
+		mt->options = (newz << ZSHIFT) | newflags;
+
+		// if NiGHTS is facing backwards, orient the Thing angle forwards so that the sprite angle
+		// displays correctly. Backwards movement via the Thing flags is unaffected.
+		if (vertangle < 90 || vertangle > 270)
+			mt->angle = (mt->angle + 180) % 360;
+
 		P_SpawnMapThing(mt);
 	}
 
-	// This places a ring!
+	// This places a sphere!
 	if (cmd->buttons & BT_WEAPONNEXT)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
 			return;
 
-		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_RING].doomednum, false);
-		P_SpawnHoopsAndRings(mt);
+		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_BLUESPHERE].doomednum, false);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
-	// This places a wing item!
+	// This places a ring!
 	if (cmd->buttons & BT_WEAPONPREV)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
 			return;
 
-		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_NIGHTSWING].doomednum, false);
-		P_SpawnHoopsAndRings(mt);
+		mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_RING].doomednum, false);
+		P_SpawnHoopsAndRings(mt, false);
 	}
 
 	// This places a custom object as defined in the console cv_mapthingnum.
@@ -1077,12 +1283,12 @@ void OP_NightsObjectplace(player_t *player)
 
 		if (mt->type == 300 // Ring
 		|| mt->type == 308 || mt->type == 309 // Team Rings
-		|| mt->type == 1706 // Nights Wing
+		|| mt->type == 1706 // Sphere
 		|| (mt->type >= 600 && mt->type <= 609) // Placement patterns
 		|| mt->type == 1705 || mt->type == 1713 // NiGHTS Hoops
 		|| mt->type == 1800) // Mario Coin
 		{
-			P_SpawnHoopsAndRings(mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 		else
 			P_SpawnMapThing(mt);
@@ -1227,7 +1433,7 @@ void OP_ObjectplaceMovement(player_t *player)
 		|| mt->type == 1705 || mt->type == 1713 // NiGHTS Hoops
 		|| mt->type == 1800) // Mario Coin
 		{
-			P_SpawnHoopsAndRings(mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 		else
 			P_SpawnMapThing(mt);
@@ -1266,7 +1472,7 @@ void Command_ObjectPlace_f(void)
 
 		if (!COM_CheckParm("-silent"))
 		{
-			HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE);
+			HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE|V_AUTOFADEOUT);
 			HU_SetCEchoDuration(10);
 			HU_DoCEcho(va(M_GetText(
 				"\\\\\\\\\\\\\\\\\\\\\\\\\x82"
diff --git a/src/m_cheat.h b/src/m_cheat.h
index 951c7a16a261f435fc464f37dd93585c0c156b2e..aa9b0bfebb9cea80853fd2a49b71c376bf5708a9 100644
--- a/src/m_cheat.h
+++ b/src/m_cheat.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,7 +28,7 @@ void cht_Init(void);
 void Command_ObjectPlace_f(void);
 void Command_Writethings_f(void);
 
-extern consvar_t cv_opflags, cv_mapthingnum, cv_speed;
+extern consvar_t cv_opflags, cv_ophoopflags, cv_mapthingnum, cv_speed;
 //extern consvar_t cv_snapto, cv_grid;
 
 extern boolean objectplacing;
diff --git a/src/m_cond.c b/src/m_cond.c
index e9d6556942febe8b5c81916d35b243fc8654eccc..539c6d1f6d1fc38efacad7d1e56329a323010afb 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,649 +28,18 @@ UINT32 unlocktriggers;
 // The meat of this system lies in condition sets
 conditionset_t conditionSets[MAXCONDITIONSETS];
 
-// Default Emblem locations
-emblem_t emblemlocations[MAXEMBLEMS] =
-{
-	// GREEN FLOWER 1
-	// ---
-	{0,  8156,  6936,   129, 1, 'A', SKINCOLOR_BLUE, 0,
-		"Go get your feet wet\n"
-		"to find this, the first emblem.\n"
-		"Yes, it's very deep.", 0},
-	{0,  3184,  1812,   928, 1, 'B', SKINCOLOR_LAVENDER, 0,
-		"There are many rings,\n"
-		"but this one's not what you think.\n"
-		"There lies the emblem.", 0},
-	{0,  9024,  6716,   769, 1, 'C', SKINCOLOR_RED, 0,
-		"Right next to a lake,\n"
-		"a ledge has been constructed.\n"
-		"Near there is the goal.", 0},
-	{0,  2080,  -384,   512, 1, 'D', SKINCOLOR_ORANGE, 0,
-		"Streams come to an end\n"
-		"where they can no longer fall.\n"
-		"But if you went up...", 0},
-	{0,  -336,  2064,   195, 1, 'E', SKINCOLOR_EMERALD, 0,
-		"This one's in plain sight.\n"
-		"Why haven't you claimed it?\n"
-		"Surely you saw it.", 0},
-
-	{ET_SCORE, 0,0,0,  1, 'S', SKINCOLOR_BROWN,      125000, "", 0},
-	{ET_TIME,  0,0,0,  1, 'T', SKINCOLOR_GREY,   20*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  1, 'R', SKINCOLOR_GOLD,          200, "", 0},
-
-
-	// GREEN FLOWER 2
-	// ---
-	{0,  2624, -6816,  1332, 2, 'A', SKINCOLOR_BLUE, 0,
-		"Near the giant lake\n"
-		"lies a cave with a 1-Up.\n"
-		"An emblem's there, too!", 0},
-	{0, -5728, -2848,  2848, 2, 'B', SKINCOLOR_LAVENDER, 0,
-		"Near the final lake,\n"
-		"a higher lake falls on in.\n"
-		"Three platforms await.", 0},
-	{0,  3648,  6464,  2576, 2, 'C', SKINCOLOR_RED, 0,
-		"Near the level's start,\n"
-		"a bridge crosses a river.\n"
-		"What's that river's source?", 0},
-	{0, -2032,-10048,   986, 2, 'D', SKINCOLOR_ORANGE, 0,
-		"Near the level's end,\n"
-		"another bridge spans a lake.\n"
-		"What could be under...?", 0},
-	{0,  -170,   491,  3821, 2, 'E', SKINCOLOR_EMERALD, 0,
-		"An ivied tunnel\n"
-		"has a corner that's sunlit.\n"
-		"Go reach for the sky!", 0},
-
-	{ET_SCORE, 0,0,0,  2, 'S', SKINCOLOR_BROWN,      150000, "", 0},
-	{ET_TIME,  0,0,0,  2, 'T', SKINCOLOR_GREY,   40*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  2, 'R', SKINCOLOR_GOLD,          200, "", 0},
-
-
-	// GREEN FLOWER 3
-	// ---
-	{ET_TIME,  0,0,0,  3, 'T', SKINCOLOR_GREY,   30*TICRATE, "", 0},
-
-
-	// TECHNO HILL 1
-	// ---
-	{0, -5664, -5072,  2396, 4, 'A', SKINCOLOR_BLUE, 0,
-		"Three pipes reside near\n"
-		"where our heroes' paths split off.\n"
-		"You'll have to look up!", 0},
-	{0,  -784,-13968,  2888, 4, 'B', SKINCOLOR_LAVENDER, 0,
-		"Climbing yields great range.\n"
-		"Yet, on a path for climbers,\n"
-		"flying is the key.", 0},
-	{0,  4160, -5824,  3776, 4, 'C', SKINCOLOR_RED, 0,
-		"That's sure lots of slime.\n"
-		"Say, do you ever wonder\n"
-		"what's dumping it all?", 0},
-	{0,  6400, -8352,  1764, 4, 'D', SKINCOLOR_ORANGE, 0,
-		"Spinning through small gaps\n"
-		"can slip you into a cave.\n"
-		"In that cave's first stretch...", 0},
-	{0,  2848, -9088,   488, 4, 'E', SKINCOLOR_EMERALD, 0,
-		"The slime lake is deep,\n"
-		"but reaching the floor takes height.\n"
-		"Scream \"Geronimo!\"...", 0},
-
-	{ET_SCORE, 0,0,0,  4, 'S', SKINCOLOR_BROWN,       75000, "", 0},
-	{ET_TIME,  0,0,0,  4, 'T', SKINCOLOR_GREY,   75*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  4, 'R', SKINCOLOR_GOLD,          300, "", 0},
-
-
-	// TECHNO HILL 2
-	// ---
-	{0,-19138, -2692,   688, 5, 'A', SKINCOLOR_BLUE, 0,
-		"Near the first checkpoint,\n"
-		"a bridge crosses a slime pool.\n"
-		"(Sensing a pattern?)", 0},
-	{0,-13120,  8062,  1248, 5, 'B', SKINCOLOR_LAVENDER, 0,
-		"Behind the windows,\n"
-		"near crushers, ever smashing\n"
-		"a conveyor belt.", 0},
-	{0,   580,  4552,  1344, 5, 'C', SKINCOLOR_RED, 0,
-		"A pipe drops onto\n"
-		"a half-outdoors conveyor.\n"
-		"But is it empty?", 0},
-	{0,   192, -8768,    24, 5, 'D', SKINCOLOR_ORANGE, 0,
-		"There is a hallway\n"
-		"that a button floods with slime.\n"
-		"Go through it again!", 0},
-	{0, -2468,-12128,  1312, 5, 'E', SKINCOLOR_EMERALD, 0,
-		"Jumping on turtles\n"
-		"will send you springing skyward.\n"
-		"Now, do that six times...", 0},
-
-	{ET_SCORE, 0,0,0,  5, 'S', SKINCOLOR_BROWN,      100000, "", 0},
-	{ET_TIME,  0,0,0,  5, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  5, 'R', SKINCOLOR_GOLD,          600, "", 0},
-
-
-	// TECHNO HILL 3
-	// ---
-	{ET_TIME,  0,0,0,  6, 'T', SKINCOLOR_GREY,   40*TICRATE, "", 0},
-
-
-	// DEEP SEA 1
-	// ---
-	{0,-16224, -2880,  3530, 7, 'A', SKINCOLOR_BLUE, 0,
-		"Climb up two maze walls.\n"
-		"Break the roof, then a corner.\n"
-		"There, glide, but stay dry.", 0},
-	{0, -8224,   896,  1056, 7, 'B', SKINCOLOR_LAVENDER, 0,
-		"Follow the left path.\n"
-		"A square green button lurks deep.\n"
-		"Weight it down, somehow.", 0},
-	{0,  4992, -5072,  4136, 7, 'C', SKINCOLOR_RED, 0,
-		"A certain path holds\n"
-		"many gargoyle puzzles.\n"
-		"Victors reach a \"V\".", 0},
-	{0,  4576,  5168,  2660, 7, 'D', SKINCOLOR_ORANGE, 0,
-		"A caved-in hallway?\n"
-		"The floor falls; the path goes down.\n"
-		"But those rocks looked weak...", 0},
-	{0, 12576, 16096,  -992, 7, 'E', SKINCOLOR_EMERALD, 0,
-		"The end is quite dry.\n"
-		"Some rocks dam the water in.\n"
-		"Knuckles can fix that...", 0},
-
-	{ET_SCORE, 0,0,0,  7, 'S', SKINCOLOR_BROWN,       75000, "", 0},
-	{ET_TIME,  0,0,0,  7, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  7, 'R', SKINCOLOR_GOLD,          400, "", 0},
-
-
-	// DEEP SEA 2
-	// ---
-	{0,-15040,  6976,  2016, 8, 'A', SKINCOLOR_BLUE, 0,
-		"A waterfall lands\n"
-		"near a starpost in a cave.\n"
-		"It's dark up there, but...", 0},
-	{0,  4288,  2912,   544, 8, 'B', SKINCOLOR_LAVENDER, 0,
-		"So many blocks here!\n"
-		"Take five; bathe in the fountain.\n"
-		"Hmmm? A hidden path...?", 0},
-	{0, -5696, 16992,   791, 8, 'C', SKINCOLOR_RED, 0,
-		"An ornate dragon\n"
-		"faces a secret passage.\n"
-		"Knuckles! Don't get crushed!", 0},
-	{0,-13344, 18688,  1034, 8, 'D', SKINCOLOR_ORANGE, 0,
-		"In the current maze\n"
-		"hides a dark room of columns.\n"
-		"Find it, then look up.", 0},
-	{0,  3104, 16192,  2408, 8, 'E', SKINCOLOR_EMERALD,  0,
-		"That same dragon's eye\n"
-		"hides another secret room.\n"
-		"There, solve its riddle.", 0},
-
-	{ET_SCORE, 0,0,0,  8, 'S', SKINCOLOR_BROWN,       50000, "", 0},
-	{ET_TIME,  0,0,0,  8, 'T', SKINCOLOR_GREY,  150*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0,  8, 'R', SKINCOLOR_GOLD,          250, "", 0},
-
-
-	// DEEP SEA 3
-	// ---
-	{ET_TIME,  0,0,0,  9, 'T', SKINCOLOR_GREY,   90*TICRATE, "", 0},
-
-
-	// CASTLE EGGMAN 1
-	// ---
-	{0, -6176, -5184,  -128, 10, 'A', SKINCOLOR_BLUE, 0,
-		"A drain feeds the lake.\n"
-		"Water rushes quickly through.\n"
-		"Go against the flow.", 0},
-	{0,  3648,-15296,  -992, 10, 'B', SKINCOLOR_LAVENDER, 0,
-		"The left starting path\n"
-		"goes atop a large wood deck.\n"
-		"Checked underneath yet?", 0},
-	{0, 11712, 21312,  5472, 10, 'C', SKINCOLOR_RED, 0,
-		"At last, the castle!\n"
-		"Hold up! Don't just barge right in!\n"
-		"What's the facade hold...?", 0},
-	{0, 20224, 13344,  3104, 10, 'D', SKINCOLOR_ORANGE, 0,
-		"The final approach!\n"
-		"A tower holds the emblem\n"
-		"near a ring arrow.", 0},
-	{0,  9472, -5890,   710, 10, 'E', SKINCOLOR_EMERALD, 0,
-		"The right starting path\n"
-		"hides this near a canopy,\n"
-		"high, where two trees meet.", 0},
-
-	{ET_SCORE, 0,0,0, 10, 'S', SKINCOLOR_BROWN,       50000, "", 0},
-	{ET_TIME,  0,0,0, 10, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 10, 'R', SKINCOLOR_GOLD,          200, "", 0},
-
-
-	// CASTLE EGGMAN 2
-	// ---
-	{0,   832,-15168,  7808, 11, 'A', SKINCOLOR_BLUE, 0,
-		"Find a trick bookcase\n"
-		"that hides a darkened hallway.\n"
-		"There, climb a tower.", 0},
-	{0,-18460,-22180,  2416, 11, 'B', SKINCOLOR_LAVENDER, 0,
-		"Down in the dungeon,\n"
-		"a cracked wall hides secret paths.\n"
-		"Echidnas only!", 0},
-	{0, -6144,-11792,  3232, 11, 'C', SKINCOLOR_RED, 0,
-		"A room you can flood!\n"
-		"A brown grate's near its exit.\n"
-		"Knuckles can break it...", 0},
-	{0,  4608, -7024,  4256, 11, 'D', SKINCOLOR_ORANGE, 0,
-		"Some of these bookshelves\n"
-		"are not flush against the walls.\n"
-		"Wonder why that is?", 0},
-	{0, 12708,-13536,  4768, 11, 'E', SKINCOLOR_EMERALD, 0,
-		"The ending's towers\n"
-		"are hiding a small alcove.\n"
-		"Check around outside.", 0},
-
-	{ET_SCORE, 0,0,0, 11, 'S', SKINCOLOR_BROWN,      400000, "", 0},
-	{ET_TIME,  0,0,0, 11, 'T', SKINCOLOR_GREY,  210*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 11, 'R', SKINCOLOR_GOLD,          600, "", 0},
-
-
-	// CASTLE EGGMAN 3
-	// ---
-	{ET_TIME,  0,0,0, 12, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-
-
-	// ARID CANYON 1
-	// ---
-	{0,  3488,  2208,  3072, 13, 'A', SKINCOLOR_BLUE, 0,
-		"A rather large gap\n"
-		"must be crossed by way of tram.\n"
-		"At its end, jump left.", 0},
-	{0, -7552, 10464,  4094, 13, 'B', SKINCOLOR_LAVENDER, 0,
-		"Crushers that go up!\n"
-		"Mind your step; if they're triggered,\n"
-		"they'll block this emblem.", 0},
-	{0,-12093, 14575,  5752, 13, 'C', SKINCOLOR_RED, 0,
-		"There's an oil lake\n"
-		"that you can sink deep into.\n"
-		"Drain it, and explore.", 0},
-	{0,   512, -7136,  4640, 13, 'D', SKINCOLOR_ORANGE, 0,
-		"Not far from the start,\n"
-		"if you climb toward the sky,\n"
-		"the cliffs hide something.", 0},
-	{0, 12504,  6848,  3424, 13, 'E', SKINCOLOR_EMERALD, 0,
-		"Right by the exit,\n"
-		"an emblem lies on a cliff.\n"
-		"Ride ropes to reach it.", 0},
-
-	{ET_SCORE, 0,0,0, 13, 'S', SKINCOLOR_BROWN,       50000, "", 0},
-	{ET_TIME,  0,0,0, 13, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 13, 'R', SKINCOLOR_GOLD,          300, "", 0},
-
-
-	// RED VOLCANO 1
-	// ---
-	{0,-13184, 11424,  3080, 16, 'A', SKINCOLOR_BLUE, 0,
-		"Look around the room,\n"
-		"just before you clear the stage;\n"
-		"something's hidden there!", 0},
-	{0, -2816,  3120,  3044, 16, 'B', SKINCOLOR_LAVENDER, 0,
-		"Ever look upwards\n"
-		"when you're traversing across\n"
-		"collapsing platforms?", 0},
-	{0,  6720,  6784,  1452, 16, 'C', SKINCOLOR_RED, 0,
-		"Check out a corner\n"
-		"of a lake of magma near\n"
-		"spinning jets of flame.", 0},
-	{0, -5504,  9824,   800, 16, 'D', SKINCOLOR_ORANGE, 0,
-		"Where once a bridge stood,\n"
-		"now magma falls from above.\n"
-		"The bridge dropped something...", 0},
-	{0,  8287,-11043,  1328, 16, 'E', SKINCOLOR_EMERALD, 0,
-		"A lake of magma\n"
-		"ebbs and flows unendingly.\n"
-		"Wait for its nadir.", 0},
-
-	{ET_SCORE, 0,0,0, 16, 'S', SKINCOLOR_BROWN,       30000, "", 0},
-	{ET_TIME,  0,0,0, 16, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 16, 'R', SKINCOLOR_GOLD,          100, "", 0},
-
-
-	// EGG ROCK 1
-	// ---
-	{0,-10976, -7328,  1584, 22, 'A', SKINCOLOR_BLUE, 0,
-		"Vanishing platforms,\n"
-		"then collapsing ones herald\n"
-		"a last-second jump.", 0},
-	{0, -6592,-11200,  2208, 22, 'B', SKINCOLOR_LAVENDER, 0,
-		"What is this red stuff?\n"
-		"You can't breathe it in, but look!\n"
-		"It can't reach up there...", 0},
-	{0,  6816,   832,   936, 22, 'C', SKINCOLOR_RED, 0,
-		"The team's paths diverge.\n"
-		"Should Tails run the crusher path?\n"
-		"No! Fly outside it!", 0},
-	{0,  6942, -8902,  2080, 22, 'D', SKINCOLOR_ORANGE, 0,
-		"Don't jump too high here!\n"
-		"No conveyor will catch you;\n"
-		"you'd fall to your death.", 0},
-	{0, -6432, -6192,   584, 22, 'E', SKINCOLOR_EMERALD, 0,
-		"Conveyors! Magma!\n"
-		"What an intense room this is!\n"
-		"But, what brought you here?", 0},
-
-	{ET_SCORE, 0,0,0, 22, 'S', SKINCOLOR_BROWN,       25000, "", 0},
-	{ET_TIME,  0,0,0, 22, 'T', SKINCOLOR_GREY,  120*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 22, 'R', SKINCOLOR_GOLD,          150, "", 0},
-
-
-	// EGG ROCK 2
-	// ---
-	{0, -6672,  7792,   352, 23, 'A', SKINCOLOR_BLUE, 0,
-		"Walk on the ceiling;\n"
-		"resist the urge to flip back!\n"
-		"Find the cyan path...", 0},
-	{0,-12256, 15136,  -288, 23, 'B', SKINCOLOR_LAVENDER, 0,
-		"X marks the spot? Nope!\n"
-		"Try standing further away\n"
-		"when the timer flips.", 0},
-	{0,  1536, 16224,  1144, 23, 'C', SKINCOLOR_RED, 0,
-		"There is more than one\n"
-		"elevator inside the\n"
-		"elevator shaft...", 0},
-	{0,-15968, 14192,  3152, 23, 'D', SKINCOLOR_ORANGE, 0,
-		"Gears with missing teeth\n"
-		"can hide a clever secret!\n"
-		"Think Green Hill Zone boss.", 0},
-	{0,  1920, 20608,  1064, 23, 'E', SKINCOLOR_EMERALD, 0,
-		"Just before you reach\n"
-		"the defective cargo bay,\n"
-		"fly under a bridge.", 0},
-
-	{ET_SCORE, 0,0,0, 23, 'S', SKINCOLOR_BROWN,       60000, "", 0},
-	{ET_TIME,  0,0,0, 23, 'T', SKINCOLOR_GREY,  300*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 23, 'R', SKINCOLOR_GOLD,          250, "", 0},
-
-
-	// EGG ROCK 3
-	// ---
-/* Just in case, I'll leave these here in the source.
-	{0,   848, -3584,   592, 24, 'A', SKINCOLOR_BLUE, 0,
-		"[PH] Hiding at the end of the first hallway.", 0},
-	{0,-10368, -2816,   144, 24, 'B', SKINCOLOR_LAVENDER, 0,
-		"Directions are meaningless.", 0},
-	{0, -8160, -5952,   560, 24, 'C', SKINCOLOR_RED, 0,
-		"[PH] In the ceiling of the conveyor belt + laser hallway.", 0},
-	{0,-13728,-13728,  1552, 24, 'D', SKINCOLOR_ORANGE, 0,
-		"[PH] On top of the platform with rows of spikes in reverse gravity.", 0},
-	{0,-14944,   768,  1232, 24, 'E', SKINCOLOR_EMERALD, 0,
-		"Follow the leader.", 0},
-*/
-
-	{ET_SCORE, 0,0,0, 24, 'S', SKINCOLOR_BROWN,       14000, "", 0},
-	{ET_TIME,  0,0,0, 24, 'T', SKINCOLOR_GREY,  210*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 24, 'R', SKINCOLOR_GOLD,          100, "", 0},
-
-
-	// EGG ROCK CORE
-	// ---
-	{ET_TIME,  0,0,0, 25, 'T', SKINCOLOR_GREY,  100*TICRATE, "", 0},
-
-
-	// PIPE TOWERS
-	// ---
-	{0,  3182,  5040,  3008, 30, 'A', SKINCOLOR_BLUE, 0,
-		"A pipe in the roof\n"
-		"eternally drops water.\n"
-		"Something's stuck up there.", 0},
-	{0, -2400,  5984,  2752, 30, 'B', SKINCOLOR_LAVENDER, 0,
-		"Pushing a red switch\n"
-		"raises the water level;\n"
-		"from there, can't miss it.", 0},
-	{0,  6112,  7008,  4032, 30, 'C', SKINCOLOR_RED, 0,
-		"A high-up passage\n"
-		"hides near the second checkpoint.\n"
-		"Climb in; then, climb more.", 0},
-	{0, 11424, -4832,  1376, 30, 'D', SKINCOLOR_ORANGE, 0,
-		"The underground room\n"
-		"with platforms that fall and rise\n"
-		"only LOOKS empty...", 0},
-	{0 , 4960, -6112,  1312, 30, 'E', SKINCOLOR_EMERALD, 0,
-		"This one's straightforward.\n"
-		"What comes to mind when I say:\n"
-		"\"WELCOME TO WARP ZONE!\"?", 0},
-
-	{ET_SCORE, 0,0,0, 30, 'S', SKINCOLOR_BROWN,       75000, "", 0},
-	{ET_TIME,  0,0,0, 30, 'T', SKINCOLOR_GREY,  100*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 30, 'R', SKINCOLOR_GOLD,          300, "", 0},
-
-
-	// AERIAL GARDEN
-	// ---
-	{0, 10176,-14304,  1796, 40, 'A', SKINCOLOR_BLUE, 0,
-		"A central tower,\n"
-		"one with many waterfalls,\n"
-		"hides a secret room.", 0},
-	{0,   480, 17696,  6496, 40, 'B', SKINCOLOR_LAVENDER, 0,
-		"Hidden off the path\n"
-		"lies a skyscraping tower.\n"
-		"A lake's at the top.", 0},
-	{0, -8896, 13248,  3362, 40, 'C', SKINCOLOR_RED, 0,
-		"Find all four buttons\n"
-		"that sink when you stand on them.\n"
-		"They'll open a door...", 0},
-	{0, -8896, -9952,  2480, 40, 'D', SKINCOLOR_ORANGE, 0,
-		"Much like the last one,\n"
-		"you need to find some switches.\n"
-		"Only two, this time.", 0},
-	{0, 13184, 18880,  6672, 40, 'E', SKINCOLOR_EMERALD, 0,
-		"The inner sanctum!\n"
-		"Teleport to its switches;\n"
-		"then, check near the goal.", 0},
-
-	{ET_SCORE, 0,0,0, 40, 'S', SKINCOLOR_BROWN,      300000, "", 0},
-	{ET_TIME,  0,0,0, 40, 'T', SKINCOLOR_GREY,  240*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 40, 'R', SKINCOLOR_GOLD,         1200, "", 0},
-
-
-	// AZURE TEMPLE
-	// ---
-	{0, -2400,  7552,  1120, 41, 'A', SKINCOLOR_BLUE, 0,
-		"For those who can swim,\n"
-		"a long tunnel hides rewards.\n"
-		"Do mind the Buzzes!", 0},
-	{0,   -64, 14016,  2072, 41, 'B', SKINCOLOR_LAVENDER, 0,
-		"So many skylights!\n"
-		"A markedly large one hides\n"
-		"behind a starpost...", 0},
-	{0,  2976, 13920,   -32, 41, 'C', SKINCOLOR_RED, 0,
-		"When you reach gauntlets\n"
-		"of diagonal fire,\n"
-		"check out the corners.", 0},
-	{0,  2176, 22592,  1376, 41, 'D', SKINCOLOR_ORANGE, 0,
-		"A room of currents;\n"
-		"most of them are marked by spikes.\n"
-		"This one? A corner.", 0},
-	{0, -4128, 21344,  1120, 41, 'E', SKINCOLOR_EMERALD, 0,
-		"The only way to hit\n"
-		"all those gems at once is with\n"
-		"a radial blast.", 0},
-
-	{ET_SCORE, 0,0,0, 41, 'S', SKINCOLOR_BROWN,      425000, "", 0},
-	{ET_TIME,  0,0,0, 41, 'T', SKINCOLOR_GREY,  240*TICRATE, "", 0},
-	{ET_RINGS, 0,0,0, 41, 'R', SKINCOLOR_GOLD,          300, "", 0},
-
-
-	// FLORAL FIELD
-	// ---
-	{0, 5394, -996, 160, 50, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,   50, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,   50, 'T', SKINCOLOR_GREY,  40*TICRATE, "", 0},
-
-
-	// TOXIC PLATEAU
-	// ---
-	{0, 780, -1664, 32, 51, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  51, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,  51, 'T', SKINCOLOR_GREY,  50*TICRATE, "", 0},
-
-
-	// FLOODED COVE
-	// ---
-	{0, 1824, -1888, 2448, 52, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     52, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,     52, 'T', SKINCOLOR_GREY,  90*TICRATE, "", 0},
-
-
-	// CAVERN FORTRESS
-	// ---
-	{0, -3089, -431, 1328, 53, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     53, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,     53, 'T', SKINCOLOR_GREY,  75*TICRATE, "", 0},
-
-
-	// DUSTY WASTELAND
-	// ---
-	{0, 957, 924, 2956, 54, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  54, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,  54, 'T', SKINCOLOR_GREY,  65*TICRATE, "", 0},
-
-
-	// MAGMA CAVES
-	// ---
-	{0, -2752, 3104, 1800, 55, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     55, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,     55, 'T', SKINCOLOR_GREY,  80*TICRATE, "", 0},
-
-
-	// EGG SATELLITE
-	// ---
-	{0, 5334, -609, 3426, 56, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,    56, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,    56, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0},
-
-
-	// BLACK HOLE
-	// ---
-	{0, 2108, 3776, 32, 57, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  57, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,  57, 'T', SKINCOLOR_GREY, 150*TICRATE, "", 0},
-
-
-	// SPRING HILL
-	// ---
-	{0, -1840, -1024, 1644, 58, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,      58, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,      58, 'T', SKINCOLOR_GREY,  60*TICRATE, "", 0},
-};
-
-// Default Extra Emblems
-extraemblem_t extraemblems[MAXEXTRAEMBLEMS] =
-{
-	{"Game Complete",  "Complete 1P Mode",                    10, 'X', SKINCOLOR_BLUE, 0},
-	{"All Emeralds",   "Complete 1P Mode with all Emeralds",  11, 'V', SKINCOLOR_GREY, 0},
-	{"Perfect Bonus",  "Perfect Bonus on a non-secret stage", 30, 'P', SKINCOLOR_GOLD, 0},
-	{"PLACEHOLDER", "PLACEHOLDER", 0, 'O', SKINCOLOR_RUST, 0},
-	{"NiGHTS Mastery", "Show your mastery of NiGHTS!",        22, 'W', SKINCOLOR_CYAN, 0},
-};
-
-// Default Unlockables
-unlockable_t unlockables[MAXUNLOCKABLES] =
-{
-	// Name, Objective, Menu Height, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist
-	/* 01 */ {"Record Attack",     "/", 0, 1, SECRET_RECORDATTACK,  0,  true,  true, 0},
-	/* 02 */ {"NiGHTS Mode",       "/",            0, 2, SECRET_NIGHTSMODE,    0,  true,  true, 0},
-
-	/* 03 */ {"Play Credits",      "/", 30, 10, SECRET_CREDITS,   0,  true,  true, 0},
-	/* 04 */ {"Sound Test",        "/", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0},
-
-	/* 05 */ {"EXTRA LEVELS", "/", 58, 0, SECRET_HEADER, 0, true, true, 0},
-
-	/* 06 */ {"Aerial Garden Zone", "/", 70, 11, SECRET_WARP, 40, false, false, 0},
-	/* 07 */ {"Azure Temple Zone",  "/",      80, 20, SECRET_WARP, 41, false, false, 0},
+// Emblem locations
+emblem_t emblemlocations[MAXEMBLEMS];
 
-	/* 08 */ {"BONUS LEVELS", "/", 98, 0, SECRET_HEADER, 0, true, true, 0},
+// Extra emblems
+extraemblem_t extraemblems[MAXEXTRAEMBLEMS];
 
-	/* 09 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
-	/* 10 */ {"Mario Koopa Blast", "/",   110, 42, SECRET_WARP,         30, false, false, 0},
-	/* 11 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
+// Unlockables
+unlockable_t unlockables[MAXUNLOCKABLES];
 
-	/* 12 */ {"Spring Hill Zone", "/",          0, 44, SECRET_NONE, 0, false, false, 0},
-	/* 13 */ {"Black Hole",       "Get grade A in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0},
-
-	/* 14 */ {"Emblem Hints", "/", 0, 41, SECRET_EMBLEMHINTS, 0, false,  true, 0},
-	/* 15 */ {"Emblem Radar", "/", 0, 43, SECRET_ITEMFINDER,  0, false,  true, 0},
-
-	/* 16 */ {"Pandora's Box", "/",  0, 45, SECRET_PANDORA,     0, false, false, 0},
-	/* 17 */ {"Level Select",  "/", 20, 45, SECRET_LEVELSELECT, 1, false,  true, 0},
-};
-
-// Default number of emblems and extra emblems
-INT32 numemblems = 155;
-INT32 numextraemblems = 5;
-
-// DEFAULT CONDITION SETS FOR SRB2 2.1:
-void M_SetupDefaultConditionSets(void)
-{
-	memset(conditionSets, 0, sizeof(conditionSets));
-
-	// --   1: Complete GFZ1
-	M_AddRawCondition(1, 1, UC_MAPBEATEN, 1, 0, 0);
-
-	// --   2: Complete SS1
-	M_AddRawCondition(2, 1, UC_MAPBEATEN, 50, 0, 0);
-
-	// --  10: Complete the game
-	M_AddRawCondition(10, 1, UC_GAMECLEAR, 1, 0, 0);
-
-	// --  11: Complete the game with all emeralds
-	M_AddRawCondition(11, 1, UC_ALLEMERALDS, 1, 0, 0);
-
-	// --  20: Beat AGZ
-	M_AddRawCondition(20, 1, UC_MAPBEATEN, 40, 0, 0);
-
-	// --  22: Beat Black Hole
-	M_AddRawCondition(22, 1, UC_MAPBEATEN, 57, 0, 0);
-
-	// --  30: Perfect Bonus
-	M_AddRawCondition(30, 1,  UC_MAPPERFECT,  1, 0, 0);
-	M_AddRawCondition(30, 2,  UC_MAPPERFECT,  2, 0, 0);
-	M_AddRawCondition(30, 3,  UC_MAPPERFECT,  4, 0, 0);
-	M_AddRawCondition(30, 4,  UC_MAPPERFECT,  5, 0, 0);
-	M_AddRawCondition(30, 5,  UC_MAPPERFECT,  7, 0, 0);
-	M_AddRawCondition(30, 6,  UC_MAPPERFECT,  8, 0, 0);
-	M_AddRawCondition(30, 7,  UC_MAPPERFECT, 10, 0, 0);
-	M_AddRawCondition(30, 8,  UC_MAPPERFECT, 11, 0, 0);
-	M_AddRawCondition(30, 9,  UC_MAPPERFECT, 13, 0, 0);
-	M_AddRawCondition(30, 10, UC_MAPPERFECT, 16, 0, 0);
-	M_AddRawCondition(30, 11, UC_MAPPERFECT, 22, 0, 0);
-	M_AddRawCondition(30, 12, UC_MAPPERFECT, 23, 0, 0);
-	M_AddRawCondition(30, 13, UC_MAPPERFECT, 24, 0, 0);
-	M_AddRawCondition(30, 14, UC_MAPPERFECT, 40, 0, 0);
-	M_AddRawCondition(30, 15, UC_MAPPERFECT, 41, 0, 0);
-
-	// --  40: Find 20 emblems
-	M_AddRawCondition(40, 1, UC_TOTALEMBLEMS, 20, 0, 0);
-
-	// --  41: Find 40 emblems
-	M_AddRawCondition(41, 1, UC_TOTALEMBLEMS, 40, 0, 0);
-
-	// --  42: Find 60 emblems
-	M_AddRawCondition(42, 1, UC_TOTALEMBLEMS, 60, 0, 0);
-
-	// --  43: Find 80 emblems
-	M_AddRawCondition(43, 1, UC_TOTALEMBLEMS, 80, 0, 0);
-
-	// --  44: Find 100 emblems
-	M_AddRawCondition(44, 1, UC_TOTALEMBLEMS, 100, 0, 0);
-
-	// --  45: Find 160 (all) emblems
-	M_AddRawCondition(45, 1, UC_TOTALEMBLEMS, 160, 0, 0);
-
-	// --  50: A rank all NiGHTS special stages
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 50, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 51, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 52, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 53, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 54, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 55, 0);
-	M_AddRawCondition(50, 1, UC_NIGHTSGRADE, GRADE_A, 56, 0);
-}
+// Number of emblems and extra emblems
+INT32 numemblems = 0;
+INT32 numextraemblems = 0;
 
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
 {
@@ -871,7 +240,7 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 	if (cechoLines)
 	{
 		char slashed[1024] = "";
-		for (i = 0; (i < 21) && (i < 24 - cechoLines); ++i)
+		for (i = 0; (i < 19) && (i < 24 - cechoLines); ++i)
 			slashed[i] = '\\';
 		slashed[i] = 0;
 
diff --git a/src/m_cond.h b/src/m_cond.h
index 00f633a8d079e1c79a6e57b2189fc349f23a2c0b..f82e49372ed0120da76cdd2bd8d8c0a7f2a128c5 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 2012-2016 by Sonic Team Junior.
+// Copyright (C) 2012-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -87,9 +87,7 @@ typedef struct
 typedef struct
 {
 	UINT8 type;      ///< Emblem type
-	INT16 x;         ///< X coordinate.
-	INT16 y;         ///< Y coordinate.
-	INT16 z;         ///< Z coordinate.
+	INT16 tag;       ///< Tag of emblem mapthing
 	INT16 level;     ///< Level on which this emblem can be found.
 	UINT8 sprite;    ///< emblem sprite to use, 0 - 25
 	UINT8 color;     ///< skincolor to use
@@ -150,8 +148,7 @@ extern INT32 numextraemblems;
 
 extern UINT32 unlocktriggers;
 
-// Condition Set Setup
-void M_SetupDefaultConditionSets(void);
+// Condition set setup
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2);
 
 // Clearing secrets
diff --git a/src/m_dllist.h b/src/m_dllist.h
index f19659cca7dc2ccbf12b08ecc156cfadcf36c3ca..18f3511442afde28f2272977a526e59864462b65 100644
--- a/src/m_dllist.h
+++ b/src/m_dllist.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2005      by James Haley
-// Copyright (C) 2005-2016 by Sonic Team Junior.
+// Copyright (C) 2005-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_fixed.c b/src/m_fixed.c
index ce7471a28dac8cbd57aa96c58ed74d04fc00ded3..d45bb70bf572dabacef5f9874dfe84d3b9bdef32 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -33,7 +33,9 @@
 */
 fixed_t FixedMul(fixed_t a, fixed_t b)
 {
-	return (fixed_t)((((INT64)a * b) ) / FRACUNIT);
+	// 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__
@@ -430,7 +432,7 @@ vector3_t *FV3_Cross(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a_o)
 //
 vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vector3_t *out)
 {
-	// Determine t (the length of the vector from ‘Line[0]’ to ‘p’)
+	// Determine t (the length of the vector from �Line[0]� to �p�)
 	vector3_t c, V;
 	fixed_t t, d = 0;
 	FV3_SubEx(p, &Line[0], &c);
@@ -440,7 +442,7 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 	d = FV3_Distance(&Line[0], &Line[1]);
 	t = FV3_Dot(&V, &c);
 
-	// Check to see if ‘t’ is beyond the extents of the line segment
+	// Check to see if �t� is beyond the extents of the line segment
 	if (t < 0)
 	{
 		return FV3_Copy(out, &Line[0]);
@@ -450,7 +452,7 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 		return FV3_Copy(out, &Line[1]);
 	}
 
-	// Return the point between ‘Line[0]’ and ‘Line[1]’
+	// Return the point between �Line[0]� and �Line[1]�
 	FV3_Mul(&V, t);
 
 	return FV3_AddEx(&Line[0], &V, out);
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 8050324dbe1015bf5f3dc9036f97abd9c62f1a60..d8e722b13c8f0117f10098064973997cf0695df8 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -70,7 +70,7 @@ typedef INT32 fixed_t;
 			:"=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
+			:"cc", "%edx"         // edx and condition codes clobbered
 		);
 		return ret;
 	}
diff --git a/src/m_menu.c b/src/m_menu.c
index dedbd86f08ff663cf38331de2634174b2214bdf6..ca389a94dd8a56358ea9c2bebbf4b8ed6c4ea601 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -58,6 +58,8 @@
 #include "i_sound.h"
 #include "fastcmp.h"
 
+#include "i_joy.h" // for joystick menu controls
+
 // Condition Sets
 #include "m_cond.h"
 
@@ -70,6 +72,10 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 //int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
 #endif
 
+#if defined (__GNUC__) && (__GNUC__ >= 4)
+#define FIXUPO0
+#endif
+
 #define SKULLXOFF -32
 #define LINEHEIGHT 16
 #define STRINGHEIGHT 8
@@ -111,44 +117,7 @@ typedef enum
 const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
-// A rare case.
-// External files modify this menu, so we can't call it static.
-// And I'm too lazy to go through and rename it everywhere. ARRGH!
-description_t description[32] =
-{
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0},
-	{false, "???", "", "", 0, 0}
-};
+description_t description[MAXSKINS];
 INT16 char_on = -1, startchar = 1;
 static char *char_notes = NULL;
 static fixed_t char_scroll = 0;
@@ -167,7 +136,7 @@ typedef enum
 levellist_mode_t levellistmode = LLM_CREATESERVER;
 UINT8 maplistoption = 0;
 
-static char joystickInfo[8][25];
+static char joystickInfo[8][29];
 #ifndef NONET
 static UINT32 serverlistpage;
 #endif
@@ -269,6 +238,7 @@ menu_t SP_MainDef, OP_MainDef;
 menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
 
 // Single Player
+static void M_StartTutorial(INT32 choice);
 static void M_LoadGame(INT32 choice);
 static void M_TimeAttackLevelSelect(INT32 choice);
 static void M_TimeAttack(INT32 choice);
@@ -278,7 +248,6 @@ static void M_Statistics(INT32 choice);
 static void M_ReplayTimeAttack(INT32 choice);
 static void M_ChooseTimeAttack(INT32 choice);
 static void M_ChooseNightsAttack(INT32 choice);
-static void M_ModeAttackRetry(INT32 choice);
 static void M_ModeAttackEndGame(INT32 choice);
 static void M_SetGuestReplay(INT32 choice);
 static void M_HandleChoosePlayerMenu(INT32 choice);
@@ -310,7 +279,6 @@ menu_t OP_MPControlsDef, OP_CameraControlsDef, OP_MiscControlsDef;
 menu_t OP_P1ControlsDef, OP_P2ControlsDef, OP_MouseOptionsDef;
 menu_t OP_Mouse2OptionsDef, OP_Joystick1Def, OP_Joystick2Def;
 static void M_VideoModeMenu(INT32 choice);
-static void M_SoundMenu(INT32 choice);
 static void M_Setup1PControlsMenu(INT32 choice);
 static void M_Setup2PControlsMenu(INT32 choice);
 static void M_Setup1PJoystickMenu(INT32 choice);
@@ -324,9 +292,9 @@ menu_t OP_VideoOptionsDef, OP_VideoModeDef, OP_ColorOptionsDef;
 menu_t OP_OpenGLOptionsDef, OP_OpenGLFogDef, OP_OpenGLColorDef;
 #endif
 menu_t OP_SoundOptionsDef;
-static void M_ToggleSFX(INT32 choice);
-static void M_ToggleDigital(INT32 choice);
-static void M_ToggleMIDI(INT32 choice);
+#ifdef HAVE_MIXERX
+menu_t OP_SoundAdvancedDef;
+#endif
 
 //Misc
 menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
@@ -337,8 +305,7 @@ static void M_EraseData(INT32 choice);
 
 static void M_Addons(INT32 choice);
 static void M_AddonsOptions(INT32 choice);
-static patch_t *addonsp[NUM_EXT+6];
-static UINT8 addonsresponselimit = 0;
+static patch_t *addonsp[NUM_EXT+5];
 
 #define numaddonsshown 4
 
@@ -365,7 +332,6 @@ static void M_DrawControl(void);
 static void M_DrawMainVideoMenu(void);
 static void M_DrawVideoMode(void);
 static void M_DrawColorMenu(void);
-static void M_DrawSoundMenu(void);
 static void M_DrawScreenshotMenu(void);
 static void M_DrawMonitorToggles(void);
 #ifdef HWRENDER
@@ -436,6 +402,9 @@ static CV_PossibleValue_t serversort_cons_t[] = {
 };
 consvar_t cv_serversort = {"serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL};
 
+// first time memory
+consvar_t cv_tutorialprompt = {"tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 // autorecord demos for time attack
 static consvar_t cv_autorecord = {"autorecord", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -454,7 +423,7 @@ consvar_t cv_ghost_guest     = {"ghost_guest",     "Show", CV_SAVE, ghost2_cons_
 static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}};
 static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}};
 static CV_PossibleValue_t ringlimit_cons_t[] = {{0, "MIN"}, {9999, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t liveslimit_cons_t[] = {{0, "MIN"}, {99, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t liveslimit_cons_t[] = {{-1, "MIN"}, {99, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t dummymares_cons_t[] = {
 	{-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL}
 };
@@ -727,6 +696,7 @@ static menuitem_t SR_EmblemHintMenu[] =
 // Single Player Main
 static menuitem_t SP_MainMenu[] =
 {
+	{IT_CALL | IT_STRING,                       NULL, "Tutorial",      M_StartTutorial,            84},
 	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 92},
 	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,              100},
 	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,            108},
@@ -735,6 +705,7 @@ static menuitem_t SP_MainMenu[] =
 
 enum
 {
+	sptutorial,
 	sploadgame,
 	sprecordattack,
 	spnightsmode,
@@ -1018,7 +989,7 @@ static menuitem_t OP_MainMenu[] =
 	{IT_CVAR    | IT_STRING, NULL, "Controls per key",     &cv_controlperkey,   30},
 
 	{IT_SUBMENU | IT_STRING, NULL, "Video Options...",     &OP_VideoOptionsDef, 50},
-	{IT_CALL    | IT_STRING, NULL, "Sound Options...",     M_SoundMenu,         60},
+	{IT_SUBMENU | IT_STRING, NULL, "Sound Options...",     &OP_SoundOptionsDef, 60},
 
 	{IT_CALL    | IT_STRING, NULL, "Server Options...",    M_ServerOptions,     80},
 
@@ -1067,10 +1038,10 @@ static menuitem_t OP_ChangeControlsMenu[] =
 	{IT_CALL | IT_STRING2, NULL, "Spin",             M_ChangeControl, gc_use     },
 	{IT_HEADER, NULL, "Camera", NULL, 0},
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
-	{IT_CALL | IT_STRING2, NULL, "Camera Up",        M_ChangeControl, gc_lookup      },
-	{IT_CALL | IT_STRING2, NULL, "Camera Down",      M_ChangeControl, gc_lookdown    },
-	{IT_CALL | IT_STRING2, NULL, "Camera Left",      M_ChangeControl, gc_turnleft    },
-	{IT_CALL | IT_STRING2, NULL, "Camera Right",     M_ChangeControl, gc_turnright   },
+	{IT_CALL | IT_STRING2, NULL, "Look Up",        M_ChangeControl, gc_lookup      },
+	{IT_CALL | IT_STRING2, NULL, "Look Down",      M_ChangeControl, gc_lookdown    },
+	{IT_CALL | IT_STRING2, NULL, "Look Left",      M_ChangeControl, gc_turnleft    },
+	{IT_CALL | IT_STRING2, NULL, "Look Right",     M_ChangeControl, gc_turnright   },
 	{IT_CALL | IT_STRING2, NULL, "Center View",      M_ChangeControl, gc_centerview  },
 	{IT_CALL | IT_STRING2, NULL, "Toggle Mouselook", M_ChangeControl, gc_mouseaiming },
 	{IT_CALL | IT_STRING2, NULL, "Toggle Third-Person", M_ChangeControl, gc_camtoggle},
@@ -1079,7 +1050,11 @@ static menuitem_t OP_ChangeControlsMenu[] =
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
 	{IT_CALL | IT_STRING2, NULL, "Game Status",
     M_ChangeControl, gc_scores      },
-	{IT_CALL | IT_STRING2, NULL, "Pause",            M_ChangeControl, gc_pause       },
+	{IT_CALL | IT_STRING2, NULL, "Pause / Run Retry", M_ChangeControl, gc_pause      },
+	{IT_CALL | IT_STRING2, NULL, "Screenshot",            M_ChangeControl, gc_screenshot },
+	{IT_CALL | IT_STRING2, NULL, "Toggle GIF Recording",  M_ChangeControl, gc_recordgif  },
+	{IT_CALL | IT_STRING2, NULL, "Open/Close Menu (ESC)", M_ChangeControl, gc_systemmenu },
+	{IT_CALL | IT_STRING2, NULL, "Change Viewpoint",      M_ChangeControl, gc_viewpoint  },
 	{IT_CALL | IT_STRING2, NULL, "Console",          M_ChangeControl, gc_console     },
 	{IT_HEADER, NULL, "Multiplayer", NULL, 0},
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
@@ -1117,6 +1092,9 @@ static menuitem_t OP_Joystick1Menu[] =
 	{IT_STRING | IT_CVAR,  NULL, "Spin Axis"         , &cv_spinaxis         , 80},
 	{IT_STRING | IT_CVAR,  NULL, "Fire Axis"         , &cv_fireaxis         , 90},
 	{IT_STRING | IT_CVAR,  NULL, "Fire Normal Axis"  , &cv_firenaxis        ,100},
+
+	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook, 120},
+	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook,  130},
 };
 
 static menuitem_t OP_Joystick2Menu[] =
@@ -1130,6 +1108,9 @@ static menuitem_t OP_Joystick2Menu[] =
 	{IT_STRING | IT_CVAR,  NULL, "Spin Axis"         , &cv_spinaxis2        , 80},
 	{IT_STRING | IT_CVAR,  NULL, "Fire Axis"         , &cv_fireaxis2        , 90},
 	{IT_STRING | IT_CVAR,  NULL, "Fire Normal Axis"  , &cv_firenaxis2       ,100},
+
+	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook2,120},
+	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook2, 130},
 };
 
 static menuitem_t OP_JoystickSetMenu[] =
@@ -1146,13 +1127,14 @@ static menuitem_t OP_MouseOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Use Mouse",        &cv_usemouse,         10},
 
 
-	{IT_STRING | IT_CVAR, NULL, "Always Mouselook", &cv_alwaysfreelook,   30},
-	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove,        40},
-	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse,      50},
+	{IT_STRING | IT_CVAR, NULL, "First-Person MouseLook", &cv_alwaysfreelook,   30},
+	{IT_STRING | IT_CVAR, NULL, "Third-Person MouseLook", &cv_chasefreelook,   40},
+	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove,        50},
+	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse,      60},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse X Sensitivity",    &cv_mousesens,        60},
+	                      NULL, "Mouse X Sensitivity",    &cv_mousesens,        70},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens,        70},
+	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens,        80},
 };
 
 static menuitem_t OP_Mouse2OptionsMenu[] =
@@ -1160,13 +1142,14 @@ static menuitem_t OP_Mouse2OptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Use Mouse 2",      &cv_usemouse2,        10},
 	{IT_STRING | IT_CVAR, NULL, "Second Mouse Serial Port",
 	                                                &cv_mouse2port,       20},
-	{IT_STRING | IT_CVAR, NULL, "Always Mouselook", &cv_alwaysfreelook2,  30},
-	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove2,       40},
-	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse2,     50},
+	{IT_STRING | IT_CVAR, NULL, "First-Person MouseLook", &cv_alwaysfreelook2,  30},
+	{IT_STRING | IT_CVAR, NULL, "Third-Person MouseLook", &cv_chasefreelook2,  40},
+	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove2,       50},
+	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse2,     60},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse X Sensitivity",    &cv_mousesens2,       60},
+	                      NULL, "Mouse X Sensitivity",    &cv_mousesens2,       70},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens2,      70},
+	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens2,      80},
 };
 
 static menuitem_t OP_VideoOptionsMenu[] =
@@ -1184,7 +1167,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 #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 (F11)", &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},
 
@@ -1192,24 +1175,33 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Show HUD",                  &cv_showhud,          61},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
 	                      NULL, "HUD Transparency",          &cv_translucenthud,   66},
-	{IT_STRING | IT_CVAR, NULL, "Time Display",              &cv_timetic,          71},
+	{IT_STRING | IT_CVAR, NULL, "Score/Time/Rings",          &cv_timetic,          71},
+	{IT_STRING | IT_CVAR, NULL, "Show Powerups",             &cv_powerupdisplay,   76},
 #ifdef SEENAMES
-	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         76},
+	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         81},
 #endif
 
-	{IT_HEADER, NULL, "Console", NULL, 85},
-	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      91},
-	{IT_STRING | IT_CVAR, NULL, "Text Size",                 &cv_constextsize,     96},
-
-	{IT_HEADER, NULL, "Level", NULL, 105},
-	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        111},
-	{IT_STRING | IT_CVAR, NULL, "NiGHTS Draw Dist.",         &cv_drawdist_nights, 116},
-	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 121},
-	{IT_STRING | IT_CVAR, NULL, "Weather Density",           &cv_precipdensity,   126},
-
-	{IT_HEADER, NULL, "Diagnostic", NULL, 135},
-	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         141},
-	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      146},
+	{IT_HEADER, NULL, "Console", NULL, 90},
+	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      96},
+	{IT_STRING | IT_CVAR, NULL, "Text Size",                 &cv_constextsize,    101},
+
+	{IT_HEADER, NULL, "Chat", NULL, 110},
+	{IT_STRING | IT_CVAR, NULL, "Chat Mode",            		 	 &cv_consolechat,  116},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Width",    &cv_chatwidth,     121},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Height",   &cv_chatheight,    126},
+	{IT_STRING | IT_CVAR, NULL, "Message Fadeout Time",              &cv_chattime,    131},
+	{IT_STRING | IT_CVAR, NULL, "Chat Notifications",           	 &cv_chatnotifications,  136},
+	{IT_STRING | IT_CVAR, NULL, "Spam Protection",           		 &cv_chatspamprotection,  141},
+	{IT_STRING | IT_CVAR, NULL, "Chat background tint",           	 &cv_chatbacktint,  146},
+
+	{IT_HEADER, NULL, "Level", NULL, 155},
+	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        161},
+	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 166},
+	{IT_STRING | IT_CVAR, NULL, "NiGHTS mode Draw Dist.",    &cv_drawdist_nights, 171},
+
+	{IT_HEADER, NULL, "Diagnostic", NULL, 180},
+	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         186},
+	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      191},
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -1303,18 +1295,52 @@ static menuitem_t OP_OpenGLColorMenu[] =
 
 static menuitem_t OP_SoundOptionsMenu[] =
 {
-	{IT_STRING | IT_KEYHANDLER,  NULL,  "Sound Effects", M_ToggleSFX, 10},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 20},
+	{IT_HEADER, NULL, "Game Audio", NULL, 0}, // 0 // ScrollMenu offsets
+	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 13}, // 6
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 23}, // 11
+
+	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 43}, // 21
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  53}, // 26
 
-	{IT_STRING | IT_KEYHANDLER,  NULL,  "Digital Music", M_ToggleDigital, 40},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  50},
+	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 73}, // 36
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 83}, // 41
 
-	{IT_STRING | IT_KEYHANDLER,  NULL,  "MIDI Music", M_ToggleMIDI, 70},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 80},
+	{IT_HEADER, NULL, "Accessibility", NULL, 103}, // 50
+	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 115}, // 56
+	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 125}, // 62
 
-	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 100},
+#ifdef HAVE_MIXERX
+	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 143},
+#endif
 };
 
+#ifdef HAVE_MIXERX
+
+#ifdef HAVE_OPENMPT
+#define OPENMPT_MENUOFFSET 32
+#else
+#define OPENMPT_MENUOFFSET 0
+#endif
+
+static menuitem_t OP_SoundAdvancedMenu[] =
+{
+#ifdef HAVE_OPENMPT
+	{IT_HEADER, NULL, "OpenMPT Settings", NULL, 10},
+	{IT_STRING | IT_CVAR, NULL, "Instrument Filter", &cv_modfilter, 22},
+#endif
+
+#ifdef HAVE_MIXERX
+	{IT_HEADER, NULL, "MIDI Settings", NULL, OPENMPT_MENUOFFSET+10},
+	{IT_STRING | IT_CVAR, NULL, "MIDI Player", &cv_midiplayer, OPENMPT_MENUOFFSET+22},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "FluidSynth Sound Font File", &cv_midisoundfontpath, OPENMPT_MENUOFFSET+34},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+61}
+#endif
+};
+
+#undef OPENMPT_MENUOFFSET
+
+#endif
+
 static menuitem_t OP_DataOptionsMenu[] =
 {
 	{IT_STRING | IT_CALL,    NULL, "Add-on Options...",     M_AddonsOptions,     10},
@@ -1462,10 +1488,11 @@ static menuitem_t OP_MonitorToggleMenu[] =
 // ==========================================================================
 
 // Main Menu and related
-menu_t MainDef = CENTERMENUSTYLE(NULL, MainMenu, NULL, 72);
+menu_t MainDef = CENTERMENUSTYLE(MN_MAIN, NULL, MainMenu, NULL, 72);
 
 menu_t MISC_AddonsDef =
 {
+	MN_AD_MAIN,
 	NULL,
 	sizeof (MISC_AddonsMenu)/sizeof (menuitem_t),
 	&MainDef,
@@ -1481,12 +1508,13 @@ menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72);
 menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
 
 // Misc Main Menu
-menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
-menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
+menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(MN_SPECIAL, NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
+menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(MN_SPECIAL, NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
 
 // MP Gametype and map change menu
 menu_t MISC_ChangeLevelDef =
 {
+	MN_SPECIAL,
 	NULL,
 	sizeof (MISC_ChangeLevelMenu)/sizeof (menuitem_t),
 	&MainDef,  // Doesn't matter.
@@ -1499,9 +1527,13 @@ menu_t MISC_ChangeLevelDef =
 
 menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu);
 
+static INT32 highlightflags, recommendedflags, warningflags;
+
+
 // Sky Room
 menu_t SR_PandoraDef =
 {
+	MN_SR_MAIN + (MN_SR_PANDORA << 6),
 	"M_PANDRA",
 	sizeof (SR_PandorasBox)/sizeof (menuitem_t),
 	&SPauseDef,
@@ -1513,6 +1545,7 @@ menu_t SR_PandoraDef =
 };
 menu_t SR_MainDef =
 {
+	MN_SR_MAIN,
 	"M_SECRET",
 	sizeof (SR_MainMenu)/sizeof (menuitem_t),
 	&MainDef,
@@ -1523,10 +1556,13 @@ menu_t SR_MainDef =
 	NULL
 };
 
-menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SR_LevelSelectMenu);
+menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(
+	MN_SR_MAIN + (MN_SR_LEVELSELECT << 6),
+	NULL, SR_LevelSelectMenu);
 
 menu_t SR_UnlockChecklistDef =
 {
+	MN_SR_MAIN + (MN_SR_UNLOCKCHECKLIST << 6),
 	"M_SECRET",
 	1,
 	&SR_MainDef,
@@ -1538,6 +1574,7 @@ menu_t SR_UnlockChecklistDef =
 };
 menu_t SR_EmblemHintDef =
 {
+	MN_SR_MAIN + (MN_SR_EMBLEMHINT << 6),
 	NULL,
 	sizeof (SR_EmblemHintMenu)/sizeof (menuitem_t),
 	&SPauseDef,
@@ -1549,9 +1586,22 @@ menu_t SR_EmblemHintDef =
 };
 
 // Single Player
-menu_t SP_MainDef = CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
+menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
+{
+	MN_SP_MAIN,
+	NULL,
+	sizeof(SP_MainMenu)/sizeof(menuitem_t),
+	&MainDef,
+	SP_MainMenu,
+	M_DrawCenteredMenu,
+	BASEVIDWIDTH/2, 72,
+	1, // start at "Start Game" on first entry
+	NULL
+};
+
 menu_t SP_LoadDef =
 {
+	MN_SP_MAIN + (MN_SP_LOAD << 6),
 	"M_PICKG",
 	1,
 	&SP_MainDef,
@@ -1562,10 +1612,13 @@ menu_t SP_LoadDef =
 	NULL
 };
 
-menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SP_LevelSelectMenu);
+menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(
+	MN_SP_MAIN + (MN_SP_LOAD << 6) + (MN_SP_PLAYER << 12) + (MN_SP_LEVELSELECT << 18),
+	NULL, SP_LevelSelectMenu);
 
 menu_t SP_LevelStatsDef =
 {
+	MN_SP_MAIN + (MN_SP_LEVELSTATS << 6),
 	"M_STATS",
 	1,
 	&SP_MainDef,
@@ -1576,10 +1629,13 @@ menu_t SP_LevelStatsDef =
 	NULL
 };
 
-menu_t SP_TimeAttackLevelSelectDef = MAPPLATTERMENUSTYLE("M_ATTACK", SP_TimeAttackLevelSelectMenu);
+menu_t SP_TimeAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
+	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_TIMEATTACK_LEVELSELECT << 12),
+	"M_ATTACK", SP_TimeAttackLevelSelectMenu);
 
 static menu_t SP_TimeAttackDef =
 {
+	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6),
 	"M_ATTACK",
 	sizeof (SP_TimeAttackMenu)/sizeof (menuitem_t),
 	&MainDef,  // Doesn't matter.
@@ -1591,6 +1647,7 @@ static menu_t SP_TimeAttackDef =
 };
 static menu_t SP_ReplayDef =
 {
+	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_REPLAY << 12),
 	"M_ATTACK",
 	sizeof(SP_ReplayMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1602,6 +1659,7 @@ static menu_t SP_ReplayDef =
 };
 static menu_t SP_GuestReplayDef =
 {
+	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_GUESTREPLAY << 12),
 	"M_ATTACK",
 	sizeof(SP_GuestReplayMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1613,6 +1671,7 @@ static menu_t SP_GuestReplayDef =
 };
 static menu_t SP_GhostDef =
 {
+	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_GHOST << 12),
 	"M_ATTACK",
 	sizeof(SP_GhostMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1623,10 +1682,13 @@ static menu_t SP_GhostDef =
 	NULL
 };
 
-menu_t SP_NightsAttackLevelSelectDef = MAPPLATTERMENUSTYLE("M_NIGHTS", SP_NightsAttackLevelSelectMenu);
+menu_t SP_NightsAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
+	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_LEVELSELECT << 12),
+	 "M_NIGHTS", SP_NightsAttackLevelSelectMenu);
 
 static menu_t SP_NightsAttackDef =
 {
+	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6),
 	"M_NIGHTS",
 	sizeof (SP_NightsAttackMenu)/sizeof (menuitem_t),
 	&MainDef,  // Doesn't matter.
@@ -1638,6 +1700,7 @@ static menu_t SP_NightsAttackDef =
 };
 static menu_t SP_NightsReplayDef =
 {
+	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_REPLAY << 12),
 	"M_NIGHTS",
 	sizeof(SP_NightsReplayMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1649,6 +1712,7 @@ static menu_t SP_NightsReplayDef =
 };
 static menu_t SP_NightsGuestReplayDef =
 {
+	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_GUESTREPLAY << 12),
 	"M_NIGHTS",
 	sizeof(SP_NightsGuestReplayMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1660,6 +1724,7 @@ static menu_t SP_NightsGuestReplayDef =
 };
 static menu_t SP_NightsGhostDef =
 {
+	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_GHOST << 12),
 	"M_NIGHTS",
 	sizeof(SP_NightsGhostMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1673,6 +1738,7 @@ static menu_t SP_NightsGhostDef =
 
 menu_t SP_PlayerDef =
 {
+	MN_SP_MAIN + (MN_SP_LOAD << 6) + (MN_SP_PLAYER << 12),
 	"M_PICKP",
 	sizeof (SP_PlayerMenu)/sizeof (menuitem_t),
 	&SP_MainDef,
@@ -1687,6 +1753,7 @@ menu_t SP_PlayerDef =
 
 menu_t MP_SplitServerDef =
 {
+	MN_MP_MAIN + (MN_MP_SPLITSCREEN << 6),
 	"M_MULTI",
 	sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
 #ifndef NONET
@@ -1705,6 +1772,7 @@ menu_t MP_SplitServerDef =
 
 menu_t MP_MainDef =
 {
+	MN_MP_MAIN,
 	"M_MULTI",
 	sizeof (MP_MainMenu)/sizeof (menuitem_t),
 	&MainDef,
@@ -1717,6 +1785,7 @@ menu_t MP_MainDef =
 
 menu_t MP_ServerDef =
 {
+	MN_MP_MAIN + (MN_MP_SERVER << 6),
 	"M_MULTI",
 	sizeof (MP_ServerMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
@@ -1729,6 +1798,7 @@ menu_t MP_ServerDef =
 
 menu_t MP_ConnectDef =
 {
+	MN_MP_MAIN + (MN_MP_CONNECT << 6),
 	"M_MULTI",
 	sizeof (MP_ConnectMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
@@ -1741,6 +1811,7 @@ menu_t MP_ConnectDef =
 
 menu_t MP_RoomDef =
 {
+	MN_MP_MAIN + (MN_MP_ROOM << 6),
 	"M_MULTI",
 	sizeof (MP_RoomMenu)/sizeof (menuitem_t),
 	&MP_ConnectDef,
@@ -1754,6 +1825,11 @@ menu_t MP_RoomDef =
 
 menu_t MP_PlayerSetupDef =
 {
+#ifdef NONET
+	MN_MP_MAIN + (MN_MP_PLAYERSETUP << 6),
+#else
+	MN_MP_MAIN + (MN_MP_SPLITSCREEN << 6) + (MN_MP_PLAYERSETUP << 12),
+#endif
 	"M_SPLAYR",
 	sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t),
 	&MainDef, // doesn't matter
@@ -1765,16 +1841,33 @@ menu_t MP_PlayerSetupDef =
 };
 
 // Options
-menu_t OP_MainDef = DEFAULTMENUSTYLE("M_OPTTTL", OP_MainMenu, &MainDef, 50, 30);
-menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(OP_ChangeControlsMenu, &OP_MainDef);
-menu_t OP_P1ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P1ControlsMenu, &OP_MainDef, 50, 30);
-menu_t OP_P2ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P2ControlsMenu, &OP_MainDef, 50, 30);
-menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
-menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 35, 30);
-menu_t OP_Joystick1Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 50, 30);
-menu_t OP_Joystick2Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 50, 30);
+menu_t OP_MainDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN,
+	"M_OPTTTL", OP_MainMenu, &MainDef, 50, 30);
+menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_CHANGECONTROLS << 12), // second level (<<6) set on runtime
+	OP_ChangeControlsMenu, &OP_MainDef);
+menu_t OP_P1ControlsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6),
+	"M_CONTRO", OP_P1ControlsMenu, &OP_MainDef, 50, 30);
+menu_t OP_P2ControlsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6),
+	"M_CONTRO", OP_P2ControlsMenu, &OP_MainDef, 50, 30);
+menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1MOUSE << 12),
+	"M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
+menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2MOUSE << 12),
+	"M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 35, 30);
+menu_t OP_Joystick1Def = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1JOYSTICK << 12),
+	"M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 50, 30);
+menu_t OP_Joystick2Def = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2JOYSTICK << 12),
+	"M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 50, 30);
 menu_t OP_JoystickSetDef =
 {
+	MN_OP_MAIN + (MN_OP_JOYSTICKSET << MENUBITS*3),  // second (<<6) and third level (<<12) set on runtime
 	"M_CONTRO",
 	sizeof (OP_JoystickSetMenu)/sizeof (menuitem_t),
 	&OP_Joystick1Def,
@@ -1787,6 +1880,7 @@ menu_t OP_JoystickSetDef =
 
 menu_t OP_VideoOptionsDef =
 {
+	MN_OP_MAIN + (MN_OP_VIDEO << 6),
 	"M_VIDEO",
 	sizeof (OP_VideoOptionsMenu)/sizeof (menuitem_t),
 	&OP_MainDef,
@@ -1798,6 +1892,7 @@ menu_t OP_VideoOptionsDef =
 };
 menu_t OP_VideoModeDef =
 {
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_VIDEOMODE << 12),
 	"M_VIDEO",
 	1,
 	&OP_VideoOptionsDef,
@@ -1809,6 +1904,7 @@ menu_t OP_VideoModeDef =
 };
 menu_t OP_ColorOptionsDef =
 {
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_COLOR << 12),
 	"M_VIDEO",
 	sizeof (OP_ColorOptionsMenu)/sizeof (menuitem_t),
 	&OP_VideoOptionsDef,
@@ -1820,20 +1916,27 @@ menu_t OP_ColorOptionsDef =
 };
 menu_t OP_SoundOptionsDef =
 {
+	MN_OP_MAIN + (MN_OP_SOUND << 6),
 	"M_SOUND",
 	sizeof (OP_SoundOptionsMenu)/sizeof (menuitem_t),
 	&OP_MainDef,
 	OP_SoundOptionsMenu,
-	M_DrawSoundMenu,
+	M_DrawGenericMenu,
 	30, 30,
 	0,
 	NULL
 };
+#ifdef HAVE_MIXERX
+menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(MN_OP_MAIN + (MN_OP_SOUND << 6), "M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
+#endif
 
-menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE("M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
+menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_SERVER << 6),
+	"M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
 
 menu_t OP_MonitorToggleDef =
 {
+	MN_OP_MAIN + (MN_OP_SERVER << 6) + (MN_OP_MONITORTOGGLE << 12),
 	"M_SERVER",
 	sizeof (OP_MonitorToggleMenu)/sizeof (menuitem_t),
 	&OP_ServerOptionsDef,
@@ -1845,12 +1948,17 @@ menu_t OP_MonitorToggleDef =
 };
 
 #ifdef HWRENDER
-menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE("M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30);
+menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12),
+	"M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30);
 #ifdef ALAM_LIGHTING
-menu_t OP_OpenGLLightingDef = DEFAULTMENUSTYLE("M_VIDEO", OP_OpenGLLightingMenu, &OP_OpenGLOptionsDef, 60, 40);
+menu_t OP_OpenGLLightingDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12) + (MN_OP_OPENGL_LIGHTING << 18),
+	"M_VIDEO", OP_OpenGLLightingMenu, &OP_OpenGLOptionsDef, 60, 40);
 #endif
 menu_t OP_OpenGLFogDef =
 {
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12) + (MN_OP_OPENGL_FOG << 18),
 	"M_VIDEO",
 	sizeof (OP_OpenGLFogMenu)/sizeof (menuitem_t),
 	&OP_OpenGLOptionsDef,
@@ -1862,6 +1970,7 @@ menu_t OP_OpenGLFogDef =
 };
 menu_t OP_OpenGLColorDef =
 {
+	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12) + (MN_OP_OPENGL_COLOR << 18),
 	"M_VIDEO",
 	sizeof (OP_OpenGLColorMenu)/sizeof (menuitem_t),
 	&OP_OpenGLOptionsDef,
@@ -1872,10 +1981,13 @@ menu_t OP_OpenGLColorDef =
 	NULL
 };
 #endif
-menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
+menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_DATA << 6),
+	"M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
 
 menu_t OP_ScreenshotOptionsDef =
 {
+	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_SCREENSHOTS << 12),
 	"M_DATA",
 	sizeof (OP_ScreenshotOptionsMenu)/sizeof (menuitem_t),
 	&OP_DataOptionsDef,
@@ -1886,9 +1998,13 @@ menu_t OP_ScreenshotOptionsDef =
 	NULL
 };
 
-menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
+menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_ADDONS << 12),
+	"M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
 
-menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30);
+menu_t OP_EraseDataDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_ERASEDATA << 12),
+	"M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30);
 
 // ==========================================================================
 // CVAR ONCHANGE EVENTS GO HERE
@@ -2045,35 +2161,7 @@ static void Newgametype_OnChange(void)
 			P_AllocMapHeader((INT16)(cv_nextmap.value-1));
 
 		if (!M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value))
-		{
-			INT32 value = 0;
-
-			switch (cv_newgametype.value)
-			{
-				case GT_COOP:
-					value = TOL_COOP;
-					break;
-				case GT_COMPETITION:
-					value = TOL_COMPETITION;
-					break;
-				case GT_RACE:
-					value = TOL_RACE;
-					break;
-				case GT_MATCH:
-				case GT_TEAMMATCH:
-					value = TOL_MATCH;
-					break;
-				case GT_TAG:
-				case GT_HIDEANDSEEK:
-					value = TOL_TAG;
-					break;
-				case GT_CTF:
-					value = TOL_CTF;
-					break;
-			}
-
-			CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(value));
-		}
+			CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(cv_newgametype.value));
 	}
 }
 
@@ -2119,6 +2207,475 @@ void Addons_option_Onchange(void)
 // current menudef
 menu_t *currentMenu = &MainDef;
 
+// =========================================================================
+// MENU PRESENTATION PARAMETER HANDLING (BACKGROUNDS)
+// =========================================================================
+
+// menu IDs are equal to current/prevMenu in most cases, except MN_SPECIAL when we don't want to operate on Message, Pause, etc.
+UINT32 prevMenuId = 0;
+UINT32 activeMenuId = 0;
+
+menupres_t menupres[NUMMENUTYPES];
+
+void M_InitMenuPresTables(void)
+{
+	INT32 i;
+
+	// Called in d_main before SOC can get to the tables
+	// Set menupres defaults
+	for (i = 0; i < NUMMENUTYPES; i++)
+	{
+		// so-called "undefined"
+		menupres[i].fadestrength = -1;
+		menupres[i].hidetitlepics = -1; // inherits global hidetitlepics
+		menupres[i].enterwipe = -1;
+		menupres[i].exitwipe = -1;
+		menupres[i].bgcolor = -1;
+		menupres[i].titlescrollxspeed = INT32_MAX;
+		menupres[i].titlescrollyspeed = INT32_MAX;
+		menupres[i].bghide = true;
+		// default true
+		menupres[i].enterbubble = true;
+		menupres[i].exitbubble = true;
+
+		if (i != MN_MAIN)
+		{
+			menupres[i].muslooping = true;
+		}
+		if (i == MN_SP_TIMEATTACK || i == MN_SP_NIGHTSATTACK)
+			strncpy(menupres[i].musname, "_inter", 7);
+		else if (i == MN_SP_PLAYER)
+			strncpy(menupres[i].musname, "_chsel", 7);
+	}
+}
+
+// ====================================
+// TREE ITERATION
+// ====================================
+
+// UINT32 menutype - current menutype_t
+// INT32 level - current level up the tree, higher means younger
+// INT32 *retval - Return value
+// void *input - Pointer to input of any type
+//
+// return true - stop iterating
+// return false - continue
+typedef boolean (*menutree_iterator)(UINT32, INT32, INT32 *, void **, boolean fromoldest);
+
+// HACK: Used in the ChangeMusic iterator because we only allow
+// a single input. Maybe someday use this struct program-wide.
+typedef struct
+{
+	char musname[7];
+	UINT16 mustrack;
+	boolean muslooping;
+} menupresmusic_t;
+
+static INT32 M_IterateMenuTree(menutree_iterator itfunc, void *input)
+{
+	INT32 i, retval = 0;
+	UINT32 bitmask, menutype;
+
+	for (i = NUMMENULEVELS; i >= 0; i--)
+	{
+		bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+		menutype = (activeMenuId & bitmask) >> (MENUBITS*i);
+		if (itfunc(menutype, i, &retval, &input, false))
+			break;
+	}
+
+	return retval;
+}
+
+// ====================================
+// ITERATORS
+// ====================================
+
+static boolean MIT_GetMenuAtLevel(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+{
+	INT32 *inputptr = (INT32*)*input;
+	INT32 targetlevel = *inputptr;
+	if (menutype)
+	{
+		if (level == targetlevel || targetlevel < 0)
+		{
+			*retval = menutype;
+			return true;
+		}
+	}
+	else if (targetlevel >= 0)
+	{
+		// offset targetlevel by failed attempts; this should only happen in beginning of iteration
+		if (fromoldest)
+			(*inputptr)++;
+		else
+			(*inputptr)--; // iterating backwards, so count from highest
+	}
+	return false;
+}
+
+static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+{
+	char *defaultname = (char*)*input;
+
+	(void)retval;
+	(void)fromoldest;
+
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
+	if (menupres[menutype].bgcolor >= 0)
+	{
+		curbgcolor = menupres[menutype].bgcolor;
+		return true;
+	}
+	else if (menupres[menutype].bghide && titlemapinaction) // hide the background
+	{
+		curbghide = true;
+		return true;
+	}
+	else if (menupres[menutype].bgname[0])
+	{
+		strncpy(curbgname, menupres[menutype].bgname, 9);
+		curbgxspeed = menupres[menutype].titlescrollxspeed != INT32_MAX ? menupres[menutype].titlescrollxspeed : titlescrollxspeed;
+		curbgyspeed = menupres[menutype].titlescrollyspeed != INT32_MAX ? menupres[menutype].titlescrollyspeed : titlescrollyspeed;
+		return true;
+	}
+	else if (!level)
+	{
+		if (M_GetYoungestChildMenu() == MN_SP_PLAYER || !defaultname || !defaultname[0])
+			curbgcolor = 31;
+		else if (titlemapinaction) // hide the background by default in titlemap
+			curbghide = true;
+		else
+		{
+			strncpy(curbgname, defaultname, 9);
+			curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
+			curbgyspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollyspeed;
+		}
+	}
+	return false;
+}
+
+static boolean MIT_ChangeMusic(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+{
+	menupresmusic_t *defaultmusic = (menupresmusic_t*)*input;
+
+	(void)retval;
+	(void)fromoldest;
+
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
+	if (menupres[menutype].musname[0])
+	{
+		S_ChangeMusic(menupres[menutype].musname, menupres[menutype].mustrack, menupres[menutype].muslooping);
+		return true;
+	}
+	else if (menupres[menutype].musstop)
+	{
+		S_StopMusic();
+		return true;
+	}
+	else if (menupres[menutype].musignore)
+		return true;
+	else if (!level && defaultmusic && defaultmusic->musname[0])
+		S_ChangeMusic(defaultmusic->musname, defaultmusic->mustrack, defaultmusic->muslooping);
+	return false;
+}
+
+static boolean MIT_SetCurFadeValue(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+{
+	UINT8 defaultvalue = *(UINT8*)*input;
+
+	(void)retval;
+	(void)fromoldest;
+
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
+	if (menupres[menutype].fadestrength >= 0)
+	{
+		curfadevalue = (menupres[menutype].fadestrength % 32);
+		return true;
+	}
+	else if (!level)
+		curfadevalue = (gamestate == GS_TIMEATTACK) ? 0 : (defaultvalue % 32);
+	return false;
+}
+
+static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+{
+	(void)input;
+	(void)retval;
+	(void)fromoldest;
+
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
+	if (menupres[menutype].hidetitlepics >= 0)
+	{
+		curhidepics = menupres[menutype].hidetitlepics;
+		return true;
+	}
+	else if (!level)
+		curhidepics = hidetitlepics;
+	return false;
+}
+
+// ====================================
+// TREE RETRIEVAL
+// ====================================
+
+UINT8 M_GetYoungestChildMenu(void) // aka the active menu
+{
+	INT32 targetlevel = -1;
+	return M_IterateMenuTree(MIT_GetMenuAtLevel, &targetlevel);
+}
+
+// ====================================
+// EFFECTS
+// ====================================
+
+void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping)
+{
+	menupresmusic_t defaultmusic;
+
+	if (!defaultmusname)
+		defaultmusname = "";
+
+	strncpy(defaultmusic.musname, defaultmusname, 7);
+	defaultmusic.musname[6] = 0;
+	defaultmusic.mustrack = 0;
+	defaultmusic.muslooping = defaultmuslooping;
+
+	M_IterateMenuTree(MIT_ChangeMusic, &defaultmusic);
+}
+
+void M_SetMenuCurBackground(const char *defaultname)
+{
+	char name[8];
+	strncpy(name, defaultname, 8);
+	M_IterateMenuTree(MIT_SetCurBackground, &name);
+}
+
+void M_SetMenuCurFadeValue(UINT8 defaultvalue)
+{
+	M_IterateMenuTree(MIT_SetCurFadeValue, &defaultvalue);
+}
+
+void M_SetMenuCurHideTitlePics(void)
+{
+	M_IterateMenuTree(MIT_SetCurHideTitlePics, NULL);
+}
+
+// ====================================
+// MENU STATE
+// ====================================
+
+static INT32 exitlevel, enterlevel, anceslevel;
+static INT16 exittype, entertype;
+static INT16 exitwipe, enterwipe;
+static boolean exitbubble, enterbubble;
+static INT16 exittag, entertag;
+
+static void M_HandleMenuPresState(menu_t *newMenu)
+{
+	INT32 i;
+	UINT32 bitmask;
+	SINT8 prevtype, activetype, menutype;
+
+	if (!newMenu)
+		return;
+
+	// Look for MN_SPECIAL here, because our iterators can't look at new menu IDs
+	for (i = 0; i <= NUMMENULEVELS; i++)
+	{
+		bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+		menutype = (newMenu->menuid & bitmask) >> (MENUBITS*i);
+		prevtype = (currentMenu->menuid & bitmask) >> (MENUBITS*i);
+		if (menutype == MN_SPECIAL || prevtype == MN_SPECIAL)
+			return;
+	}
+
+	if (currentMenu && newMenu && currentMenu->menuid == newMenu->menuid) // same menu?
+		return;
+
+	exittype = entertype = exitlevel = enterlevel = anceslevel = exitwipe = enterwipe = -1;
+	exitbubble = enterbubble = true;
+
+	prevMenuId = currentMenu ? currentMenu->menuid : 0;
+	activeMenuId = newMenu ? newMenu->menuid : 0;
+
+	// Set defaults for presentation values
+	strncpy(curbgname, "TITLESKY", 9);
+	curfadevalue = 16;
+	curhidepics = hidetitlepics;
+	curbgcolor = -1;
+	curbgxspeed = titlescrollxspeed;
+	curbgyspeed = titlescrollyspeed;
+	curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
+
+	// don't do the below during the in-game menus
+	if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
+		return;
+
+	// Find current presentation values
+	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "SRB2BACK" : "TITLESKY");
+	M_SetMenuCurFadeValue(16);
+	M_SetMenuCurHideTitlePics();
+
+	// Loop through both menu IDs in parallel and look for type changes
+	// The youngest child in activeMenuId is the entered menu
+	// The youngest child in prevMenuId is the exited menu
+
+	// 0. Get the type and level of each menu, and level of common ancestor
+	// 1. Get the wipes for both, then run the exit wipe
+	// 2. Change music (so that execs can change it again later)
+	// 3. Run each exit exec on the prevMenuId up to the common ancestor (UNLESS NoBubbleExecs)
+	// 4. Run each entrance exec on the activeMenuId down from the common ancestor (UNLESS NoBubbleExecs)
+	// 5. Run the entrance wipe
+
+	// Get the parameters for each menu
+	for (i = NUMMENULEVELS; i >= 0; i--)
+	{
+		bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+		prevtype = (prevMenuId & bitmask) >> (MENUBITS*i);
+		activetype = (activeMenuId & bitmask) >> (MENUBITS*i);
+
+		if (prevtype && (exittype < 0))
+		{
+			exittype = prevtype;
+			exitlevel = i;
+			exitwipe = menupres[exittype].exitwipe;
+			exitbubble = menupres[exittype].exitbubble;
+			exittag = menupres[exittype].exittag;
+		}
+
+		if (activetype && (entertype < 0))
+		{
+			entertype = activetype;
+			enterlevel = i;
+			enterwipe = menupres[entertype].enterwipe;
+			enterbubble = menupres[entertype].enterbubble;
+			entertag = menupres[entertype].entertag;
+		}
+
+		if (prevtype && activetype && prevtype == activetype && anceslevel < 0)
+		{
+			anceslevel = i;
+			break;
+		}
+	}
+
+	// if no common ancestor (top menu), force a wipe. Look for a specified wipe first.
+	// Don't force a wipe if you're actually going to/from the main menu
+	if (anceslevel < 0 && exitwipe < 0 && newMenu != &MainDef && currentMenu != &MainDef)
+	{
+		for (i = NUMMENULEVELS; i >= 0; i--)
+		{
+			bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+			prevtype = (prevMenuId & bitmask) >> (MENUBITS*i);
+
+			if (menupres[prevtype].exitwipe >= 0)
+			{
+				exitwipe = menupres[prevtype].exitwipe;
+				break;
+			}
+		}
+
+		if (exitwipe < 0)
+			exitwipe = menupres[MN_MAIN].exitwipe;
+	}
+
+	// do the same for enter wipe
+	if (anceslevel < 0 && enterwipe < 0 && newMenu != &MainDef && currentMenu != &MainDef)
+	{
+		for (i = NUMMENULEVELS; i >= 0; i--)
+		{
+			bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+			activetype = (activeMenuId & bitmask) >> (MENUBITS*i);
+
+			if (menupres[activetype].enterwipe >= 0)
+			{
+				exitwipe = menupres[activetype].enterwipe;
+				break;
+			}
+		}
+
+		if (enterwipe < 0)
+			enterwipe = menupres[MN_MAIN].enterwipe;
+	}
+
+	// Change the music
+	M_ChangeMenuMusic("_title", false);
+
+	// Run the linedef execs
+	if (titlemapinaction)
+	{
+		// Run the exit tags
+		if (enterlevel <= exitlevel) // equals is an edge case
+		{
+			if (exitbubble)
+			{
+				for (i = exitlevel; i > anceslevel; i--) // don't run the common ancestor's exit tag
+				{
+					bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+					menutype = (prevMenuId & bitmask) >> (MENUBITS*i);
+					if (menupres[menutype].exittag)
+						P_LinedefExecute(menupres[menutype].exittag, players[displayplayer].mo, NULL);
+				}
+			}
+			else if (exittag)
+				P_LinedefExecute(exittag, players[displayplayer].mo, NULL);
+		}
+
+		// Run the enter tags
+		if (enterlevel >= exitlevel) // equals is an edge case
+		{
+			if (enterbubble)
+			{
+				for (i = anceslevel+1; i <= enterlevel; i++) // don't run the common ancestor's enter tag
+				{
+					bitmask = ((1 << MENUBITS) - 1) << (MENUBITS*i);
+					menutype = (activeMenuId & bitmask) >> (MENUBITS*i);
+					if (menupres[menutype].entertag)
+						P_LinedefExecute(menupres[menutype].entertag, players[displayplayer].mo, NULL);
+				}
+			}
+			else if (entertag)
+				P_LinedefExecute(entertag, players[displayplayer].mo, NULL);
+		}
+	}
+
+
+	// Set the wipes for next frame
+	if (
+		(exitwipe >= 0 && enterlevel <= exitlevel) ||
+		(enterwipe >= 0 && enterlevel >= exitlevel) ||
+		(anceslevel < 0 && newMenu != &MainDef && currentMenu != &MainDef)
+	)
+	{
+		if (gamestate == GS_TIMEATTACK)
+			wipetypepre = ((exitwipe && enterlevel <= exitlevel) || anceslevel < 0) ? exitwipe : -1; // force default
+		else
+			// HACK: INT16_MAX signals to not wipe
+			// because 0 is a valid index and -1 means default
+			wipetypepre = ((exitwipe && enterlevel <= exitlevel) || anceslevel < 0) ? exitwipe : INT16_MAX;
+		wipetypepost = ((enterwipe && enterlevel >= exitlevel) || anceslevel < 0) ? enterwipe : INT16_MAX;
+		wipegamestate = FORCEWIPE;
+
+		// If just one of the above is a force not-wipe,
+		// mirror the other wipe.
+		if (wipetypepre != INT16_MAX && wipetypepost == INT16_MAX)
+			wipetypepost = wipetypepre;
+		else if (wipetypepost != INT16_MAX && wipetypepre == INT16_MAX)
+			wipetypepre = wipetypepost;
+
+		// D_Display runs the next step of processing
+	}
+}
+
 // =========================================================================
 // BASIC MENU HANDLING
 // =========================================================================
@@ -2134,16 +2691,21 @@ static void M_GoBack(INT32 choice)
 		if (!Playing() && netgame && multiplayer)
 		{
 			MSCloseUDPSocket();		// Clean up so we can re-open the connection later.
-			netgame = false;
-			multiplayer = false;
+			netgame = multiplayer = false;
 		}
 
 		if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
 		{
 			// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
-			Z_Free(levelselect.rows);
-			levelselect.rows = NULL;
+
+			if (levelselect.rows)
+			{
+				Z_Free(levelselect.rows);
+				levelselect.rows = NULL;
+			}
+
 			menuactive = false;
+			wipetypepre = menupres[M_GetYoungestChildMenu()].exitwipe;
 			D_StartTitle();
 		}
 		else
@@ -2157,20 +2719,35 @@ static void M_ChangeCvar(INT32 choice)
 {
 	consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
 
+	if (choice == -1)
+	{
+		if (cv == &cv_playercolor)
+		{
+			SINT8 skinno = R_SkinAvailable(cv_chooseskin.string);
+			if (skinno != -1)
+				CV_SetValue(cv,skins[skinno].prefcolor);
+			return;
+		}
+		CV_Set(cv,cv->defaultvalue);
+		return;
+	}
+
+	choice = (choice<<1) - 1;
+
 	if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD))
 	{
-		CV_SetValue(cv,cv->value+(choice*2-1));
+		CV_SetValue(cv,cv->value+(choice));
 	}
 	else if (cv->flags & CV_FLOAT)
 	{
 		char s[20];
-		sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice*2-1)*(1.0f/16.0f));
+		sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
 		CV_Set(cv,s);
 	}
 	else
-		CV_AddValue(cv,choice*2-1);
+		CV_AddValue(cv,choice);
 }
 
 static boolean M_ChangeStringCvar(INT32 choice)
@@ -2260,13 +2837,14 @@ boolean M_Responder(event_t *ev)
 	INT32 ch = -1;
 //	INT32 i;
 	static tic_t joywait = 0, mousewait = 0;
+	static INT32 pjoyx = 0, pjoyy = 0;
 	static INT32 pmousex = 0, pmousey = 0;
 	static INT32 lastx = 0, lasty = 0;
 	void (*routine)(INT32 choice); // for some casting problem
 
 	if (dedicated || (demoplayback && titledemo)
-	|| gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND
-	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
+	|| gamestate == GS_INTRO || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
+	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND)
 		return false;
 
 	if (noFurtherInput)
@@ -2275,63 +2853,84 @@ boolean M_Responder(event_t *ev)
 		// (but still allow shift keyup so caps doesn't get stuck)
 		return false;
 	}
-	else if (ev->type == ev_keydown)
-	{
-		ch = ev->data1;
-
-		// added 5-2-98 remap virtual keys (mouse & joystick buttons)
-		switch (ch)
-		{
-			case KEY_MOUSE1:
-			case KEY_JOY1:
-			case KEY_JOY1 + 2:
-				ch = KEY_ENTER;
-				break;
-			case KEY_JOY1 + 3:
-				ch = 'n';
-				break;
-			case KEY_MOUSE1 + 1:
-			case KEY_JOY1 + 1:
-				ch = KEY_BACKSPACE;
-				break;
-			case KEY_HAT1:
-				ch = KEY_UPARROW;
-				break;
-			case KEY_HAT1 + 1:
-				ch = KEY_DOWNARROW;
-				break;
-			case KEY_HAT1 + 2:
-				ch = KEY_LEFTARROW;
-				break;
-			case KEY_HAT1 + 3:
-				ch = KEY_RIGHTARROW;
-				break;
-		}
-	}
 	else if (menuactive)
 	{
-		if (ev->type == ev_joystick  && ev->data1 == 0 && joywait < I_GetTime())
+		if (ev->type == ev_keydown)
 		{
-			if (ev->data3 == -1)
+			ch = ev->data1;
+
+			// added 5-2-98 remap virtual keys (mouse & joystick buttons)
+			switch (ch)
 			{
-				ch = KEY_UPARROW;
-				joywait = I_GetTime() + NEWTICRATE/7;
+				case KEY_MOUSE1:
+				case KEY_JOY1:
+					ch = KEY_ENTER;
+					break;
+				case KEY_JOY1 + 3:
+					ch = 'n';
+					break;
+				case KEY_MOUSE1 + 1:
+				case KEY_JOY1 + 1:
+					ch = KEY_ESCAPE;
+					break;
+				case KEY_JOY1 + 2:
+					ch = KEY_BACKSPACE;
+					break;
+				case KEY_HAT1:
+					ch = KEY_UPARROW;
+					break;
+				case KEY_HAT1 + 1:
+					ch = KEY_DOWNARROW;
+					break;
+				case KEY_HAT1 + 2:
+					ch = KEY_LEFTARROW;
+					break;
+				case KEY_HAT1 + 3:
+					ch = KEY_RIGHTARROW;
+					break;
 			}
-			else if (ev->data3 == 1)
+		}
+		else if (ev->type == ev_joystick  && ev->data1 == 0 && joywait < I_GetTime())
+		{
+			const INT32 jdeadzone = JOYAXISRANGE/4;
+			if (ev->data3 != INT32_MAX)
 			{
-				ch = KEY_DOWNARROW;
-				joywait = I_GetTime() + NEWTICRATE/7;
+				if (Joystick.bGamepadStyle || abs(ev->data3) > jdeadzone)
+				{
+					if (ev->data3 < 0 && pjoyy >= 0)
+					{
+						ch = KEY_UPARROW;
+						joywait = I_GetTime() + NEWTICRATE/7;
+					}
+					else if (ev->data3 > 0 && pjoyy <= 0)
+					{
+						ch = KEY_DOWNARROW;
+						joywait = I_GetTime() + NEWTICRATE/7;
+					}
+					pjoyy = ev->data3;
+				}
+				else
+					pjoyy = 0;
 			}
 
-			if (ev->data2 == -1)
+			if (ev->data2 != INT32_MAX)
 			{
-				ch = KEY_LEFTARROW;
-				joywait = I_GetTime() + NEWTICRATE/17;
-			}
-			else if (ev->data2 == 1)
-			{
-				ch = KEY_RIGHTARROW;
-				joywait = I_GetTime() + NEWTICRATE/17;
+				if (Joystick.bGamepadStyle || abs(ev->data2) > jdeadzone)
+				{
+					if (ev->data2 < 0 && pjoyx >= 0)
+					{
+						ch = KEY_LEFTARROW;
+						joywait = I_GetTime() + NEWTICRATE/17;
+					}
+					else if (ev->data2 > 0 && pjoyx <= 0)
+					{
+						ch = KEY_RIGHTARROW;
+						joywait = I_GetTime() + NEWTICRATE/17;
+					}
+					pjoyx = ev->data2;
+				}
+				else
+					pjoyx = 0;
 			}
 		}
 		else if (ev->type == ev_mouse && mousewait < I_GetTime())
@@ -2365,9 +2964,13 @@ boolean M_Responder(event_t *ev)
 			}
 		}
 	}
+	else if (ev->type == ev_keydown) // Preserve event for other responders
+		ch = ev->data1;
 
 	if (ch == -1)
 		return false;
+	else if (ch == gamecontrol[gc_systemmenu][0] || ch == gamecontrol[gc_systemmenu][1]) // allow remappable ESC key
+		ch = KEY_ESCAPE;
 
 	// F-Keys
 	if (!menuactive)
@@ -2395,8 +2998,9 @@ boolean M_Responder(event_t *ev)
 					return true;
 				M_StartControlPanel();
 				M_Options(0);
-				currentMenu = &OP_SoundOptionsDef;
-				itemOn = 0;
+				// Uncomment the below if you want the menu to reset to the top each time like before. M_SetupNextMenu will fix it automatically.
+				//OP_SoundOptionsDef.lastOn = 0;
+				M_SetupNextMenu(&OP_SoundOptionsDef);
 				return true;
 
 			case KEY_F5: // Video Mode
@@ -2580,6 +3184,23 @@ boolean M_Responder(event_t *ev)
 				S_StartSound(NULL, sfx_shldls);
 				return true;
 			}
+
+			if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
+				|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
+			{
+				consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
+
+				if (cv == &cv_chooseskin
+					|| cv == &cv_nextmap
+					|| cv == &cv_newgametype)
+					return true;
+
+				if (currentMenu != &OP_SoundOptionsDef || itemOn > 3)
+					S_StartSound(NULL, sfx_menu1);
+				routine(-1);
+				return true;
+			}
+
 			// Why _does_ backspace go back anyway?
 			//currentMenu->lastOn = itemOn;
 			//if (currentMenu->prevMenu)
@@ -2606,8 +3227,8 @@ void M_Drawer(void)
 	if (menuactive)
 	{
 		// now that's more readable with a faded background (yeah like Quake...)
-		if (!WipeInAction)
-			V_DrawFadeScreen();
+		if (!WipeInAction && (curfadevalue || (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)))
+			V_DrawFadeScreen(0xFF00, (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK) ? 16 : curfadevalue);
 
 		if (currentMenu->drawroutine)
 			currentMenu->drawroutine(); // call current menu Draw routine
@@ -2734,7 +3355,7 @@ void M_StartControlPanel(void)
 		MPauseMenu[mpause_switchteam].status = IT_DISABLED;
 		MPauseMenu[mpause_psetup].status = IT_DISABLED;
 
-		if ((server || adminplayer == consoleplayer))
+		if ((server || IsPlayerAdmin(consoleplayer)))
 		{
 			MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL;
 			MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL;
@@ -2804,6 +3425,9 @@ void M_SetupNextMenu(menu_t *menudef)
 		if (currentMenu != menudef && !currentMenu->quitroutine())
 			return; // we can't quit this menu (also used to set parameter from the menu)
 	}
+
+	M_HandleMenuPresState(menudef);
+
 	currentMenu = menudef;
 	itemOn = currentMenu->lastOn;
 
@@ -2908,6 +3532,22 @@ void M_Init(void)
 #endif
 }
 
+void M_InitCharacterTables(void)
+{
+	UINT8 i;
+
+	// Setup description table
+	for (i = 0; i < MAXSKINS; i++)
+	{
+		description[i].used = false;
+		strcpy(description[i].notes, "???");
+		strcpy(description[i].picname, "");
+		strcpy(description[i].skinname, "");
+		description[i].prev = description[i].next = 0;
+		description[i].pic = NULL;
+	}
+}
+
 // ==========================================================================
 // SPECIAL MENU OPTION DRAW ROUTINES GO HERE
 // ==========================================================================
@@ -3106,6 +3746,7 @@ static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_
 //
 // Draw border for the savegame description
 //
+#if 0 // once used for joysticks and savegames, now no longer
 static void M_DrawSaveLoadBorder(INT32 x,INT32 y)
 {
 	INT32 i;
@@ -3120,6 +3761,7 @@ static void M_DrawSaveLoadBorder(INT32 x,INT32 y)
 
 	V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSRGHT",PU_CACHE));
 }
+#endif
 
 // horizontally centered text
 static void M_CentreText(INT32 y, const char *string)
@@ -3825,7 +4467,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 			if (mapnum+1 == spstage_start)
 				return true;
 
-			// intentional fallthrough
+			/* FALLTHRU */
 		case LLM_RECORDATTACK:
 		case LLM_NIGHTSATTACK:
 			if (mapvisited[mapnum] & MV_MAX)
@@ -3889,7 +4531,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 			return false;
 
 		case LLM_LEVELSELECT:
-			if (mapheaderinfo[mapnum]->levelselect != maplistoption)
+			if (!(mapheaderinfo[mapnum]->levelselect & maplistoption))
 				return false;
 
 			return true;
@@ -4235,7 +4877,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 				}
 				break;
 			}
-			// intentionall fallthrough
+			/* FALLTHRU */
 		case KEY_RIGHTARROW:
 			if (levellistmode == LLM_CREATESERVER && !lsrow)
 			{
@@ -4446,17 +5088,24 @@ static void M_DrawLevelPlatterMenu(void)
 	const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
 
 	if (gamestate == GS_TIMEATTACK)
-		V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+	{
+		if (curbgcolor >= 0)
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+		else if (!curbghide || !titlemapinaction)
+			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		if (curfadevalue)
+			V_DrawFadeScreen(0xFF00, curfadevalue);
+	}
 
 	// finds row at top of the screen
-	while (y > 0)
+	while (y > -8)
 	{
 		iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
 		y -= lsvseperation(iter);
 	}
 
 	// draw from top to bottom
-	while (y < 200)
+	while (y < (vid.height/vid.dupy))
 	{
 		M_DrawLevelPlatterRow(iter, y);
 		y += lsvseperation(iter);
@@ -4522,6 +5171,7 @@ static menuitem_t MessageMenu[] =
 
 menu_t MessageDef =
 {
+	MN_SPECIAL,
 	NULL,               // title
 	1,                  // # of menu items
 	NULL,               // previous menu       (TO HACK)
@@ -4647,7 +5297,14 @@ static void M_DrawMessageMenu(void)
 
 	// hack: draw RA background in RA menus
 	if (gamestate == GS_TIMEATTACK)
-		V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+	{
+		if (curbgcolor >= 0)
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+		else if (!curbghide || !titlemapinaction)
+			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		if (curfadevalue)
+			V_DrawFadeScreen(0xFF00, curfadevalue);
+	}
 
 	M_DrawTextBox(currentMenu->x, y - 8, (max+7)>>3, mlines);
 
@@ -4775,7 +5432,8 @@ static void M_AddonsOptions(INT32 choice)
 	M_SetupNextMenu(&OP_AddonsOptionsDef);
 }
 
-#define LOCATIONSTRING "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!"
+#define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!"
+//#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make add-ons!"
 
 static void M_Addons(INT32 choice)
 {
@@ -4783,22 +5441,29 @@ static void M_Addons(INT32 choice)
 
 	(void)choice;
 
-	/*if (cv_addons_option.value == 0)
-		pathname = srb2home; usehome ? srb2home : srb2path;
+	// If M_GetGameypeColor() is ever ported from Kart, then remove this.
+	highlightflags = V_YELLOWMAP;
+	recommendedflags = V_GREENMAP;
+	warningflags = V_REDMAP;
+
+#if 1
+	if (cv_addons_option.value == 0)
+		pathname = usehome ? srb2home : srb2path;
 	else if (cv_addons_option.value == 1)
 		pathname = srb2home;
 	else if (cv_addons_option.value == 2)
 		pathname = srb2path;
-	else*/
+	else
+#endif
 	if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0')
 		pathname = cv_addons_folder.string;
 
 	strlcpy(menupath, pathname, 1024);
 	menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1;
 
-	if (menupath[menupathindex[menudepthleft]-2] != '/')
+	if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0])
 	{
-		menupath[menupathindex[menudepthleft]-1] = '/';
+		menupath[menupathindex[menudepthleft]-1] = PATHSEP[0];
 		menupath[menupathindex[menudepthleft]] = 0;
 	}
 	else
@@ -4806,7 +5471,8 @@ static void M_Addons(INT32 choice)
 
 	if (!preparefilemenu(false))
 	{
-		M_StartMessage(M_GetText("No files/folders found.\n\n"LOCATIONSTRING"\n\n(Press a key)\n"),NULL,MM_NOTHING);
+		M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n",LOCATIONSTRING1),NULL,MM_NOTHING);
+			// (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1))
 		return;
 	}
 	else
@@ -4815,7 +5481,7 @@ static void M_Addons(INT32 choice)
 	if (addonsp[0]) // never going to have some provided but not all, saves individually checking
 	{
 		size_t i;
-		for (i = 0; i < NUM_EXT+6; i++)
+		for (i = 0; i < NUM_EXT+5; i++)
 			W_UnlockCachedPatch(addonsp[i]);
 	}
 
@@ -4825,15 +5491,17 @@ static void M_Addons(INT32 choice)
 	addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC);
 	addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC);
 	addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC);
+#ifdef USE_KART
+	addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC);
+#endif
 	addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC);
 	addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC);
 	addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC);
 	addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC);
-	addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL1", PU_STATIC);
-	addonsp[NUM_EXT+2] = W_CachePatchName("M_FSEL2", PU_STATIC);
-	addonsp[NUM_EXT+3] = W_CachePatchName("M_FLOAD", PU_STATIC);
-	addonsp[NUM_EXT+4] = W_CachePatchName("M_FSRCH", PU_STATIC);
-	addonsp[NUM_EXT+5] = W_CachePatchName("M_FSAVE", PU_STATIC);
+	addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC);
+	addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC);
+	addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC);
+	addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC);
 
 	MISC_AddonsDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&MISC_AddonsDef);
@@ -4892,11 +5560,7 @@ static char *M_AddonsHeaderPath(void)
 	UINT32 len;
 	static char header[1024];
 
-	if (menupath[0] == '.')
-		strlcpy(header, va("SRB2 folder%s", menupath+1), 1024);
-	else
-		strcpy(header, menupath);
-
+	strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024);
 	len = strlen(header);
 	if (len > 34)
 	{
@@ -4913,6 +5577,15 @@ static char *M_AddonsHeaderPath(void)
 		M_SetupNextMenu(MISC_AddonsDef.prevMenu);\
 		M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING)
 
+#define CLEARNAME Z_Free(refreshdirname);\
+					refreshdirname = NULL
+
+static void M_AddonsClearName(INT32 choice)
+{
+	CLEARNAME;
+	M_StopMessage(choice);
+}
+
 // returns whether to do message draw
 static boolean M_AddonsRefresh(void)
 {
@@ -4924,54 +5597,54 @@ static boolean M_AddonsRefresh(void)
 
 	if (refreshdirmenu & REFRESHDIR_ADDFILE)
 	{
-		addonsresponselimit = 0;
+		char *message = NULL;
 
 		if (refreshdirmenu & REFRESHDIR_NOTLOADED)
 		{
-			char *message = NULL;
 			S_StartSound(NULL, sfx_lose);
 			if (refreshdirmenu & REFRESHDIR_MAX)
-				message = va("\x82%s\x80\nMaximum number of add-ons reached.\nThis file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING);
+				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			else
-				message = va("\x82%s\x80\nThe file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING);
-			M_StartMessage(message,NULL,MM_NOTHING);
-			return true;
+				message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 		}
-
-		if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR))
+		else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR))
 		{
 			S_StartSound(NULL, sfx_skid);
-			M_StartMessage(va("\x82%s\x80\nThe file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")),NULL,MM_NOTHING);
+			message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
+		}
+
+		if (message)
+		{
+			M_StartMessage(message,M_AddonsClearName,MM_EVENTHANDLER);
 			return true;
 		}
 
 		S_StartSound(NULL, sfx_strpst);
+		CLEARNAME;
 	}
 
 	return false;
 }
 
-#define offs 1
-
-#ifdef __GNUC__
-#pragma GCC optimize ("0")
-#endif
-
 static void M_DrawAddons(void)
 {
 	INT32 x, y;
-	ssize_t i, max;
+	ssize_t i, m;
+	const UINT8 *flashcol = NULL;
+	UINT8 hilicol;
 
 	// hack - need to refresh at end of frame to handle addfile...
 	if (refreshdirmenu & M_AddonsRefresh())
-		return M_DrawMessageMenu();
-
-	if (addonsresponselimit)
-		addonsresponselimit--;
+	{
+		M_DrawMessageMenu();
+		return;
+	}
 
-	V_DrawCenteredString(BASEVIDWIDTH/2, 4+offs, 0, (Playing()
-	? "\x85""Adding files mid-game may cause problems."
-	: LOCATIONSTRING));
+	if (Playing())
+		V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems.");
+	else
+		V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, LOCATIONSTRING1);
+			// (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)
 
 	if (numwadfiles <= mainwads+1)
 		y = 0;
@@ -4979,8 +5652,8 @@ static void M_DrawAddons(void)
 		y = FRACUNIT;
 	else
 	{
-		x = FixedDiv((numwadfiles - mainwads+1)<<FRACBITS, (MAX_WADFILES - mainwads+1)<<FRACBITS);
-		y = FixedDiv(((packetsizetally-mainwadstally)<<FRACBITS), (((MAXFILENEEDED*sizeof(UINT8)-mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally
+		x = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))<<FRACBITS, ((ssize_t)MAX_WADFILES - (ssize_t)(mainwads+1))<<FRACBITS);
+		y = FixedDiv((((ssize_t)packetsizetally-(ssize_t)mainwadstally)<<FRACBITS), ((((ssize_t)MAXFILENEEDED*sizeof(UINT8)-(ssize_t)mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally
 		if (x > y)
 			y = x;
 		if (y > FRACUNIT) // happens because of how we're shrinkin' it a little
@@ -4991,36 +5664,56 @@ static void M_DrawAddons(void)
 
 	// DRAW MENU
 	x = currentMenu->x;
-	y = currentMenu->y + offs;
+	y = currentMenu->y + 1;
+
+	hilicol = 0; // white
+
+	V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath());
+	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol);
+	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30);
 
-	//M_DrawLevelPlatterHeader(y - 16, M_AddonsHeaderPath(), true, true); -- wanted different width
-	V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), V_YELLOWMAP|V_ALLOWLOWERCASE, M_AddonsHeaderPath());
-	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), (MAXSTRINGLENGTH*8+6 - 1), 1, yellowmap[3]);
-	V_DrawFill(x-21 + (MAXSTRINGLENGTH*8+6 - 1), (y - 16) + (lsheadingheight - 3), 1, 1, 26);
-	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 26);
+	m = (BASEVIDHEIGHT - currentMenu->y + 2) - (y - 1);
+	// addons menu back color
+	V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 159);
+
+	// scrollbar!
+	if (sizedirmenu <= (2*numaddonsshown + 1))
+		i = 0;
+	else
+	{
+		ssize_t q = m;
+		m = ((2*numaddonsshown + 1) * m)/sizedirmenu;
+		if (dir_on[menudepthleft] <= numaddonsshown) // all the way up
+			i = 0;
+		else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down
+			i = q-m;
+		else
+			i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1));
+	}
 
-	V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, (BASEVIDHEIGHT - currentMenu->y + 1 + offs) - (y - 1), 159);
+	V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol);
 
 	// get bottom...
-	max = dir_on[menudepthleft] + numaddonsshown + 1;
-	if (max > (ssize_t)sizedirmenu)
-		max = sizedirmenu;
-
-	// then top...
-	i = max - (2*numaddonsshown + 1);
+	m = dir_on[menudepthleft] + numaddonsshown + 1;
+	if (m > (ssize_t)sizedirmenu)
+		m = sizedirmenu;
 
-	// then adjust!
-	if (i < 0)
+	// then compute top and adjust bottom if needed!
+	if (m < (2*numaddonsshown + 1))
 	{
-		if ((max -= i) > (ssize_t)sizedirmenu)
-			max = sizedirmenu;
+		m = min(sizedirmenu, 2*numaddonsshown + 1);
 		i = 0;
 	}
+	else
+		i = m - (2*numaddonsshown + 1);
 
 	if (i != 0)
-		V_DrawString(19, y+4 - (skullAnimCounter/5), V_YELLOWMAP, "\x1A");
+		V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A");
 
-	for (; i < max; i++)
+	if (skullAnimCounter < 4)
+		flashcol = V_GetStringColormap(highlightflags);
+
+	for (; i < m; i++)
 	{
 		UINT32 flags = V_ALLOWLOWERCASE;
 		if (y > BASEVIDHEIGHT) break;
@@ -5028,17 +5721,18 @@ static void M_DrawAddons(void)
 #define type (UINT8)(dirmenu[i][DIR_TYPE])
 		{
 			if (type & EXT_LOADED)
-			flags |= V_TRANSLUCENT;
-
-			V_DrawSmallScaledPatch(x-(16+4), y, (flags & V_TRANSLUCENT), addonsp[((UINT8)(dirmenu[i][DIR_TYPE]) & ~EXT_LOADED)]);
-
-			if (type & EXT_LOADED)
-				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+3]);
+			{
+				flags |= V_TRANSLUCENT;
+				V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]);
+				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]);
+			}
+			else
+				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]);
 
 			if ((size_t)i == dir_on[menudepthleft])
 			{
-				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+1+((skullAnimCounter/4) ? 1 : 0)]);
-				flags = V_ALLOWLOWERCASE|V_YELLOWMAP;
+				V_DrawFixedPatch((x-(16+4))<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, 0, addonsp[NUM_EXT+1], flashcol);
+				flags = V_ALLOWLOWERCASE|highlightflags;
 			}
 
 #define charsonside 14
@@ -5052,10 +5746,10 @@ static void M_DrawAddons(void)
 		y += 16;
 	}
 
-	if (max != (ssize_t)sizedirmenu)
-		V_DrawString(19, y-12 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
+	if (m != (ssize_t)sizedirmenu)
+		V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B");
 
-	y = BASEVIDHEIGHT - currentMenu->y + offs;
+	y = BASEVIDHEIGHT - currentMenu->y + 1;
 
 	M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1);
 	if (menusearch[0])
@@ -5067,30 +5761,22 @@ static void M_DrawAddons(void)
 			'_' | 0x80, false);
 
 	x -= (21 + 5 + 16);
-	V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
+	V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]);
 
-#define CANSAVE (!modifiedgame || savemoddata)
 	x = BASEVIDWIDTH - x - 16;
-	V_DrawSmallScaledPatch(x, y + 4, (CANSAVE ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+5]);
+	V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
 
 	if (modifiedgame)
-		V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+3]);
-#undef CANSAVE
+		V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]);
 }
 
-#ifdef __GNUC__
-#pragma GCC reset_options
-#endif
-
-#undef offs
-
 static void M_AddonExec(INT32 ch)
 {
 	if (ch != 'y' && ch != KEY_ENTER)
 		return;
 
 	S_StartSound(NULL, sfx_zoom);
-	COM_BufAddText(va("exec %s%s", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
+	COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
 }
 
 #define len menusearch[0]
@@ -5135,16 +5821,20 @@ static void M_HandleAddons(INT32 choice)
 {
 	boolean exitmenu = false; // exit to previous menu
 
-	if (addonsresponselimit)
-		return;
-
 	if (M_ChangeStringAddons(choice))
 	{
+		char *tempname = NULL;
+		if (dirmenu && dirmenu[dir_on[menudepthleft]])
+			tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL
+#if 0 // much slower
 		if (!preparefilemenu(true))
 		{
 			UNEXIST;
 			return;
 		}
+#else // streamlined
+		searchfilemenu(tempname);
+#endif
 	}
 
 	switch (choice)
@@ -5194,7 +5884,7 @@ static void M_HandleAddons(INT32 choice)
 								if (!preparefilemenu(false))
 								{
 									S_StartSound(NULL, sfx_skid);
-									M_StartMessage(va("\x82%s\x80\nThis folder is empty.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING);
+									M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
 									menupath[menupathindex[++menudepthleft]] = 0;
 
 									if (!preparefilemenu(true))
@@ -5213,7 +5903,7 @@ static void M_HandleAddons(INT32 choice)
 							else
 							{
 								S_StartSound(NULL, sfx_lose);
-								M_StartMessage(va("\x82%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING);
+								M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
 								menupath[menupathindex[menudepthleft]] = 0;
 							}
 							break;
@@ -5227,7 +5917,7 @@ static void M_HandleAddons(INT32 choice)
 							}
 							break;
 						case EXT_TXT:
-							M_StartMessage(va("\x82%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO);
+							M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO);
 							break;
 						case EXT_CFG:
 							M_AddonExec(KEY_ENTER);
@@ -5235,15 +5925,17 @@ static void M_HandleAddons(INT32 choice)
 						case EXT_LUA:
 #ifndef HAVE_BLUA
 							S_StartSound(NULL, sfx_lose);
-							M_StartMessage(va("\x82%s\x80\nThis copy of SRB2 was compiled\nwithout support for .lua files.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING),NULL,MM_NOTHING);
+							M_StartMessage(va("%c%s\x80\nThis copy of SRB2 was compiled\nwithout support for .lua files.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),NULL,MM_NOTHING);
 							break;
 #endif
-						// else intentional fallthrough
+						/* FALLTHRU */
 						case EXT_SOC:
 						case EXT_WAD:
+#ifdef USE_KART
+						case EXT_KART:
+#endif
 						case EXT_PK3:
-							COM_BufAddText(va("addfile %s%s", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
-							addonsresponselimit = 5;
+							COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
 							break;
 						default:
 							S_StartSound(NULL, sfx_lose);
@@ -5263,14 +5955,7 @@ static void M_HandleAddons(INT32 choice)
 	}
 	if (exitmenu)
 	{
-		for (; sizedirmenu > 0; sizedirmenu--)
-		{
-			Z_Free(dirmenu[sizedirmenu-1]);
-			dirmenu[sizedirmenu-1] = NULL;
-		}
-
-		Z_Free(dirmenu);
-		dirmenu = NULL;
+		closefilemenu(true);
 
 		// secrets disabled by addfile...
 		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
@@ -5285,8 +5970,14 @@ static void M_HandleAddons(INT32 choice)
 static void M_PandorasBox(INT32 choice)
 {
 	(void)choice;
-	CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
-	CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
+	if (maptol & TOL_NIGHTS)
+		CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].spheres, 0));
+	else
+		CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
+	if (players[consoleplayer].lives == INFLIVES)
+		CV_StealthSetValue(&cv_dummylives, -1);
+	else
+		CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
 	CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
 	SR_PandorasBox[6].status = ((players[consoleplayer].charflags & SF_SUPER)
 #ifndef DEVELOP
@@ -5300,7 +5991,12 @@ static void M_PandorasBox(INT32 choice)
 static boolean M_ExitPandorasBox(void)
 {
 	if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
-		COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
+	{
+		if (maptol & TOL_NIGHTS)
+			COM_ImmedExecute(va("setspheres %d", cv_dummyrings.value));
+		else
+			COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
+	}
 	if (cv_dummylives.value != players[consoleplayer].lives)
 		COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
 	if (cv_dummycontinues.value != players[consoleplayer].continues)
@@ -5387,7 +6083,7 @@ static void M_Options(INT32 choice)
 	(void)choice;
 
 	// if the player is not admin or server, disable server options
-	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
+	OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
 
 	// if the player is playing _at all_, disable the erase data options
 	OP_DataOptionsMenu[2].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
@@ -5703,7 +6399,7 @@ static void M_DrawChecklist(void)
 												beat = va("Get %d points in %s", cond[condnum].requirement, level);
 												break;
 											case UC_MAPTIME:
-												beat = va("Beat %s in %d:%d.%d", level,
+												beat = va("Beat %s in %d:%02d.%02d", level,
 												G_TicsToMinutes(cond[condnum].requirement, true),
 												G_TicsToSeconds(cond[condnum].requirement),
 												G_TicsToCentiseconds(cond[condnum].requirement));
@@ -5728,7 +6424,7 @@ static void M_DrawChecklist(void)
 											beat = va("Get %d points over all maps", cond[condnum].requirement);
 											break;
 										case UC_OVERALLTIME:
-											beat = va("Get a total time of less than %d:%d.%d",
+											beat = va("Get a total time of less than %d:%02d.%02d",
 											G_TicsToMinutes(cond[condnum].requirement, true),
 											G_TicsToSeconds(cond[condnum].requirement),
 											G_TicsToCentiseconds(cond[condnum].requirement));
@@ -5776,12 +6472,12 @@ static void M_DrawChecklist(void)
 												break;
 											case UC_NIGHTSTIME:
 												if (cond[condnum].extrainfo2)
-													beat = va("Beat %s, mare %d in %d:%d.%d", level, cond[condnum].extrainfo2,
+													beat = va("Beat %s, mare %d in %d:%02d.%02d", level, cond[condnum].extrainfo2,
 													G_TicsToMinutes(cond[condnum].requirement, true),
 													G_TicsToSeconds(cond[condnum].requirement),
 													G_TicsToCentiseconds(cond[condnum].requirement));
 												else
-													beat = va("Beat %s in %d:%d.%d",
+													beat = va("Beat %s in %d:%02d.%02d",
 													level,
 													G_TicsToMinutes(cond[condnum].requirement, true),
 													G_TicsToSeconds(cond[condnum].requirement),
@@ -6094,6 +6790,8 @@ static void M_CustomLevelSelect(INT32 choice)
 static void M_SinglePlayerMenu(INT32 choice)
 {
 	(void)choice;
+	SP_MainMenu[sptutorial].status =
+		tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED;
 	SP_MainMenu[sprecordattack].status =
 		(M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
 	SP_MainMenu[spnightsmode].status =
@@ -6108,7 +6806,7 @@ static void M_LoadGameLevelSelect(INT32 choice)
 
 	SP_LevelSelectDef.prevMenu = currentMenu;
 	levellistmode = LLM_LEVELSELECT;
-	maplistoption = 1;
+	maplistoption = 1+2;
 
 	if (!M_PrepareLevelPlatter(-1, true))
 	{
@@ -6119,6 +6817,80 @@ static void M_LoadGameLevelSelect(INT32 choice)
 	M_SetupNextMenu(&SP_LevelSelectDef);
 }
 
+void M_TutorialSaveControlResponse(INT32 ch)
+{
+	if (ch == 'y' || ch == KEY_ENTER)
+	{
+		G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full);
+		CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
+		CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
+		CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
+		CV_Set(&cv_analog, cv_analog.defaultvalue);
+		S_StartSound(NULL, sfx_itemup);
+	}
+	else
+		S_StartSound(NULL, sfx_menu1);
+}
+
+static void M_TutorialControlResponse(INT32 ch)
+{
+	if (ch != KEY_ESCAPE)
+	{
+		G_CopyControls(gamecontroldefault[gcs_custom], gamecontrol, NULL, 0); // using gcs_custom as temp storage for old controls
+		if (ch == 'y' || ch == KEY_ENTER)
+		{
+			tutorialgcs = gcs_fps;
+			tutorialusemouse = cv_usemouse.value;
+			tutorialfreelook = cv_alwaysfreelook.value;
+			tutorialmousemove = cv_mousemove.value;
+			tutorialanalog = cv_analog.value;
+
+			G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full);
+			CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
+			CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
+			CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
+			CV_Set(&cv_analog, cv_analog.defaultvalue);
+
+			//S_StartSound(NULL, sfx_itemup);
+		}
+		else
+		{
+			tutorialgcs = gcs_custom;
+			S_StartSound(NULL, sfx_menu1);
+		}
+		M_StartTutorial(INT32_MAX);
+	}
+	else
+		S_StartSound(NULL, sfx_menu1);
+
+	MessageDef.prevMenu = &SP_MainDef; // if FirstPrompt -> ControlsPrompt -> ESC, we would go to the main menu unless we force this
+}
+
+// Starts up the tutorial immediately (tbh I wasn't sure where else to put this)
+static void M_StartTutorial(INT32 choice)
+{
+	if (!tutorialmap)
+		return; // no map to go to, don't bother
+
+	if (choice != INT32_MAX && G_GetControlScheme(gamecontrol, gcl_tutorial_check, num_gcl_tutorial_check) != gcs_fps)
+	{
+		M_StartMessage("Do you want to try the \202recommended \202movement controls\x80?\n\nWe will set them just for this tutorial.\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls.\n",M_TutorialControlResponse,MM_YESNO);
+		return;
+	}
+	else if (choice != INT32_MAX)
+		tutorialgcs = gcs_custom;
+
+	CV_SetValue(&cv_tutorialprompt, 0); // first-time prompt
+
+	tutorialmode = true; // turn on tutorial mode
+
+	emeralds = 0;
+	M_ClearMenus(true);
+	gamecomplete = false;
+	cursaveslot = 0;
+	G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
+}
+
 // ==============
 // LOAD GAME MENU
 // ==============
@@ -6186,7 +6958,7 @@ static void M_DrawLoadGameData(void)
 			{
 				V_DrawSmallScaledPatch(x+2, y+64, 0, savselp[5]);
 			}
-#ifndef PERFECTSAVE // disabled, don't touch
+#ifdef PERFECTSAVE // disabled on request
 			else if ((savegameinfo[savetodraw].skinnum == 1)
 			&& (savegameinfo[savetodraw].lives == 99)
 			&& (savegameinfo[savetodraw].gamemap & 8192)
@@ -6274,7 +7046,7 @@ static void M_DrawLoadGameData(void)
 			for (j = 0; j < 7; ++j)
 			{
 				if (savegameinfo[savetodraw].numemeralds & (1 << j))
-					V_DrawScaledPatch(workx, y, 0, tinyemeraldpics[j]);
+					V_DrawScaledPatch(workx, y, 0, emeraldpics[1][j]);
 				workx += 10;
 			}
 		}
@@ -6335,7 +7107,7 @@ skipsign:
 			y += 25;
 
 			tempx = x + 10;
-			if (savegameinfo[savetodraw].lives != 0x7f
+			if (savegameinfo[savetodraw].lives != INFLIVES
 			&& savegameinfo[savetodraw].lives > 9)
 				tempx -= 4;
 
@@ -6362,7 +7134,7 @@ skiplife:
 
 			V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
 			tempx += 16;
-			if (savegameinfo[savetodraw].lives == 0x7f)
+			if (savegameinfo[savetodraw].lives == INFLIVES)
 				V_DrawCharacter(tempx, y + 1, '\x16', false);
 			else
 				V_DrawString(tempx, y, 0, va("%d", savegameinfo[savetodraw].lives));
@@ -6726,6 +7498,26 @@ static void M_HandleLoadSave(INT32 choice)
 	}
 }
 
+static void M_FirstTimeResponse(INT32 ch)
+{
+	S_StartSound(NULL, sfx_menu1);
+
+	if (ch == KEY_ESCAPE)
+		return;
+
+	if (ch != 'y' && ch != KEY_ENTER)
+	{
+		CV_SetValue(&cv_tutorialprompt, 0);
+		M_ReadSaveStrings();
+		MessageDef.prevMenu = &SP_LoadDef; // calls M_SetupNextMenu
+	}
+	else
+	{
+		M_StartTutorial(0);
+		MessageDef.prevMenu = &MessageDef; // otherwise, the controls prompt won't fire
+	}
+}
+
 //
 // Selected from SRB2 menu
 //
@@ -6733,6 +7525,13 @@ static void M_LoadGame(INT32 choice)
 {
 	(void)choice;
 
+	if (tutorialmap && cv_tutorialprompt.value)
+	{
+		M_StartMessage("Do you want to \x82play a brief Tutorial\x80?\n\nWe highly recommend this because \nthe controls are slightly different \nfrom other games.\n\nPress 'Y' or 'Enter' to go\nPress 'N' or any key to skip\n",
+			M_FirstTimeResponse, MM_YESNO);
+		return;
+	}
+
 	M_ReadSaveStrings();
 	M_SetupNextMenu(&SP_LoadDef);
 }
@@ -6792,8 +7591,19 @@ static void M_SetupChoosePlayer(INT32 choice)
 				if (i == char_on)
 					allowed = true;
 
-				if (description[i].picname[0] == '\0')
-					strncpy(description[i].picname, skins[skinnum].charsel, 8);
+				if (!(description[i].picname[0]))
+				{
+					if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 2)
+					{
+						spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+						spriteframe_t *sprframe = &sprdef->spriteframes[1];
+						description[i].pic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+					}
+					else
+						description[i].pic = W_CachePatchName("MISSING", PU_CACHE);
+				}
+				else
+					description[i].pic = W_CachePatchName(description[i].picname, PU_CACHE);
 			}
 			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 			Z_Free(name);
@@ -6816,12 +7626,8 @@ static void M_SetupChoosePlayer(INT32 choice)
 		return;
 	}
 
-
 	if (Playing() == false)
-	{
-		S_StopMusic();
-		S_ChangeMusicInternal("_chsel", true);
-	}
+		M_ChangeMenuMusic("_chsel", true);
 
 	SP_PlayerDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SP_PlayerDef);
@@ -6915,8 +7721,12 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	UINT8 prev, next;
 
 	// Black BG
-	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
-	//V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+	if (curbgcolor >= 0)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+	else if (!curbghide || !titlemapinaction)
+		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+	if (curfadevalue)
+		V_DrawFadeScreen(0xFF00, curfadevalue);
 
 	// Character select profile images!1
 	M_DrawTextBox(0, my, 16, 20);
@@ -6947,7 +7757,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
 		if (o < 32)
 		{
-			patch = W_CachePatchName(description[prev].picname, PU_CACHE);
+			patch = description[prev].pic;
 			if (SHORT(patch->width) >= 256)
 				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, SHORT(patch->height) + 2*(o-32), SHORT(patch->width), 64 - 2*o);
 			else
@@ -6958,7 +7768,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		// Draw next character if it's visible and its number isn't less than the current one or there's more than two
 		if (o < 128) // (next != i) was previously a part of this, but it's implicitly true if (prev != i) is true.
 		{
-			patch = W_CachePatchName(description[next].picname, PU_CACHE);
+			patch = description[next].pic;
 			if (SHORT(patch->width) >= 256)
 				V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT/2, 0, patch, 0, 0, SHORT(patch->width), 2*o);
 			else
@@ -6967,7 +7777,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		}
 	}
 
-	patch = W_CachePatchName(description[i].picname, PU_CACHE);
+	patch = description[i].pic;
 	if (o >= 0 && o <= 32)
 	{
 		if (SHORT(patch->width) >= 256)
@@ -7304,9 +8114,14 @@ void M_DrawTimeAttackMenu(void)
 	UINT16 dispstatus;
 	patch_t *PictureOfUrFace;
 
-	S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
+	M_ChangeMenuMusic("_inter", true); // Eww, but needed for when user hits escape during demo playback
 
-	V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+	if (curbgcolor >= 0)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+	else if (!curbghide || !titlemapinaction)
+		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+	if (curfadevalue)
+		V_DrawFadeScreen(0xFF00, curfadevalue);
 
 	M_DrawMenuTitle();
 
@@ -7354,9 +8169,16 @@ void M_DrawTimeAttackMenu(void)
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
 	// Character face!
-	if (W_CheckNumForName(skins[cv_chooseskin.value-1].charsel) != LUMPERROR)
 	{
-		PictureOfUrFace = W_CachePatchName(skins[cv_chooseskin.value-1].charsel, PU_CACHE);
+		if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes >= 2)
+		{
+			spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA];
+			spriteframe_t *sprframe = &sprdef->spriteframes[1];
+			PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+		}
+		else
+			PictureOfUrFace = W_CachePatchName("MISSING", PU_CACHE);
+
 		if (PictureOfUrFace->width >= 256)
 			V_DrawTinyScaledPatch(224, 120, 0, PictureOfUrFace);
 		else
@@ -7475,15 +8297,14 @@ static void M_TimeAttack(INT32 choice)
 
 	M_PatchSkinNameTable();
 
+	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
+	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	M_SetupNextMenu(&SP_TimeAttackDef);
 	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
 		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
 	else
 		Nextmap_OnChange();
 
-	G_SetGamestate(GS_TIMEATTACK);
-	S_ChangeMusicInternal("_inter", true);
-
 	itemOn = tastart; // "Start" is selected.
 }
 
@@ -7493,9 +8314,14 @@ void M_DrawNightsAttackMenu(void)
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
 
-	S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
+	M_ChangeMenuMusic("_inter", true); // Eww, but needed for when user hits escape during demo playback
 
-	V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+	if (curbgcolor >= 0)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+	else if (!curbghide || !titlemapinaction)
+		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+	if (curfadevalue)
+		V_DrawFadeScreen(0xFF00, curfadevalue);
 
 	M_DrawMenuTitle();
 
@@ -7652,15 +8478,14 @@ static void M_NightsAttack(INT32 choice)
 	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
+	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
 	M_SetupNextMenu(&SP_NightsAttackDef);
+	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
 		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
 	else
 		Nextmap_OnChange();
 
-	G_SetGamestate(GS_TIMEATTACK);
-	S_ChangeMusicInternal("_inter", true);
-
 	itemOn = nastart; // "Start" is selected.
 }
 
@@ -7861,9 +8686,10 @@ static void M_SetGuestReplay(INT32 choice)
 		which(0);
 }
 
-static void M_ModeAttackRetry(INT32 choice)
+void M_ModeAttackRetry(INT32 choice)
 {
 	(void)choice;
+	// todo -- maybe seperate this out and G_SetRetryFlag() here instead? is just calling this from the menu 100% safe?
 	G_CheckDemoStatus(); // Cancel recording
 	if (modeattacking == ATTACKING_RECORD)
 		M_ChooseTimeAttack(0);
@@ -7885,15 +8711,17 @@ static void M_ModeAttackEndGame(INT32 choice)
 	default:
 	case ATTACKING_RECORD:
 		currentMenu = &SP_TimeAttackDef;
+		wipetypepost = menupres[MN_SP_TIMEATTACK].enterwipe;
 		break;
 	case ATTACKING_NIGHTS:
 		currentMenu = &SP_NightsAttackDef;
+		wipetypepost = menupres[MN_SP_NIGHTSATTACK].enterwipe;
 		break;
 	}
 	itemOn = currentMenu->lastOn;
 	G_SetGamestate(GS_TIMEATTACK);
 	modeattacking = ATTACKING_NONE;
-	S_ChangeMusicInternal("_inter", true);
+	M_ChangeMenuMusic("_title", true);
 	Nextmap_OnChange();
 }
 
@@ -8172,7 +9000,7 @@ void M_SortServerList(void)
 
 #ifndef NONET
 #ifdef UPDATE_ALERT
-static int M_CheckMODVersion(void)
+static boolean M_CheckMODVersion(void)
 {
 	char updatestring[500];
 	const char *updatecheck = GetMODVersion();
@@ -8461,6 +9289,13 @@ Update the maxplayers label...
 static void M_ConnectIP(INT32 choice)
 {
 	(void)choice;
+
+	if (*setupm_ip == 0)
+	{
+		M_StartMessage("You must specify an IP address.\n", NULL, MM_NOTHING);
+		return;
+	}
+
 	COM_BufAddText(va("connect \"%s\"\n", setupm_ip));
 
 	// A little "please wait" message.
@@ -8827,6 +9662,16 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_name[l-1] = 0;
 			}
+			else if (itemOn == 2)
+			{
+				UINT8 col = skins[setupm_fakeskin].prefcolor;
+				if (setupm_fakecolor != col)
+				{
+					S_StartSound(NULL,sfx_menu1); // Tails
+					setupm_fakecolor = col;
+				}
+			}
+			break;
 			break;
 
 		case KEY_DEL:
@@ -8842,7 +9687,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 				break;
 			S_StartSound(NULL,sfx_menu1); // Tails
 			l = strlen(setupm_name);
-			if (l < MAXPLAYERNAME-1)
+			if (l < MAXPLAYERNAME)
 			{
 				setupm_name[l] = (char)choice;
 				setupm_name[l+1] = 0;
@@ -9020,17 +9865,18 @@ static void M_DrawJoystick(void)
 
 	for (i = 0; i <= 4; i++) // See MAX_JOYSTICKS
 	{
-		M_DrawSaveLoadBorder(OP_JoystickSetDef.x+4, OP_JoystickSetDef.y+1+LINEHEIGHT*i);
+		M_DrawTextBox(OP_JoystickSetDef.x-8, OP_JoystickSetDef.y+LINEHEIGHT*i-12, 28, 1);
+		//M_DrawSaveLoadBorder(OP_JoystickSetDef.x+4, OP_JoystickSetDef.y+1+LINEHEIGHT*i);
 
 		if ((setupcontrols_secondaryplayer && (i == cv_usejoystick2.value))
 			|| (!setupcontrols_secondaryplayer && (i == cv_usejoystick.value)))
-			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i,V_GREENMAP,joystickInfo[i]);
+			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i-4,V_GREENMAP,joystickInfo[i]);
 		else
-			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i,0,joystickInfo[i]);
+			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i-4,0,joystickInfo[i]);
 
 		if (i == itemOn)
 		{
-			V_DrawScaledPatch(currentMenu->x - 24, OP_JoystickSetDef.y+LINEHEIGHT*i, 0,
+			V_DrawScaledPatch(currentMenu->x - 24, OP_JoystickSetDef.y+LINEHEIGHT*i-4, 0,
 				W_CachePatchName("M_CURSOR", PU_CACHE));
 		}
 	}
@@ -9039,20 +9885,16 @@ static void M_DrawJoystick(void)
 static void M_SetupJoystickMenu(INT32 choice)
 {
 	INT32 i = 0;
-	const char *joyname = "None";
 	const char *joyNA = "Unavailable";
 	INT32 n = I_NumJoys();
 	(void)choice;
 
-	strcpy(joystickInfo[i], joyname);
+	strcpy(joystickInfo[i], "None");
 
 	for (i = 1; i < 8; i++)
 	{
-		if (i <= n && (joyname = I_GetJoyName(i)) != NULL)
-		{
-			strncpy(joystickInfo[i], joyname, 24);
-			joystickInfo[i][24] = '\0';
-		}
+		if (i <= n && (I_GetJoyName(i)) != NULL)
+			strncpy(joystickInfo[i], I_GetJoyName(i), 28);
 		else
 			strcpy(joystickInfo[i], joyNA);
 	}
@@ -9064,6 +9906,10 @@ static void M_Setup1PJoystickMenu(INT32 choice)
 {
 	setupcontrols_secondaryplayer = false;
 	OP_JoystickSetDef.prevMenu = &OP_Joystick1Def;
+	OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS);
+	OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << (MENUBITS*2));
+	OP_JoystickSetDef.menuid |= MN_OP_P1CONTROLS << MENUBITS;
+	OP_JoystickSetDef.menuid |= MN_OP_P1JOYSTICK << (MENUBITS*2);
 	M_SetupJoystickMenu(choice);
 }
 
@@ -9071,6 +9917,10 @@ static void M_Setup2PJoystickMenu(INT32 choice)
 {
 	setupcontrols_secondaryplayer = true;
 	OP_JoystickSetDef.prevMenu = &OP_Joystick2Def;
+	OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS);
+	OP_JoystickSetDef.menuid &= ~(((1 << MENUBITS) - 1) << (MENUBITS*2));
+	OP_JoystickSetDef.menuid |= MN_OP_P2CONTROLS << MENUBITS;
+	OP_JoystickSetDef.menuid |= MN_OP_P2JOYSTICK << (MENUBITS*2);
 	M_SetupJoystickMenu(choice);
 }
 
@@ -9093,21 +9943,27 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	setupcontrols = gamecontrol;        // was called from main Options (for console player, then)
 	currentMenu->lastOn = itemOn;
 
-	// Unhide the five non-P2 controls and their headers
-	OP_ChangeControlsMenu[18+0].status = IT_HEADER;
-	OP_ChangeControlsMenu[18+1].status = IT_SPACE;
+	// Unhide the nine non-P2 controls and their headers
+	//OP_ChangeControlsMenu[18+0].status = IT_HEADER;
+	//OP_ChangeControlsMenu[18+1].status = IT_SPACE;
 	// ...
 	OP_ChangeControlsMenu[18+2].status = IT_CALL|IT_STRING2;
 	OP_ChangeControlsMenu[18+3].status = IT_CALL|IT_STRING2;
 	OP_ChangeControlsMenu[18+4].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+5].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+6].status = IT_CALL|IT_STRING2;
+	//OP_ChangeControlsMenu[18+7].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+8].status = IT_CALL|IT_STRING2;
 	// ...
-	OP_ChangeControlsMenu[23+0].status = IT_HEADER;
-	OP_ChangeControlsMenu[23+1].status = IT_SPACE;
+	OP_ChangeControlsMenu[27+0].status = IT_HEADER;
+	OP_ChangeControlsMenu[27+1].status = IT_SPACE;
 	// ...
-	OP_ChangeControlsMenu[23+2].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[23+3].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[27+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[27+3].status = IT_CALL|IT_STRING2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
+	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove first level (<< 6)
+	OP_ChangeControlsDef.menuid |= MN_OP_P1CONTROLS << MENUBITS; // combine first level (<< 6)
 	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
@@ -9118,21 +9974,27 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	setupcontrols = gamecontrolbis;
 	currentMenu->lastOn = itemOn;
 
-	// Hide the five non-P2 controls and their headers
-	OP_ChangeControlsMenu[18+0].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+1].status = IT_GRAYEDOUT2;
+	// Hide the nine non-P2 controls and their headers
+	//OP_ChangeControlsMenu[18+0].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[18+1].status = IT_GRAYEDOUT2;
 	// ...
 	OP_ChangeControlsMenu[18+2].status = IT_GRAYEDOUT2;
 	OP_ChangeControlsMenu[18+3].status = IT_GRAYEDOUT2;
 	OP_ChangeControlsMenu[18+4].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+5].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+6].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[18+7].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+8].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[23+0].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[23+1].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[27+0].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[27+1].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[23+2].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[23+3].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[27+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[27+3].status = IT_GRAYEDOUT2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
+	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove first level (<< 6)
+	OP_ChangeControlsDef.menuid |= MN_OP_P2CONTROLS << MENUBITS; // combine first level (<< 6)
 	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
@@ -9192,9 +10054,17 @@ static void M_DrawControl(void)
 	// draw title (or big pic)
 	M_DrawMenuTitle();
 
-	M_CentreText(30,
-		 (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" :
-		                                  "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR"));
+	if (tutorialmode && tutorialgcs)
+	{
+		if ((gametic / TICRATE) % 2)
+			M_CentreText(30, "\202EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
+		else
+			M_CentreText(30, "EXIT THE TUTORIAL TO CHANGE THE CONTROLS");
+	}
+	else
+		M_CentreText(30,
+		    (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" :
+		                                     "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR"));
 
 	if (i)
 		V_DrawString(currentMenu->x - 16, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
@@ -9250,6 +10120,7 @@ static void M_DrawControl(void)
 #undef controlbuffer
 
 static INT32 controltochange;
+static char controltochangetext[33];
 
 static void M_ChangecontrolResponse(event_t *ev)
 {
@@ -9257,8 +10128,8 @@ static void M_ChangecontrolResponse(event_t *ev)
 	INT32        found;
 	INT32        ch = ev->data1;
 
-	// ESCAPE cancels
-	if (ch != KEY_ESCAPE)
+	// ESCAPE cancels; dummy out PAUSE
+	if (ch != KEY_ESCAPE && ch != KEY_PAUSE)
 	{
 
 		switch (ev->type)
@@ -9314,11 +10185,30 @@ static void M_ChangecontrolResponse(event_t *ev)
 				found = 0;
 				setupcontrols[control][1] = KEY_NULL;  //replace key 1,clear key2
 			}
-			G_CheckDoubleUsage(ch);
+			(void)G_CheckDoubleUsage(ch, true);
 			setupcontrols[control][found] = ch;
 		}
 		S_StartSound(NULL, sfx_strpst);
 	}
+	else if (ch == KEY_PAUSE)
+	{
+		// This buffer assumes a 125-character message plus a 32-character control name (per controltochangetext buffer size)
+		static char tmp[158];
+		menu_t *prev = currentMenu->prevMenu;
+
+		if (controltochange == gc_pause)
+			sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nit cannot be used to retry runs \nduring Record Attack. \n\nHit another key for\n%s\nESC for Cancel"),
+				controltochangetext);
+		else
+			sprintf(tmp, M_GetText("The \x82Pause Key \x80is enabled, but \nit is not configurable. \n\nHit another key for\n%s\nESC for Cancel"),
+				controltochangetext);
+
+		M_StartMessage(tmp, M_ChangecontrolResponse, MM_EVENTHANDLER);
+		currentMenu->prevMenu = prev;
+
+		S_StartSound(NULL, sfx_s3k42);
+		return;
+	}
 	else
 		S_StartSound(NULL, sfx_skid);
 
@@ -9327,231 +10217,21 @@ static void M_ChangecontrolResponse(event_t *ev)
 
 static void M_ChangeControl(INT32 choice)
 {
-	static char tmp[55];
+	// This buffer assumes a 35-character message (per below) plus a max control name limit of 32 chars (per controltochangetext)
+	// If you change the below message, then change the size of this buffer!
+	static char tmp[68];
+
+	if (tutorialmode && tutorialgcs) // don't allow control changes if temp control override is active
+		return;
 
 	controltochange = currentMenu->menuitems[choice].alphaKey;
 	sprintf(tmp, M_GetText("Hit the new key for\n%s\nESC for Cancel"),
 		currentMenu->menuitems[choice].text);
+	strlcpy(controltochangetext, currentMenu->menuitems[choice].text, 33);
 
 	M_StartMessage(tmp, M_ChangecontrolResponse, MM_EVENTHANDLER);
 }
 
-// =====
-// SOUND
-// =====
-
-static void M_SoundMenu(INT32 choice)
-{
-	(void)choice;
-
-	OP_SoundOptionsMenu[6].status = ((nosound || sound_disabled) ? IT_GRAYEDOUT : (IT_STRING | IT_CVAR));
-	M_SetupNextMenu(&OP_SoundOptionsDef);
-}
-
-void M_DrawSoundMenu(void)
-{
-	const char* onstring = "ON";
-	const char* offstring = "OFF";
-	INT32 lengthstring;
-	M_DrawGenericMenu();
-
-	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
-		currentMenu->y+currentMenu->menuitems[0].alphaKey,
-		(nosound ? V_REDMAP : V_YELLOWMAP),
-		((nosound || sound_disabled) ? offstring : onstring));
-
-	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
-		currentMenu->y+currentMenu->menuitems[2].alphaKey,
-		(nodigimusic ? V_REDMAP : V_YELLOWMAP),
-		((nodigimusic || digital_disabled) ? offstring : onstring));
-
-	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
-		currentMenu->y+currentMenu->menuitems[4].alphaKey,
-		(nomidimusic ? V_REDMAP : V_YELLOWMAP),
-		((nomidimusic || music_disabled) ? offstring : onstring));
-
-	if (itemOn == 0)
-		lengthstring = ((nosound || sound_disabled) ? 3 : 2);
-	else if (itemOn == 2)
-		lengthstring = ((nodigimusic || digital_disabled) ? 3 : 2);
-	else if (itemOn == 4)
-		lengthstring = ((nomidimusic || music_disabled) ? 3 : 2);
-	else
-		return;
-
-	V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - (lengthstring*8) - (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
-		'\x1C' | V_YELLOWMAP, false);
-	V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
-		'\x1D' | V_YELLOWMAP, false);
-}
-
-// Toggles sound systems in-game.
-static void M_ToggleSFX(INT32 choice)
-{
-	switch (choice)
-	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn++;
-			return;
-
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn = currentMenu->numitems-1;
-			return;
-
-		case KEY_ESCAPE:
-			if (currentMenu->prevMenu)
-				M_SetupNextMenu(currentMenu->prevMenu);
-			else
-				M_ClearMenus(true);
-			return;
-		default:
-			break;
-	}
-
-	if (nosound)
-	{
-		nosound = false;
-		I_StartupSound();
-		if (nosound) return;
-		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
-		S_StartSound(NULL, sfx_strpst);
-		OP_SoundOptionsMenu[6].status = IT_STRING | IT_CVAR;
-		//M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
-	}
-	else
-	{
-		if (sound_disabled)
-		{
-			sound_disabled = false;
-			S_StartSound(NULL, sfx_strpst);
-			OP_SoundOptionsMenu[6].status = IT_STRING | IT_CVAR;
-			//M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
-		}
-		else
-		{
-			sound_disabled = true;
-			S_StopSounds();
-			OP_SoundOptionsMenu[6].status = IT_GRAYEDOUT;
-			//M_StartMessage(M_GetText("SFX Disabled\n"), NULL, MM_NOTHING);
-		}
-	}
-}
-
-static void M_ToggleDigital(INT32 choice)
-{
-	switch (choice)
-	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn++;
-			return;
-
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn--;
-			return;
-
-		case KEY_ESCAPE:
-			if (currentMenu->prevMenu)
-				M_SetupNextMenu(currentMenu->prevMenu);
-			else
-				M_ClearMenus(true);
-			return;
-		default:
-			break;
-	}
-
-	if (nodigimusic)
-	{
-		nodigimusic = false;
-		I_InitDigMusic();
-		if (nodigimusic) return;
-		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
-		S_StopMusic();
-		if (Playing())
-			P_RestoreMusic(&players[consoleplayer]);
-		else
-			S_ChangeMusicInternal("_clear", false);
-		//M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
-	}
-	else
-	{
-		if (digital_disabled)
-		{
-			digital_disabled = false;
-			if (Playing())
-				P_RestoreMusic(&players[consoleplayer]);
-			else
-				S_ChangeMusicInternal("_clear", false);
-			//M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
-		}
-		else
-		{
-			digital_disabled = true;
-			S_StopMusic();
-			//M_StartMessage(M_GetText("Digital Music Disabled\n"), NULL, MM_NOTHING);
-		}
-	}
-}
-
-static void M_ToggleMIDI(INT32 choice)
-{
-	switch (choice)
-	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn++;
-			return;
-
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
-			itemOn--;
-			return;
-
-		case KEY_ESCAPE:
-			if (currentMenu->prevMenu)
-				M_SetupNextMenu(currentMenu->prevMenu);
-			else
-				M_ClearMenus(true);
-			return;
-		default:
-			break;
-	}
-
-	if (nomidimusic)
-	{
-		nomidimusic = false;
-		I_InitMIDIMusic();
-		if (nomidimusic) return;
-		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
-		if (Playing())
-			P_RestoreMusic(&players[consoleplayer]);
-		else
-			S_ChangeMusicInternal("_clear", false);
-		//M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
-	}
-	else
-	{
-		if (music_disabled)
-		{
-			music_disabled = false;
-			if (Playing())
-				P_RestoreMusic(&players[consoleplayer]);
-			else
-				S_ChangeMusicInternal("_clear", false);
-			//M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
-		}
-		else
-		{
-			music_disabled = true;
-			S_StopMusic();
-			//M_StartMessage(M_GetText("MIDI Music Disabled\n"), NULL, MM_NOTHING);
-		}
-	}
-}
-
 // ===============
 // VIDEO MODE MENU
 // ===============
diff --git a/src/m_menu.h b/src/m_menu.h
index 9df56e897690f2044951fe85d16e96f4ee36f202..347725e10ba3262ef64eb76073031fb15849fb69 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -22,6 +22,140 @@
 //
 // MENUS
 //
+
+// If menu hierarchies go deeper, change this up to 5.
+// Zero-based, inclusive.
+#define NUMMENULEVELS 3
+#define MENUBITS 6
+
+// Menu IDs sectioned by numeric places to signify hierarchy
+typedef enum
+{
+	MN_NONE,
+
+	MN_MAIN,
+
+	// Single Player
+	MN_SP_MAIN,
+
+	MN_SP_LOAD,
+	MN_SP_PLAYER,
+
+	MN_SP_LEVELSELECT,
+	MN_SP_LEVELSTATS,
+
+	MN_SP_TIMEATTACK,
+	MN_SP_TIMEATTACK_LEVELSELECT,
+	MN_SP_GUESTREPLAY,
+	MN_SP_REPLAY,
+	MN_SP_GHOST,
+
+	MN_SP_NIGHTSATTACK,
+	MN_SP_NIGHTS_LEVELSELECT,
+	MN_SP_NIGHTS_GUESTREPLAY,
+	MN_SP_NIGHTS_REPLAY,
+	MN_SP_NIGHTS_GHOST,
+
+	// Multiplayer
+	MN_MP_MAIN,
+	MN_MP_SPLITSCREEN, // SplitServer
+	MN_MP_SERVER,
+	MN_MP_CONNECT,
+	MN_MP_ROOM,
+	MN_MP_PLAYERSETUP, // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
+
+	// Options
+	MN_OP_MAIN,
+
+	MN_OP_P1CONTROLS,
+	MN_OP_CHANGECONTROLS, // OP_ChangeControlsDef shared with P2
+	MN_OP_P1MOUSE,
+	MN_OP_P1JOYSTICK,
+	MN_OP_JOYSTICKSET, // OP_JoystickSetDef shared with P2
+
+	MN_OP_P2CONTROLS,
+	MN_OP_P2MOUSE,
+	MN_OP_P2JOYSTICK,
+
+	MN_OP_VIDEO,
+	MN_OP_VIDEOMODE,
+	MN_OP_COLOR,
+	MN_OP_OPENGL,
+	MN_OP_OPENGL_LIGHTING,
+	MN_OP_OPENGL_FOG,
+	MN_OP_OPENGL_COLOR,
+
+	MN_OP_SOUND,
+
+	MN_OP_SERVER,
+	MN_OP_MONITORTOGGLE,
+
+	MN_OP_DATA,
+	MN_OP_ADDONS,
+	MN_OP_SCREENSHOTS,
+	MN_OP_ERASEDATA,
+
+	// Secrets
+	MN_SR_MAIN,
+	MN_SR_PANDORA,
+	MN_SR_LEVELSELECT,
+	MN_SR_UNLOCKCHECKLIST,
+	MN_SR_EMBLEMHINT,
+
+	// Addons (Part of MISC, but let's make it our own)
+	MN_AD_MAIN,
+
+	// MISC
+	// MN_MESSAGE,
+	// MN_SPAUSE,
+
+	// MN_MPAUSE,
+	// MN_SCRAMBLETEAM,
+	// MN_CHANGETEAM,
+	// MN_CHANGELEVEL,
+
+	// MN_MAPAUSE,
+	// MN_HELP,
+
+	MN_SPECIAL,
+	NUMMENUTYPES,
+} menutype_t; // up to 63; MN_SPECIAL = 53
+
+typedef struct
+{
+	char bgname[8]; // name for background gfx lump; lays over titlemap if this is set
+	SINT8 hidetitlepics; // hide title gfx per menu; -1 means undefined, inherits global setting
+	INT32 titlescrollxspeed; // background gfx scroll per menu; inherits global setting
+	INT32 titlescrollyspeed; // y scroll
+	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
+	boolean bghide; // for titlemaps, hide the background.
+
+	char musname[7]; ///< Music track to play. "" for no music.
+	UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
+	boolean muslooping; ///< Loop the music
+	boolean musstop; ///< Don't play any music
+	boolean musignore; ///< Let the current music keep playing
+
+	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
+	boolean enterbubble; // run all entrance line execs after common ancestor and up to child. If false, only run the child's exec
+	boolean exitbubble; // run all exit line execs from child and up to before common ancestor. If false, only run the child's exec
+	INT32 entertag; // line exec to run on menu enter, if titlemap
+	INT32 exittag; // line exec to run on menu exit, if titlemap
+	INT16 enterwipe; // wipe type to run on menu enter, -1 means default
+	INT16 exitwipe; // wipe type to run on menu exit, -1 means default
+} menupres_t;
+
+extern menupres_t menupres[NUMMENUTYPES];
+extern UINT32 prevMenuId;
+extern UINT32 activeMenuId;
+
+void M_InitMenuPresTables(void);
+UINT8 M_GetYoungestChildMenu(void);
+void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping);
+void M_SetMenuCurBackground(const char *defaultname);
+void M_SetMenuCurFadeValue(UINT8 defaultvalue);
+void M_SetMenuCurHideTitlePics(void);
+
 // Called by main loop,
 // saves config file and calls I_Quit when user exits.
 // Even when the menu is not displayed,
@@ -38,6 +172,9 @@ void M_Drawer(void);
 // Called by D_SRB2Main, loads the config file.
 void M_Init(void);
 
+// Called by D_SRB2Main also, sets up the playermenu and description tables.
+void M_InitCharacterTables(void);
+
 // Called by intro code to force menu up upon a keypress,
 // does nothing if menu is already up.
 void M_StartControlPanel(void);
@@ -153,6 +290,7 @@ typedef struct menuitem_s
 
 typedef struct menu_s
 {
+	UINT32         menuid;             // ID to encode menu type and hierarchy
 	const char    *menutitlepic;
 	INT16          numitems;           // # of menu items
 	struct menu_s *prevMenu;           // previous menu
@@ -178,6 +316,7 @@ typedef struct
 	char notes[441];
 	char picname[8];
 	char skinname[SKINNAMESIZE*2+2]; // skin&skin\0
+	patch_t *pic;
 	UINT8 prev;
 	UINT8 next;
 } description_t;
@@ -226,7 +365,7 @@ typedef struct
 	INT32 gamemap;
 } saveinfo_t;
 
-extern description_t description[32];
+extern description_t description[MAXSKINS];
 
 extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort;
 extern CV_PossibleValue_t gametype_cons_t[];
@@ -235,15 +374,19 @@ extern INT16 startmap;
 extern INT32 ultimate_selectable;
 extern INT16 char_on, startchar;
 
-#define MAXSAVEGAMES 31 //note: last save game is "no save"
+#define MAXSAVEGAMES 31
 #define NOSAVESLOT 0 //slot where Play Without Saving appears
 
 #define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he
 
+void M_TutorialSaveControlResponse(INT32 ch);
+
 void M_ForceSaveSlotSelected(INT32 sslot);
 
 void M_CheatActivationResponder(INT32 ch);
 
+void M_ModeAttackRetry(INT32 choice);
+
 // Level select updating
 void Nextmap_OnChange(void);
 
@@ -255,8 +398,9 @@ void Screenshot_option_Onchange(void);
 void Addons_option_Onchange(void);
 
 // These defines make it a little easier to make menus
-#define DEFAULTMENUSTYLE(header, source, prev, x, y)\
+#define DEFAULTMENUSTYLE(id, header, source, prev, x, y)\
 {\
+	id,\
 	header,\
 	sizeof(source)/sizeof(menuitem_t),\
 	prev,\
@@ -267,8 +411,9 @@ void Addons_option_Onchange(void);
 	NULL\
 }
 
-#define DEFAULTSCROLLMENUSTYLE(header, source, prev, x, y)\
+#define DEFAULTSCROLLMENUSTYLE(id, header, source, prev, x, y)\
 {\
+	id,\
 	header,\
 	sizeof(source)/sizeof(menuitem_t),\
 	prev,\
@@ -281,6 +426,7 @@ void Addons_option_Onchange(void);
 
 #define PAUSEMENUSTYLE(source, x, y)\
 {\
+	MN_SPECIAL,\
 	NULL,\
 	sizeof(source)/sizeof(menuitem_t),\
 	NULL,\
@@ -291,8 +437,9 @@ void Addons_option_Onchange(void);
 	NULL\
 }
 
-#define CENTERMENUSTYLE(header, source, prev, y)\
+#define CENTERMENUSTYLE(id, header, source, prev, y)\
 {\
+	id,\
 	header,\
 	sizeof(source)/sizeof(menuitem_t),\
 	prev,\
@@ -303,8 +450,9 @@ void Addons_option_Onchange(void);
 	NULL\
 }
 
-#define MAPPLATTERMENUSTYLE(header, source)\
+#define MAPPLATTERMENUSTYLE(id, header, source)\
 {\
+	id,\
 	header,\
 	sizeof (source)/sizeof (menuitem_t),\
 	&MainDef,\
@@ -315,8 +463,9 @@ void Addons_option_Onchange(void);
 	NULL\
 }
 
-#define CONTROLMENUSTYLE(source, prev)\
+#define CONTROLMENUSTYLE(id, source, prev)\
 {\
+	id,\
 	"M_CONTRO",\
 	sizeof (source)/sizeof (menuitem_t),\
 	prev,\
@@ -329,6 +478,7 @@ void Addons_option_Onchange(void);
 
 #define IMAGEDEF(source)\
 {\
+	MN_SPECIAL,\
 	NULL,\
 	sizeof (source)/sizeof (menuitem_t),\
 	NULL,\
diff --git a/src/m_misc.c b/src/m_misc.c
index 1cf244da02d96fbc86f8ec83a61a20a2a1137f70..22e19df73d567000facee5d666af31c579ec86c5 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -30,6 +30,7 @@
 #include "g_game.h"
 #include "m_misc.h"
 #include "hu_stuff.h"
+#include "st_stuff.h"
 #include "v_video.h"
 #include "z_zone.h"
 #include "g_input.h"
@@ -37,6 +38,7 @@
 #include "d_main.h"
 #include "m_argv.h"
 #include "i_system.h"
+#include "command.h" // cv_execversion
 
 #include "m_anigif.h"
 
@@ -56,7 +58,9 @@ typedef off_t off64_t;
 #endif
 #endif
 
-#if defined (_WIN32)
+#if defined(__MINGW32__) && ((__GNUC__ > 7) || (__GNUC__ == 6 && __GNUC_MINOR__ >= 3)) && (__GNUC__ < 8)
+#define PRIdS "u"
+#elif defined (_WIN32)
 #define PRIdS "Iu"
 #elif defined (DJGPP)
 #define PRIdS "u"
@@ -88,7 +92,8 @@ typedef off_t off64_t;
  #ifdef PNG_WRITE_SUPPORTED
   #define USE_PNG // Only actually use PNG if write is supported.
   #if defined (PNG_WRITE_APNG_SUPPORTED) //|| !defined(PNG_STATIC)
-   #define USE_APNG
+    #include "apng.h"
+    #define USE_APNG
   #endif
   // See hardware/hw_draw.c for a similar check to this one.
  #endif
@@ -438,7 +443,23 @@ void Command_LoadConfig_f(void)
 
 	strcpy(configfile, COM_Argv(1));
 	FIL_ForceExtension(configfile, ".cfg");
+
+	// load default control
+	G_ClearAllControlKeys();
+	G_CopyControls(gamecontrol, gamecontroldefault[gcs_fps], NULL, 0);
+	G_CopyControls(gamecontrolbis, gamecontrolbisdefault[gcs_fps], NULL, 0);
+
+	// temporarily reset execversion to default
+	CV_ToggleExecVersion(true);
+	COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue));
+	CV_InitFilterVar();
+
+	// exec the config
 	COM_BufInsertText(va("exec \"%s\"\n", configfile));
+
+	// don't filter anymore vars and don't let this convsvar be changed
+	COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION));
+	CV_ToggleExecVersion(false);
 }
 
 /** Saves the current configuration and loads another.
@@ -473,12 +494,27 @@ void M_FirstLoadConfig(void)
 	}
 
 	// load default control
-	G_Controldefault();
+	G_DefineDefaultControls();
+	G_CopyControls(gamecontrol, gamecontroldefault[gcs_fps], NULL, 0);
+	G_CopyControls(gamecontrolbis, gamecontrolbisdefault[gcs_fps], NULL, 0);
+
+	// register execversion here before we load any configs
+	CV_RegisterVar(&cv_execversion);
+
+	// temporarily reset execversion to default
+	// we shouldn't need to do this, but JUST in case...
+	CV_ToggleExecVersion(true);
+	COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue));
+	CV_InitFilterVar();
 
 	// load config, make sure those commands doesnt require the screen...
 	COM_BufInsertText(va("exec \"%s\"\n", configfile));
 	// no COM_BufExecute() needed; that does it right away
 
+	// don't filter anymore vars and don't let this convsvar be changed
+	COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION));
+	CV_ToggleExecVersion(false);
+
 	// make sure I_Quit() will write back the correct config
 	// (do not write back the config if it crash before)
 	gameconfig_loaded = true;
@@ -491,6 +527,7 @@ void M_FirstLoadConfig(void)
 void M_SaveConfig(const char *filename)
 {
 	FILE *f;
+	char *filepath;
 
 	// make sure not to write back the config until it's been correctly loaded
 	if (!gameconfig_loaded)
@@ -505,13 +542,20 @@ void M_SaveConfig(const char *filename)
 			return;
 		}
 
-		f = fopen(filename, "w");
+		// append srb2home to beginning of filename
+		// but check if srb2home isn't already there, first
+		if (!strstr(filename, srb2home))
+			filepath = va(pandf,srb2home, filename);
+		else
+			filepath = Z_StrDup(filename);
+
+		f = fopen(filepath, "w");
 		// change it only if valid
 		if (f)
-			strcpy(configfile, filename);
+			strcpy(configfile, filepath);
 		else
 		{
-			CONS_Alert(CONS_ERROR, M_GetText("Couldn't save game config file %s\n"), filename);
+			CONS_Alert(CONS_ERROR, M_GetText("Couldn't save game config file %s\n"), filepath);
 			return;
 		}
 	}
@@ -534,14 +578,55 @@ void M_SaveConfig(const char *filename)
 	// header message
 	fprintf(f, "// SRB2 configuration file.\n");
 
+	// print execversion FIRST, because subsequent consvars need to be filtered
+	// always print current EXECVERSION
+	fprintf(f, "%s \"%d\"\n", cv_execversion.name, EXECVERSION);
+
 	// FIXME: save key aliases if ever implemented..
 
-	CV_SaveVariables(f);
-	if (!dedicated) G_SaveKeySetting(f);
+	if (tutorialmode && tutorialgcs)
+	{
+		CV_SetValue(&cv_usemouse, tutorialusemouse);
+		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
+		CV_SetValue(&cv_mousemove, tutorialmousemove);
+		CV_SetValue(&cv_analog, tutorialanalog);
+		CV_SaveVariables(f);
+		CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
+		CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
+		CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
+		CV_Set(&cv_analog, cv_analog.defaultvalue);
+	}
+	else
+		CV_SaveVariables(f);
+
+	if (!dedicated)
+	{
+		if (tutorialmode && tutorialgcs)
+			G_SaveKeySetting(f, gamecontroldefault[gcs_custom], gamecontrolbis); // using gcs_custom as temp storage
+		else
+			G_SaveKeySetting(f, gamecontrol, gamecontrolbis);
+	}
 
 	fclose(f);
 }
 
+// ==========================================================================
+//                              SCREENSHOTS
+// ==========================================================================
+static UINT8 screenshot_palette[768];
+static void M_CreateScreenShotPalette(void)
+{
+	size_t i, j;
+	for (i = 0, j = 0; i < 768; i += 3, j++)
+	{
+		RGBA_t locpal = ((cv_screenshot_colorprofile.value)
+		? pLocalPalette[(max(st_palette,0)*256)+j]
+		: pMasterPalette[(max(st_palette,0)*256)+j]);
+		screenshot_palette[i] = locpal.s.red;
+		screenshot_palette[i+1] = locpal.s.green;
+		screenshot_palette[i+2] = locpal.s.blue;
+	}
+}
 
 #if NUMSCREENS > 2
 static const char *Newsnapshotfile(const char *pathname, const char *ext)
@@ -610,25 +695,20 @@ static void PNG_warn(png_structp PNG, png_const_charp pngtext)
 	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
 }
 
-static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 width, PNG_CONST png_uint_32 height, const boolean palette)
+static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 width, PNG_CONST png_uint_32 height, PNG_CONST png_byte *palette)
 {
 	const png_byte png_interlace = PNG_INTERLACE_NONE; //PNG_INTERLACE_ADAM7
 	if (palette)
 	{
 		png_colorp png_PLTE = png_malloc(png_ptr, sizeof(png_color)*256); //palette
+		const png_byte *pal = palette;
 		png_uint_16 i;
-
-		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
-		? pLocalPalette
-		: pMasterPalette);
-
 		for (i = 0; i < 256; i++)
 		{
-			png_PLTE[i].red   = pal[i].s.red;
-			png_PLTE[i].green = pal[i].s.green;
-			png_PLTE[i].blue  = pal[i].s.blue;
+			png_PLTE[i].red   = *pal; pal++;
+			png_PLTE[i].green = *pal; pal++;
+			png_PLTE[i].blue  = *pal; pal++;
 		}
-
 		png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE,
 		 png_interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 		png_write_info_before_PLTE(png_ptr, png_info_ptr);
@@ -652,10 +732,9 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 #define SRB2PNGTXT 11 //PNG_KEYWORD_MAX_LENGTH(79) is the max
 	png_text png_infotext[SRB2PNGTXT];
 	char keytxt[SRB2PNGTXT][12] = {
-	"Title", "Author", "Description", "Playername", "Mapnum", "Mapname",
-	"Location", "Interface", "Revision", "Build Date", "Build Time"};
+	"Title", "Description", "Playername", "Mapnum", "Mapname",
+	"Location", "Interface", "Render Mode", "Revision", "Build Date", "Build Time"};
 	char titletxt[] = "Sonic Robo Blast 2 " VERSIONSTRING;
-	png_charp authortxt = I_GetUserName();
 	png_charp playertxt =  cv_playername.zstring;
 	char desctxt[] = "SRB2 Screenshot";
 	char Movietxt[] = "SRB2 Movie";
@@ -670,6 +749,7 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 #else
 	 "Unknown";
 #endif
+	char rendermodetxt[9];
 	char maptext[8];
 	char lvlttltext[48];
 	char locationtxt[40];
@@ -677,6 +757,19 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 	char ctdate[40];
 	char cttime[40];
 
+	switch (rendermode)
+	{
+		case render_soft:
+			strcpy(rendermodetxt, "Software");
+			break;
+		case render_opengl:
+			strcpy(rendermodetxt, "OpenGL");
+			break;
+		default: // Just in case
+			strcpy(rendermodetxt, "None");
+			break;
+	}
+
 	if (gamestate == GS_LEVEL)
 		snprintf(maptext, 8, "%s", G_BuildMapName(gamemap));
 	else
@@ -705,16 +798,16 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 		png_infotext[i].key  = keytxt[i];
 
 	png_infotext[0].text = titletxt;
-	png_infotext[1].text = authortxt;
 	if (movie)
-		png_infotext[2].text = Movietxt;
+		png_infotext[1].text = Movietxt;
 	else
-		png_infotext[2].text = desctxt;
-	png_infotext[3].text = playertxt;
-	png_infotext[4].text = maptext;
-	png_infotext[5].text = lvlttltext;
-	png_infotext[6].text = locationtxt;
-	png_infotext[7].text = interfacetxt;
+		png_infotext[1].text = desctxt;
+	png_infotext[2].text = playertxt;
+	png_infotext[3].text = maptext;
+	png_infotext[4].text = lvlttltext;
+	png_infotext[5].text = locationtxt;
+	png_infotext[6].text = interfacetxt;
+	png_infotext[7].text = rendermodetxt;
 	png_infotext[8].text = strncpy(ctrevision, comprevision, sizeof(ctrevision)-1);
 	png_infotext[9].text = strncpy(ctdate, compdate, sizeof(ctdate)-1);
 	png_infotext[10].text = strncpy(cttime, comptime, sizeof(cttime)-1);
@@ -741,13 +834,13 @@ static inline void M_PNGImage(png_structp png_ptr, png_infop png_info_ptr, PNG_C
 #ifdef USE_APNG
 static png_structp apng_ptr = NULL;
 static png_infop   apng_info_ptr = NULL;
+static apng_infop  apng_ainfo_ptr = NULL;
 static png_FILE_p  apng_FILE = NULL;
 static png_uint_32 apng_frames = 0;
-static png_byte    acTL_cn[5] = { 97,  99,  84,  76, '\0'};
 #ifdef PNG_STATIC // Win32 build have static libpng
-#define apng_set_acTL png_set_acTL
-#define apng_write_frame_head png_write_frame_head
-#define apng_write_frame_tail png_write_frame_tail
+#define aPNG_set_acTL png_set_acTL
+#define aPNG_write_frame_head png_write_frame_head
+#define aPNG_write_frame_tail png_write_frame_tail
 #else // outside libpng may not have apng support
 
 #ifndef PNG_WRITE_APNG_SUPPORTED // libpng header may not have apng patch
@@ -784,20 +877,20 @@ static png_byte    acTL_cn[5] = { 97,  99,  84,  76, '\0'};
 #endif
 
 #endif
-typedef PNG_EXPORT(png_uint_32, (*P_png_set_acTL)) PNGARG((png_structp png_ptr,
-   png_infop info_ptr, png_uint_32 num_frames, png_uint_32 num_plays));
-typedef PNG_EXPORT (void, (*P_png_write_frame_head)) PNGARG((png_structp png_ptr,
+typedef png_uint_32 (*P_png_set_acTL) (png_structp png_ptr,
+   png_infop info_ptr, png_uint_32 num_frames, png_uint_32 num_plays);
+typedef void (*P_png_write_frame_head) (png_structp png_ptr,
    png_infop info_ptr, png_bytepp row_pointers,
    png_uint_32 width, png_uint_32 height,
    png_uint_32 x_offset, png_uint_32 y_offset,
    png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op,
-   png_byte blend_op));
+   png_byte blend_op);
 
-typedef PNG_EXPORT (void, (*P_png_write_frame_tail)) PNGARG((png_structp png_ptr,
-   png_infop info_ptr));
-static P_png_set_acTL apng_set_acTL = NULL;
-static P_png_write_frame_head apng_write_frame_head = NULL;
-static P_png_write_frame_tail apng_write_frame_tail = NULL;
+typedef void (*P_png_write_frame_tail) (png_structp png_ptr,
+   png_infop info_ptr);
+static P_png_set_acTL aPNG_set_acTL = NULL;
+static P_png_write_frame_head aPNG_write_frame_head = NULL;
+static P_png_write_frame_tail aPNG_write_frame_tail = NULL;
 #endif
 
 static inline boolean M_PNGLib(void)
@@ -806,7 +899,7 @@ static inline boolean M_PNGLib(void)
 	return true;
 #else
 	static void *pnglib = NULL;
-	if (apng_set_acTL && apng_write_frame_head && apng_write_frame_tail)
+	if (aPNG_set_acTL && aPNG_write_frame_head && aPNG_write_frame_tail)
 		return true;
 	if (pnglib)
 		return false;
@@ -826,16 +919,16 @@ static inline boolean M_PNGLib(void)
 	if (!pnglib)
 		return false;
 #ifdef HAVE_SDL
-	apng_set_acTL = hwSym("png_set_acTL", pnglib);
-	apng_write_frame_head = hwSym("png_write_frame_head", pnglib);
-	apng_write_frame_tail = hwSym("png_write_frame_tail", pnglib);
+	aPNG_set_acTL = hwSym("png_set_acTL", pnglib);
+	aPNG_write_frame_head = hwSym("png_write_frame_head", pnglib);
+	aPNG_write_frame_tail = hwSym("png_write_frame_tail", pnglib);
 #endif
 #ifdef _WIN32
-	apng_set_acTL = GetProcAddress("png_set_acTL", pnglib);
-	apng_write_frame_head = GetProcAddress("png_write_frame_head", pnglib);
-	apng_write_frame_tail = GetProcAddress("png_write_frame_tail", pnglib);
+	aPNG_set_acTL = GetProcAddress("png_set_acTL", pnglib);
+	aPNG_write_frame_head = GetProcAddress("png_write_frame_head", pnglib);
+	aPNG_write_frame_tail = GetProcAddress("png_write_frame_tail", pnglib);
 #endif
-	return (apng_set_acTL && apng_write_frame_head && apng_write_frame_tail);
+	return (aPNG_set_acTL && aPNG_write_frame_head && aPNG_write_frame_tail);
 #endif
 }
 
@@ -849,11 +942,6 @@ static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep pn
 
 	apng_frames++;
 
-#ifndef PNG_STATIC
-	if (apng_set_acTL)
-#endif
-		apng_set_acTL(apng_ptr, apng_info_ptr, apng_frames, 0);
-
 	for (y = 0; y < height; y++)
 	{
 		row_pointers[y] = png_buf;
@@ -861,9 +949,9 @@ static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep pn
 	}
 
 #ifndef PNG_STATIC
-	if (apng_write_frame_head)
+	if (aPNG_write_frame_head)
 #endif
-		apng_write_frame_head(apng_ptr, apng_info_ptr, row_pointers,
+		aPNG_write_frame_head(apng_ptr, apng_info_ptr, row_pointers,
 			vid.width, /* width */
 			height,    /* height */
 			0,         /* x offset */
@@ -876,60 +964,24 @@ static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep pn
 	png_write_image(png_ptr, row_pointers);
 
 #ifndef PNG_STATIC
-	if (apng_write_frame_tail)
+	if (aPNG_write_frame_tail)
 #endif
-		apng_write_frame_tail(apng_ptr, apng_info_ptr);
+		aPNG_write_frame_tail(apng_ptr, apng_info_ptr);
 
 	png_free(png_ptr, (png_voidp)row_pointers);
 }
 
-static inline boolean M_PNGfind_acTL(void)
-{
-	png_byte cn[8]; // 4 bytes for len then 4 byes for name
-	long endpos = ftell(apng_FILE); // not the real end of file, just what of libpng wrote
-	for (fseek(apng_FILE, 0, SEEK_SET); // let go to the start of the file
-	     ftell(apng_FILE)+12 < endpos;  // let not go over the file bound
-	     fseek(apng_FILE, 1, SEEK_CUR)  //  we went 8 steps back and now we go 1 step forward
-	    )
-	{
-		if (fread(cn, sizeof(cn), 1, apng_FILE) != 1) // read 8 bytes
-			return false; // failed to read data
-		if (fseek(apng_FILE, -8, SEEK_CUR) != 0) //rewind 8 bytes
-			return false; // failed to rewird
-		if (!png_memcmp(cn+4, acTL_cn, 4)) //cmp for chuck header
-			return true; // found it
-	}
-	return false; // acTL chuck not found
-}
-
-static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr)
+static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr,
+		apng_infop png_ainfo_ptr)
 {
-	png_byte data[16];
-	long oldpos;
-
-#ifndef PNG_STATIC
-	if (apng_set_acTL)
-#endif
-		apng_set_acTL(png_ptr, png_info_ptr, apng_frames, 0);
+	apng_set_acTL(png_ptr, png_info_ptr, png_ainfo_ptr, apng_frames, 0);
 
 #ifndef NO_PNG_DEBUG
 	png_debug(1, "in png_write_acTL\n");
 #endif
-
-	png_ptr->num_frames_to_write = apng_frames;
-
-	png_save_uint_32(data, apng_frames);
-	png_save_uint_32(data + 4, 0);
-
-	oldpos = ftell(apng_FILE);
-
-	if (M_PNGfind_acTL())
-		png_write_chunk(png_ptr, (png_bytep)acTL_cn, data, (png_size_t)8);
-
-	fseek(apng_FILE, oldpos, SEEK_SET);
 }
 
-static boolean M_SetupaPNG(png_const_charp filename, boolean palette)
+static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 {
 	apng_FILE = fopen(filename,"wb+"); // + mode for reading
 	if (!apng_FILE)
@@ -958,6 +1010,16 @@ static boolean M_SetupaPNG(png_const_charp filename, boolean palette)
 		return false;
 	}
 
+	apng_ainfo_ptr = apng_create_info_struct(apng_ptr);
+	if (!apng_ainfo_ptr)
+	{
+		CONS_Debug(DBG_RENDER, "M_StartMovie: Error on allocate for apng\n");
+		png_destroy_write_struct(&apng_ptr, &apng_info_ptr);
+		fclose(apng_FILE);
+		remove(filename);
+		return false;
+	}
+
 	png_init_io(apng_ptr, apng_FILE);
 
 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
@@ -971,16 +1033,15 @@ static boolean M_SetupaPNG(png_const_charp filename, boolean palette)
 	png_set_compression_strategy(apng_ptr, cv_zlib_strategya.value);
 	png_set_compression_window_bits(apng_ptr, cv_zlib_window_bitsa.value);
 
-	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, palette);
+	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, pal);
 
 	M_PNGText(apng_ptr, apng_info_ptr, true);
 
-#ifndef PNG_STATIC
-	if (apng_set_acTL)
-#endif
-		apng_set_acTL(apng_ptr, apng_info_ptr, PNG_UINT_31_MAX, 0);
+	apng_set_set_acTL_fn(apng_ptr, apng_ainfo_ptr, aPNG_set_acTL);
 
-	png_write_info(apng_ptr, apng_info_ptr);
+	apng_set_acTL(apng_ptr, apng_info_ptr, apng_ainfo_ptr, PNG_UINT_31_MAX, 0);
+
+	apng_write_info(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
 
 	apng_frames = 0;
 
@@ -996,6 +1057,7 @@ static boolean M_SetupaPNG(png_const_charp filename, boolean palette)
 static inline moviemode_t M_StartMovieAPNG(const char *pathname)
 {
 #ifdef USE_APNG
+	UINT8 *palette = NULL;
 	const char *freename = NULL;
 	boolean ret = false;
 
@@ -1012,9 +1074,12 @@ static inline moviemode_t M_StartMovieAPNG(const char *pathname)
 	}
 
 	if (rendermode == render_soft)
-		ret = M_SetupaPNG(va(pandf,pathname,freename), true);
-	else
-		ret = M_SetupaPNG(va(pandf,pathname,freename), false);
+	{
+		M_CreateScreenShotPalette();
+		palette = screenshot_palette;
+	}
+
+	ret = M_SetupaPNG(va(pandf,pathname,freename), palette);
 
 	if (!ret)
 	{
@@ -1183,8 +1248,8 @@ void M_StopMovie(void)
 
 			if (apng_frames)
 			{
-				M_PNGfix_acTL(apng_ptr, apng_info_ptr);
-				png_write_end(apng_ptr, apng_info_ptr);
+				M_PNGfix_acTL(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
+				apng_write_end(apng_ptr, apng_info_ptr, apng_ainfo_ptr);
 			}
 
 			png_destroy_write_struct(&apng_ptr, &apng_info_ptr);
@@ -1217,13 +1282,14 @@ void M_StopMovie(void)
   * \param data     The image data.
   * \param width    Width of the picture.
   * \param height   Height of the picture.
-  * \param palette  Palette of image data
+  * \param palette  Palette of image data.
   *  \note if palette is NULL, BGR888 format
   */
-boolean M_SavePNG(const char *filename, void *data, int width, int height, const boolean palette)
+boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette)
 {
 	png_structp png_ptr;
 	png_infop png_info_ptr;
+	PNG_CONST png_byte *PLTE = (const png_byte *)palette;
 #ifdef PNG_SETJMP_SUPPORTED
 #ifdef USE_FAR_KEYWORD
 	jmp_buf jmpbuf;
@@ -1238,8 +1304,7 @@ boolean M_SavePNG(const char *filename, void *data, int width, int height, const
 		return false;
 	}
 
-	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
-	 PNG_error, PNG_warn);
+	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
 	if (!png_ptr)
 	{
 		CONS_Debug(DBG_RENDER, "M_SavePNG: Error on initialize libpng\n");
@@ -1286,7 +1351,7 @@ boolean M_SavePNG(const char *filename, void *data, int width, int height, const
 	png_set_compression_strategy(png_ptr, cv_zlib_strategy.value);
 	png_set_compression_window_bits(png_ptr, cv_zlib_window_bits.value);
 
-	M_PNGhdr(png_ptr, png_info_ptr, width, height, palette);
+	M_PNGhdr(png_ptr, png_info_ptr, width, height, PLTE);
 
 	M_PNGText(png_ptr, png_info_ptr, false);
 
@@ -1333,7 +1398,7 @@ typedef struct
   * \param palette  Palette of image data
   */
 #if NUMSCREENS > 2
-static boolean WritePCXfile(const char *filename, const UINT8 *data, int width, int height)
+static boolean WritePCXfile(const char *filename, const UINT8 *data, int width, int height, const UINT8 *pal)
 {
 	int i;
 	size_t length;
@@ -1377,15 +1442,11 @@ static boolean WritePCXfile(const char *filename, const UINT8 *data, int width,
 
 	// write color table
 	{
-		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
-		? pLocalPalette
-		: pMasterPalette);
-
 		for (i = 0; i < 256; i++)
 		{
-			*pack++ = pal[i].s.red;
-			*pack++ = pal[i].s.green;
-			*pack++ = pal[i].s.blue;
+			*pack++ = *pal; pal++;
+			*pack++ = *pal; pal++;
+			*pack++ = *pal; pal++;
 		}
 	}
 
@@ -1405,9 +1466,8 @@ void M_ScreenShot(void)
 }
 
 /** Takes a screenshot.
-  * The screenshot is saved as "srb2xxxx.pcx" (or "srb2xxxx.tga" in hardware
-  * rendermode) where xxxx is the lowest four-digit number for which a file
-  * does not already exist.
+  * The screenshot is saved as "srb2xxxx.png" where xxxx is the lowest
+  * four-digit number for which a file does not already exist.
   *
   * \sa HWR_ScreenShot
   */
@@ -1421,6 +1481,10 @@ void M_DoScreenShot(void)
 	// Don't take multiple screenshots, obviously
 	takescreenshot = false;
 
+	// how does one take a screenshot without a render system?
+	if (rendermode == render_none)
+		return;
+
 	if (cv_screenshot_option.value == 0)
 		pathname = usehome ? srb2home : srb2path;
 	else if (cv_screenshot_option.value == 1)
@@ -1431,16 +1495,13 @@ void M_DoScreenShot(void)
 		pathname = cv_screenshot_folder.string;
 
 #ifdef USE_PNG
-	if (rendermode != render_none)
-		freename = Newsnapshotfile(pathname,"png");
+	freename = Newsnapshotfile(pathname,"png");
 #else
 	if (rendermode == render_soft)
 		freename = Newsnapshotfile(pathname,"pcx");
-	else if (rendermode != render_none)
+	else if (rendermode == render_opengl)
 		freename = Newsnapshotfile(pathname,"tga");
 #endif
-	else
-		I_Error("Can't take a screenshot without a render system");
 
 	if (rendermode == render_soft)
 	{
@@ -1454,16 +1515,16 @@ void M_DoScreenShot(void)
 
 	// save the pcx file
 #ifdef HWRENDER
-	if (rendermode != render_soft)
+	if (rendermode == render_opengl)
 		ret = HWR_Screenshot(va(pandf,pathname,freename));
 	else
 #endif
-	if (rendermode != render_none)
 	{
+		M_CreateScreenShotPalette();
 #ifdef USE_PNG
-		ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height, true);
+		ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette);
 #else
-		ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height);
+		ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height, screenshot_palette);
 #endif
 	}
 
@@ -1471,14 +1532,14 @@ failure:
 	if (ret)
 	{
 		if (moviemode != MM_SCREENSHOT)
-			CONS_Printf(M_GetText("screen shot %s saved in %s\n"), freename, pathname);
+			CONS_Printf(M_GetText("Screen shot %s saved in %s\n"), freename, pathname);
 	}
 	else
 	{
 		if (freename)
-			CONS_Printf(M_GetText("Couldn't create screen shot %s in %s\n"), freename, pathname);
+			CONS_Alert(CONS_ERROR, M_GetText("Couldn't create screen shot %s in %s\n"), freename, pathname);
 		else
-			CONS_Printf(M_GetText("Couldn't create screen shot (all 10000 slots used!) in %s\n"), pathname);
+			CONS_Alert(CONS_ERROR, M_GetText("Couldn't create screen shot in %s (all 10000 slots used!)\n"), pathname);
 
 		if (moviemode == MM_SCREENSHOT)
 			M_StopMovie();
@@ -1493,9 +1554,13 @@ boolean M_ScreenshotResponder(event_t *ev)
 		return false;
 
 	ch = ev->data1;
-	if (ch == KEY_F8)
+
+	if (ch >= KEY_MOUSE1 && menuactive) // If it's not a keyboard key, then don't allow it in the menus!
+		return false;
+
+	if (ch == KEY_F8 || ch == gamecontrol[gc_screenshot][0] || ch == gamecontrol[gc_screenshot][1]) // remappable F8
 		M_ScreenShot();
-	else if (ch == KEY_F9)
+	else if (ch == KEY_F9 || ch == gamecontrol[gc_recordgif][0] || ch == gamecontrol[gc_recordgif][1]) // remappable F9
 		((moviemode) ? M_StopMovie : M_StartMovie)();
 	else
 		return false;
diff --git a/src/m_misc.h b/src/m_misc.h
index 14f590516e3555092b1f482f64ea112001449e1e..6ac92dbcc085324c5c5bcba48c8f22fd2824c85f 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -58,7 +58,7 @@ void FIL_ForceExtension(char *path, const char *extension);
 boolean FIL_CheckExtension(const char *in);
 
 #ifdef HAVE_PNG
-boolean M_SavePNG(const char *filename, void *data, int width, int height, const boolean palette);
+boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette);
 #endif
 
 extern boolean takescreenshot;
diff --git a/src/m_queue.c b/src/m_queue.c
index daa7d7a241f9b54d04b3a3a18a737c689450d869..a3aa04871de0c444569ac237d78dc5b02eced8f7 100644
--- a/src/m_queue.c
+++ b/src/m_queue.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2003      by James Haley
-// Copyright (C) 2003-2016 by Sonic Team Junior.
+// Copyright (C) 2003-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_queue.h b/src/m_queue.h
index 7e0d58d396c9004370c9a715c9ddce6bd637288d..eebb21468e6651cc25338c357d3771eb451ec124 100644
--- a/src/m_queue.h
+++ b/src/m_queue.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2003      by James Haley
-// Copyright (C) 2003-2016 by Sonic Team Junior.
+// Copyright (C) 2003-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_random.c b/src/m_random.c
index 839b06e40c901b75ae98c86237a18ba09e6c9ad1..3d46f76b725e8d10be5a34e3661aba8a32595e96 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_random.h b/src/m_random.h
index 76dd9f1db5c6767c103b74752afe520d4e6ecde6..6187bcf1ce789dde3dbf7d66c8d3442c956df81a 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_swap.h b/src/m_swap.h
index 2352a0b2325ead9feb571c38b8707b02e9e0e89d..2d42f6138efda6115d1d0527877ddb55a983d79a 100644
--- a/src/m_swap.h
+++ b/src/m_swap.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/mserv.c b/src/mserv.c
index 76383ac35f2575ae8f097a5d10cbaa69cbc74fca..cf636dfdfb74082fc219d312819a686fc8d6bb0f 100644
--- a/src/mserv.c
+++ b/src/mserv.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -177,7 +177,7 @@ static void ServerName_OnChange(void);
 
 #define DEF_PORT "28900"
 consvar_t cv_masterserver = {"masterserver", "ms.srb2.org:"DEF_PORT, CV_SAVE, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_servername = {"servername", "SRB2 server", CV_SAVE, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_servername = {"servername", "SRB2 server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 INT16 ms_RoomId = -1;
 
@@ -528,9 +528,21 @@ const char *GetMODVersion(void)
 	msg.room = MODID; // Might as well use it for something.
 	sprintf(msg.buffer,"%d",MODVERSION);
 	if (MS_Write(&msg) < 0)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
+		M_StartMessage(M_GetText("Could not send to the Master Server\n"), NULL, MM_NOTHING);
+		CloseConnection();
 		return NULL;
+	}
+
+	if (MS_Read(&msg) < 0)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
+		M_StartMessage(M_GetText("No reply from the Master Server\n"), NULL, MM_NOTHING);
+		CloseConnection();
+		return NULL;
+	}
 
-	MS_Read(&msg);
 	CloseConnection();
 
 	if(strcmp(msg.buffer,"NULL") != 0)
@@ -558,9 +570,19 @@ void GetMODVersion_Console(void)
 	msg.room = MODID; // Might as well use it for something.
 	sprintf(msg.buffer,"%d",MODVERSION);
 	if (MS_Write(&msg) < 0)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
+		CloseConnection();
 		return;
+	}
+
+	if (MS_Read(&msg) < 0)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
+		CloseConnection();
+		return;
+	}
 
-	MS_Read(&msg);
 	CloseConnection();
 
 	if(strcmp(msg.buffer,"NULL") != 0)
@@ -927,8 +949,8 @@ void MasterClient_Ticker(void)
 
 static void ServerName_OnChange(void)
 {
-	UnregisterServer();
-	RegisterServer();
+	if (con_state == MSCS_REGISTERED)
+		AddToMasterServer(false);
 }
 
 static void MasterServer_OnChange(void)
diff --git a/src/mserv.h b/src/mserv.h
index c28ece2643feed52013b03976a69ae53796ca543..09cd4f0895f313afb9fa68549f4f52e001810e69 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 27d7394147c553ce7bf82fe2cd910dbbb80a3757..5395f618c5e6dd8361b168bb9bd1c58101f650b3 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -409,7 +409,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 		// new door thinker
 		rtn = 1;
 		ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL);
-		P_AddThinker(&ceiling->thinker);
+		P_AddThinker(THINK_MAIN, &ceiling->thinker);
 		sec->ceilingdata = ceiling;
 		ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling;
 		ceiling->sector = sec;
@@ -629,7 +629,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 		// new door thinker
 		rtn = 1;
 		ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL);
-		P_AddThinker(&ceiling->thinker);
+		P_AddThinker(THINK_MAIN, &ceiling->thinker);
 		sec->ceilingdata = ceiling;
 		ceiling->thinker.function.acp1 = (actionf_p1)T_CrushCeiling;
 		ceiling->sector = sec;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 70386c85840ac3827330f1d7e1fd2757c06efb20..0d1b80bfa93bff179b171848978e6054c36b73d8 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,7 @@
 #include "doomdef.h"
 #include "g_game.h"
 #include "p_local.h"
+#include "p_setup.h"
 #include "r_main.h"
 #include "r_state.h"
 #include "s_sound.h"
@@ -22,6 +23,7 @@
 #include "m_misc.h"
 #include "r_things.h"
 #include "i_video.h"
+#include "z_zone.h"
 #include "lua_hook.h"
 
 #ifdef HW3SOUND
@@ -35,6 +37,7 @@ boolean LUA_CallAction(const char *action, mobj_t *actor);
 player_t *stplyr;
 INT32 var1;
 INT32 var2;
+INT32 modulothing;
 
 //
 // P_NewChaseDir related LUT.
@@ -55,17 +58,31 @@ void A_Fall(mobj_t *actor);
 void A_Look(mobj_t *actor);
 void A_Chase(mobj_t *actor);
 void A_FaceStabChase(mobj_t *actor);
+void A_FaceStabRev(mobj_t *actor);
+void A_FaceStabHurl(mobj_t *actor);
+void A_FaceStabMiss(mobj_t *actor);
+void A_StatueBurst(mobj_t *actor);
 void A_JetJawRoam(mobj_t *actor);
 void A_JetJawChomp(mobj_t *actor);
 void A_PointyThink(mobj_t *actor);
 void A_CheckBuddy(mobj_t *actor);
+void A_HoodFire(mobj_t *actor);
 void A_HoodThink(mobj_t *actor);
-void A_ArrowCheck(mobj_t *actor);
+void A_HoodFall(mobj_t *actor);
+void A_ArrowBonks(mobj_t *actor);
 void A_SnailerThink(mobj_t *actor);
 void A_SharpChase(mobj_t *actor);
 void A_SharpSpin(mobj_t *actor);
+void A_SharpDecel(mobj_t *actor);
+void A_CrushstaceanWalk(mobj_t *actor);
+void A_CrushstaceanPunch(mobj_t *actor);
+void A_CrushclawAim(mobj_t *actor);
+void A_CrushclawLaunch(mobj_t *actor);
 void A_VultureVtol(mobj_t *actor);
 void A_VultureCheck(mobj_t *actor);
+void A_VultureHover(mobj_t *actor);
+void A_VultureBlast(mobj_t *actor);
+void A_VultureFly(mobj_t *actor);
 void A_SkimChase(mobj_t *actor);
 void A_FaceTarget(mobj_t *actor);
 void A_FaceTracer(mobj_t *actor);
@@ -101,7 +118,6 @@ void A_ExtraLife(mobj_t *actor);
 void A_GiveShield(mobj_t *actor);
 void A_GravityBox(mobj_t *actor);
 void A_ScoreRise(mobj_t *actor);
-void A_ParticleSpawn(mobj_t *actor);
 void A_BunnyHop(mobj_t *actor);
 void A_BubbleSpawn(mobj_t *actor);
 void A_FanBubbleSpawn(mobj_t *actor);
@@ -220,7 +236,6 @@ void A_SetScale(mobj_t *actor);
 void A_RemoteDamage(mobj_t *actor);
 void A_HomingChase(mobj_t *actor);
 void A_TrapShot(mobj_t *actor);
-//for p_enemy.c
 void A_Boss1Chase(mobj_t *actor);
 void A_Boss2Chase(mobj_t *actor);
 void A_Boss2Pogo(mobj_t *actor);
@@ -234,6 +249,7 @@ void A_BrakLobShot(mobj_t *actor);
 void A_NapalmScatter(mobj_t *actor);
 void A_SpawnFreshCopy(mobj_t *actor);
 void A_FlickySpawn(mobj_t *actor);
+void A_FlickyCenter(mobj_t *actor);
 void A_FlickyAim(mobj_t *actor);
 void A_FlickyFly(mobj_t *actor);
 void A_FlickySoar(mobj_t *actor);
@@ -246,6 +262,42 @@ void A_FlickyFlutter(mobj_t *actor);
 void A_FlameParticle(mobj_t *actor);
 void A_FadeOverlay(mobj_t *actor);
 void A_Boss5Jump(mobj_t *actor);
+void A_LightBeamReset(mobj_t *actor);
+void A_MineExplode(mobj_t *actor);
+void A_MineRange(mobj_t *actor);
+void A_ConnectToGround(mobj_t *actor);
+void A_SpawnParticleRelative(mobj_t *actor);
+void A_MultiShotDist(mobj_t *actor);
+void A_WhoCaresIfYourSonIsABee(mobj_t *actor);
+void A_ParentTriesToSleep(mobj_t *actor);
+void A_CryingToMomma(mobj_t *actor);
+void A_CheckFlags2(mobj_t *actor);
+void A_Boss5FindWaypoint(mobj_t *actor);
+void A_DoNPCSkid(mobj_t *actor);
+void A_DoNPCPain(mobj_t *actor);
+void A_PrepareRepeat(mobj_t *actor);
+void A_Boss5ExtraRepeat(mobj_t *actor);
+void A_Boss5Calm(mobj_t *actor);
+void A_Boss5CheckOnGround(mobj_t *actor);
+void A_Boss5CheckFalling(mobj_t *actor);
+void A_Boss5PinchShot(mobj_t *actor);
+void A_Boss5MakeItRain(mobj_t *actor);
+void A_LookForBetter(mobj_t *actor);
+void A_Boss5BombExplode(mobj_t *actor);
+void A_DustDevilThink(mobj_t *actor);
+void A_TNTExplode(mobj_t *actor);
+void A_DebrisRandom(mobj_t *actor);
+void A_TrainCameo(mobj_t *actor);
+void A_TrainCameo2(mobj_t *actor);
+void A_CanarivoreGas(mobj_t *actor);
+void A_KillSegments(mobj_t *actor);
+void A_SnapperSpawn(mobj_t *actor);
+void A_SnapperThinker(mobj_t *actor);
+void A_SaloonDoorSpawn(mobj_t *actor);
+void A_MinecartSparkThink(mobj_t *actor);
+void A_ModuloToState(mobj_t *actor);
+
+//for p_enemy.c
 
 //
 // ENEMY THINKING
@@ -749,6 +801,12 @@ static boolean P_LookForShield(mobj_t *actor)
 			&& (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)))
 		{
 			P_SetTarget(&actor->tracer, player->mo);
+
+			if (actor->hnext)
+				P_SetTarget(&actor->hnext->hprev, actor->hprev);
+			if (actor->hprev)
+				P_SetTarget(&actor->hprev->hnext, actor->hnext);
+
 			return true;
 		}
 	}
@@ -1019,7 +1077,7 @@ nomissile:
 
 // Function: A_FaceStabChase
 //
-// Description: A_Chase for CastleBot FaceStabber.
+// Description: Unused variant of A_Chase for Castlebot Facestabber.
 //
 // var1 = unused
 // var2 = unused
@@ -1107,6 +1165,269 @@ nomissile:
 		P_NewChaseDir(actor);
 }
 
+static void P_SharpDust(mobj_t *actor, mobjtype_t type, angle_t ang)
+{
+	mobj_t *dust;
+
+	if (!type || !P_IsObjectOnGround(actor))
+		return;
+
+	dust = P_SpawnMobjFromMobj(actor,
+			-P_ReturnThrustX(actor, ang, 16<<FRACBITS),
+			-P_ReturnThrustY(actor, ang, 16<<FRACBITS),
+			0, type);
+	P_SetObjectMomZ(dust, P_RandomRange(1, 4)<<FRACBITS, false);
+}
+
+static void P_FaceStabFlume(mobj_t *actor)
+{
+	mobj_t *flume;
+	if (leveltime & 1)
+		return;
+
+	flume = P_SpawnMobjFromMobj(actor,
+		-P_ReturnThrustX(actor, actor->angle, actor->radius),
+		-P_ReturnThrustY(actor, actor->angle, actor->radius),
+		actor->height/3,
+		MT_PARTICLE);
+	flume->destscale = actor->scale*3;
+	P_SetScale(flume, flume->destscale);
+	P_SetTarget(&flume->target, actor);
+	flume->sprite = SPR_JETF;
+	flume->frame = FF_FULLBRIGHT;
+	flume->tics = 2;
+}
+
+// Function: A_FaceStabRev
+//
+// Description: Facestabber rev action
+//
+// var1 = effective duration
+// var2 = effective nextstate
+//
+void A_FaceStabRev(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FaceStabRev", actor))
+		return;
+#endif
+
+	if (!actor->target)
+	{
+		P_SetMobjState(actor, actor->info->spawnstate);
+		return;
+	}
+
+	actor->extravalue1 = 0;
+
+	if (!actor->reactiontime)
+	{
+		actor->reactiontime = locvar1;
+		S_StartSound(actor, actor->info->activesound);
+	}
+	else
+	{
+		if ((--actor->reactiontime) == 0)
+		{
+			S_StartSound(actor, actor->info->attacksound);
+			P_SetMobjState(actor, locvar2);
+		}
+		else
+		{
+			P_TryMove(actor, actor->x - P_ReturnThrustX(actor, actor->angle, 2<<FRACBITS), actor->y - P_ReturnThrustY(actor, actor->angle, 2<<FRACBITS), false);
+			P_FaceStabFlume(actor);
+		}
+	}
+}
+
+// Function: A_FaceStabHurl
+//
+// Description: Facestabber hurl action
+//
+// var1 = homing strength (recommended strength between 0-8)
+// var2 = effective nextstate
+//
+void A_FaceStabHurl(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FaceStabHurl", actor))
+		return;
+#endif
+
+	if (actor->target)
+	{
+		angle_t visang = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
+		// Calculate new direction.
+		angle_t dirang = actor->angle;
+		angle_t diffang = visang - dirang;
+
+		if (locvar1) // Allow homing?
+		{
+			if (diffang > ANGLE_180)
+			{
+				angle_t workang = locvar1*(InvAngle(diffang)>>5);
+				diffang += InvAngle(workang);
+			}
+			else
+				diffang += (locvar1*(diffang>>5));
+		}
+		diffang += ANGLE_45;
+
+		// Check the sight cone.
+		if (diffang < ANGLE_90)
+		{
+			actor->angle = dirang;
+			if (++actor->extravalue2 < 4)
+				actor->extravalue2 = 4;
+			else if (actor->extravalue2 > 26)
+				actor->extravalue2 = 26;
+
+			if (P_TryMove(actor,
+				actor->x + P_ReturnThrustX(actor, dirang, actor->extravalue2<<FRACBITS),
+				actor->y + P_ReturnThrustY(actor, dirang, actor->extravalue2<<FRACBITS),
+				false))
+			{
+				// Do the spear damage.
+#define NUMSTEPS 3
+#define NUMGRADS 5
+#define MAXVAL (NUMSTEPS*NUMGRADS)
+				SINT8 step = (++actor->extravalue1);
+				fixed_t basesize = FRACUNIT/MAXVAL;
+				mobj_t *hwork = actor;
+				INT32 dist = 113;
+				fixed_t xo = P_ReturnThrustX(actor, actor->angle, dist*basesize);
+				fixed_t yo = P_ReturnThrustY(actor, actor->angle, dist*basesize);
+
+				while (step > 0)
+				{
+					if (!hwork->hnext)
+						P_SetTarget(&hwork->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_FACESTABBERSPEAR));
+					hwork = hwork->hnext;
+					hwork->angle = actor->angle + ANGLE_90;
+					hwork->destscale = FixedSqrt(step*basesize);
+					P_SetScale(hwork, hwork->destscale);
+					hwork->fuse = 2;
+					P_TeleportMove(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<<FRACBITS)));
+					step -= NUMGRADS;
+				}
+
+				if (actor->extravalue1 >= MAXVAL)
+					actor->extravalue1 -= NUMGRADS;
+
+				if ((step % 5) == 0)
+					P_SharpDust(actor, MT_SPINDUST, actor->angle);
+
+				P_FaceStabFlume(actor);
+				return;
+#undef MAXVAL
+#undef NUMGRADS
+#undef NUMSTEPS
+			}
+		}
+	}
+
+	P_SetMobjState(actor, locvar2);
+	actor->reactiontime = actor->info->reactiontime;
+}
+
+// Function: A_FaceStabMiss
+//
+// Description: Facestabber miss action
+//
+// var1 = unused
+// var2 = effective nextstate
+//
+void A_FaceStabMiss(mobj_t *actor)
+{
+	//INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FaceStabMiss", actor))
+		return;
+#endif
+
+	if (++actor->extravalue1 >= 3)
+	{
+		actor->extravalue2 -= 2;
+		actor->extravalue1 = 0;
+		S_StartSound(actor, sfx_s3k47);
+		P_SharpDust(actor, MT_SPINDUST, actor->angle);
+	}
+
+	if (actor->extravalue2 <= 0 || !P_TryMove(actor,
+		actor->x + P_ReturnThrustX(actor, actor->angle, actor->extravalue2<<FRACBITS),
+		actor->y + P_ReturnThrustY(actor, actor->angle, actor->extravalue2<<FRACBITS),
+		false))
+	{
+		actor->extravalue2 = 0;
+		P_SetMobjState(actor, locvar2);
+	}
+}
+
+// Function: A_StatueBurst
+//
+// Description: For suspicious statues only...
+//
+// var1 = object to create
+// var2 = effective nextstate for created object
+//
+void A_StatueBurst(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	mobjtype_t chunktype = (mobjtype_t)actor->info->raisestate;
+	mobj_t *new;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_StatueBurst", actor))
+		return;
+#endif
+
+	if (!locvar1 || !(new = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1)))
+		return;
+
+	new->angle = actor->angle;
+	P_SetTarget(&new->target, actor->target);
+	if (locvar2)
+		P_SetMobjState(new, (statenum_t)locvar2);
+	S_StartSound(new, new->info->attacksound);
+	S_StopSound(actor);
+	S_StartSound(actor, sfx_s3k96);
+
+	{
+		fixed_t a, b;
+		fixed_t c = (actor->height>>2) - FixedMul(actor->scale, mobjinfo[chunktype].height>>1);
+		fixed_t v = 4<<FRACBITS;
+		const fixed_t r = (actor->radius>>1);
+		mobj_t *spawned;
+		UINT8 i;
+		for (i = 0; i < 8; i++)
+		{
+			a = ((i & 1) ? r : (-r));
+			b = ((i & 2) ? r : (-r));
+			if (i == 4)
+			{
+				c += (actor->height>>1);
+				v = 8<<FRACBITS;
+			}
+
+			spawned = P_SpawnMobjFromMobj(actor, a, b, c, chunktype);
+
+			P_InstaThrust(spawned, R_PointToAngle2(0, 0, a, b), 8<<FRACBITS);
+			P_SetObjectMomZ(spawned, v, false);
+
+			spawned->fuse = 3*TICRATE;
+		}
+	}
+}
+
 // Function: A_JetJawRoam
 //
 // Description: Roaming routine for JetJaw
@@ -1303,6 +1624,62 @@ void A_CheckBuddy(mobj_t *actor)
 		P_RemoveMobj(actor);
 }
 
+// Helper function for the Robo Hood.
+// Don't ask me how it works. Nev3r made it with dark majyks.
+static void P_ParabolicMove(mobj_t *actor, fixed_t x, fixed_t y, fixed_t z, fixed_t speed)
+{
+	fixed_t dh;
+
+	x -= actor->x;
+	y -= actor->y;
+	z -= actor->z;
+
+	dh = P_AproxDistance(x, y);
+
+	actor->momx = FixedMul(FixedDiv(x, dh), speed);
+	actor->momy = FixedMul(FixedDiv(y, dh), speed);
+
+	if (!gravity)
+		return;
+
+	dh = FixedDiv(FixedMul(dh, gravity), speed);
+	actor->momz = (dh>>1) + FixedDiv(z, dh<<1);
+}
+
+// Function: A_HoodFire
+//
+// Description: Firing Robo-Hood
+//
+// var1 = object type to fire
+// var2 = unused
+//
+void A_HoodFire(mobj_t *actor)
+{
+	mobj_t *arrow;
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_HoodFire", actor))
+		return;
+#endif
+
+	// Check target first.
+	if (!actor->target)
+	{
+		actor->reactiontime = actor->info->reactiontime;
+		P_SetMobjState(actor, actor->info->spawnstate);
+		return;
+	}
+
+	A_FaceTarget(actor);
+
+	if (!(arrow = P_SpawnMissile(actor, actor->target, (mobjtype_t)locvar1)))
+		return;
+
+	// Set a parabolic trajectory for the arrow.
+	P_ParabolicMove(arrow, actor->target->x, actor->target->y, actor->target->z, arrow->info->speed);
+}
+
 // Function: A_HoodThink
 //
 // Description: Thinker for Robo-Hood
@@ -1312,97 +1689,112 @@ void A_CheckBuddy(mobj_t *actor)
 //
 void A_HoodThink(mobj_t *actor)
 {
+	fixed_t dx, dy, dz, dm;
+	boolean checksight;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_HoodThink", actor))
 		return;
 #endif
-	// Currently in the air...
-	if (!(actor->eflags & MFE_VERTICALFLIP) && actor->z > actor->floorz)
+
+	// Check target first.
+	if (!actor->target)
 	{
-		if (actor->momz > 0)
-			P_SetMobjStateNF(actor, actor->info->xdeathstate); // Rising
-		else
-			P_SetMobjStateNF(actor, actor->info->raisestate); // Falling
+		actor->reactiontime = actor->info->reactiontime;
+		P_SetMobjState(actor, actor->info->spawnstate);
+		return;
+	}
 
+	dx = (actor->target->x - actor->x), dy = (actor->target->y - actor->y), dz = (actor->target->z - actor->z);
+	dm = P_AproxDistance(dx, dy);
+	// Target dangerously close to robohood, retreat then.
+	if ((dm < 256<<FRACBITS) && (abs(dz) < 128<<FRACBITS))
+	{
+		P_SetMobjState(actor, actor->info->raisestate);
 		return;
 	}
-	else if ((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height < actor->ceilingz)
+
+	// If target on sight, look at it.
+	if ((checksight = P_CheckSight(actor, actor->target)))
 	{
-		if (actor->momz < 0)
-			P_SetMobjStateNF(actor, actor->info->xdeathstate); // Rising
+		angle_t dang = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
+		if (actor->angle >= ANGLE_180)
+		{
+			actor->angle = InvAngle(actor->angle)>>1;
+			actor->angle = InvAngle(actor->angle);
+		}
 		else
-			P_SetMobjStateNF(actor, actor->info->raisestate); // Falling
+			actor->angle >>= 1;
 
-		return;
-	}
+		if (dang >= ANGLE_180)
+		{
+			dang = InvAngle(dang)>>1;
+			dang = InvAngle(dang);
+		}
+		else
+			dang >>= 1;
 
-	if (actor->state == &states[actor->info->xdeathstate]
-		|| actor->state == &states[actor->info->raisestate])
-		P_SetMobjStateNF(actor, actor->info->seestate);
+		actor->angle += dang;
+	}
 
-	if (!actor->target)
+	// Check whether to do anything.
+	if ((--actor->reactiontime) <= 0)
 	{
-		P_SetMobjState(actor, actor->info->spawnstate);
-		return;
-	}
+		actor->reactiontime = actor->info->reactiontime;
 
-	A_FaceTarget(actor); // Aiming... aiming...
+		// If way too far, don't shoot.
+		if ((dm < (3072<<FRACBITS)) && checksight)
+		{
+			P_SetMobjState(actor, actor->info->missilestate);
+			return;
+		}
+	}
+}
 
-	if (--actor->reactiontime > 0)
+// Function: A_HoodFall
+//
+// Description: Falling Robo-Hood
+//
+// var1 = unused
+// var2 = unused
+//
+void A_HoodFall(mobj_t *actor)
+{
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_HoodFall", actor))
 		return;
+#endif
 
-	// Shoot, if not too close (cheap shots are lame)
-	if ((P_AproxDistance(actor->x-actor->target->x, actor->y-actor->target->y) > FixedMul(192*FRACUNIT, actor->scale))
-	|| (actor->spawnpoint && (actor->spawnpoint->options & MTF_AMBUSH))) // If you can't jump, might as well shoot regardless of distance!
-		P_SetMobjState(actor, actor->info->missilestate);
-	else if (!(actor->spawnpoint && (actor->spawnpoint->options & MTF_AMBUSH)))// But we WILL jump!
-		P_SetMobjState(actor, actor->info->painstate);
+	if (!P_IsObjectOnGround(actor))
+		return;
 
+	actor->momx = actor->momy = 0;
 	actor->reactiontime = actor->info->reactiontime;
+	P_SetMobjState(actor, actor->info->seestate);
 }
 
-// Function: A_ArrowCheck
+// Function: A_ArrowBonks
 //
-// Description: Checks arrow direction and adjusts sprite accordingly
+// Description: Arrow momentum setting on collision
 //
 // var1 = unused
 // var2 = unused
 //
-void A_ArrowCheck(mobj_t *actor)
+void A_ArrowBonks(mobj_t *actor)
 {
-	fixed_t x,y,z;
-	angle_t angle;
-	fixed_t dist;
-
 #ifdef HAVE_BLUA
-	if (LUA_CallAction("A_ArrowCheck", actor))
+	if (LUA_CallAction("A_ArrowBonks", actor))
 		return;
 #endif
 
-	// Movement vector
-	x = actor->momx;
-	y = actor->momy;
-	z = actor->momz;
-
-	// Calculate the angle of movement.
-	/*
-	       Z
-	     / |
-	   /   |
-	 /     |
-	0------dist(X,Y)
-	*/
-
-	dist = P_AproxDistance(x, y);
+	if (((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz)
+		|| (!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz))
+		actor->angle += ANGLE_180;
 
-	angle = R_PointToAngle2(0, 0, dist, z);
+	P_SetObjectMomZ(actor, 8*actor->scale, false);
+	P_InstaThrust(actor, actor->angle, -6*actor->scale);
 
-	if (angle > ANG20 && angle <= ANGLE_180)
-		P_SetMobjStateNF(actor, actor->info->raisestate);
-	else if (angle < ANG340 && angle > ANGLE_180)
-		P_SetMobjStateNF(actor, actor->info->xdeathstate);
-	else
-		P_SetMobjStateNF(actor, actor->info->spawnstate);
+	actor->flags = (actor->flags|MF_NOCLIPHEIGHT) & ~MF_NOGRAVITY;
+	actor->z += P_MobjFlip(actor);
 }
 
 // Function: A_SnailerThink
@@ -1482,7 +1874,7 @@ void A_SnailerThink(mobj_t *actor)
 
 // Function: A_SharpChase
 //
-// Description: Thinker/Chase routine for Sharps
+// Description: Thinker/Chase routine for Spincushions
 //
 // var1 = unused
 // var2 = unused
@@ -1494,12 +1886,6 @@ void A_SharpChase(mobj_t *actor)
 		return;
 #endif
 
-	if (!actor->health)
-	{
-		P_SetMobjState(actor, actor->info->deathstate);
-		return;
-	}
-
 	if (actor->reactiontime)
 	{
 		INT32 delta;
@@ -1542,63 +1928,348 @@ void A_SharpChase(mobj_t *actor)
 
 // Function: A_SharpSpin
 //
-// Description: Spin chase routine for Sharps
+// Description: Spin chase routine for Spincushions
 //
-// var1 = unused
-// var2 = unused
+// var1 = object # to spawn as dust (if not provided not done)
+// var2 = if nonzero, do the old-style spinning using this as the angle difference
 //
 void A_SharpSpin(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	angle_t oldang = actor->angle;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SharpSpin", actor))
 		return;
 #endif
 
-	if (!actor->health)
-	{
-		P_SetMobjState(actor, actor->info->deathstate);
-		return;
-	}
-
 	if (actor->threshold && actor->target)
 	{
-		actor->angle += ANGLE_22h;
-		P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y), FixedMul(actor->info->speed*FRACUNIT, actor->scale));
+		angle_t ang = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
+		P_Thrust(actor, ang, actor->info->speed*actor->scale);
+		if (locvar2)
+			actor->angle += locvar2; // ANGLE_22h;
+		else
+			actor->angle = ang;
 		actor->threshold--;
+		if (leveltime & 1)
+			S_StartSound(actor, actor->info->painsound);
 	}
 	else
 	{
 		actor->reactiontime = actor->info->reactiontime;
-		P_SetMobjState(actor, actor->info->spawnstate);
-
-		var1 = 1;
-		A_Look(actor);
-		if (actor->target)
-			actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
+		P_SetMobjState(actor, actor->info->meleestate);
 	}
+
+	P_SharpDust(actor, locvar1, oldang);
 }
 
-// Function: A_VultureVtol
+// Function: A_SharpDecel
 //
-// Description: Vulture rising up to match target's height
+// Description: Slow down the Spincushion
 //
 // var1 = unused
 // var2 = unused
 //
-void A_VultureVtol(mobj_t *actor)
+void A_SharpDecel(mobj_t *actor)
 {
 #ifdef HAVE_BLUA
-	if (LUA_CallAction("A_VultureVtol", actor))
+	if (LUA_CallAction("A_SharpDecel", actor))
 		return;
 #endif
 
-	if (!actor->target)
+	if (actor->momx > 2 || actor->momy > 2)
+	{
+		actor->momx >>= 1;
+		actor->momy >>= 1;
+	}
+	else
+		P_SetMobjState(actor, actor->info->xdeathstate);
+}
+
+// Function: A_CrushstaceanWalk
+//
+// Description: Crushstacean movement
+//
+// var1 = speed (actor info's speed if 0)
+// var2 = state to switch to when blocked (spawnstate if 0)
+//
+void A_CrushstaceanWalk(mobj_t *actor)
+{
+	INT32 locvar1 = (var1 ? var1 : (INT32)actor->info->speed);
+	INT32 locvar2 = (var2 ? var2 : (INT32)actor->info->spawnstate);
+	angle_t ang = actor->angle + ((actor->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CrushstaceanWalk", actor))
 		return;
+#endif
 
-	actor->flags |= MF_NOGRAVITY;
-	actor->flags |= MF_FLOAT;
+	actor->reactiontime--;
 
-	A_FaceTarget(actor);
+	if (!P_TryMove(actor,
+		actor->x + P_ReturnThrustX(actor, ang, locvar1*actor->scale),
+		actor->y + P_ReturnThrustY(actor, ang, locvar1*actor->scale),
+		false)
+	|| (actor->reactiontime-- <= 0))
+	{
+		actor->flags2 ^= MF2_AMBUSH;
+		P_SetMobjState(actor, locvar2);
+		actor->reactiontime = actor->info->reactiontime;
+	}
+}
+
+// Function: A_CrushstaceanPunch
+//
+// Description: Crushstacean attack
+//
+// var1 = unused
+// var2 = state to go to if unsuccessful (spawnstate if 0)
+//
+void A_CrushstaceanPunch(mobj_t *actor)
+{
+	//INT32 locvar1 = var1;
+	INT32 locvar2 = (var2 ? var2 : (INT32)actor->info->spawnstate);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CrushstaceanPunch", actor))
+		return;
+#endif
+
+	if (!actor->tracer)
+		return;
+
+	if (!actor->target)
+	{
+		P_SetMobjState(actor, locvar2);
+		return;
+	}
+
+	actor->tracer->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
+	P_SetMobjState(actor->tracer, actor->tracer->info->missilestate);
+	actor->tracer->extravalue1 = actor->tracer->extravalue2 = 0;
+	S_StartSound(actor, actor->info->attacksound);
+}
+
+// Function: A_CrushclawAim
+//
+// Description: Crushstacean claw aiming
+//
+// var1 = sideways offset
+// var2 = vertical offset
+//
+void A_CrushclawAim(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	mobj_t *crab = actor->tracer;
+	angle_t ang;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CrushclawAim", actor))
+		return;
+#endif
+
+	if (!crab)
+	{
+		P_RemoveMobj(actor);
+		return; // there is only one step and it is crab
+	}
+
+	if (crab->target || P_LookForPlayers(crab, true, false, 600*crab->scale))
+		ang = R_PointToAngle2(crab->x, crab->y, crab->target->x, crab->target->y);
+	else
+		ang = crab->angle + ((crab->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
+	ang -= actor->angle;
+
+#define anglimit ANGLE_22h
+#define angfactor 5
+	if (ang < ANGLE_180)
+	{
+		if (ang > anglimit)
+			ang = anglimit;
+		ang /= angfactor;
+	}
+	else
+	{
+		ang = InvAngle(ang);
+		if (ang > anglimit)
+			ang = anglimit;
+		ang = InvAngle(ang/angfactor);
+	}
+	actor->angle += ang;
+#undef anglimit
+#undef angfactor
+
+	P_TeleportMove(actor,
+		crab->x + P_ReturnThrustX(actor, actor->angle, locvar1*crab->scale),
+		crab->y + P_ReturnThrustY(actor, actor->angle, locvar1*crab->scale),
+		crab->z + locvar2*crab->scale);
+
+	if (!crab->target || !crab->info->missilestate || (statenum_t)(crab->state-states) == crab->info->missilestate)
+		return;
+
+	if (((ang + ANG1) < ANG2) || P_AproxDistance(crab->x - crab->target->x, crab->y - crab->target->y) < 333*crab->scale)
+		P_SetMobjState(crab, crab->info->missilestate);
+}
+
+// Function: A_CrushclawLaunch
+//
+// Description: Crushstacean claw launching
+//
+// var1:
+//		0 - forwards
+//		anything else - backwards
+// var2 = state to change to when done
+//
+void A_CrushclawLaunch(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	mobj_t *crab = actor->tracer;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CrushclawLaunch", actor))
+		return;
+#endif
+
+	if (!crab)
+	{
+		mobj_t *chainnext;
+		while (actor)
+		{
+			chainnext = actor->target;
+			P_RemoveMobj(actor);
+			actor = chainnext;
+		}
+		return; // there is only one step and it is crab
+	}
+
+	if (!actor->extravalue1)
+	{
+		S_StartSound(actor, actor->info->activesound);
+		actor->extravalue1 = ((locvar1) ? -1 : 32);
+	}
+	else if (actor->extravalue1 != 1)
+		actor->extravalue1 -= 1;
+
+#define CSEGS 5
+	if (!actor->target)
+	{
+		mobj_t *prevchain = actor;
+		UINT8 i = 0;
+		for (i = 0; (i < CSEGS); i++)
+		{
+			mobj_t *newchain = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->info->raisestate);
+			P_SetTarget(&prevchain->target, newchain);
+			prevchain = newchain;
+		}
+		actor->target->angle = R_PointToAngle2(actor->target->x, actor->target->y, crab->target->x, crab->target->y);
+	}
+
+	if ((!locvar1) && crab->target)
+	{
+#define anglimit ANGLE_22h
+#define angfactor 7
+		angle_t ang = R_PointToAngle2(actor->target->x, actor->target->y, crab->target->x, crab->target->y) - actor->target->angle;
+		if (ang < ANGLE_180)
+		{
+			if (ang > anglimit)
+				ang = anglimit;
+			ang /= angfactor;
+		}
+		else
+		{
+			ang = InvAngle(ang);
+			if (ang > anglimit)
+				ang = anglimit;
+			ang /= angfactor;
+			ang = InvAngle(ang);
+		}
+		actor->target->angle += ang;
+		actor->angle = actor->target->angle;
+	}
+
+	actor->extravalue2 += actor->extravalue1;
+
+	if (!P_TryMove(actor,
+		actor->target->x + P_ReturnThrustX(actor, actor->target->angle, actor->extravalue2*actor->scale),
+		actor->target->y + P_ReturnThrustY(actor, actor->target->angle, actor->extravalue2*actor->scale),
+		true)
+		&& !locvar1)
+	{
+		actor->extravalue1 = 0;
+		actor->extravalue2 = FixedHypot(actor->x - actor->target->x, actor->y - actor->target->y)>>FRACBITS;
+		P_SetMobjState(actor, locvar2);
+		S_StopSound(actor);
+		S_StartSound(actor, sfx_s3k49);
+	}
+	else
+	{
+		actor->z = actor->target->z;
+		if ((!locvar1 && (actor->extravalue2 > 256)) || (locvar1 && (actor->extravalue2 < 16)))
+		{
+			if (locvar1) // In case of retracting, resume crab and remove the chain.
+			{
+				mobj_t *chain = actor->target, *chainnext;
+				while (chain)
+				{
+					chainnext = chain->target;
+					P_RemoveMobj(chain);
+					chain = chainnext;
+				}
+				actor->extravalue2 = 0;
+				actor->angle = R_PointToAngle2(crab->x, crab->y, actor->x, actor->y);
+				P_SetTarget(&actor->target, NULL);
+				P_SetTarget(&crab->target, NULL);
+				P_SetMobjState(crab, crab->state->nextstate);
+			}
+			actor->extravalue1 = 0;
+			P_SetMobjState(actor, locvar2);
+			S_StopSound(actor);
+			if (!locvar1)
+				S_StartSound(actor, sfx_s3k64);
+		}
+	}
+
+	if (!actor->target)
+		return;
+
+	{
+		mobj_t *chain = actor->target->target;
+		fixed_t dx = (actor->x - actor->target->x)/CSEGS, dy = (actor->y - actor->target->y)/CSEGS, dz = (actor->z - actor->target->z)/CSEGS;
+		fixed_t idx = dx, idy = dy, idz = dz;
+		while (chain)
+		{
+			P_TeleportMove(chain, actor->target->x + idx, actor->target->y + idy, actor->target->z + idz);
+			chain->watertop = chain->z;
+			idx += dx;
+			idy += dy;
+			idz += dz;
+			chain = chain->target;
+		}
+	}
+#undef CSEGS
+}
+
+// Function: A_VultureVtol
+//
+// Description: Vulture rising up to match target's height
+//
+// var1 = unused
+// var2 = unused
+//
+void A_VultureVtol(mobj_t *actor)
+{
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_VultureVtol", actor))
+		return;
+#endif
+
+	if (!actor->target)
+		return;
+
+	actor->flags |= MF_NOGRAVITY;
+	actor->flags |= MF_FLOAT;
+
+	A_FaceTarget(actor);
 
 	S_StopSound(actor);
 
@@ -1641,6 +2312,227 @@ void A_VultureCheck(mobj_t *actor)
 	}
 }
 
+static void P_VultureHoverParticle(mobj_t *actor)
+{
+	fixed_t fdist = actor->z - P_FloorzAtPos(actor->x, actor->y, actor->z, actor->height);
+
+	if (fdist < 128*FRACUNIT)
+	{
+		mobj_t *dust;
+		UINT8 i;
+		angle_t angle = (leveltime % 2)*ANGLE_45/2;
+
+		for (i = 0; i <= 7; i++)
+		{
+			angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
+			fixed_t px = actor->x + FixedMul(fdist + 64*FRACUNIT, FINECOSINE(fa));
+			fixed_t py = actor->y + FixedMul(fdist + 64*FRACUNIT, FINESINE(fa));
+			fixed_t pz = P_FloorzAtPos(px, py, actor->z, actor->height);
+
+			dust = P_SpawnMobj(px, py, pz, MT_ARIDDUST);
+			P_SetMobjState(dust, (statenum_t)(dust->state - states + P_RandomRange(0, 2)));
+			P_Thrust(dust, angle, FixedDiv(12*FRACUNIT, max(FRACUNIT, fdist/2)));
+			dust->momx += actor->momx;
+			dust->momy += actor->momy;
+			angle += ANGLE_45;
+		}
+	}
+}
+
+// Function: A_VultureHover
+//
+// Description: Vulture hovering and checking whether to attack.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_VultureHover(mobj_t *actor)
+{
+	fixed_t targetz;
+	fixed_t distdif;
+	fixed_t memz = actor->z;
+	SINT8 i;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_VultureHover", actor))
+		return;
+#endif
+
+	if (!actor->target || P_MobjWasRemoved(actor->target))
+	{
+		P_SetMobjState(actor, actor->info->spawnstate);
+		return;
+	}
+
+	actor->flags |= MF_NOGRAVITY;
+
+	actor->momx -= actor->momx/24;
+	actor->momy -= actor->momy/24;
+
+	P_VultureHoverParticle(actor);
+
+	A_FaceTarget(actor);
+	targetz = actor->target->z + actor->target->height / 2;
+	for (i = -1; i <= 1; i++)
+	{
+		actor->z = targetz - i * 128 * FRACUNIT;
+		if (P_CheckSight(actor, actor->target))
+		{
+			targetz -= i * 128 * FRACUNIT;
+			break;
+		}
+	}
+	actor->z = memz;
+
+	distdif = (actor->z + actor->height/2) - targetz;
+
+	if (abs(actor->momz*16) > abs(distdif))
+		actor->momz -= actor->momz/16;
+	else if (distdif < 0)
+		actor->momz = min(actor->momz+FRACUNIT/8, actor->info->speed*FRACUNIT);
+	else
+		actor->momz = max(actor->momz-FRACUNIT/8, -actor->info->speed*FRACUNIT);
+
+	if (abs(distdif) < 128*FRACUNIT && abs(actor->momz) < FRACUNIT && P_CheckSight(actor, actor->target))
+	{
+		P_SetMobjState(actor, actor->info->missilestate);
+		actor->momx = 0;
+		actor->momy = 0;
+		actor->momz = 0;
+		actor->extravalue1 = 0;
+	}
+}
+
+// Function: A_VultureBlast
+//
+// Description: Vulture spawning a dust cloud.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_VultureBlast(mobj_t *actor)
+{
+	mobj_t *dust;
+	UINT8 i;
+	angle_t faa;
+	fixed_t faacos, faasin;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_VultureBlast", actor))
+		return;
+#endif
+
+	S_StartSound(actor, actor->info->attacksound);
+
+	faa = (actor->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	faacos = FINECOSINE(faa);
+	faasin = FINESINE(faa);
+
+	for (i = 0; i <= 7; i++)
+	{
+		angle_t fa = ((i*(angle_t)ANGLE_45) >> ANGLETOFINESHIFT) & FINEMASK;
+		dust = P_SpawnMobj(actor->x + 48*FixedMul(FINECOSINE(fa), -faasin), actor->y + 48*FixedMul(FINECOSINE(fa), faacos), actor->z + actor->height/2 + 48*FINESINE(fa), MT_PARTICLE);
+
+		P_SetScale(dust, 4*FRACUNIT);
+		dust->destscale = FRACUNIT;
+		dust->scalespeed = 4*FRACUNIT/TICRATE;
+		dust->fuse = TICRATE;
+		dust->momx = FixedMul(FINECOSINE(fa), -faasin)*3;
+		dust->momy = FixedMul(FINECOSINE(fa), faacos)*3;
+		dust->momz = FINESINE(fa)*6;
+	}
+}
+
+// Function: A_VultureFly
+//
+// Description: Vulture charging towards target.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_VultureFly(mobj_t *actor)
+{
+	fixed_t speedmax = 18*FRACUNIT;
+	angle_t angledif = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y) - actor->angle;
+	fixed_t dx = actor->target->x - actor->x;
+	fixed_t dy = actor->target->y - actor->y;
+	fixed_t dz = actor->target->z - actor->z;
+	fixed_t dxy = FixedHypot(dx, dy);
+	fixed_t dm;
+	mobj_t *dust;
+	fixed_t momm;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_VultureFly", actor))
+		return;
+#endif
+
+	if (leveltime % 4 == 0)
+		S_StartSound(actor, actor->info->activesound);
+
+	if (angledif > ANGLE_180)
+		angledif = InvAngle(angledif);
+
+	// Tweak the target height according to the position.
+	if (angledif < ANGLE_45) // Centered?
+	{
+		actor->reactiontime = actor->info->reactiontime;
+		if (dxy > 768*FRACUNIT)
+			dz = max(P_FloorzAtPos(actor->target->x, actor->target->y, actor->target->z, 0) - actor->z + min(dxy/8, 128*FRACUNIT), dz);
+	}
+	else
+	{
+		actor->reactiontime--;
+
+		if (angledif < ANGLE_90)
+			dz = max(P_FloorzAtPos(actor->target->x, actor->target->y, actor->target->z, 0) - actor->z + min(dxy/2, 192*FRACUNIT), dz);
+		else
+			dz = max(P_FloorzAtPos(actor->target->x, actor->target->y, actor->target->z, 0) - actor->z + 232*FRACUNIT, dz);
+	}
+
+	dm = FixedHypot(dz, dxy);
+
+	P_VultureHoverParticle(actor);
+
+	dust = P_SpawnMobj(actor->x + P_RandomFixed() - FRACUNIT/2, actor->y + P_RandomFixed() - FRACUNIT/2, actor->z + actor->height/2 + P_RandomFixed() - FRACUNIT/2, MT_PARTICLE);
+	P_SetScale(dust, 2*FRACUNIT);
+	dust->destscale = FRACUNIT/3;
+	dust->scalespeed = FRACUNIT/40;
+	dust->fuse = TICRATE*2;
+
+	actor->momx += FixedDiv(dx, dm)*2;
+	actor->momy += FixedDiv(dy, dm)*2;
+	actor->momz += FixedDiv(dz, dm)*2;
+
+	momm = FixedHypot(actor->momz, FixedHypot(actor->momx, actor->momy));
+
+	if (momm > speedmax/2 && actor->reactiontime == 0)
+	{
+		P_SetMobjState(actor, actor->info->seestate);
+		return;
+	}
+
+	//Hits a wall hard?
+	if (actor->extravalue1 - momm > 15*FRACUNIT)
+	{
+		actor->flags &= ~MF_NOGRAVITY;
+		P_SetMobjState(actor, actor->info->painstate);
+		S_StopSound(actor);
+		S_StartSound(actor, actor->info->painsound);
+		return;
+	}
+	actor->extravalue1 = momm;
+
+	if (momm > speedmax)
+	{
+		actor->momx = FixedMul(FixedDiv(actor->momx, momm), speedmax);
+		actor->momy = FixedMul(FixedDiv(actor->momy, momm), speedmax);
+		actor->momz = FixedMul(FixedDiv(actor->momz, momm), speedmax);
+	}
+
+	actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+}
+
 // Function: A_SkimChase
 //
 // Description: Thinker/Chase routine for Skims
@@ -1974,6 +2866,7 @@ void A_BossFireShot(mobj_t *actor)
 	fixed_t x, y, z;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
+	mobj_t *missile;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_BossFireShot", actor))
@@ -2041,7 +2934,10 @@ void A_BossFireShot(mobj_t *actor)
 			break;
 	}
 
-	P_SpawnXYZMissile(actor, actor->target, locvar1, x, y, z);
+	missile = P_SpawnXYZMissile(actor, actor->target, locvar1, x, y, z);
+
+	if (missile && actor->tracer && (actor->tracer->flags & MF_BOSS)) // Don't harm your papa.
+		P_SetTarget(&missile->target, actor->tracer);
 }
 
 // Function: A_Boss7FireMissiles
@@ -2207,7 +3103,7 @@ void A_Boss1Laser(mobj_t *actor)
 	if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1)
 	{
 		point = P_SpawnMobj(x, y, floorz+1, MT_EGGMOBILE_FIRE);
-		point->target = actor;
+		P_SetTarget(&point->target, actor);
 		point->destscale = 3*FRACUNIT;
 		point->scalespeed = FRACUNIT>>2;
 		point->fuse = TICRATE;
@@ -2282,21 +3178,35 @@ void A_FocusTarget(mobj_t *actor)
 // Description: Reverse arms direction.
 //
 // var1 = sfx to play
-// var2 = unused
+// var2 = sfx to play in pinch
 //
 void A_Boss4Reverse(mobj_t *actor)
 {
 	sfxenum_t locvar1 = (sfxenum_t)var1;
+	sfxenum_t locvar2 = (sfxenum_t)var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Boss4Reverse", actor))
 		return;
 #endif
-	S_StartSound(NULL, locvar1);
 	actor->reactiontime = 0;
-	if (actor->movedir == 1)
-		actor->movedir = 2;
+	if (actor->movedir < 3)
+	{
+		S_StartSound(NULL, locvar1);
+		if (actor->movedir == 1)
+			actor->movedir = 2;
+		else
+			actor->movedir = 1;
+	}
 	else
-		actor->movedir = 1;
+	{
+		S_StartSound(NULL, locvar2);
+		if (actor->movedir == 4)
+			actor->movedir = 5;
+		else
+			actor->movedir = 4;
+		actor->angle += ANGLE_180;
+		actor->movefactor = -actor->movefactor;
+	}
 }
 
 // Function: A_Boss4SpeedUp
@@ -2593,9 +3503,11 @@ void A_1upThinker(mobj_t *actor)
 
 	if (closestplayer == -1 || skins[players[closestplayer].skin].sprites[SPR2_LIFE].numframes == 0)
 	{ // Closest player not found (no players in game?? may be empty dedicated server!), or does not have correct sprite.
-		if (actor->tracer) {
-			P_RemoveMobj(actor->tracer);
-			actor->tracer = NULL;
+		if (actor->tracer)
+		{
+			mobj_t *tracer = actor->tracer;
+			P_SetTarget(&actor->tracer, NULL);
+			P_RemoveMobj(tracer);
 		}
 		return;
 	}
@@ -2696,6 +3608,11 @@ void A_MonitorPop(mobj_t *actor)
 			newmobj->sprite = SPR_TV1P;
 		}
 	}
+
+	// Run a linedef executor immediately upon popping
+	// You may want to delay your effects by 18 tics to sync with the reward giving
+	if (actor->spawnpoint && actor->lastlook)
+		P_LinedefExecute(actor->lastlook, actor->target, NULL);
 }
 
 // Function: A_GoldMonitorPop
@@ -2724,7 +3641,6 @@ void A_GoldMonitorPop(mobj_t *actor)
 	// Players can now stand on top of us.
 	P_UnsetThingPosition(actor);
 	actor->flags  &= ~(MF_MONITOR|MF_SHOOTABLE);
-	actor->flags2 |= MF2_STANDONME;
 	P_SetThingPosition(actor);
 
 	// Don't count this box in statistics. Sorry.
@@ -2780,6 +3696,11 @@ void A_GoldMonitorPop(mobj_t *actor)
 			newmobj->sprite = SPR_TV1P;
 		}
 	}
+
+	// Run a linedef executor immediately upon popping
+	// You may want to delay your effects by 18 tics to sync with the reward giving
+	if (actor->spawnpoint && actor->lastlook)
+		P_LinedefExecute(actor->lastlook, actor->target, NULL);
 }
 
 // Function: A_GoldMonitorRestore
@@ -2797,7 +3718,6 @@ void A_GoldMonitorRestore(mobj_t *actor)
 #endif
 
 	actor->flags |= MF_MONITOR|MF_SHOOTABLE;
-	actor->flags2 &= ~MF2_STANDONME;
 	actor->health = 1; // Just in case.
 }
 
@@ -2829,16 +3749,17 @@ void A_GoldMonitorSparkle(mobj_t *actor)
 //
 // Description: Explodes an object, doing damage to any objects nearby. The target is used as the cause of the explosion. Damage value is used as explosion range.
 //
-// var1 = unused
+// var1 = damagetype
 // var2 = unused
 //
 void A_Explode(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Explode", actor))
 		return;
 #endif
-	P_RadiusAttack(actor, actor->target, actor->info->damage);
+	P_RadiusAttack(actor, actor->target, actor->info->damage, locvar1);
 }
 
 // Function: A_BossDeath
@@ -2859,7 +3780,10 @@ void A_BossDeath(mobj_t *mo)
 		return;
 #endif
 
-	P_LinedefExecute(LE_BOSSDEAD, mo, NULL);
+	if (mo->spawnpoint && mo->spawnpoint->extrainfo)
+		P_LinedefExecute(LE_BOSSDEAD+(mo->spawnpoint->extrainfo*LE_PARAMWIDTH), mo, NULL);
+	else
+		P_LinedefExecute(LE_BOSSDEAD, mo, NULL);
 	mo->health = 0;
 
 	// Boss is dead (but not necessarily fleeing...)
@@ -2877,9 +3801,9 @@ void A_BossDeath(mobj_t *mo)
 
 	// scan the remaining thinkers to see
 	// if all bosses are dead
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -2897,12 +3821,30 @@ void A_BossDeath(mobj_t *mo)
 	else
 	{
 		// Bring the egg trap up to the surface
-		junk.tag = 680;
+		junk.tag = LE_CAPSULE0;
 		EV_DoElevator(&junk, elevateHighest, false);
-		junk.tag = 681;
+		junk.tag = LE_CAPSULE1;
 		EV_DoElevator(&junk, elevateUp, false);
-		junk.tag = 682;
+		junk.tag = LE_CAPSULE2;
 		EV_DoElevator(&junk, elevateHighest, false);
+
+		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
+			S_MusicExists(mapheaderinfo[gamemap-1]->muspostbossname, !midi_disabled, !digital_disabled))
+		{
+			// Touching the egg trap button calls P_DoPlayerExit, which calls P_RestoreMusic.
+			// So just park ourselves in the mapmus variables.
+			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, 7);
+			strncpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, 7);
+			mapmusname[6] = 0;
+			mapmusflags = (mapheaderinfo[gamemap-1]->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
+			mapmusposition = mapheaderinfo[gamemap-1]->muspostbosspos;
+
+			// don't change if we're in another tune
+			// but in case we're in jingle, use our parked mapmus variables so the correct track restores
+			if (!changed)
+				S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, (1*MUSICRATE)+(MUSICRATE/2),
+					mapheaderinfo[gamemap-1]->muspostbossfadein);
+		}
 	}
 
 bossjustdie:
@@ -2912,59 +3854,110 @@ bossjustdie:
 	else if (P_MobjWasRemoved(mo))
 		return;
 #endif
-	if (mo->type == MT_BLACKEGGMAN || mo->type == MT_CYBRAKDEMON)
+	switch (mo->type)
 	{
-		mo->flags |= MF_NOCLIP;
-		mo->flags &= ~MF_SPECIAL;
+		case MT_BLACKEGGMAN:
+		case MT_CYBRAKDEMON:
+		{
+			mo->flags |= MF_NOCLIP;
+			mo->flags &= ~MF_SPECIAL;
 
-		S_StartSound(NULL, sfx_befall);
-	}
-	else if (mo->type == MT_KOOPA)
-	{
-		junk.tag = 650;
-		EV_DoCeiling(&junk, raiseToHighest);
-		return;
-	}
-	else // eggmobiles
-	{
-		// Stop exploding and prepare to run.
-		P_SetMobjState(mo, mo->info->xdeathstate);
-		if (P_MobjWasRemoved(mo))
+			S_StartSound(NULL, sfx_befall);
+			break;
+		}
+		case MT_KOOPA:
+		{
+			junk.tag = LE_KOOPA;
+			EV_DoCeiling(&junk, raiseToHighest);
 			return;
-
-		P_SetTarget(&mo->target, NULL);
-
-		// Flee! Flee! Find a point to escape to! If none, just shoot upward!
-		// scan the thinkers to find the runaway point
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		}
+		case MT_FANG:
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
+			if (mo->tracer)
+			{
+				var1 = var2 = 0;
+				A_Boss5Jump(mo);
+				mo->momx = ((16 - 1)*mo->momx)/16;
+				mo->momy = ((16 - 1)*mo->momy)/16;
+				if (!(mo->flags2 & MF2_AMBUSH))
+				{
+					const fixed_t time = FixedHypot(mo->tracer->x - mo->x, mo->tracer->y - mo->y)/FixedHypot(mo->momx, mo->momy);
+					const fixed_t speed = 64*FRACUNIT;
+					mobj_t *pole = P_SpawnMobj(
+						mo->tracer->x - P_ReturnThrustX(mo->tracer, mo->tracer->angle, speed*time),
+						mo->tracer->y - P_ReturnThrustY(mo->tracer, mo->tracer->angle, speed*time),
+						mo->tracer->floorz + 4*FRACUNIT,
+						MT_FSGNB);
+					P_SetTarget(&pole->tracer, P_SpawnMobj(
+						pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT),
+						pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT),
+						pole->z + 256*FRACUNIT,
+						MT_FSGNA));
+					pole->angle = mo->tracer->angle;
+					pole->tracer->angle = pole->angle - ANGLE_90;
+					pole->momx = P_ReturnThrustX(pole, pole->angle, speed);
+					pole->momy = P_ReturnThrustY(pole, pole->angle, speed);
+					pole->tracer->momx = pole->momx;
+					pole->tracer->momy = pole->momy;
+				}
+			}
+			else
+			{
+				P_SetObjectMomZ(mo, 10*FRACUNIT, false);
+				mo->flags |= MF_NOGRAVITY;
+			}
+			mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+			return;
+		}
+		default: //eggmobiles
+		{
+			UINT8 extrainfo = (mo->spawnpoint ? mo->spawnpoint->extrainfo : 0);
 
-			mo2 = (mobj_t *)th;
+			// Stop exploding and prepare to run.
+			P_SetMobjState(mo, mo->info->xdeathstate);
+			if (P_MobjWasRemoved(mo))
+				return;
 
-			if (mo2->type == MT_BOSSFLYPOINT)
+			P_SetTarget(&mo->target, NULL);
+
+			// Flee! Flee! Find a point to escape to! If none, just shoot upward!
+			// scan the thinkers to find the runaway point
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				// If this one's closer then the last one, go for it.
-				if (!mo->target ||
-					P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z) <
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+
+				if (mo2->type != MT_BOSSFLYPOINT)
+					continue;
+
+				if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
+					continue;
+
+				// If this one's further then the last one, don't go for it.
+				if (mo->target &&
+					P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z) >
 					P_AproxDistance(P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y), mo->z - mo->target->z))
-						P_SetTarget(&mo->target, mo2);
-				// Otherwise... Don't!
+						continue;
+
+				// Otherwise... Do!
+				P_SetTarget(&mo->target, mo2);
 			}
-		}
 
-		mo->flags |= MF_NOGRAVITY|MF_NOCLIP;
-		mo->flags |= MF_NOCLIPHEIGHT;
+			mo->flags |= MF_NOGRAVITY|MF_NOCLIP;
+			mo->flags |= MF_NOCLIPHEIGHT;
 
-		if (mo->target)
-		{
-			mo->angle = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y);
-			mo->flags2 |= MF2_BOSSFLEE;
-			mo->momz = FixedMul(FixedDiv(mo->target->z - mo->z, P_AproxDistance(mo->x-mo->target->x,mo->y-mo->target->y)), FixedMul(2*FRACUNIT, mo->scale));
+			if (mo->target)
+			{
+				mo->angle = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y);
+				mo->flags2 |= MF2_BOSSFLEE;
+				mo->momz = FixedMul(FixedDiv(mo->target->z - mo->z, P_AproxDistance(mo->x-mo->target->x,mo->y-mo->target->y)), FixedMul(2*FRACUNIT, mo->scale));
+			}
+			else
+				mo->momz = FixedMul(2*FRACUNIT, mo->scale);
+			break;
 		}
-		else
-			mo->momz = FixedMul(2*FRACUNIT, mo->scale);
 	}
 
 	if (mo->type == MT_EGGMOBILE2)
@@ -3144,12 +4137,11 @@ void A_Invincibility(mobj_t *actor)
 
 	if (P_IsLocalPlayer(player) && !player->powers[pw_super])
 	{
-		S_StopMusic();
 		if (mariomode)
 			G_GhostAddColor(GHC_INVINCIBLE);
 		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
 		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
-		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
+		P_PlayJingle(player, (mariomode) ? JT_MINV : JT_INV);
 	}
 }
 
@@ -3183,10 +4175,7 @@ void A_SuperSneakers(mobj_t *actor)
 		if (S_SpeedMusic(0.0f) && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
 			S_SpeedMusic(1.4f);
 		else
-		{
-			S_StopMusic();
-			S_ChangeMusicInternal("_shoes", false);
-		}
+			P_PlayJingle(player, JT_SHOES);
 		strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
 		S_StartCaption(sfx_None, -1, player->powers[pw_sneakers]);
 	}
@@ -3255,14 +4244,7 @@ void A_ExtraLife(mobj_t *actor)
 		return;
 	}
 
-	// In shooter gametypes, give the player 100 rings instead of an extra life.
-	if (gametype != GT_COOP && gametype != GT_COMPETITION)
-	{
-		P_GivePlayerRings(player, 100);
-		P_PlayLivesJingle(player);
-	}
-	else
-		P_GiveCoopLives(player, 1, true);
+	P_GiveCoopLives(player, 1, true);
 }
 
 // Function: A_GiveShield
@@ -3338,53 +4320,6 @@ void A_ScoreRise(mobj_t *actor)
 	P_SetObjectMomZ(actor, actor->info->speed, false);
 }
 
-// Function: A_ParticleSpawn
-//
-// Description: Hyper-specialised function for spawning a particle for MT_PARTICLEGEN.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_ParticleSpawn(mobj_t *actor)
-{
-	INT32 i = 0;
-	mobj_t *spawn;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_ParticleSpawn", actor))
-		return;
-#endif
-	if (!actor->health)
-		return;
-
-	if (!actor->lastlook)
-		return;
-
-	if (!actor->threshold)
-		return;
-
-	for (i = 0; i < actor->lastlook; i++)
-	{
-		spawn = P_SpawnMobj(
-			actor->x + FixedMul(FixedMul(actor->friction, actor->scale), FINECOSINE(actor->angle>>ANGLETOFINESHIFT)),
-			actor->y + FixedMul(FixedMul(actor->friction, actor->scale), FINESINE(actor->angle>>ANGLETOFINESHIFT)),
-			actor->z,
-			(mobjtype_t)actor->threshold);
-		P_SetScale(spawn, actor->scale);
-		spawn->momz = FixedMul(actor->movefactor, spawn->scale);
-		spawn->destscale = spawn->scale/100;
-		spawn->scalespeed = spawn->scale/actor->health;
-		spawn->tics = (tic_t)actor->health;
-		spawn->flags2 |= (actor->flags2 & MF2_OBJECTFLIP);
-		spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
-
-		actor->angle += actor->movedir;
-	}
-
-	actor->angle += (angle_t)actor->movecount;
-	actor->tics = (tic_t)actor->reactiontime;
-}
-
 // Function: A_BunnyHop
 //
 // Description: Makes object hop like a bunny.
@@ -3436,7 +4371,7 @@ void A_BubbleSpawn(mobj_t *actor)
 	if (!(actor->flags2 & MF2_AMBUSH))
 	{
 		// Quick! Look through players!
-		// Don't spawn bubbles unless a player is relatively close by (var2).
+		// Don't spawn bubbles unless a player is relatively close by (var1).
 		for (i = 0; i < MAXPLAYERS; ++i)
 			if (playeringame[i] && players[i].mo
 			 && P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (locvar1<<FRACBITS))
@@ -4168,7 +5103,8 @@ void A_JetbThink(mobj_t *actor)
 	{
 		A_JetChase(actor);
 		// check for melee attack
-		if ((actor->z > (actor->floorz + FixedMul((32<<FRACBITS), actor->scale)))
+		if (actor->info->raisestate
+			&& (actor->z > (actor->floorz + FixedMul((32<<FRACBITS), actor->scale)))
 			&& P_JetbCheckMeleeRange(actor) && !actor->reactiontime
 			&& (actor->target->z >= actor->floorz))
 		{
@@ -4279,56 +5215,111 @@ void A_ShootBullet(mobj_t *actor)
 		S_StartSound(actor, actor->info->attacksound);
 }
 
+static mobj_t *minus;
+
+static boolean PIT_MinusCarry(mobj_t *thing)
+{
+	if (minus->type == thing->type)
+		return true;
+
+	if (!(thing->flags & MF_SHOOTABLE) || !(thing->flags & MF_ENEMY))
+		return true;
+
+	if (P_AproxDistance(minus->x - thing->x, minus->y - thing->y) >= minus->radius * 3)
+		return true;
+
+	if (abs(thing->z - minus->z) > minus->height)
+		return true;
+
+	P_SetTarget(&minus->tracer, thing);
+	minus->tracer->flags &= ~MF_PUSHABLE;
+
+	return true;
+}
+
 // Function: A_MinusDigging
 //
 // Description: Minus digging in the ground.
 //
-// var1 = unused
+// var1 = If 1, play digging sound.
 // var2 = unused
 //
 void A_MinusDigging(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+	INT32 rad = 32;
+	angle_t fa = (actor->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t dis = actor->info->speed*4;
+	fixed_t x = FINECOSINE(fa)*dis + actor->x + FRACUNIT*P_RandomRange(-rad, rad);
+	fixed_t y = FINESINE(fa)*dis + actor->y + FRACUNIT*P_RandomRange(-rad, rad);
+	fixed_t mz = (actor->eflags & MFE_VERTICALFLIP) ? actor->ceilingz : actor->floorz;
+	mobj_t *par;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_MinusDigging", actor))
 		return;
 #endif
-	actor->flags &= ~MF_SPECIAL;
-	actor->flags &= ~MF_SHOOTABLE;
 
 	if (!actor->target)
 	{
-		A_Look(actor);
+		P_SetMobjState(actor, actor->info->spawnstate);
 		return;
 	}
 
-	if (actor->reactiontime)
+	par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT);
+	if (actor->eflags & MFE_VERTICALFLIP)
+		par->eflags |= MFE_VERTICALFLIP;
+	P_TryMove(par, x, y, false);
+
+	// If close enough, prepare to attack
+	if (P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) < actor->radius*2)
 	{
-		actor->reactiontime--;
+		P_SetMobjState(actor, actor->info->meleestate);
+		P_TryMove(actor, actor->target->x, actor->target->y, false);
+		S_StartSound(actor, actor->info->attacksound);
+
+		// Spawn growing dirt pile.
+		par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT);
+		P_SetMobjState(par, actor->info->raisestate);
+		P_SetScale(par, actor->scale*2);
+		if (actor->eflags & MFE_VERTICALFLIP)
+			par->eflags |= MFE_VERTICALFLIP;
 		return;
 	}
 
-	// Dirt trail
-	P_SpawnGhostMobj(actor);
+	// Play digging sound
+	if (locvar1 == 1)
+		A_PlayActiveSound(actor);
 
-	actor->flags |= MF_NOCLIPTHING;
+	// Move
 	var1 = 3;
 	A_Chase(actor);
-	actor->flags &= ~MF_NOCLIPTHING;
 
-	// Play digging sound
-	if (!(leveltime & 15))
-		S_StartSound(actor, actor->info->activesound);
+	// Carry over shit, maybe
+	if (!actor->tracer)
+	{
+		fixed_t radius = 3*actor->radius;
+		fixed_t yh = (unsigned)(actor->y + radius - bmaporgy) >> MAPBLOCKSHIFT;
+		fixed_t yl = (unsigned)(actor->y - radius - bmaporgy) >> MAPBLOCKSHIFT;
+		fixed_t xh = (unsigned)(actor->x + radius - bmaporgx) >> MAPBLOCKSHIFT;
+		fixed_t xl = (unsigned)(actor->x - radius - bmaporgx) >> MAPBLOCKSHIFT;
+		fixed_t bx, by;
 
-	// If we're close enough to our target, pop out of the ground
-	if (P_AproxDistance(actor->target->x-actor->x, actor->target->y-actor->y) < actor->radius
-		&& abs(actor->target->z - actor->z) < 2*actor->height)
-		P_SetMobjState(actor, actor->info->missilestate);
+		BMBOUNDFIX(xl, xh, yl, yh);
 
-	// Snap to ground
-	if (actor->eflags & MFE_VERTICALFLIP)
-		actor->z = actor->ceilingz - actor->height;
+		minus = actor;
+
+		for (bx = xl; bx <= xh; bx++)
+			for (by = yl; by <= yh; by++)
+				P_BlockThingsIterator(bx, by, PIT_MinusCarry);
+	}
 	else
-		actor->z = actor->floorz;
+	{
+		if (P_TryMove(actor->tracer, actor->x, actor->y, false))
+			actor->tracer->z = mz;
+		else
+			P_SetTarget(&actor->tracer, NULL);
+	}
 }
 
 // Function: A_MinusPopup
@@ -4340,45 +5331,56 @@ void A_MinusDigging(mobj_t *actor)
 //
 void A_MinusPopup(mobj_t *actor)
 {
+	INT32 num = 6;
+	angle_t ani = FixedAngle(FRACUNIT*360/num);
+	INT32 i;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_MinusPopup", actor))
 		return;
 #endif
-	P_SetObjectMomZ(actor, 10*FRACUNIT, false);
 
-	actor->flags |= MF_SPECIAL;
-	actor->flags |= MF_SHOOTABLE;
+	if (actor->eflags & MFE_VERTICALFLIP)
+		actor->momz = -10*FRACUNIT;
+	else
+		actor->momz = 10*FRACUNIT;
+
+	actor->flags |= MF_SPECIAL|MF_SHOOTABLE;
+	S_StartSound(actor, sfx_s3k82);
+	for (i = 1; i <= num; i++)
+	{
+		mobj_t *rock = P_SpawnMobj(actor->x, actor->y, actor->z + actor->height/4, MT_ROCKCRUMBLE1);
+		P_Thrust(rock, ani*i, FRACUNIT);
+		rock->momz = 3*FRACUNIT;
+		P_SetScale(rock, FRACUNIT/3);
+	}
+	P_RadiusAttack(actor, actor, 2*actor->radius, 0);
+	if (actor->tracer)
+		P_DamageMobj(actor->tracer, actor, actor, 1, 0);
 
-	// Sound for busting out of the ground.
-	S_StartSound(actor, actor->info->attacksound);
 }
 
 // Function: A_MinusCheck
 //
 // Description: If the minus hits the floor, dig back into the ground.
 //
-// var1 = unused
+// var1 = State to switch to (if 0, use seestate).
 // var2 = unused
 //
 void A_MinusCheck(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_MinusCheck", actor))
 		return;
 #endif
-	if ((!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz)
-	|| ((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz))
+
+	if (((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz) || (!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz))
 	{
-		actor->flags &= ~MF_SPECIAL;
-		actor->flags &= ~MF_SHOOTABLE;
-		actor->reactiontime = TICRATE;
-		P_SetMobjState(actor, actor->info->seestate);
-		return;
+		P_SetMobjState(actor, locvar1 ? (statenum_t)locvar1 : actor->info->seestate);
+		actor->flags = actor->info->flags;
 	}
-
-	// 'Falling' animation
-	if (P_MobjFlip(actor)*actor->momz < 0 && actor->state < &states[actor->info->meleestate])
-		P_SetMobjState(actor, actor->info->meleestate);
 }
 
 // Function: A_ChickenCheck
@@ -4725,9 +5727,6 @@ void A_RotateSpikeBall(mobj_t *actor)
 		return;
 #endif
 
-	if (actor->type == MT_SPECIALSPIKEBALL) // don't remove this, these spikeballs share the same states as the rotating spikeballs
-		return;
-
 	if (!((!locvar1 && (actor->target)) || (locvar1 && (actor->tracer))))// This should NEVER happen.
 	{
 		CONS_Debug(DBG_GAMELOGIC, "A_RotateSpikeBall: Spikeball has no target\n");
@@ -4926,9 +5925,8 @@ void A_RockSpawn(mobj_t *actor)
 //
 void A_SlingAppear(mobj_t *actor)
 {
-	boolean firsttime = true;
 	UINT8 mlength = 4;
-	mobj_t *spawnee;
+	mobj_t *spawnee, *hprev;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SlingAppear", actor))
 		return;
@@ -4939,26 +5937,29 @@ void A_SlingAppear(mobj_t *actor)
 	P_SetThingPosition(actor);
 	actor->lastlook = 128;
 	actor->movecount = actor->lastlook;
-	actor->health = actor->angle>>ANGLETOFINESHIFT;
 	actor->threshold = 0;
 	actor->movefactor = actor->threshold;
 	actor->friction = 128;
 
+	hprev = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLGRABCHAIN);
+	P_SetTarget(&hprev->tracer, actor);
+	P_SetTarget(&hprev->hprev, actor);
+	P_SetTarget(&actor->hnext, hprev);
+	hprev->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+	hprev->movecount = mlength;
+
+	mlength--;
+
 	while (mlength > 0)
 	{
 		spawnee = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLMACECHAIN);
+		P_SetTarget(&spawnee->tracer, actor);
+		P_SetTarget(&spawnee->hprev, hprev);
+		P_SetTarget(&hprev->hnext, spawnee);
+		hprev = spawnee;
 
-		P_SetTarget(&spawnee->target, actor);
-
-		spawnee->threshold = 0;
-		spawnee->reactiontime = mlength;
-
-		if (firsttime)
-		{
-			// This is the outermost link in the chain
-			spawnee->flags2 |= MF2_AMBUSH;
-			firsttime = false;
-		}
+		spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+		spawnee->movecount = mlength;
 
 		mlength--;
 	}
@@ -5006,6 +6007,7 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 	fixed_t thefloor;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
+	boolean hovermode = (actor->health > 1 || actor->fuse);
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_CrawlaCommanderThink", actor))
 		return;
@@ -5017,10 +6019,16 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 	else
 		thefloor = actor->floorz;
 
-	if (actor->fuse & 1)
-		actor->flags2 |= MF2_DONTDRAW;
-	else
-		actor->flags2 &= ~MF2_DONTDRAW;
+	if (!actor->fuse && actor->flags2 & MF2_FRET)
+	{
+		if (actor->info->painsound)
+			S_StartSound(actor, actor->info->painsound);
+
+		actor->fuse = TICRATE/2;
+		actor->momz = 0;
+
+		P_InstaThrust(actor, actor->angle-ANGLE_180, FixedMul(5*FRACUNIT, actor->scale));
+	}
 
 	if (actor->reactiontime > 0)
 		actor->reactiontime--;
@@ -5032,7 +6040,7 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 	}
 
 	// Hover mode
-	if (actor->health > 1 || actor->fuse)
+	if (hovermode)
 	{
 		if (actor->z < thefloor + FixedMul(16*FRACUNIT, actor->scale))
 			actor->momz += FixedMul(FRACUNIT, actor->scale);
@@ -5042,7 +6050,7 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 			actor->momz += FixedMul(16, actor->scale);
 	}
 
-	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
+	if (!actor->target)
 	{
 		// look for a new target
 		if (P_LookForPlayers(actor, true, false, 0))
@@ -5055,14 +6063,13 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 
 	dist = P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y);
 
-	if (actor->target->player && actor->health > 1)
+	if (actor->target->player && (!hovermode || actor->reactiontime <= 2*TICRATE))
 	{
-		if (dist < FixedMul(128*FRACUNIT, actor->scale)
+		if (dist < FixedMul(64<<(FRACBITS+(hovermode ? 1 : 0)), actor->scale)
 			&& ((actor->target->player->pflags & PF_JUMPED) || (actor->target->player->pflags & PF_SPINNING)))
 		{
-			// Auugh! He's trying to kill you! Strafe! STRAAAAFFEEE!!
-			if (actor->target->momx || actor->target->momy)
-				P_InstaThrust(actor, actor->angle - ANGLE_180, FixedMul(20*FRACUNIT, actor->scale));
+			// Auugh! She's trying to kill you! Strafe! STRAAAAFFEEE!!
+			P_InstaThrust(actor, actor->angle - ANGLE_180, FixedMul(20*FRACUNIT, actor->scale));
 			return;
 		}
 	}
@@ -5087,29 +6094,44 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 		actor->angle += (P_RandomByte()<<10);
 		actor->angle -= (P_RandomByte()<<10);
 
-		if (actor->health > 1)
-			P_InstaThrust(actor, actor->angle, FixedMul(10*FRACUNIT, actor->scale));
+		if (hovermode)
+		{
+			fixed_t mom;
+			P_Thrust(actor, actor->angle, 2*actor->scale);
+			mom = P_AproxDistance(actor->momx, actor->momy);
+			if (mom > 20*actor->scale)
+			{
+				mom += 20*actor->scale;
+				mom >>= 1;
+				P_InstaThrust(actor, R_PointToAngle2(0, 0, actor->momx, actor->momy), mom);
+			}
+		}
 	}
 	else if (!actor->reactiontime)
 	{
-		if (actor->health > 1) // Hover Mode
+		if (hovermode && !(actor->flags2 & MF2_FRET)) // Hover Mode
 		{
 			if (dist < FixedMul(512*FRACUNIT, actor->scale))
 			{
 				actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
-				P_InstaThrust(actor, actor->angle, FixedMul(60*FRACUNIT, actor->scale));
+				P_InstaThrust(actor, actor->angle, FixedMul(40*FRACUNIT, actor->scale));
 				actor->threshold = 1;
+				if (actor->info->attacksound)
+					S_StartSound(actor, actor->info->attacksound);
 			}
 		}
-		actor->reactiontime = 2*TICRATE + P_RandomByte()/2;
+		actor->reactiontime = 3*TICRATE + (P_RandomByte()>>2);
 	}
 
 	if (actor->health == 1)
 		P_Thrust(actor, actor->angle, 1);
 
 	// Pogo Mode
-	if (!actor->fuse && actor->health == 1 && actor->z <= actor->floorz)
+	if (!hovermode && actor->z <= actor->floorz)
 	{
+		if (actor->info->activesound)
+			S_StartSound(actor, actor->info->activesound);
+
 		if (dist < FixedMul(256*FRACUNIT, actor->scale))
 		{
 			actor->momz = FixedMul(locvar2, actor->scale);
@@ -5155,9 +6177,9 @@ void A_RingExplode(mobj_t *actor)
 
 	S_StartSound(actor, sfx_prloop);
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -5310,7 +6332,7 @@ void A_MixUp(mobj_t *actor)
 	else if (numplayers == 2) // Special case -- simple swap
 	{
 		fixed_t x, y, z;
-		angle_t angle;
+		angle_t angle, drawangle;
 		INT32 one = -1, two = 0; // default value 0 to make the compiler shut up
 
 		// Zoom tube stuff
@@ -5343,6 +6365,10 @@ void A_MixUp(mobj_t *actor)
 			}
 
 		//get this done first!
+		if (players[one].powers[pw_carry] == CR_MINECART && players[one].mo->tracer && !(P_MobjWasRemoved(players[one].mo->tracer)))
+			P_SetTarget(&players[one].mo->tracer->target, players[two].mo);
+		if (players[two].powers[pw_carry] == CR_MINECART && players[two].mo->tracer && !(P_MobjWasRemoved(players[two].mo->tracer)))
+			P_SetTarget(&players[two].mo->tracer->target, players[one].mo);
 		tempthing = players[one].mo->tracer;
 		P_SetTarget(&players[one].mo->tracer, players[two].mo->tracer);
 		P_SetTarget(&players[two].mo->tracer, tempthing);
@@ -5360,6 +6386,7 @@ void A_MixUp(mobj_t *actor)
 		y = players[one].mo->y;
 		z = players[one].mo->z;
 		angle = players[one].mo->angle;
+		drawangle = players[one].drawangle;
 
 		starpostx = players[one].starpostx;
 		starposty = players[one].starposty;
@@ -5375,10 +6402,14 @@ void A_MixUp(mobj_t *actor)
 				players[two].starpostnum, players[two].starposttime, players[two].starpostangle,
 				players[two].mo->flags2);
 
+		players[one].drawangle = players[two].drawangle;
+
 		P_MixUp(players[two].mo, x, y, z, angle, starpostx, starposty, starpostz,
 				starpostnum, starposttime, starpostangle,
 				mflags2);
 
+		players[two].drawangle = drawangle;
+
 		//carry set after mixup.  Stupid P_ResetPlayer() takes away some of the stuff we look for...
 		//but not all of it!  So we need to make sure they aren't set wrong or anything.
 		players[one].powers[pw_carry] = carry2;
@@ -5390,7 +6421,7 @@ void A_MixUp(mobj_t *actor)
 	else
 	{
 		fixed_t position[MAXPLAYERS][3];
-		angle_t anglepos[MAXPLAYERS];
+		angle_t anglepos[MAXPLAYERS][2];
 		INT32 pindex[MAXPLAYERS], counter = 0, teleportfrom = 0;
 
 		// Zoom tube stuff
@@ -5409,7 +6440,7 @@ void A_MixUp(mobj_t *actor)
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			position[i][0] = position[i][1] = position[i][2] = anglepos[i] = pindex[i] = -1;
+			position[i][0] = position[i][1] = position[i][2] = anglepos[i][0] = anglepos[i][1] = pindex[i] = -1;
 			teleported[i] = false;
 		}
 
@@ -5425,7 +6456,8 @@ void A_MixUp(mobj_t *actor)
 				position[counter][1] = players[i].mo->y;
 				position[counter][2] = players[i].mo->z;
 				pindex[counter] = i;
-				anglepos[counter] = players[i].mo->angle;
+				anglepos[counter][0] = players[i].mo->angle;
+				anglepos[counter][1] = players[i].drawangle;
 				players[i].mo->momx = players[i].mo->momy = players[i].mo->momz =
 					players[i].rmomx = players[i].rmomy = 1;
 				players[i].cmomx = players[i].cmomy = 0;
@@ -5477,13 +6509,17 @@ void A_MixUp(mobj_t *actor)
 				players[i].speed = transspeed[teleportfrom];
 				P_SetTarget(&players[i].mo->tracer, transtracer[teleportfrom]);
 
-				P_MixUp(players[i].mo, position[teleportfrom][0], position[teleportfrom][1], position[teleportfrom][2], anglepos[teleportfrom],
+				P_MixUp(players[i].mo, position[teleportfrom][0], position[teleportfrom][1], position[teleportfrom][2], anglepos[teleportfrom][0],
 					spposition[teleportfrom][0], spposition[teleportfrom][1], spposition[teleportfrom][2],
 					starpostnum[teleportfrom], starposttime[teleportfrom], starpostangle[teleportfrom],
 					flags2[teleportfrom]);
 
+				players[i].drawangle = anglepos[teleportfrom][1];
+
 				//...carry after.  same reasoning.
 				players[i].powers[pw_carry] = transcarry[teleportfrom];
+				if (transcarry[teleportfrom] == CR_MINECART && transtracer[teleportfrom] && !(P_MobjWasRemoved(transtracer[teleportfrom])))
+					P_SetTarget(&transtracer[teleportfrom]->target, players[i].mo);
 
 				teleported[i] = true;
 				counter++;
@@ -5654,6 +6690,9 @@ void A_RecyclePowers(mobj_t *actor)
 		players[recv_pl].ringweapons = weapons[send_pl];
 		players[recv_pl].currentweapon = weaponheld[send_pl];
 
+		if (((players[recv_pl].powers[pw_shield] & SH_NOSTACK) == SH_PINK) && (players[recv_pl].revitem == MT_LHRT || players[recv_pl].spinitem == MT_LHRT || players[recv_pl].thokitem == MT_LHRT)) // Healers can't keep their buff.
+			players[recv_pl].powers[pw_shield] &= SH_STACK;
+
 		P_SpawnShieldOrb(&players[recv_pl]);
 		if (P_IsLocalPlayer(&players[recv_pl]))
 			P_RestoreMusic(&players[recv_pl]);
@@ -5728,7 +6767,10 @@ void A_Boss1Chase(mobj_t *actor)
 		}
 		else
 		{
-			P_LinedefExecute(LE_PINCHPHASE, actor, NULL);
+			if (actor->spawnpoint && actor->spawnpoint->extrainfo)
+				P_LinedefExecute(LE_PINCHPHASE+(actor->spawnpoint->extrainfo*LE_PARAMWIDTH), actor, NULL);
+			else
+				P_LinedefExecute(LE_PINCHPHASE, actor, NULL);
 			P_SetMobjState(actor, actor->info->raisestate);
 		}
 
@@ -6266,7 +7308,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	if (actor->info->missilestate) // spawn the pogo stick collision box
 	{
 		mobj_t *pogo = P_SpawnMobj(actor->x, actor->y, actor->z - mobjinfo[actor->info->missilestate].height, (mobjtype_t)actor->info->missilestate);
-		pogo->target = actor;
+		P_SetTarget(&pogo->target, actor);
 	}
 
 	actor->reactiontime = 1;
@@ -6548,47 +7590,77 @@ void A_GuardChase(mobj_t *actor)
 	if (actor->reactiontime)
 		actor->reactiontime--;
 
-	if ((!actor->tracer || !actor->tracer->health) && actor->threshold != 42)
+	if (actor->threshold != 42) // In formation...
 	{
-		P_SetTarget(&actor->tracer, NULL);
-		actor->threshold = 42;
-		P_SetMobjState(actor, actor->info->painstate);
-		actor->flags |= MF_SPECIAL|MF_SHOOTABLE;
-		return;
-	}
+		fixed_t speed;
 
-	// turn towards movement direction if not there yet
-	if (actor->movedir < NUMDIRS)
-	{
-		actor->angle &= (7<<29);
-		delta = actor->angle - (actor->movedir << 29);
+		if (!actor->tracer || !actor->tracer->health)
+		{
+			P_SetTarget(&actor->tracer, NULL);
+			actor->threshold = 42;
+			P_SetMobjState(actor, actor->info->painstate);
+			actor->flags |= MF_SPECIAL|MF_SHOOTABLE;
+			return;
+		}
 
-		if (delta > 0)
-			actor->angle -= ANGLE_45;
-		else if (delta < 0)
-			actor->angle += ANGLE_45;
-	}
+		speed = actor->extravalue1*actor->scale;
 
-	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
-	{
-		// look for a new target
-		if (P_LookForPlayers(actor, true, false, 0))
-			return; // got a new target
+		if (actor->flags2 & MF2_AMBUSH)
+			speed <<= 1;
+
+		if (speed
+		&& !P_TryMove(actor,
+			actor->x + P_ReturnThrustX(actor, actor->angle, speed),
+			actor->y + P_ReturnThrustY(actor, actor->angle, speed),
+			false)
+		&& speed > 0) // can't be the same check as previous so that P_TryMove gets to happen.
+		{
+			if (actor->spawnpoint && ((actor->spawnpoint->options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_OBJECTSPECIAL))
+				actor->angle += ANGLE_90;
+			else if (actor->spawnpoint && ((actor->spawnpoint->options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_EXTRA))
+				actor->angle -= ANGLE_90;
+			else
+				actor->angle += ANGLE_180;
+		}
 
-		P_SetMobjStateNF(actor, actor->info->spawnstate);
-		return;
+		if (actor->extravalue1 < actor->info->speed)
+			actor->extravalue1++;
 	}
+	else // Break ranks!
+	{
+		// turn towards movement direction if not there yet
+		if (actor->movedir < NUMDIRS)
+		{
+			actor->angle &= (7<<29);
+			delta = actor->angle - (actor->movedir << 29);
 
-	// possibly choose another target
-	if (multiplayer && (actor->target->health <= 0 || !P_CheckSight(actor, actor->target))
-		&& P_LookForPlayers(actor, true, false, 0))
-		return; // got a new target
+			if (delta > 0)
+				actor->angle -= ANGLE_45;
+			else if (delta < 0)
+				actor->angle += ANGLE_45;
+		}
 
-	// chase towards player
-	if (--actor->movecount < 0 || !P_Move(actor, (actor->flags2 & MF2_AMBUSH) ? actor->info->speed * 2 : actor->info->speed))
-	{
-		P_NewChaseDir(actor);
-		actor->movecount += 5; // Increase tics before change in direction allowed.
+		if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
+		{
+			// look for a new target
+			if (P_LookForPlayers(actor, true, false, 0))
+				return; // got a new target
+
+			P_SetMobjStateNF(actor, actor->info->spawnstate);
+			return;
+		}
+
+		// possibly choose another target
+		if (multiplayer && (actor->target->health <= 0 || !P_CheckSight(actor, actor->target))
+			&& P_LookForPlayers(actor, true, false, 0))
+			return; // got a new target
+
+		// chase towards player
+		if (--actor->movecount < 0 || !P_Move(actor, (actor->flags2 & MF2_AMBUSH) ? actor->info->speed * 2 : actor->info->speed))
+		{
+			P_NewChaseDir(actor);
+			actor->movecount += 5; // Increase tics before change in direction allowed.
+		}
 	}
 
 	// Now that we've moved, its time for our shield to move!
@@ -6752,9 +7824,11 @@ void A_Boss3TakeDamage(mobj_t *actor)
 		return;
 #endif
 	actor->movecount = var1;
+	actor->movefactor = -512*FRACUNIT;
+
+	/*if (actor->target && actor->target->spawnpoint)
+		actor->threshold = actor->target->spawnpoint->extrainfo;*/
 
-	if (actor->target && actor->target->spawnpoint)
-		actor->threshold = actor->target->spawnpoint->extrainfo;
 }
 
 // Function: A_Boss3Path
@@ -6791,24 +7865,34 @@ void A_Boss3Path(mobj_t *actor)
 	}
 	else if (actor->threshold >= 0) // Traveling mode
 	{
-		thinker_t *th;
-		mobj_t *mo2;
-		fixed_t dist, dist2;
+		fixed_t dist = 0;
 		fixed_t speed;
 
-		P_SetTarget(&actor->target, NULL);
-
-		// scan the thinkers
-		// to find a point that matches
-		// the number
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		if (!(actor->flags2 & MF2_STRONGBOX))
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
+			thinker_t *th;
+			mobj_t *mo2;
 
-			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && mo2->spawnpoint->angle == actor->threshold)
+			P_SetTarget(&actor->target, NULL);
+
+			// scan the thinkers
+			// to find a point that matches
+			// the number
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+				if (mo2->type != MT_BOSS3WAYPOINT)
+					continue;
+				if (!mo2->spawnpoint)
+					continue;
+				if (mo2->spawnpoint->angle != actor->threshold)
+					continue;
+				if (mo2->spawnpoint->extrainfo != actor->cusval)
+					continue;
+
 				P_SetTarget(&actor->target, mo2);
 				break;
 			}
@@ -6816,67 +7900,62 @@ void A_Boss3Path(mobj_t *actor)
 
 		if (!actor->target) // Should NEVER happen
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d\n", actor->threshold);
+			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d, %d\n", actor->threshold, actor->cusval);
 			return;
 		}
 
-		dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z - actor->z);
-
-		if (dist < 1)
-			dist = 1;
-
 		if (actor->tracer && ((actor->tracer->movedir)
 		|| (actor->tracer->health <= actor->tracer->info->damage)))
 			speed = actor->info->speed * 2;
 		else
 			speed = actor->info->speed;
 
-		actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed);
-		actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed);
-		actor->momz = FixedMul(FixedDiv(actor->target->z - actor->z, dist), speed);
+		if (actor->target->x == actor->x && actor->target->y == actor->y)
+		{
+			dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z + actor->movefactor - actor->z);
 
-		if (actor->momx != 0 || actor->momy != 0)
-			actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+			if (dist < 1)
+				dist = 1;
 
-		dist2 = P_AproxDistance(P_AproxDistance(actor->target->x - (actor->x + actor->momx), actor->target->y - (actor->y + actor->momy)), actor->target->z - (actor->z + actor->momz));
+			actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed);
+			actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed);
+			actor->momz = FixedMul(FixedDiv(actor->target->z + actor->movefactor - actor->z, dist), speed);
 
-		if (dist2 < 1)
-			dist2 = 1;
+			if (actor->momx != 0 || actor->momy != 0)
+				actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+		}
 
-		if ((dist >> FRACBITS) <= (dist2 >> FRACBITS))
+		if (dist <= speed)
 		{
 			// If further away, set XYZ of mobj to waypoint location
 			P_UnsetThingPosition(actor);
 			actor->x = actor->target->x;
 			actor->y = actor->target->y;
-			actor->z = actor->target->z;
+			actor->z = actor->target->z + actor->movefactor;
 			actor->momx = actor->momy = actor->momz = 0;
 			P_SetThingPosition(actor);
 
-			if (actor->threshold == 0)
+			if (!actor->movefactor) // firing mode
 			{
-				P_RemoveMobj(actor); // Cycle completed. Dummy removed.
-				return;
+				actor->movecount |= 2;
+				actor->movefactor = -512*FRACUNIT;
+				actor->flags2 &= ~MF2_STRONGBOX;
 			}
-
-			// Set to next waypoint in sequence
-			if (actor->target->spawnpoint)
+			else if (!(actor->flags2 & MF2_STRONGBOX)) // just spawned or going down
 			{
-				// From the center point, choose one of the five paths
-				if (actor->target->spawnpoint->angle == 0)
-				{
-					P_RemoveMobj(actor); // Cycle completed. Dummy removed.
-					return;
-				}
-				else
-					actor->threshold = actor->target->spawnpoint->extrainfo;
-
-				// If the deaf flag is set, go into firing mode
-				if (actor->target->spawnpoint->options & MTF_AMBUSH)
-					actor->movecount |= 2;
+				actor->flags2 |= MF2_STRONGBOX;
+				actor->movefactor = -512*FRACUNIT;
+			}
+			else if (!(actor->flags2 & MF2_AMBUSH)) // just shifted tube
+			{
+				actor->flags2 |= MF2_AMBUSH;
+				actor->movefactor = 0;
+			}
+			else // just hit the bottom of your tube
+			{
+				P_RemoveMobj(actor); // Cycle completed. Dummy removed.
+				return;
 			}
-			else // This should never happen, as well
-				CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy waypoint has no spawnpoint associated with it.\n");
 		}
 	}
 }
@@ -6903,6 +7982,8 @@ void A_LinedefExecute(mobj_t *actor)
 
 	if (locvar2)
 		tagnum += locvar2*(AngleFixed(actor->angle)>>FRACBITS);
+	else if (actor->spawnpoint && actor->spawnpoint->extrainfo)
+		tagnum += (actor->spawnpoint->extrainfo*LE_PARAMWIDTH);
 
 	CONS_Debug(DBG_GAMELOGIC, "A_LinedefExecute: Running mobjtype %d's sector with tag %d\n", actor->type, tagnum);
 
@@ -7074,6 +8155,7 @@ void A_SpawnObjectRelative(mobj_t *actor)
 
 	if (actor->eflags & MFE_VERTICALFLIP)
 		mo->flags2 |= MF2_OBJECTFLIP;
+
 }
 
 // Function: A_ChangeAngleRelative
@@ -7191,9 +8273,9 @@ void A_FindTarget(mobj_t *actor)
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTarget called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -7256,9 +8338,9 @@ void A_FindTracer(mobj_t *actor)
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTracer called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -7671,8 +8753,8 @@ void A_BossJetFume(mobj_t *actor)
 	{
 		fixed_t jetx, jety, jetz;
 
-		jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale));
-		jety = actor->y + P_ReturnThrustY(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale));
+		jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -60*actor->scale);
+		jety = actor->y + P_ReturnThrustY(actor, actor->angle, -60*actor->scale);
 		if (actor->eflags & MFE_VERTICALFLIP)
 			jetz = actor->z + actor->height - FixedMul(17*FRACUNIT + mobjinfo[MT_PROPELLER].height, actor->scale);
 		else
@@ -7705,7 +8787,7 @@ void A_BossJetFume(mobj_t *actor)
 		if (actor->eflags & MFE_VERTICALFLIP)
 			jetz = actor->z + actor->height + FixedMul(50*FRACUNIT - mobjinfo[MT_JETFLAME].height, actor->scale);
 		else
-			jetz = actor->z - FixedMul(50*FRACUNIT, actor->scale);
+			jetz = actor->z - 50*actor->scale;
 		filler = P_SpawnMobj(actor->x, actor->y, jetz, MT_JETFLAME);
 		P_SetTarget(&filler->target, actor);
 		// Boss 4 already uses its tracer for other things
@@ -7714,6 +8796,30 @@ void A_BossJetFume(mobj_t *actor)
 		if (actor->eflags & MFE_VERTICALFLIP)
 			filler->flags2 |= MF2_OBJECTFLIP;
 	}
+	else if (locvar1 == 4) // Boss 4 Spectator Eggrobo jet flame
+	{
+		fixed_t jetx, jety, jetz, movefactor = 12;
+
+		jetz = actor->z;
+		if (actor->eflags & MFE_VERTICALFLIP)
+			jetz += (actor->height - FixedMul(mobjinfo[MT_EGGROBO1JET].height, actor->scale));
+
+		while (true)
+		{
+			jetx = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustX(actor, actor->angle, 19*actor->scale);
+			jety = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustY(actor, actor->angle, 19*actor->scale);
+			filler = P_SpawnMobj(jetx, jety, jetz, MT_EGGROBO1JET);
+			filler->movefactor = movefactor;
+			P_SetTarget(&filler->target, actor);
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			if (movefactor <= 0)
+				break;
+			movefactor = -movefactor;
+		}
+	}
 }
 
 // Function: A_RandomState
@@ -7819,9 +8925,9 @@ void A_RemoteAction(mobj_t *actor)
 		fixed_t dist1 = 0, dist2 = 0;
 
 		// scan the thinkers
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -7918,21 +9024,49 @@ void A_ToggleFlameJet(mobj_t* actor)
 // Description: Used by Chaos Emeralds to orbit around Nights (aka Super Sonic.)
 //
 // var1 = Angle adjustment (aka orbit speed)
-// var2 = Lower four bits: height offset, Upper 4 bits = set if object is Nightopian Helper
-//
+// var2:
+//        Bits 1-10: height offset, max 1023
+//        Bits 11-16: X radius factor (max 63, default 20)
+//        Bit 17: set if object is Nightopian Helper
+//        Bit 18: set to define X/Y/Z rotation factor
+//        Bits 19-20: Unused
+//        Bits 21-26: Y radius factor (max 63, default 32)
+//        Bits 27-32: Z radius factor (max 63, default 32)
+//
+// If MF_GRENADEBOUNCE is flagged on mobj, use actor->threshold to define X/Y/Z radius factor, max 1023 each:
+//        Bits 1-10: X factor
+//        Bits 11-20: Y factor
+//        Bits 21-30: Z factor
 void A_OrbitNights(mobj_t* actor)
 {
-	INT32 ofs = (var2 & 0xFFFF);
-	boolean ishelper = (var2 & 0xFFFF0000);
+	INT32 ofs = (var2 & 0x3FF);
+	boolean ishelper = (var2 & 0x10000);
+	boolean donotrescale = (var2 & 0x40000);
+	INT32 xfactor = 32, yfactor = 32, zfactor = 20;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_OrbitNights", actor))
 		return;
 #endif
 
-	if (!actor->target || !actor->target->player ||
-	    !(actor->target->player->powers[pw_carry] == CR_NIGHTSMODE) || !actor->target->player->nightstime
+	if (actor->flags & MF_GRENADEBOUNCE)
+	{
+		xfactor = (actor->threshold & 0x3FF);
+		yfactor = (actor->threshold & 0xFFC00) >> 10;
+		zfactor = (actor->threshold & 0x3FF00000) >> 20;
+	}
+	else if (var2 & 0x20000)
+	{
+		xfactor = (var2 & 0xFC00) >> 10;
+		yfactor = (var2 & 0x3F00000) >> 20;
+		zfactor = (var2 & 0xFC000000) >> 26;
+	}
+
+	if (!actor->target
+	|| (actor->target->player &&
+		// if NiGHTS special stage and not NiGHTSmode.
+	    (((maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && !(actor->target->player->powers[pw_carry] == CR_NIGHTSMODE))
 	    // Also remove this object if they no longer have a NiGHTS helper
-		|| (ishelper && !actor->target->player->powers[pw_nights_helper]))
+		|| (ishelper && !actor->target->player->powers[pw_nights_helper]))))
 	{
 		P_RemoveMobj(actor);
 		return;
@@ -7945,9 +9079,9 @@ void A_OrbitNights(mobj_t* actor)
 			const angle_t fa  = (angle_t)actor->extravalue1 >> ANGLETOFINESHIFT;
 			const angle_t ofa = ((angle_t)actor->extravalue1 + (ofs*ANG1)) >> ANGLETOFINESHIFT;
 
-			const fixed_t fc = FixedMul(FINECOSINE(fa),FixedMul(32*FRACUNIT, actor->scale));
-			const fixed_t fh = FixedMul(FINECOSINE(ofa),FixedMul(20*FRACUNIT, actor->scale));
-			const fixed_t fs = FixedMul(FINESINE(fa),FixedMul(32*FRACUNIT, actor->scale));
+			const fixed_t fc = FixedMul(FINECOSINE(fa),FixedMul(xfactor*FRACUNIT, actor->scale));
+			const fixed_t fh = FixedMul(FINECOSINE(ofa),FixedMul(zfactor*FRACUNIT, actor->scale));
+			const fixed_t fs = FixedMul(FINESINE(fa),FixedMul(yfactor*FRACUNIT, actor->scale));
 
 			actor->x = actor->target->x + fc;
 			actor->y = actor->target->y + fs;
@@ -7958,7 +9092,7 @@ void A_OrbitNights(mobj_t* actor)
 		}
 		P_SetThingPosition(actor);
 
-		if (ishelper) // Flash a helper that's about to be removed.
+		if (ishelper && actor->target->player) // Flash a helper that's about to be removed.
 		{
 			if ((actor->target->player->powers[pw_nights_helper] < TICRATE)
 			&& (actor->target->player->powers[pw_nights_helper] & 1))
@@ -7966,6 +9100,9 @@ void A_OrbitNights(mobj_t* actor)
 			else
 				actor->flags2 &= ~MF2_DONTDRAW;
 		}
+
+		if (!donotrescale && actor->destscale != actor->target->destscale)
+			actor->destscale = actor->target->destscale;
 	}
 }
 
@@ -8054,9 +9191,9 @@ void A_SetObjectTypeState(mobj_t *actor)
 		return;
 #endif
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8692,9 +9829,9 @@ void A_CheckThingCount(mobj_t *actor)
 		return;
 #endif
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -9566,7 +10703,7 @@ void A_VileAttack(mobj_t *actor)
 						actor->target->x - P_ReturnThrustX(fire, actor->angle, FixedMul(24*FRACUNIT, fire->scale)),
 						actor->target->y - P_ReturnThrustY(fire, actor->angle, FixedMul(24*FRACUNIT, fire->scale)),
 						fire->z);
-		P_RadiusAttack(fire, actor, 70*FRACUNIT);
+		P_RadiusAttack(fire, actor, 70*FRACUNIT, 0);
 	}
 	else
 	{
@@ -9611,7 +10748,7 @@ void A_VileAttack(mobj_t *actor)
 							actor->target->x - P_ReturnThrustX(fire, actor->angle, FixedMul(24*FRACUNIT, fire->scale)),
 							actor->target->y - P_ReturnThrustY(fire, actor->angle, FixedMul(24*FRACUNIT, fire->scale)),
 							fire->z);
-			P_RadiusAttack(fire, actor, 70*FRACUNIT);
+			P_RadiusAttack(fire, actor, 70*FRACUNIT, 0);
 		}
 	}
 
@@ -10075,10 +11212,10 @@ void A_SpawnFreshCopy(mobj_t *actor)
 		return;
 #endif
 
-	newObject = P_SpawnMobj(actor->x, actor->y, actor->z, actor->type);
+	newObject = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->type);
+	newObject->flags2 = actor->flags2 & MF2_AMBUSH;
 	newObject->angle = actor->angle;
-	P_SetScale(newObject, actor->scale);
-	newObject->destscale = actor->destscale;
+	newObject->color = actor->color;
 	P_SetTarget(&newObject->target, actor->target);
 	P_SetTarget(&newObject->tracer, actor->tracer);
 
@@ -10148,6 +11285,145 @@ void A_FlickySpawn(mobj_t *actor)
 	P_InternalFlickySpawn(actor, locvar1, ((locvar2) ? locvar2 : 8*FRACUNIT), true);
 }
 
+// Internal Flicky color setting
+void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo)
+{
+	UINT8 flickycolors[] = {
+		SKINCOLOR_RED,
+		SKINCOLOR_CYAN,
+		SKINCOLOR_BLUE,
+		SKINCOLOR_VAPOR,
+		SKINCOLOR_PURPLE,
+		SKINCOLOR_BUBBLEGUM,
+		SKINCOLOR_NEON,
+		SKINCOLOR_BLACK,
+		SKINCOLOR_BEIGE,
+		SKINCOLOR_LAVENDER,
+		SKINCOLOR_RUBY,
+		SKINCOLOR_SALMON,
+		SKINCOLOR_SUNSET,
+		SKINCOLOR_ORANGE,
+		SKINCOLOR_YELLOW,
+	};
+
+	if (extrainfo == 0)
+		// until we can customize flicky colors by level header, just stick to SRB2's defaults
+		actor->color = flickycolors[P_RandomKey(2)]; //flickycolors[P_RandomKey(sizeof(flickycolors))];
+	else
+		actor->color = flickycolors[min(extrainfo-1, 14)]; // sizeof(flickycolors)-1
+}
+
+// Function: A_FlickyCenter
+//
+// Description: Place flickies in-level.
+//
+// var1:
+//        Lower 16 bits = if 0, spawns random flicky based on level header. Else, spawns the designated thing type.
+//        Bits 17-20 = Flicky color, up to 15. Applies to fish.
+//        Bit 21 = Flag MF_SLIDEME (see below)
+//        Bit 22 = Flag MF_GRENADEBOUNCE (see below)
+//        Bit 23 = Flag MF_NOCLIPTHING (see below)
+//
+//        If actor is placed from a spawnpoint (map Thing), the Thing's properties take precedence.
+//
+// var2 = maximum default distance away from spawn the flickies are allowed to travel. If angle != 0, then that's the radius.
+//
+// If MTF_EXTRA (MF_SLIDEME): is flagged, Flickies move aimlessly. Else, orbit around the target.
+// If MTF_OBJECTSPECIAL (MF_GRENADEBOUNCE): Flickies stand in-place without gravity (unless they hop, then gravity is applied.)
+// If MTF_AMBUSH (MF_NOCLIPTHING): is flagged, Flickies hop.
+//
+void A_FlickyCenter(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	UINT16 flickytype = (locvar1 & 0xFFFF);
+	UINT8 flickycolor = ((locvar1 >> 16) & 0xFF);
+	UINT8 flickyflags = ((locvar1 >> 20) & 0xF);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FlickyCenter", actor))
+		return;
+#endif
+
+	if (!actor->tracer)
+	{
+		mobj_t *flicky = P_InternalFlickySpawn(actor, locvar1, 1, false);
+		P_SetTarget(&flicky->target, actor);
+		P_SetTarget(&actor->tracer, flicky);
+
+		if (actor->spawnpoint)
+		{
+			actor->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE|MF_NOCLIPTHING);
+			actor->flags |= (
+				((actor->spawnpoint->options & MTF_EXTRA) ? MF_SLIDEME : 0)
+				| ((actor->spawnpoint->options & MTF_OBJECTSPECIAL) ? MF_GRENADEBOUNCE : 0)
+				| ((actor->spawnpoint->options & MTF_AMBUSH) ? MF_NOCLIPTHING : 0)
+			);
+			actor->extravalue1 = actor->spawnpoint->angle ? abs(actor->spawnpoint->angle) * FRACUNIT
+				: locvar2 ? abs(locvar2) : 384 * FRACUNIT;
+			actor->extravalue2 = actor->spawnpoint->extrainfo;
+			actor->friction = actor->spawnpoint->x*FRACUNIT;
+			actor->movefactor = actor->spawnpoint->y*FRACUNIT;
+			actor->watertop = actor->spawnpoint->z*FRACUNIT;
+		}
+		else
+		{
+			actor->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE|MF_NOCLIPTHING);
+			actor->flags |= (
+				((flickyflags & 1) ? MF_SLIDEME : 0)
+				| ((flickyflags & 2) ? MF_GRENADEBOUNCE : 0)
+				| ((flickyflags & 4) ? MF_NOCLIPTHING : 0)
+			);
+			actor->extravalue1 = abs(locvar2);
+			actor->extravalue2 = flickycolor;
+			actor->friction = actor->x;
+			actor->movefactor = actor->y;
+			actor->watertop = actor->z;
+			locvar1 = flickytype;
+		}
+
+		if (actor->flags & MF_GRENADEBOUNCE) // in-place
+			actor->tracer->fuse = 0;
+		else if (actor->flags & MF_SLIDEME) // aimless
+		{
+			actor->tracer->fuse = 0; // less than 2*TICRATE means move aimlessly.
+			actor->tracer->angle = P_RandomKey(180)*ANG2;
+		}
+		else //orbit
+			actor->tracer->fuse = FRACUNIT;
+
+		if (locvar1 == MT_FLICKY_08)
+			P_InternalFlickySetColor(actor->tracer, actor->extravalue2);
+
+		actor->extravalue2 = 0;
+	}
+
+	if (!(actor->flags & MF_SLIDEME) && !(actor->flags & MF_GRENADEBOUNCE))
+	{
+		fixed_t originx = actor->friction;
+		fixed_t originy = actor->movefactor;
+		fixed_t originz = actor->watertop;
+
+		actor->tracer->fuse = FRACUNIT;
+
+		// Impose default home radius if flicky orbits around player
+		if (!actor->extravalue1)
+			actor->extravalue1 = locvar2 ? abs(locvar2) : 384 * FRACUNIT;
+
+		P_LookForPlayers(actor, true, false, actor->extravalue1);
+
+		if (actor->target && P_AproxDistance(actor->target->x - originx, actor->target->y - originy) < actor->extravalue1)
+		{
+			actor->extravalue2 = 1;
+		 	P_TeleportMove(actor, actor->target->x, actor->target->y, actor->target->z);
+		}
+		else if(actor->extravalue2)
+		{
+			actor->extravalue2 = 0;
+			P_TeleportMove(actor, originx, originy, originz);
+		}
+	}
+}
+
 // Internal Flicky bubbling function.
 void P_InternalFlickyBubble(mobj_t *actor)
 {
@@ -10190,7 +11466,10 @@ void A_FlickyAim(mobj_t *actor)
 		return;
 #endif
 
-	if (actor->momx == actor->momy && actor->momy == 0)
+	if ((actor->momx == actor->momy && actor->momy == 0)
+		|| (actor->target && P_IsFlickyCenter(actor->target->type)
+			&& actor->target->extravalue1 && (actor->target->flags & MF_SLIDEME)
+			&& P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) >= actor->target->extravalue1))
 		flickyhitwall = true;
 
 	P_InternalFlickyBubble(actor);
@@ -10222,7 +11501,10 @@ void A_FlickyAim(mobj_t *actor)
 	}
 	else if (flickyhitwall)
 	{
-		actor->angle += ANGLE_180;
+		if (actor->target && P_IsFlickyCenter(actor->target->type))
+			actor->angle = R_PointToAngle2(actor->target->x, actor->target->y, actor->x, actor->y) + P_RandomRange(112, 248) * ANG1;
+		else
+			actor->angle += P_RandomRange(112, 248)*ANG1;
 		actor->threshold = 0;
 	}
 }
@@ -10258,7 +11540,13 @@ void P_InternalFlickyFly(mobj_t *actor, fixed_t flyspeed, fixed_t targetdist, fi
 	if (actor->target && abs(chasez - actor->z) > targetdist)
 		targetdist = P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y);
 
-	vertangle = (R_PointToAngle2(0, actor->z, targetdist, chasez) >> ANGLETOFINESHIFT) & FINEMASK;
+	if (actor->target
+		&& P_IsFlickyCenter(actor->target->type)
+		&& (actor->target->flags & MF_SLIDEME))
+		vertangle = 0;
+	else
+		vertangle = (R_PointToAngle2(0, actor->z, targetdist, chasez) >> ANGLETOFINESHIFT) & FINEMASK;
+
 	P_InstaThrust(actor, actor->angle, FixedMul(FINECOSINE(vertangle), flyspeed));
 	actor->momz = FixedMul(FINESINE(vertangle), flyspeed);
 }
@@ -10272,14 +11560,13 @@ void P_InternalFlickyFly(mobj_t *actor, fixed_t flyspeed, fixed_t targetdist, fi
 //
 void A_FlickyFly(mobj_t *actor)
 {
-	// We're not setting up locvars here - it passes var1 and var2 through to P_InternalFlickyFly instead.
-	//INT32 locvar1 = var1;
-	//INT32 locvar2 = var2;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlickyFly", actor))
 		return;
 #endif
-	P_InternalFlickyFly(actor, var1, var2,
+	P_InternalFlickyFly(actor, locvar1, locvar2,
 	FINECOSINE((((actor->fuse % 36) * ANG10) >> ANGLETOFINESHIFT) & FINEMASK)
 	);
 }
@@ -10293,14 +11580,13 @@ void A_FlickyFly(mobj_t *actor)
 //
 void A_FlickySoar(mobj_t *actor)
 {
-	// We're not setting up locvars here - it passes var1 and var2 through to P_InternalFlickyFly instead.
-	//INT32 locvar1 = var1;
-	//INT32 locvar2 = var2;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlickySoar", actor))
 		return;
 #endif
-	P_InternalFlickyFly(actor, var1, var2,
+	P_InternalFlickyFly(actor, locvar1, locvar2,
 	2*(FRACUNIT/2 - abs(FINECOSINE((((actor->fuse % 144) * 5*ANG1/2) >> ANGLETOFINESHIFT) & FINEMASK)))
 	);
 
@@ -10365,14 +11651,13 @@ void P_InternalFlickyHop(mobj_t *actor, fixed_t momz, fixed_t momh, angle_t angl
 //
 void A_FlickyHop(mobj_t *actor)
 {
-	// We're not setting up locvars here - it passes var1 and var2 through to P_InternalFlickyHop instead.
-	//INT32 locvar1 = var1;
-	//INT32 locvar2 = var2;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlickyHop", actor))
 		return;
 #endif
-	P_InternalFlickyHop(actor, var1, var2, actor->angle);
+	P_InternalFlickyHop(actor, locvar1, locvar2, actor->angle);
 }
 
 // Function: A_FlickyFlounder
@@ -10413,7 +11698,19 @@ void A_FlickyCheck(mobj_t *actor)
 	if (LUA_CallAction("A_FlickyCheck", actor))
 		return;
 #endif
-	if (locvar2 && P_MobjFlip(actor)*actor->momz < 1)
+	if (actor->target
+		&& P_IsFlickyCenter(actor->target->type)
+		&& (actor->target->flags & MF_GRENADEBOUNCE))
+	{
+		if (!(actor->target->flags & MF_NOCLIPTHING)) // no hopping
+		{
+			actor->momz = 0;
+			actor->flags |= MF_NOGRAVITY;
+		}
+		actor->flags |= MF_NOCLIP | MF_NOBLOCKMAP | MF_SCENERY;
+		P_SetMobjState(actor, mobjinfo[actor->type].seestate);
+	}
+	else if (locvar2 && P_MobjFlip(actor)*actor->momz < 1)
 		P_SetMobjState(actor, locvar2);
 	else if (locvar1 && ((!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz)
 	|| ((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz)))
@@ -10438,7 +11735,19 @@ void A_FlickyHeightCheck(mobj_t *actor)
 	if (LUA_CallAction("A_FlickyHeightCheck", actor))
 		return;
 #endif
-	if (locvar1 && actor->target && P_MobjFlip(actor)*actor->momz < 1
+	if (actor->target
+		&& P_IsFlickyCenter(actor->target->type)
+		&& (actor->target->flags & MF_GRENADEBOUNCE))
+	{
+		if (!(actor->target->flags & MF_NOCLIPTHING)) // no hopping
+		{
+			actor->momz = 0;
+			actor->flags |= MF_NOGRAVITY;
+		}
+		actor->flags |= MF_NOCLIP | MF_NOBLOCKMAP | MF_SCENERY;
+		P_SetMobjState(actor, mobjinfo[actor->type].seestate);
+	}
+	else if (locvar1 && actor->target && P_MobjFlip(actor)*actor->momz < 1
 	&& ((P_MobjFlip(actor)*((actor->z + actor->height/2) - (actor->target->z + actor->target->height/2)) < locvar2)
 	|| (actor->z - actor->height < actor->floorz) || (actor->z + 2*actor->height > actor->ceilingz)))
 		P_SetMobjState(actor, locvar1);
@@ -10455,13 +11764,14 @@ void A_FlickyHeightCheck(mobj_t *actor)
 //
 void A_FlickyFlutter(mobj_t *actor)
 {
-	// We're not setting up locvars here - it passes var1 and var2 through to A_FlickyCheck instead.
-	//INT32 locvar1 = var1;
-	//INT32 locvar2 = var2;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlickyFlutter", actor))
 		return;
 #endif
+	var1 = locvar1;
+	var2 = locvar2;
 	A_FlickyCheck(actor);
 
 	var1 = ANG30;
@@ -10479,43 +11789,40 @@ void A_FlickyFlutter(mobj_t *actor)
 //
 // Description: Creates the mobj's painchance at a random position around the object's radius.
 //
-// var1 = momz of particle.
-// var2 = chance of particle spawn
+// var1 = unused
+// var2 = unused
 //
 void A_FlameParticle(mobj_t *actor)
 {
 	mobjtype_t type = (mobjtype_t)(mobjinfo[actor->type].painchance);
 	fixed_t rad, hei;
 	mobj_t *particle;
-	INT32 locvar1 = var1;
-	INT32 locvar2 = var2;
+	//INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlameParticle", actor))
 		return;
 #endif
 
-	if (!P_RandomChance(locvar2))
-		return;
-
 	if (!type)
 		return;
 
-	rad = 2*actor->radius>>FRACBITS;
+	rad = actor->radius>>FRACBITS;
 	hei = actor->height>>FRACBITS;
 	particle = P_SpawnMobjFromMobj(actor,
 		P_RandomRange(rad, -rad)<<FRACBITS,
 		P_RandomRange(rad, -rad)<<FRACBITS,
 		P_RandomRange(hei/2, hei)<<FRACBITS,
 		type);
-	P_SetObjectMomZ(particle, locvar1<<FRACBITS, false);
+	P_SetObjectMomZ(particle, 2<<FRACBITS, false);
 }
 
 // Function: A_FadeOverlay
 //
 // Description: Makes a pretty overlay (primarily for super/NiGHTS transformation).
 //
-// var1 = bit 1 = don't halt momentum, bit 2 = don't make fast, bit 3 = don't set tracer
+// var1 = bit 1 = bit 1 = don't make fast, bit 2 = don't set tracer
 // var2 = unused
 //
 void A_FadeOverlay(mobj_t *actor)
@@ -10529,13 +11836,10 @@ void A_FadeOverlay(mobj_t *actor)
 		return;
 #endif
 
-	if (!(locvar1 & 1))
-		actor->momx = actor->momy = actor->momz = 0;
-
 	fade = P_SpawnGhostMobj(actor);
 	fade->frame = actor->frame;
 
-	if (!(locvar1 & 2))
+	if (!(locvar1 & 1))
 	{
 		fade->fuse = 15;
 		fade->flags2 |= MF2_BOSSNOTRAP;
@@ -10543,7 +11847,7 @@ void A_FadeOverlay(mobj_t *actor)
 	else
 		fade->fuse = 20;
 
-	if (!(locvar1 & 4))
+	if (!(locvar1 & 2))
 		P_SetTarget(&actor->tracer, fade);
 }
 
@@ -10630,9 +11934,1797 @@ void A_Boss5Jump(mobj_t *actor)
 		actor->z++;
 
 	// Horizontal axes first. First parameter is initial horizontal impulse, second is to correct its angle.
-	actor->momx = FixedMul(FixedMul(v, FINECOSINE(theta >> ANGLETOFINESHIFT)), FINECOSINE(actor->angle >> ANGLETOFINESHIFT));
-	actor->momy = FixedMul(FixedMul(v, FINECOSINE(theta >> ANGLETOFINESHIFT)), FINESINE(actor->angle >> ANGLETOFINESHIFT));
+	fixedHypotenuse = FixedMul(v, FINECOSINE(theta >> ANGLETOFINESHIFT)); // variable reuse
+	actor->momx = FixedMul(fixedHypotenuse, FINECOSINE(actor->angle >> ANGLETOFINESHIFT));
+	actor->momy = FixedMul(fixedHypotenuse, FINESINE(actor->angle >> ANGLETOFINESHIFT));
 	// Then the vertical axis. No angle-correction needed here.
 	actor->momz = FixedMul(v, FINESINE(theta >> ANGLETOFINESHIFT));
 	// I hope that's all that's needed, ugh
 }
+
+// Function: A_LightBeamReset
+// Description: Resets momentum and position for DSZ's projecting light beams
+//
+// var1 = unused
+// var2 = unused
+//
+void A_LightBeamReset(mobj_t *actor)
+{
+	// INT32 locvar1 = var1;
+	// INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_LightBeamReset", actor))
+		return;
+#endif
+
+	actor->destscale = FRACUNIT + P_SignedRandom()*FRACUNIT/256;
+	P_SetScale(actor, actor->destscale);
+
+	if (!actor->spawnpoint)
+		return; // this can't work properly welp
+
+	actor->momx = -(P_SignedRandom()*FINESINE(((actor->spawnpoint->angle*ANG1)>>ANGLETOFINESHIFT) & FINEMASK))/128;
+	actor->momy = (P_SignedRandom()*FINECOSINE(((actor->spawnpoint->angle*ANG1)>>ANGLETOFINESHIFT) & FINEMASK))/128;
+	actor->momz = (P_SignedRandom()*FRACUNIT)/128;
+
+	P_TeleportMove(actor,
+		actor->spawnpoint->x*FRACUNIT - (P_SignedRandom()*FINESINE(((actor->spawnpoint->angle*ANG1)>>ANGLETOFINESHIFT) & FINEMASK))/2,
+		actor->spawnpoint->y*FRACUNIT + (P_SignedRandom()*FINECOSINE(((actor->spawnpoint->angle*ANG1)>>ANGLETOFINESHIFT) & FINEMASK))/2,
+		actor->spawnpoint->z*FRACUNIT + (P_SignedRandom()*FRACUNIT)/2);
+}
+
+// Function: A_MineExplode
+// Description: Handles the explosion of a DSZ mine.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_MineExplode(mobj_t *actor)
+{
+	// INT32 locvar1 = var1;
+	// INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_MineExplode", actor))
+		return;
+#endif
+
+	A_Scream(actor);
+	actor->flags = MF_NOGRAVITY|MF_NOCLIP;
+
+	quake.epicenter = NULL;
+	quake.radius = 512*FRACUNIT;
+	quake.intensity = 8*FRACUNIT;
+	quake.time = TICRATE/3;
+
+	P_RadiusAttack(actor, actor->tracer, 192*FRACUNIT, DMG_CANHURTSELF);
+	P_MobjCheckWater(actor);
+
+	{
+#define dist 64
+		UINT8 i;
+		mobjtype_t type = ((actor->eflags & MFE_UNDERWATER) ? MT_UWEXPLODE : MT_BOSSEXPLODE);
+		S_StartSound(actor, ((actor->eflags & MFE_UNDERWATER) ? sfx_s3k57 : sfx_s3k4e));
+		P_SpawnMobj(actor->x, actor->y, actor->z, type);
+		for (i = 0; i < 16; i++)
+		{
+			mobj_t *b = P_SpawnMobj(actor->x+P_RandomRange(-dist, dist)*FRACUNIT,
+				actor->y+P_RandomRange(-dist, dist)*FRACUNIT,
+				actor->z+P_RandomRange(((actor->eflags & MFE_UNDERWATER) ? -dist : 0), dist)*FRACUNIT,
+				type);
+			fixed_t dx = b->x - actor->x, dy = b->y - actor->y, dz = b->z - actor->z;
+			fixed_t dm = P_AproxDistance(dz, P_AproxDistance(dy, dx));
+			b->momx = FixedDiv(dx, dm)*3;
+			b->momy = FixedDiv(dy, dm)*3;
+			b->momz = FixedDiv(dz, dm)*3;
+			if ((actor->watertop == INT32_MAX) || (b->z + b->height > actor->watertop))
+				b->flags &= ~MF_NOGRAVITY;
+		}
+#undef dist
+
+		if (actor->watertop != INT32_MAX)
+			P_SpawnMobj(actor->x, actor->y, actor->watertop, MT_SPLISH);
+	}
+}
+
+// Function: A_MineRange
+// Description: If the target gets too close, change the state to meleestate.
+//
+// var1 = Distance to alert at
+// var2 = unused
+//
+void A_MineRange(mobj_t *actor)
+{
+	fixed_t dm;
+	INT32 locvar1 = var1;
+	// INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_MineRange", actor))
+		return;
+#endif
+
+	if (!actor->target)
+		return;
+
+	dm = P_AproxDistance(actor->z - actor->target->z, P_AproxDistance(actor->y - actor->target->y, actor->x - actor->target->x));
+	if ((dm>>FRACBITS) < locvar1)
+		P_SetMobjState(actor, actor->info->meleestate);
+}
+
+// Function: A_ConnectToGround
+// Description: Create a palm tree trunk/mine chain.
+//
+// var1 = Object type to connect to ground
+// var2 = Object type to place on ground
+//
+void A_ConnectToGround(mobj_t *actor)
+{
+	mobj_t *work;
+	fixed_t workz;
+	fixed_t workh;
+	SINT8 dir;
+	angle_t ang;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_ConnectToGround", actor))
+		return;
+#endif
+
+	if (actor->subsector->sector->ffloors)
+		P_AdjustMobjFloorZ_FFloors(actor, actor->subsector->sector, 2);
+
+	if (actor->flags2 & MF2_OBJECTFLIP)
+	{
+		workz = actor->ceilingz - (actor->z + actor->height);
+		dir = -1;
+	}
+	else
+	{
+		workz = actor->floorz - actor->z;
+		dir = 1;
+	}
+
+	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 += dir*workh;
+	}
+
+	if (!locvar1)
+		return;
+
+	if (!(workh = FixedMul(mobjinfo[locvar1].height, actor->scale)))
+		return;
+
+	if (actor->flags2 & MF2_OBJECTFLIP)
+		workz -= workh;
+
+	ang = actor->angle + ANGLE_45;
+	while (dir*workz < 0)
+	{
+		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar1);
+		if (work)
+			work->angle = ang;
+		ang += ANGLE_90;
+		workz += dir*workh;
+	}
+
+	if (workz != 0)
+		actor->z += workz;
+}
+
+// Function: A_SpawnParticleRelative
+//
+// Description: Spawns a particle effect relative to the location of the actor
+//
+// var1:
+//		var1 >> 16 = x
+//		var1 & 65535 = y
+// var2:
+//		var2 >> 16 = z
+//		var2 & 65535 = state
+//
+void A_SpawnParticleRelative(mobj_t *actor)
+{
+	INT16 x, y, z; // Want to be sure we can use negative values
+	statenum_t state;
+	mobj_t *mo;
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SpawnParticleRelative", actor))
+		return;
+#endif
+
+	CONS_Debug(DBG_GAMELOGIC, "A_SpawnParticleRelative called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
+
+	x = (INT16)(locvar1>>16);
+	y = (INT16)(locvar1&65535);
+	z = (INT16)(locvar2>>16);
+	state = (statenum_t)(locvar2&65535);
+
+	// Spawn objects correctly in reverse gravity.
+	// NOTE: Doing actor->z + actor->height is the bottom of the object while the object has reverse gravity. - Flame
+	mo = P_SpawnMobj(actor->x + FixedMul(x<<FRACBITS, actor->scale),
+		actor->y + FixedMul(y<<FRACBITS, actor->scale),
+		(actor->eflags & MFE_VERTICALFLIP) ? ((actor->z + actor->height - mobjinfo[MT_PARTICLE].height) - FixedMul(z<<FRACBITS, actor->scale)) : (actor->z + FixedMul(z<<FRACBITS, actor->scale)), MT_PARTICLE);
+
+	// Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn
+	mo->angle = actor->angle;
+
+	if (actor->eflags & MFE_VERTICALFLIP)
+		mo->flags2 |= MF2_OBJECTFLIP;
+
+	P_SetMobjState(mo, state);
+}
+
+// Function: A_MultiShotDist
+//
+// Description: Spawns multiple shots based on player proximity
+//
+// var1 = same as A_MultiShot
+// var2 = same as A_MultiShot
+//
+void A_MultiShotDist(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_MultiShotDist", actor))
+		return;
+#endif
+
+	{
+		UINT8 i;
+		// Quick! Look through players!
+		// Don't spawn dust unless a player is relatively close by (var1).
+		for (i = 0; i < MAXPLAYERS; ++i)
+			if (playeringame[i] && players[i].mo
+			 && P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (1600<<FRACBITS))
+				break; // Stop looking.
+		if (i == MAXPLAYERS)
+			return; // don't make bubble!
+	}
+
+	var1 = locvar1;
+	var2 = locvar2;
+	A_MultiShot(actor);
+}
+
+// Function: A_WhoCaresIfYourSonIsABee
+//
+// Description: Makes a child object, storing the number of created children in the parent's extravalue1.
+//
+// var1 = mobjtype of child
+//		var2 >> 16 = mobjtype of child
+//		var2 & 65535 = vertical momentum
+// var2:
+//		var2 >> 16 = forward offset
+//		var2 & 65535 = vertical offset
+//
+void A_WhoCaresIfYourSonIsABee(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t foffsetx;
+	fixed_t foffsety;
+	mobj_t *son;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_WhoCaresIfYourSonIsABee", actor))
+		return;
+#endif
+
+	A_FaceTarget(actor);
+
+	if (actor->extravalue1)
+		actor->extravalue1--;
+
+	if (actor->info->attacksound)
+		S_StartSound(actor, actor->info->attacksound);
+
+	foffsetx = P_ReturnThrustX(actor, actor->angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
+	foffsety = P_ReturnThrustY(actor, actor->angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
+
+	if (!(son = P_SpawnMobjFromMobj(actor, foffsetx, foffsety, (locvar2&65535)*FRACUNIT, (mobjtype_t)(locvar1 >> 16))))
+		return;
+
+	P_SetObjectMomZ(son, (locvar1 & 65535)<<FRACBITS, true);
+
+	P_SetTarget(&son->tracer, actor);
+	P_SetTarget(&son->target, actor->target);
+}
+
+// Function: A_ParentTriesToSleep
+//
+// Description: If extravalue1 is less than or equal to var1, go to var2.
+//
+// var1 = state to go to when extravalue1
+// var2 = unused
+//
+void A_ParentTriesToSleep(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_ParentTriesToSleep", actor))
+		return;
+#endif
+
+	if (actor->extravalue1)
+	{
+		if (actor->info->seesound)
+			S_StartSound(actor, actor->info->seesound);
+		actor->reactiontime = 0;
+		P_SetMobjState(actor, locvar1);
+	}
+	else if (!actor->reactiontime)
+	{
+		actor->reactiontime = 1;
+		if (actor->info->activesound) // more like INactivesound doy hoy hoy
+			S_StartSound(actor, actor->info->activesound);
+	}
+}
+
+
+// Function: A_CryingToMomma
+//
+// Description: If you're a child, let your parent know something's happened to you through extravalue1. Also, prepare to die.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_CryingToMomma(mobj_t *actor)
+{
+	//INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CryingToMomma", actor))
+		return;
+#endif
+
+	if (actor->tracer)
+		actor->tracer->extravalue1++;
+
+	actor->momx = actor->momy = actor->momz = 0;
+
+	P_UnsetThingPosition(actor);
+	if (sector_list)
+	{
+		P_DelSeclist(sector_list);
+		sector_list = NULL;
+	}
+	actor->flags = MF_NOBLOCKMAP|MF_NOCLIPTHING;
+	P_SetThingPosition(actor);
+}
+
+// Function: A_CheckFlags2
+//
+// Description: If actor->flags2 & var1, goto var2.
+//
+// var1 = mask
+// var2 = state to go
+//
+void A_CheckFlags2(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CheckFlags2", actor))
+		return;
+#endif
+
+	if (actor->flags2 & locvar1)
+		P_SetMobjState(actor, (statenum_t)locvar2);
+}
+
+// Function: A_Boss5FindWaypoint
+//
+// Description: Finds the next waypoint in sequence and sets it as its tracer.
+//
+// var1 = if 1, always go to ambush-marked waypoint. if 2, go to MT_BOSSFLYPOINT.
+// var2 = unused
+//
+void A_Boss5FindWaypoint(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+	boolean avoidcenter;
+	UINT32 i;
+	UINT8 extrainfo = (actor->spawnpoint ? actor->spawnpoint->extrainfo : 0);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5FindWaypoint", actor))
+		return;
+#endif
+
+	avoidcenter = !actor->tracer || (actor->health == actor->info->damage+1);
+
+	if (locvar1 == 2) // look for the boss waypoint
+	{
+		thinker_t *th;
+		mobj_t *mo2;
+		P_SetTarget(&actor->tracer, NULL);
+		// Flee! Flee! Find a point to escape to! If none, just shoot upward!
+		// scan the thinkers to find the runaway point
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+
+			mo2 = (mobj_t *)th;
+
+			if (mo2->type != MT_BOSSFLYPOINT)
+				continue;
+
+			if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
+				continue;
+
+			// If this one's further then the last one, don't go for it.
+			if (actor->tracer &&
+				P_AproxDistance(P_AproxDistance(actor->x - mo2->x, actor->y - mo2->y), actor->z - mo2->z) >
+				P_AproxDistance(P_AproxDistance(actor->x - actor->tracer->x, actor->y - actor->tracer->y), actor->z - actor->tracer->z))
+					continue;
+
+			// Otherwise... Do!
+			P_SetTarget(&actor->tracer, mo2);
+		}
+		if (!actor->tracer)
+			return; // no boss flypoints found
+	}
+	else if (locvar1 == 1) // always go to ambush-marked waypoint
+	{
+		if (avoidcenter)
+			goto nowaypoints; // if we can't go the center, why on earth are we doing this?
+
+		for (i = 0; i < nummapthings; i++)
+		{
+			if (!mapthings[i].mobj)
+				continue;
+			if (mapthings[i].mobj->type != MT_FANGWAYPOINT)
+				continue;
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
+			if (!(mapthings[i].options & MTF_AMBUSH))
+				continue;
+
+			P_SetTarget(&actor->tracer, mapthings[i].mobj);
+			break;
+		}
+
+		if (i == nummapthings)
+			goto nowaypoints;
+	}
+	else // locvar1 == 0
+	{
+		fixed_t hackoffset = P_MobjFlip(actor)*56*FRACUNIT;
+		INT32 numwaypoints = 0;
+		mobj_t **waypoints;
+		INT32 key;
+
+		actor->z += hackoffset;
+
+		// first, count how many waypoints we have
+		for (i = 0; i < nummapthings; i++)
+		{
+			if (!mapthings[i].mobj)
+				continue;
+			if (mapthings[i].mobj->type != MT_FANGWAYPOINT)
+				continue;
+			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
+				continue;
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
+			if (mapthings[i].options & MTF_AMBUSH)
+			{
+				if (avoidcenter)
+					continue;
+			}
+			else if (mapthings[i].mobj->reactiontime > 0)
+				continue;
+			if (!P_CheckSight(actor, mapthings[i].mobj))
+				continue;
+			numwaypoints++;
+		}
+
+		// players also count as waypoints apparently
+		if (actor->extravalue2 > 1)
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				if (!players[i].mo)
+					continue;
+				if (players[i].spectator)
+					continue;
+				if (players[i].mo->health <= 0)
+					continue;
+				if (players[i].powers[pw_flashing])
+					continue;
+				if (actor->tracer == players[i].mo) // this was your tracer last time
+					continue;
+				if (!P_CheckSight(actor, players[i].mo))
+					continue;
+				numwaypoints++;
+			}
+		}
+
+		if (!numwaypoints)
+		{
+			// restore z position
+			actor->z -= hackoffset;
+			goto nowaypoints; // no waypoints :(
+		}
+
+		// allocate the table and reset count to zero
+		waypoints = Z_Calloc(sizeof(*waypoints)*numwaypoints, PU_STATIC, NULL);
+		numwaypoints = 0;
+
+		// now find them again and add them to the table!
+		for (i = 0; i < nummapthings; i++)
+		{
+			if (!mapthings[i].mobj)
+				continue;
+			if (mapthings[i].mobj->type != MT_FANGWAYPOINT)
+				continue;
+			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
+				continue;
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
+			if (mapthings[i].options & MTF_AMBUSH)
+			{
+				if (avoidcenter)
+					continue;
+			}
+			else if (mapthings[i].mobj->reactiontime > 0)
+			{
+				mapthings[i].mobj->reactiontime--;
+				continue;
+			}
+			if (!P_CheckSight(actor, mapthings[i].mobj))
+				continue;
+			waypoints[numwaypoints++] = mapthings[i].mobj;
+		}
+
+		if (actor->extravalue2 > 1)
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				if (!players[i].mo)
+					continue;
+				if (players[i].spectator)
+					continue;
+				if (players[i].mo->health <= 0)
+					continue;
+				if (players[i].powers[pw_flashing])
+					continue;
+				if (actor->tracer == players[i].mo) // this was your tracer last time
+					continue;
+				if (!P_CheckSight(actor, players[i].mo))
+					continue;
+				waypoints[numwaypoints++] = players[i].mo;
+			}
+		}
+
+		// restore z position
+		actor->z -= hackoffset;
+
+		if (!numwaypoints)
+		{
+			Z_Free(waypoints); // free table
+			goto nowaypoints; // ???
+		}
+
+		key = P_RandomKey(numwaypoints);
+
+		P_SetTarget(&actor->tracer, waypoints[key]);
+		if (actor->tracer->type == MT_FANGWAYPOINT)
+			actor->tracer->reactiontime = numwaypoints/4; // Monster Iestyn: is this how it should be? I count center waypoints as waypoints unlike the original Lua script
+		Z_Free(waypoints); // free table
+	}
+
+	// now face the tracer you just set!
+	A_FaceTracer(actor);
+	return;
+
+nowaypoints:
+	// no waypoints at all, guess the mobj has to disappear
+	if (actor->health)
+		P_KillMobj(actor, NULL, NULL, 0);
+	else
+		P_RemoveMobj(actor);
+	return;
+}
+
+// Function: A_DoNPCSkid
+//
+// Description: Something that looks like a player is skidding.
+//
+// var1 = state to change to upon being slow enough
+// var2 = minimum speed
+//
+void A_DoNPCSkid(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t x, y, z;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_DoNPCSkid", actor))
+		return;
+#endif
+
+	x = actor->x;
+	y = actor->y;
+	z = actor->z;
+
+	if (!locvar2)
+		locvar2 = FRACUNIT/2;
+
+	if ((FixedHypot(actor->momx, actor->momy) < locvar2)
+	|| !P_TryMove(actor, actor->x + actor->momx, actor->y + actor->momy, false))
+	{
+		actor->momx = actor->momy = 0;
+		P_SetMobjState(actor, locvar1);
+		return;
+	}
+	else
+	{
+		actor->momx = (2*actor->momx)/3;
+		actor->momy = (2*actor->momy)/3;
+	}
+
+	P_TeleportMove(actor, x, y, z);
+
+	// Spawn a particle every 3 tics.
+	if (!(leveltime % 3))
+	{
+		mobj_t *particle = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_SPINDUST);
+		particle->tics = 10;
+
+		P_SetScale(particle, 2*actor->scale/3);
+		particle->destscale = actor->scale;
+		P_SetObjectMomZ(particle, FRACUNIT, false);
+	}
+}
+
+// Function: A_DoNPCPain
+//
+// Description: Something that looks like a player was hit, put them in pain.
+//
+// var1 = If zero, always fling the same amount.
+//        Otherwise, slowly reduce the vertical
+//        and horizontal speed to the base value
+//        multiplied by this the more damage is done.
+// var2 = If zero, use default fling values.
+//        Otherwise, vertical and horizontal speed
+//        will be multiplied by this.
+//
+void A_DoNPCPain(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t vspeed = 0;
+	fixed_t hspeed = FixedMul(4*FRACUNIT, actor->scale);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_DoNPCPain", actor))
+		return;
+#endif
+
+	actor->flags &= ~(MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT);
+
+	var1 = var2 = 0;
+	A_Pain(actor);
+
+	actor->z += P_MobjFlip(actor);
+
+	if (actor->eflags & MFE_UNDERWATER)
+		vspeed = FixedDiv(10511*FRACUNIT,2600*FRACUNIT);
+	else
+		vspeed = FixedDiv(69*FRACUNIT,10*FRACUNIT);
+
+	if (actor->target)
+		actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x + actor->target->momx, actor->target->y + actor->target->momy);
+
+	if (locvar1)
+	{
+		if (!actor->info->spawnhealth)
+			return; // there's something very wrong here if you're using this action on something with no starting health
+		locvar1 += ((FRACUNIT - locvar1)/actor->info->spawnhealth)*actor->health;
+		hspeed = FixedMul(hspeed, locvar1);
+		vspeed = FixedMul(vspeed, locvar1);
+	}
+
+	if (locvar2)
+	{
+		hspeed = FixedMul(hspeed, locvar2);
+		vspeed = FixedMul(vspeed, locvar2);
+	}
+
+	P_SetObjectMomZ(actor, vspeed, false);
+	P_InstaThrust(actor, actor->angle, -hspeed);
+}
+
+// Function: A_PrepareRepeat
+//
+// Description: Simple way to prepare A_Repeat.
+//
+// var1 = value to set extravalue2 to
+// var2 = unused
+//
+void A_PrepareRepeat(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_PrepareRepeat", actor))
+		return;
+#endif
+
+	actor->extravalue2 = locvar1;
+}
+
+// Function: A_Boss5ExtraRepeat
+//
+// Description: Simple way to prepare A_Repeat.
+//
+// var1 = maximum value to setextravalue2 to (normally)
+// var2 = pinch annoyance
+//
+void A_Boss5ExtraRepeat(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT32 calc;
+	INT32 locspawn;
+	INT32 lochealth;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5ExtraRepeat", actor))
+		return;
+#endif
+
+	if (actor->extravalue2 > 0 && !(actor->flags2 & MF2_FRET))
+		return;
+
+	locspawn = actor->info->spawnhealth - actor->info->damage;
+	lochealth = actor->health - actor->info->damage;
+
+	if (locspawn <= 0 || lochealth <= 0)
+		calc = locvar1;
+	else
+		calc = (locvar1*(locspawn - lochealth))/locspawn;
+
+	if (calc > 2)
+		actor->extravalue2 = 1 + calc/2 + P_RandomKey(calc/2);
+	else
+		actor->extravalue2 = 1 + calc;
+
+	if (lochealth <= 0)
+		actor->extravalue2 += locvar2;
+}
+
+// Function: A_Boss5Calm
+//
+// Description: Simple way to disable MF2_FRET (and enable MF_SHOOTABLE the first time it's called)
+//
+// var1 = unused
+// var2 = unused
+//
+void A_Boss5Calm(mobj_t *actor)
+{
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5Calm", actor))
+		return;
+#endif
+	actor->flags |= MF_SHOOTABLE;
+	actor->flags2 &= ~MF2_FRET;
+}
+
+// Function: A_Boss5CheckOnGround
+//
+// Description: Ground checker.
+//
+// var1 = state to change to upon hitting ground.
+// var2 = state to change to upon hitting ground if health == pinchhealth, assuming it exists
+//
+void A_Boss5CheckOnGround(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5CheckOnGround", actor))
+		return;
+#endif
+
+	if ((!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz)
+	|| (actor->eflags & MFE_VERTICALFLIP && actor->z + actor->height >= actor->ceilingz))
+	{
+		if (locvar2 && (!actor->health || (actor->health == actor->info->damage && !(actor->flags2 & MF2_STRONGBOX))))
+			P_SetMobjState(actor, locvar2);
+		else
+			P_SetMobjState(actor, locvar1);
+	}
+
+	if (actor->tracer && P_AproxDistance(actor->tracer->x - actor->x, actor->tracer->y - actor->y) < 2*actor->radius)
+	{
+		actor->momx = (4*actor->momx)/5;
+		actor->momy = (4*actor->momy)/5;
+	}
+}
+
+// Function: A_Boss5CheckFalling
+//
+// Description: Falling checker.
+//
+// var1 = state to change to when hitting ground.
+// var2 = state to change to when falling.
+//
+void A_Boss5CheckFalling(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5CheckFalling", actor))
+		return;
+#endif
+
+	if (actor->health && actor->extravalue2 > 1)
+	{
+		var1 = locvar1;
+		var2 = 0;
+		A_Boss5CheckOnGround(actor);
+		return;
+	}
+
+	if (P_MobjFlip(actor)*actor->momz <= 0)
+		P_SetMobjState(actor, locvar2);
+}
+
+// Function: A_Boss5PinchShot
+//
+// Description: Fires a missile directly upwards if in pinch.
+//
+// var1 = object # to shoot
+// var2 = height offset (from default of +48 FU)
+//
+void A_Boss5PinchShot(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t zoffset;
+	mobj_t *missile;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5PinchShot", actor))
+		return;
+#endif
+
+	if (actor->health > actor->info->damage)
+		return;
+
+	if (actor->eflags & MFE_VERTICALFLIP)
+		zoffset = actor->z + actor->height - FixedMul((48 + locvar2)*FRACUNIT, actor->scale);
+	else
+		zoffset = actor->z + FixedMul((48 + locvar2)*FRACUNIT, actor->scale);
+
+	missile = P_SpawnPointMissile(actor, actor->x, actor->y, zoffset, locvar1,
+										actor->x, actor->y, zoffset);
+
+	if (!missile)
+		return;
+
+	missile->momx = missile->momy = 0;
+	missile->momz = P_MobjFlip(actor)*missile->info->speed/2;
+}
+
+// Function: A_Boss5MakeItRain
+//
+// Description: Pinch crisis.
+//
+// var1 = object # to shoot
+// var2 = height offset (from default of +48 FU)
+//
+void A_Boss5MakeItRain(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT32 offset = (48 + locvar2)<<16; // upper 16 bits, not fixed_t!
+	INT32 i;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5MakeItRain", actor))
+		return;
+#endif
+
+	actor->flags2 |= MF2_STRONGBOX;
+
+	var1 = locvar1;
+	var2 = offset + 90;
+	A_TrapShot(actor);
+
+	for (i = 0; i < 8; i++)
+	{
+		actor->angle += ANGLE_45;
+
+		var1 = locvar1;
+		var2 = offset + (i & 1) ? 80 : 85;
+		A_TrapShot(actor);
+	}
+
+	actor->extravalue2 = 0;
+}
+
+// Function: A_LookForBetter
+//
+// Description: A_Look, except it finds a better target in multiplayer, and doesn't lose the target in singleplayer.
+//
+// var1 lower 16 bits = 0 - looks only in front, 1 - looks all around
+// var1 upper 16 bits = distance limit
+// var2 = unused
+//
+void A_LookForBetter(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_LookForBetter", actor))
+		return;
+#endif
+
+	P_LookForPlayers(actor, (locvar1 & 65535), false, FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale));
+	A_FaceTarget(actor);
+}
+
+/* * Spawns a dust ring.
+  *	The dust ring behaves slightly randomly so it doesn't look too uniform.
+  *
+  * \param mobjtype Thing type to make a ring of.
+  * \param div Amount of things to spawn on the ring.
+  * \param x Center X coordinates.
+  * \param y Center Y coordinates.
+  * \param z Center Z coordinates.
+  * \param radius Radius.
+  * \param speed Additional thrust on particles.
+  * \param initscale Initial scale when spawning.
+  * \param scale "Default" scale.
+  */
+static void P_DustRing(mobjtype_t mobjtype, UINT32 div, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t speed, fixed_t initscale, fixed_t scale)
+{
+	angle_t ang = FixedAngle(FixedDiv(360*FRACUNIT, div*FRACUNIT));  //(ANGLE_180/div)*2;
+	UINT32 i;
+
+	// it turned out the radius was effectively nullified thanks to errors in the original script
+	// BUT people preferred how it looked before I "fixed" it, so I got rid of the radius calculations altogether
+	// this was a bit of a mess to sort out, but at least it's probably somewhat fine now?
+	// -- Monster Iestyn (21/05/19)
+	(void)radius;
+
+	for (i = 0; i < div; i++)
+	{
+		mobj_t *dust = P_SpawnMobj(
+			x, //+ FixedMul(radius, FINECOSINE((ang*i) >> ANGLETOFINESHIFT)),
+			y, //+ FixedMul(radius, FINESINE((ang*i) >> ANGLETOFINESHIFT)),
+			z,
+			mobjtype
+			);
+
+		dust->angle = ang*i + ANGLE_90;
+		P_SetScale(dust, FixedMul(initscale, scale));
+		dust->destscale = FixedMul(4*FRACUNIT + P_RandomFixed(), scale);
+		dust->scalespeed = scale/24;
+		P_Thrust(dust, ang*i, speed + FixedMul(P_RandomFixed(), scale));
+		dust->momz = P_SignedRandom()*scale/64;
+	}
+}
+
+// Function: A_Boss5BombExplode
+//
+// Description: Boss 5's bomb exploding.
+//
+// var1 = Thing type to spawn as dust
+// var2 = unused
+//
+void A_Boss5BombExplode(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_Boss5BombExplode", actor))
+		return;
+#endif
+
+	// The original Lua script did not use |= to add flags but just set these flags exactly apparently?
+	// (I may modify this later)
+	// -- Monster Iestyn (21/05/19)
+	actor->flags = MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP;
+	actor->flags2 = MF2_EXPLOSION;
+
+	if (actor->target)
+		P_RadiusAttack(actor, actor->target, 7*actor->radius, 0);
+
+	P_DustRing(locvar1, 4, actor->x, actor->y, actor->z+actor->height, 2*actor->radius, 0, FRACUNIT, actor->scale);
+	P_DustRing(locvar1, 6, actor->x, actor->y, actor->z+actor->height/2, 3*actor->radius, FRACUNIT, FRACUNIT, actor->scale);
+	//P_StartQuake(9*actor->scale, TICRATE/6, {actor->x, actor->y, actor->z}, 20*actor->radius);
+	// the above does not exist, so we set the quake values directly instead
+	quake.intensity = 9*actor->scale;
+	quake.time = TICRATE/6;
+	// the following quake values have no effect atm? ah well, may as well set them anyway
+	{
+		mappoint_t q_epicenter = {actor->x, actor->y, actor->z};
+		quake.epicenter = &q_epicenter;
+	}
+	quake.radius = 20*actor->radius;
+}
+
+static mobj_t *dustdevil;
+
+static boolean PIT_DustDevilLaunch(mobj_t *thing)
+{
+	player_t *player = thing->player;
+
+	if (!player)
+		return true;
+
+	if (abs(thing->x - dustdevil->x) > dustdevil->radius || abs(thing->y - dustdevil->y) > dustdevil->radius)
+		return true;
+
+	if (thing->z + thing->height >= dustdevil->z && dustdevil->z + dustdevil->height >= thing->z) {
+		fixed_t pos = thing->z - dustdevil->z;
+		fixed_t thrust = max(FixedDiv(pos, dustdevil->height) * 20, 8 * FRACUNIT);
+		angle_t fa = R_PointToAngle2(thing->x, thing->y, dustdevil->x, dustdevil->y) >> ANGLETOFINESHIFT;
+		fixed_t c = FINECOSINE(fa);
+		fixed_t s = FINESINE(fa);
+		fixed_t thresh = dustdevil->scale * 20;
+
+		//Player in the swirl part.
+		if (dustdevil->height - pos > thresh)
+		{
+			fixed_t dist = FixedHypot(thing->x - dustdevil->x, thing->y - dustdevil->y);
+			fixed_t dragamount = 6 * FRACUNIT;
+			fixed_t x, y;
+
+			if (player->powers[pw_nocontrol] == 0)
+				A_PlayActiveSound(dustdevil);
+			player->powers[pw_nocontrol] = 2;
+			player->drawangle += ANG20;
+			P_SetPlayerMobjState(thing, S_PLAY_PAIN);
+
+			if (dist > dragamount)
+			{
+				x = thing->x + FixedMul(c, dragamount);
+				y = thing->y + FixedMul(s, dragamount);
+			}
+			else
+			{
+				x = dustdevil->x;
+				y = dustdevil->y;
+			}
+			P_TryMove(thing, x - thing->momx, y - thing->momy, true);
+		}
+		else
+		{ //Player on the top of the tornado.
+			thing->z = dustdevil->z + dustdevil->height;
+			thrust = 20 * FRACUNIT;
+			player->powers[pw_nocontrol] = 0;
+			S_StartSound(thing, sfx_wdjump);
+			P_SetPlayerMobjState(thing, S_PLAY_FALL);
+			player->pflags &= ~PF_JUMPED;
+		}
+
+		thing->momz = thrust;
+	}
+
+	return true;
+}
+
+// Function: A_DustDevilThink
+//
+// Description: Thinker for the dust devil.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_DustDevilThink(mobj_t *actor)
+{
+	fixed_t scale = actor->scale;
+	mobj_t *layer = actor->tracer;
+	INT32 bx, by, xl, xh, yl, yh;
+	fixed_t radius = actor->radius;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_DustDevilThink", actor))
+		return;
+#endif
+
+	//Chained thinker for the spiralling dust column.
+	while (layer && !P_MobjWasRemoved(layer)) {
+		angle_t fa = layer->angle >> ANGLETOFINESHIFT;
+		P_TeleportMove(layer, layer->x + 5 * FixedMul(scale, FINECOSINE(fa)), layer->y + 5 * FixedMul(scale, FINESINE(fa)), layer->z);
+		layer->scale = scale;
+		layer->angle += ANG10 / 2;
+		layer->momx = actor->momx;
+		layer->momy = actor->momy;
+		layer = layer->tracer;
+	}
+
+	//Spawn random dust around the column on the base.
+	if (P_IsObjectOnGround(actor)) {
+		angle_t dustang = ((P_RandomRange(0, 7)*ANGLE_45)>>ANGLETOFINESHIFT) & FINEMASK;
+		mobj_t *dust = P_SpawnMobj(actor->x + 96 * FixedMul(scale, FINECOSINE(dustang)), actor->y + 96 * FixedMul(scale, FINESINE(dustang)), actor->z, MT_ARIDDUST);
+		P_SetMobjState(dust, dust->info->spawnstate + P_RandomRange(0, 2));
+		dust->destscale = scale * 3;
+		P_SetScale(dust, dust->destscale);
+	}
+
+	actor->extravalue1++;
+	if (actor->extravalue1 == 12) {
+		size_t i = 0;
+		actor->extravalue1 = 0;
+
+		//Create a set of items for the rising dust column
+		for (; i <= 3; i++) {
+			fixed_t fa = (ANGLE_90*i) >> ANGLETOFINESHIFT;
+			fixed_t px = actor->x + 70 * FixedMul(scale, FINECOSINE(fa));
+			fixed_t py = actor->y + 70 * FixedMul(scale, FINESINE(fa));
+			fixed_t pz = actor->z;
+
+			layer = P_SpawnMobj(px, py, pz, MT_DUSTLAYER);
+			layer->momz = 5 * scale;
+			layer->angle = ANGLE_90 + ANGLE_90*i;
+			layer->extravalue1 = TICRATE * 3;
+
+			//Chain them
+			P_SetTarget(&layer->tracer, actor->tracer);
+			P_SetTarget(&actor->tracer, layer);
+		}
+	}
+
+	//The physics are handled here.
+	yh = (unsigned)(actor->y + radius - bmaporgy) >> MAPBLOCKSHIFT;
+	yl = (unsigned)(actor->y - radius - bmaporgy) >> MAPBLOCKSHIFT;
+	xh = (unsigned)(actor->x + radius - bmaporgx) >> MAPBLOCKSHIFT;
+	xl = (unsigned)(actor->x - radius - bmaporgx) >> MAPBLOCKSHIFT;
+
+	BMBOUNDFIX(xl, xh, yl, yh);
+
+	dustdevil = actor;
+
+	for (bx = xl; bx <= xh; bx++)
+		for (by = yl; by <= yh; by++)
+			P_BlockThingsIterator(bx, by, PIT_DustDevilLaunch);
+
+	//Whirlwind sound effect.
+	if (leveltime % 70 == 0)
+		S_StartSound(actor, sfx_s3kcel);
+}
+
+// stuff used by A_TNTExplode
+static mobj_t *barrel;
+static fixed_t exploderadius;
+static fixed_t explodethrust;
+
+static boolean PIT_TNTExplode(mobj_t *nearby)
+{
+	fixed_t dx, dy, dz;
+	fixed_t dm;
+
+	if (nearby == barrel)
+		return true;
+
+	dx = nearby->x - barrel->x;
+	dy = nearby->y - barrel->y;
+	dz = nearby->z - barrel->z + (nearby->height - barrel->height/2)/2;
+	dm = P_AproxDistance(P_AproxDistance(dx, dy), dz);
+
+	if (dm >= exploderadius || !P_CheckSight(barrel, nearby)) // out of range or not visible
+		return true;
+
+	if (barrel->type == nearby->type) // nearby is also a barrel
+	{
+		if (nearby->state == &states[nearby->info->spawnstate])
+		{
+			if (barrel->info->attacksound)
+				S_StartSound(nearby, barrel->info->attacksound);
+			nearby->momx = FixedMul(FixedDiv(dx, dm), explodethrust);
+			nearby->momy = FixedMul(FixedDiv(dy, dm), explodethrust);
+			nearby->momz = FixedMul(FixedDiv(dz, dm), explodethrust);
+			P_UnsetThingPosition(nearby);
+			if (sector_list)
+			{
+				P_DelSeclist(sector_list);
+				sector_list = NULL;
+			}
+			nearby->flags = MF_NOBLOCKMAP|MF_MISSILE;
+			P_SetThingPosition(nearby);
+			P_SetMobjState(nearby, nearby->info->missilestate);
+		}
+	}
+	else
+	{
+		if (barrel->target == nearby)
+		{
+			mobj_t *tar = barrel->target; // temporarily store barrel's target
+			P_SetTarget(&barrel->target, NULL);
+			P_DamageMobj(nearby, barrel, NULL, 1, 0);
+			if (!P_MobjWasRemoved(barrel))
+				P_SetTarget(&barrel->target, tar);
+		}
+		else
+		{
+			P_DamageMobj(nearby,
+				(barrel->target) ? barrel->target : barrel,
+				(barrel->target) ? barrel->target : barrel,
+				1, 0);
+		}
+	}
+
+	return true;
+}
+
+// Function: A_TNTExplode
+//
+// Description: Creates a TNT explosion. Used by TNT barrels and the like.
+//
+// var1 = Thing type to spawn as dust.
+// var2 = unused
+//
+void A_TNTExplode(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+	INT32 x, y;
+	INT32 xl, xh, yl, yh;
+	static mappoint_t epicenter = {0,0,0};
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_TNTExplode", actor))
+		return;
+#endif
+	P_UnsetThingPosition(actor);
+	if (sector_list)
+	{
+		P_DelSeclist(sector_list);
+		sector_list = NULL;
+	}
+	actor->flags = MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP;
+	P_SetThingPosition(actor);
+	actor->flags2 = MF2_EXPLOSION;
+	if (actor->info->deathsound)
+		S_StartSound(actor, actor->info->deathsound);
+
+	explodethrust = 32*FRACUNIT;
+	exploderadius = 256*FRACUNIT;
+
+	xl = (unsigned)(actor->x - exploderadius - bmaporgx)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(actor->x + exploderadius - bmaporgx)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(actor->y - exploderadius - bmaporgy)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(actor->y + exploderadius - bmaporgy)>>MAPBLOCKSHIFT;
+
+	BMBOUNDFIX(xl, xh, yl, yh);
+
+	barrel = actor;
+
+	for (x = xl; x <= xh; x++)
+		for (y = yl; y <= yh; y++)
+			P_BlockThingsIterator(x, y, PIT_TNTExplode);
+
+	// cause a quake -- P_StartQuake does not exist yet
+	epicenter.x = actor->x;
+	epicenter.y = actor->y;
+	epicenter.z = actor->z;
+	quake.intensity = 9*FRACUNIT;
+	quake.time = TICRATE/6;
+	quake.epicenter = &epicenter;
+	quake.radius = 512*FRACUNIT;
+
+	if (locvar1)
+	{
+		P_DustRing(locvar1, 4, actor->x, actor->y, actor->z+actor->height, 64, 0, FRACUNIT, actor->scale);
+		P_DustRing(locvar1, 6, actor->x, actor->y, actor->z+actor->height/2, 96, FRACUNIT, FRACUNIT, actor->scale);
+	}
+
+	actor->destscale *= 4;
+}
+
+// Function: A_DebrisRandom
+//
+// Description: Randomizes debris frame and movement.
+//
+// var1 = Frame range.
+// var2 = unused
+//
+void A_DebrisRandom(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	//INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_DebrisRandom", actor))
+		return;
+#endif
+
+	actor->frame |= P_RandomRange(0, locvar1);
+	var1 = 0;
+	var2 = 359;
+	A_ChangeAngleAbsolute(actor);
+	P_Thrust(actor, actor->angle, FRACUNIT * 2);
+}
+
+static mobj_t *P_TrainSeg(mobj_t *src, fixed_t x, fixed_t y, fixed_t z, angle_t ang, spritenum_t spr, UINT32 frame)
+{
+	mobj_t *s = P_SpawnMobj(x, y, z, MT_TRAINSEG);
+	s->fuse = 16*TICRATE;
+	s->sprite = spr;
+	s->frame = frame|FF_PAPERSPRITE;
+	s->angle = ang;
+	P_Thrust(s, src->angle, 7*FRACUNIT);
+	return s;
+}
+
+// Function: A_TrainCameo
+//
+// Description: Sets up train cameo locomotive.
+//
+// var1 = Train width.
+// var2 = Train length.
+//
+void A_TrainCameo(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t x = actor->x;
+	fixed_t y = actor->y;
+	fixed_t z = actor->z;
+	angle_t ang = actor->angle;
+	mobj_t *m;
+	spritenum_t spr = SPR_TRAE;
+	fixed_t span = locvar1*FRACUNIT;
+	fixed_t len = locvar2*FRACUNIT;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_TrainCameo", actor))
+		return;
+#endif
+
+	//Spawn sides.
+	P_TrainSeg(actor, x, y + span, z, ang, spr, 0);
+	P_TrainSeg(actor, x, y - span, z, ang, spr, 0);
+
+	//Center.
+	P_TrainSeg(actor, x, y, z, ang, spr, 1);
+
+	//Front and back.
+	P_TrainSeg(actor, x + len, y, z, ang + ANGLE_90, spr, 2);
+	P_TrainSeg(actor, x - len, y, z, ang + ANGLE_90, spr, 2);
+
+	//Smoke spawner.
+	m = P_TrainSeg(actor, x - (20 * FRACUNIT), y, z + (30 * FRACUNIT), ang + ANGLE_90, spr, 0);
+	P_SetMobjState(m, S_TRAINPUFFMAKER);
+}
+
+// Function: A_TrainCameo2
+//
+// Description: Sets up train cameo wagon.
+//
+// var1 = Train width.
+// var2 = Train length.
+//
+void A_TrainCameo2(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t x = actor->x;
+	fixed_t y = actor->y;
+	fixed_t z = actor->z;
+	angle_t ang = actor->angle;
+	spritenum_t spr = SPR_TRAI;
+	fixed_t span = locvar1*FRACUNIT;
+	fixed_t len = locvar2*FRACUNIT;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_TrainCameo2", actor))
+		return;
+#endif
+
+	//Spawn sides.
+	P_TrainSeg(actor, x, y + span, z, ang, spr, 0);
+	P_TrainSeg(actor, x, y - span, z, ang, spr, 0);
+
+	//Center.
+	P_TrainSeg(actor, x, y, z, ang, spr, 1);
+
+	//Front and back.
+	P_TrainSeg(actor, x + len, y, z, ang + ANGLE_90, spr, 2);
+	P_TrainSeg(actor, x - len, y, z, ang + ANGLE_90, spr, 2);
+}
+
+// Function: A_CanarivoreGas
+//
+// Description: Releases gas clouds. Used by the Canarivore.
+//
+// var1 = Mobj type.
+// var2 = Unused
+//
+void A_CanarivoreGas(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_CanarivoreGas", actor))
+		return;
+#endif
+
+	P_DustRing(locvar1, 4, actor->x, actor->y, actor->z + actor->height / 5, 18, 0, FRACUNIT/10, actor->scale);
+	P_DustRing(locvar1, 6, actor->x, actor->y, actor->z + actor->height / 5, 28, FRACUNIT, FRACUNIT/10, actor->scale);
+}
+
+//
+// Function: A_KillSegments
+//
+// Description: Causes segments attached via tracer chain to be killed.
+//
+// var1 = Fuse (if 0, default to TICRATE/2).
+// var2 = Unused
+//
+void A_KillSegments(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	mobj_t *seg = actor->tracer;
+	INT32 fuse = locvar1 ? locvar1 : TICRATE/2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_KillSegments", actor))
+		return;
+#endif
+
+	while (seg)
+	{
+		mobj_t *kseg = seg;
+		seg = seg->tracer;
+
+		kseg->flags = MF_NOBLOCKMAP|MF_BOUNCE;
+		kseg->flags2 = 0;
+		kseg->fuse = fuse;
+		P_Thrust(kseg, R_PointToAngle2(actor->x, actor->y, kseg->x, kseg->y), 3*actor->scale);
+		kseg->momz = 3*actor->scale;
+	}
+}
+
+static void P_SnapperLegPlace(mobj_t *mo)
+{
+	mobj_t *seg = mo->tracer;
+	fixed_t x0 = mo->x;
+	fixed_t y0 = mo->y;
+	angle_t a = mo->angle;
+	angle_t fa = (a >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t c = FINECOSINE(fa);
+	fixed_t s = FINESINE(fa);
+	fixed_t x, y;
+	INT32 o1, o2;
+	INT32 woffset = mo->extravalue1;
+	INT32 side = mo->extravalue2;
+	INT32 alt;
+
+	// Move head first.
+	fixed_t rad = mo->radius;
+	INT32 necklen = (32*(mo->info->reactiontime - mo->reactiontime))/mo->info->reactiontime; // Not in FU
+
+	P_TeleportMove(seg, mo->x + FixedMul(c, rad) + necklen*c, mo->y + FixedMul(s, rad) + necklen*s, mo->z + mo->height/3);
+	seg->angle = a;
+
+	// Move as many legs as available.
+	seg = seg->tracer;
+	do
+	{
+		o1 = seg->extravalue1;
+		o2 = seg->extravalue2;
+		alt = seg->cusval;
+
+		if (alt == 1)
+			o2 += woffset;
+		else
+			o2 -= woffset;
+
+		if (alt != side)
+		{
+			x = c*o2 + s*o1;
+			y = s*o2 - c*o1;
+			P_TryMove(seg, x0 + x, y0 + y, true);
+			P_SetMobjState(seg, seg->info->raisestate);
+		}
+		else
+			P_SetMobjState(seg, seg->info->spawnstate);
+
+		seg->angle = R_PointToAngle2(x0, y0, seg->x, seg->y);
+
+		seg = seg->tracer;
+	} while (seg);
+}
+
+//
+// Function: A_SnapperSpawn
+//
+// Description: Sets up Green Snapper legs and head.
+//
+// var1 = Leg mobj type.
+// var2 = Head mobj type.
+//
+void A_SnapperSpawn(mobj_t *actor)
+{
+	mobjtype_t legtype = (mobjtype_t)var1;
+	mobjtype_t headtype = (mobjtype_t)var2;
+	mobj_t *ptr = actor;
+	INT32 i;
+	mobj_t *seg;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SnapperSpawn", actor))
+		return;
+#endif
+
+	// It spawns 1 head.
+	seg = P_SpawnMobj(actor->x, actor->y, actor->z, headtype);
+	P_SetTarget(&ptr->tracer, seg);
+	ptr = seg;
+
+	// It spawns 4 legs which will be handled in the thinker function.
+	for (i = 1; i <= 4; i++)
+	{
+		seg = P_SpawnMobj(actor->x, actor->y, actor->z, legtype);
+		P_SetTarget(&ptr->tracer, seg);
+		ptr = seg;
+
+		// The legs' base offsets are stored as extravalues, as relative coordinates in xy space.
+		seg->extravalue1 = 28;
+		seg->extravalue2 = 28;
+		if (i % 2)
+			seg->extravalue1 = -seg->extravalue1;
+
+		if ((i/2) % 2)
+			seg->extravalue2 = -seg->extravalue2;
+
+		// Alternating motion stuff.
+		seg->cusval = ((i + 1)/2) % 2;
+	}
+
+	actor->extravalue1 = 0;
+	actor->extravalue2 = 0;
+	P_SnapperLegPlace(actor);
+}
+
+//
+// Function: A_SnapperThinker
+//
+// Description: Thinker for Green Snapper.
+//
+// var1 = Unused
+// var2 = Unused
+//
+void A_SnapperThinker(mobj_t *actor)
+{
+	fixed_t x0 = actor->x;
+	fixed_t y0 = actor->y;
+	fixed_t xs, ys;
+	fixed_t x1, y1;
+	fixed_t dist;
+	boolean chasing;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SnapperThinker", actor))
+		return;
+#endif
+
+	// We make a check just in case there's no spawnpoint.
+	if (actor->spawnpoint)
+	{
+		xs = actor->spawnpoint->x*FRACUNIT;
+		ys = actor->spawnpoint->y*FRACUNIT;
+	}
+	else
+	{
+		xs = x0;
+		ys = y0;
+	}
+
+	// Look for nearby, valid players to chase angrily at.
+	if ((actor->target || P_LookForPlayers(actor, true, false, 1024*FRACUNIT))
+		&& P_AproxDistance(actor->target->x - xs, actor->target->y - ys) < 2048*FRACUNIT
+		&& abs(actor->target->z - actor->z) < 80*FRACUNIT
+		&& P_CheckSight(actor, actor->target))
+	{
+		chasing = true;
+		x1 = actor->target->x;
+		y1 = actor->target->y;
+	}
+	else
+	{
+		chasing = false;
+		x1 = xs;
+		y1 = ys;
+	}
+
+	dist = P_AproxDistance(x1 - x0, y1 - y0);
+
+	// The snapper either chases what it considers to be a nearby player, or instead decides to go back to its spawnpoint.
+	if (chasing || dist > 32*FRACUNIT)
+	{
+		INT32 speed = actor->info->speed + actor->info->reactiontime - actor->reactiontime;
+
+		angle_t maxang = FixedAngle(speed*FRACUNIT/2);
+		angle_t ang = actor->angle;
+		angle_t realang = R_PointToAngle2(x0, y0, x1, y1);
+		angle_t dif = realang - ang;
+		angle_t fa;
+		fixed_t c, s;
+
+		if (dif < ANGLE_180 && dif > maxang)
+			actor->angle += maxang;
+		else if (dif >= ANGLE_180 && dif < InvAngle(maxang))
+			actor->angle -= maxang;
+		else
+			actor->angle = realang;
+
+		fa = (actor->angle >> ANGLETOFINESHIFT) & FINEMASK;
+		c = FINECOSINE(fa);
+		s = FINESINE(fa);
+
+		P_TryMove(actor, actor->x + c*speed, actor->y + s*speed, false);
+
+		// The snapper spawns dust if going fast!
+		if (actor->reactiontime < 4)
+		{
+			mobj_t *dust = P_SpawnMobj(x0, y0, actor->z, MT_SPINDUST);
+			P_Thrust(dust, ang + ANGLE_180 + FixedAngle(P_RandomRange(-20, 20)*FRACUNIT), speed*FRACUNIT);
+		}
+
+		if (actor->extravalue2 == 0)
+		{
+			if (actor->extravalue1 > 16)
+			{
+				A_PlayActiveSound(actor);
+				actor->extravalue2 = 1;
+
+				// If the snapper is chasing, accelerate; otherwise, decelerate.
+				if (chasing)
+					actor->reactiontime = max(0, actor->reactiontime - 1);
+				else
+					actor->reactiontime = min(actor->info->reactiontime, actor->reactiontime + 1);
+			}
+			else
+				actor->extravalue1 += speed;
+		}
+		else
+		{
+			if (actor->extravalue1 < -16)
+			{
+				A_PlayActiveSound(actor);
+				actor->extravalue2 = 0;
+
+				// If the snapper is chasing, accelerate; otherwise, decelerate.
+				if (chasing)
+					actor->reactiontime = max(0, actor->reactiontime - 1);
+				else
+					actor->reactiontime = min(actor->info->reactiontime, actor->reactiontime + 1);
+			}
+			else
+				actor->extravalue1 -= speed;
+		}
+	}
+
+	P_SnapperLegPlace(actor);
+}
+
+// Function: A_SaloonDoorSpawn
+//
+// Description: Spawns a saloon door.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_SaloonDoorSpawn(mobj_t *actor)
+{
+	angle_t ang = actor->angle;
+	angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t c = FINECOSINE(fa);
+	fixed_t s = FINESINE(fa);
+	INT32 d = 48;
+	fixed_t x = actor->x;
+	fixed_t y = actor->y;
+	fixed_t z = actor->z;
+	mobj_t *door;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SaloonDoorSpawn", actor))
+		return;
+#endif
+
+	//Front
+	door = P_SpawnMobj(x + c*d, y + s*d, z, MT_SALOONDOOR);
+	door->angle = ang + ANGLE_180;
+
+	// Origin angle
+	door->extravalue1 = AngleFixed(door->angle);
+
+	// Angular speed
+	door->extravalue2 = 0;
+
+	// Origin door
+	P_SetTarget(&door->tracer, actor);
+
+	//Back
+	door = P_SpawnMobj(x - c*d, y - s*d, z, MT_SALOONDOOR);
+	door->angle = ang;
+
+	// Origin angle
+	door->extravalue1 = AngleFixed(door->angle);
+
+	// Angular speed
+	door->extravalue2 = 0;
+
+	// Origin door
+	P_SetTarget(&door->tracer, actor);
+}
+
+// Function: A_MinecartSparkThink
+//
+// Description: Thinker for the minecart spark.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_MinecartSparkThink(mobj_t *actor)
+{
+	fixed_t dx = actor->momx;
+	fixed_t dy = actor->momy;
+	fixed_t dz, dm;
+	UINT8 i;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_MinecartSparkThink", actor))
+		return;
+#endif
+
+	if (actor->momz == 0 && P_IsObjectOnGround(actor))
+		actor->momz = P_RandomRange(2, 4)*FRACUNIT;
+
+	dz = actor->momz;
+	dm = FixedHypot(FixedHypot(dx, dy), dz);
+	dx = FixedDiv(dx, dm);
+	dy = FixedDiv(dy, dm);
+	dz = FixedDiv(dz, dm);
+
+	for (i = 1; i <= 8; i++)
+	{
+		mobj_t *trail = P_SpawnMobj(actor->x - dx*i, actor->y - dy*i, actor->z - dz*i, MT_PARTICLE);
+		trail->tics = 2;
+		trail->sprite = actor->sprite;
+		P_SetScale(trail, trail->scale/4);
+		trail->destscale = trail->scale;
+	}
+}
+
+// Function: A_ModuloToState
+//
+// Description: Modulo operation to state
+//
+// var1 = Modulo
+// var2 = State
+//
+void A_ModuloToState(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_ModuloToState", actor))
+		return;
+#endif
+
+	if ((modulothing % locvar1 == 0))
+		P_SetMobjState(actor, (locvar2));
+	modulothing++;
+}
diff --git a/src/p_floor.c b/src/p_floor.c
index c72de6b7030ba0dbb1922188431f524230e9b5f9..7887dc530a6291adb50adb1304baee52197fe440 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -719,6 +719,8 @@ void T_ContinuousFalling(levelspecthink_t *faller)
 		}
 	}
 
+	P_CheckSector(faller->sector, false); // you might think this is irrelevant. you would be wrong
+
 	faller->sector->floorspeed = faller->speed*faller->direction;
 	faller->sector->ceilspeed = 42;
 	faller->sector->moved = true;
@@ -815,6 +817,8 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		{
 			bouncer->sector->ceilingheight = actionsector->ceilingheight;
 			bouncer->sector->floorheight = bouncer->sector->ceilingheight - (halfheight*2);
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
 			P_RecalcPrecipInSector(actionsector);
 			bouncer->sector->ceilingdata = NULL;
 			bouncer->sector->floordata = NULL;
@@ -829,6 +833,8 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		{
 			bouncer->sector->ceilingheight = floorheight + (halfheight << 1);
 			bouncer->sector->floorheight = floorheight;
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
 			P_RecalcPrecipInSector(actionsector);
 			bouncer->sector->ceilingdata = NULL;
 			bouncer->sector->floordata = NULL;
@@ -845,9 +851,9 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		}
 
 		T_MovePlane(bouncer->sector, bouncer->speed/2, bouncer->sector->ceilingheight -
-			70*FRACUNIT, 0, 1, -1); // move floor
+			70*FRACUNIT, 0, 1, -1); // move ceiling
 		T_MovePlane(bouncer->sector, bouncer->speed/2, bouncer->sector->floorheight - 70*FRACUNIT,
-			0, 0, -1); // move ceiling
+			0, 0, -1); // move floor
 
 		bouncer->sector->floorspeed = -bouncer->speed/2;
 		bouncer->sector->ceilspeed = 42;
@@ -897,6 +903,8 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		{
 			bouncer->sector->floorheight = bouncer->floorwasheight;
 			bouncer->sector->ceilingheight = bouncer->ceilingwasheight;
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
 			bouncer->sector->ceilingdata = NULL;
 			bouncer->sector->floordata = NULL;
 			bouncer->sector->floorspeed = 0;
@@ -1839,6 +1847,7 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 #define ceilingwasheight vars[5]
 	fixed_t thwompx, thwompy;
 	sector_t *actionsector;
+	ffloor_t *rover = NULL;
 	INT32 secnum;
 
 	// If you just crashed down, wait a second before coming back up.
@@ -1853,7 +1862,16 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 	secnum = P_FindSectorFromTag((INT16)thwomp->vars[0], -1);
 
 	if (secnum > 0)
+	{
 		actionsector = &sectors[secnum];
+
+		// Look for thwomp FFloor
+		for (rover = actionsector->ffloors; rover; rover = rover->next)
+		{
+			if (rover->master == thwomp->sourceline)
+				break;
+		}
+	}
 	else
 		return; // Bad bad bad!
 
@@ -1942,10 +1960,13 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 		{
 			mobj_t *mp = (void *)&actionsector->soundorg;
 
-			if (thwomp->sourceline->flags & ML_EFFECT4)
-				S_StartSound(mp, sides[thwomp->sourceline->sidenum[0]].textureoffset>>FRACBITS);
-			else
-				S_StartSound(mp, sfx_thwomp);
+			if (!rover || (rover->flags & FF_EXISTS))
+			{
+				if (thwomp->sourceline->flags & ML_EFFECT4)
+					S_StartSound(mp, sides[thwomp->sourceline->sidenum[0]].textureoffset>>FRACBITS);
+				else
+					S_StartSound(mp, sfx_thwomp);
+			}
 
 			thwomp->direction = 1; // start heading back up
 			thwomp->distance = TICRATE; // but only after a small delay
@@ -1956,19 +1977,25 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 	}
 	else // Not going anywhere, so look for players.
 	{
-		thinker_t *th;
-		mobj_t *mo;
-
-		// scan the thinkers to find players!
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		if (!rover || (rover->flags & FF_EXISTS))
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
-
-			mo = (mobj_t *)th;
-			if (mo->type == MT_PLAYER && mo->health && mo->z <= thwomp->sector->ceilingheight
-				&& P_AproxDistance(thwompx - mo->x, thwompy - mo->y) <= 96*FRACUNIT)
+			UINT8 i;
+			// scan the players to find victims!
+			for (i = 0; i < MAXPLAYERS; i++)
 			{
+				if (!playeringame[i])
+					continue;
+				if (players[i].spectator)
+					continue;
+				if (!players[i].mo)
+					continue;
+				if (!players[i].mo->health)
+					continue;
+				if (players[i].mo->z > thwomp->sector->ceilingheight)
+					continue;
+				if (P_AproxDistance(thwompx - players[i].mo->x, thwompy - players[i].mo->y) > 96 * FRACUNIT)
+					continue;
+
 				thwomp->direction = -1;
 				break;
 			}
@@ -2144,6 +2171,7 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	msecnode_t *node;
+	ffloor_t *rover;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2191,6 +2219,19 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 			{
 				targetsec = &sectors[targetsecnum];
 
+				// Find the FOF corresponding to the control linedef
+				for (rover = targetsec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master == sec->lines[i])
+						break;
+				}
+
+				if (!rover) // This should be impossible, but don't complain if it is the case somehow
+					continue;
+
+				if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+					continue;
+
 				for (j = 0; j < MAXPLAYERS; j++)
 				{
 					if (!playeringame[j])
@@ -2664,7 +2705,7 @@ INT32 EV_DoFloor(line_t *line, floor_e floortype)
 		// new floor thinker
 		rtn = 1;
 		dofloor = Z_Calloc(sizeof (*dofloor), PU_LEVSPEC, NULL);
-		P_AddThinker(&dofloor->thinker);
+		P_AddThinker(THINK_MAIN, &dofloor->thinker);
 
 		// make sure another floor thinker won't get started over this one
 		sec->floordata = dofloor;
@@ -2885,7 +2926,7 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 		// create and initialize new elevator thinker
 		rtn = 1;
 		elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-		P_AddThinker(&elevator->thinker);
+		P_AddThinker(THINK_MAIN, &elevator->thinker);
 		sec->floordata = elevator;
 		sec->ceilingdata = elevator;
 		elevator->thinker.function.acp1 = (actionf_p1)T_MoveElevator;
@@ -2990,20 +3031,40 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 
 void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 {
-	size_t i;
-	size_t leftmostvertex = 0, rightmostvertex = 0;
-	size_t topmostvertex = 0, bottommostvertex = 0;
-	fixed_t leftx, rightx;
-	fixed_t topy, bottomy;
-	fixed_t topz, bottomz;
-	fixed_t widthfactor = FRACUNIT, heightfactor = FRACUNIT;
-	fixed_t a, b, c;
-	mobjtype_t type = MT_ROCKCRUMBLE1;
-	fixed_t spacing = (32<<FRACBITS);
-	tic_t lifetime = 3*TICRATE;
-	INT16 flags = 0;
-
-#define controlsec rover->master->frontsector
+	size_t i, leftmostvertex, rightmostvertex, topmostvertex, bottommostvertex;
+	fixed_t leftx, rightx, topy, bottomy, topz, bottomz, widthfactor, heightfactor, a, b, c, spacing;
+	mobjtype_t type;
+	tic_t lifetime;
+	INT16 flags;
+
+	sector_t *controlsec = rover->master->frontsector;
+
+	if (sec == NULL)
+	{
+		if (controlsec->numattached)
+		{
+			for (i = 0; i < controlsec->numattached; i++)
+			{
+				sec = &sectors[controlsec->attached[i]];
+				if (!sec->ffloors)
+					continue;
+
+				for (rover = sec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master->frontsector == controlsec)
+						EV_CrumbleChain(sec, rover);
+				}
+			}
+		}
+		return;
+	}
+
+	leftmostvertex = rightmostvertex = topmostvertex = bottommostvertex = 0;
+	widthfactor = heightfactor = FRACUNIT;
+	spacing = (32<<FRACBITS);
+	type = MT_ROCKCRUMBLE1;
+	lifetime = 3*TICRATE;
+	flags = 0;
 
 	if (controlsec->tag != 0)
 	{
@@ -3096,7 +3157,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	// no longer exists (can't collide with again)
 	rover->flags &= ~FF_EXISTS;
 	rover->master->frontsector->moved = true;
-	sec->moved = true;
+	P_RecalcPrecipInSector(sec);
 }
 
 // Used for bobbing platforms on the water
@@ -3112,7 +3173,7 @@ INT32 EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline)
 		return 0;
 
 	bouncer = Z_Calloc(sizeof (*bouncer), PU_LEVSPEC, NULL);
-	P_AddThinker(&bouncer->thinker);
+	P_AddThinker(THINK_MAIN, &bouncer->thinker);
 	sec->ceilingdata = bouncer;
 	bouncer->thinker.function.acp1 = (actionf_p1)T_BounceCheese;
 
@@ -3146,7 +3207,7 @@ INT32 EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, bool
 
 	// create and initialize new thinker
 	faller = Z_Calloc(sizeof (*faller), PU_LEVSPEC, NULL);
-	P_AddThinker(&faller->thinker);
+	P_AddThinker(THINK_MAIN, &faller->thinker);
 	faller->thinker.function.acp1 = (actionf_p1)T_ContinuousFalling;
 
 	// set up the fields
@@ -3195,7 +3256,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(&elevator->thinker);
+	P_AddThinker(THINK_MAIN, &elevator->thinker);
 	elevator->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
 
 	// Does this crumbler return?
@@ -3274,7 +3335,7 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		// create and initialize new elevator thinker
 
 		block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
-		P_AddThinker(&block->thinker);
+		P_AddThinker(THINK_MAIN, &block->thinker);
 		roversec->floordata = block;
 		roversec->ceilingdata = block;
 		block->thinker.function.acp1 = (actionf_p1)T_MarioBlock;
@@ -3304,7 +3365,7 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		P_SetThingPosition(thing);
 		if (thing->flags & MF_SHOOTABLE)
 			P_DamageMobj(thing, puncher, puncher, 1, 0);
-		else if (thing->type == MT_RING || thing->type == MT_COIN)
+		else if (thing->type == MT_RING || thing->type == MT_COIN || thing->type == MT_TOKEN)
 		{
 			thing->momz = FixedMul(3*FRACUNIT, thing->scale);
 			P_TouchSpecialThing(thing, puncher, false);
diff --git a/src/p_inter.c b/src/p_inter.c
index 4c9e231fe9587742977c74583311e8fb2ad1a62b..cbf5ce54a829505e31f0fbfee7aa22fcadebc90a 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -23,6 +23,7 @@
 #include "hu_stuff.h"
 #include "lua_hook.h"
 #include "m_cond.h" // unlockables, emblems, etc
+#include "p_setup.h"
 #include "m_cheat.h" // objectplace
 #include "m_misc.h"
 #include "v_video.h" // video flags for CEchos
@@ -96,15 +97,20 @@ void P_ClearStarPost(INT32 postnum)
 	mobj_t *mo2;
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_STARPOST && mo2->health <= postnum)
-			P_SetMobjState(mo2, mo2->info->seestate);
+		if (mo2->type != MT_STARPOST)
+			continue;
+
+		if (mo2->health > postnum)
+			continue;
+
+		P_SetMobjState(mo2, mo2->info->seestate);
 	}
 	return;
 }
@@ -120,15 +126,17 @@ void P_ResetStarposts(void)
 	thinker_t *th;
 	mobj_t *post;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		post = (mobj_t *)th;
 
-		if (post->type == MT_STARPOST)
-			P_SetMobjState(post, post->info->spawnstate);
+		if (post->type != MT_STARPOST)
+			continue;
+
+		P_SetMobjState(post, post->info->spawnstate);
 	}
 }
 
@@ -176,14 +184,14 @@ void P_DoNightsScore(player_t *player)
 			{
 				if (++players[i].linkcount > players[i].maxlink)
 					players[i].maxlink = players[i].linkcount;
-				players[i].linktimer = 2*TICRATE;
+				players[i].linktimer = nightslinktics;
 			}
 	}
 	else // Individual link counts
 	{
 		if (++player->linkcount > player->maxlink)
 			player->maxlink = player->linkcount;
-		player->linktimer = 2*TICRATE;
+		player->linktimer = nightslinktics;
 	}
 
 	if (player->linkcount < 10)
@@ -315,6 +323,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 	// Can happen with a sliding player corpse.
 	if (toucher->health <= 0)
 		return;
+	if (special->health <= 0)
+		return;
 
 	if (heightcheck)
 	{
@@ -340,17 +350,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		}
 	}
 
-	if (special->health <= 0)
-		return;
-
 	player = toucher->player;
 	I_Assert(player != NULL); // Only players can touch stuff!
 
 	if (player->spectator)
 		return;
 
-	// Ignore eggman in "ouchie" mode
-	if (special->flags & MF_BOSS && special->flags2 & MF2_FRET)
+	// Ignore multihits in "ouchie" mode
+	if (special->flags & (MF_ENEMY|MF_BOSS) && special->flags2 & MF2_FRET)
 		return;
 
 #ifdef HAVE_BLUA
@@ -363,81 +370,91 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 	? (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
 	: 0);
 
-	if (special->flags & MF_BOSS)
+	if ((special->flags & (MF_ENEMY|MF_BOSS)) && !(special->flags & MF_MISSILE))
 	{
-		if (special->type == MT_BLACKEGGMAN)
-		{
-			P_DamageMobj(toucher, special, special, 1, 0); // ouch
-			return;
-		}
+		////////////////////////////////////////////////////////
+		/////ENEMIES & BOSSES!!/////////////////////////////////
+		////////////////////////////////////////////////////////
 
-		if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
-		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
-		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
-		|| player->powers[pw_invulnerability] || player->powers[pw_super]
-		|| elementalpierce) // Do you possess the ability to subdue the object?
+		switch (special->type)
 		{
-			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
+			case MT_BLACKEGGMAN:
 			{
-				if (elementalpierce == 2)
-					P_DoBubbleBounce(player);
-				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
-					toucher->momz = -toucher->momz;
+				P_DamageMobj(toucher, special, special, 1, 0); // ouch
+				return;
 			}
-			if (player->pflags & PF_BOUNCING)
-				P_DoAbilityBounce(player, false);
-			toucher->momx = -toucher->momx;
-			toucher->momy = -toucher->momy;
-			P_DamageMobj(special, toucher, toucher, 1, 0);
-		}
-		else if (((toucher->z < special->z && !(toucher->eflags & MFE_VERTICALFLIP))
-		|| (toucher->z + toucher->height > special->z + special->height && (toucher->eflags & MFE_VERTICALFLIP)))
-		&& player->charability == CA_FLY
-		&& (player->powers[pw_tailsfly]
-		|| toucher->state-states == S_PLAY_FLY_TIRED)) // Tails can shred stuff with his propeller.
-		{
-			toucher->momz = -toucher->momz/2;
-
-			P_DamageMobj(special, toucher, toucher, 1, 0);
-		}
-		else
-			P_DamageMobj(toucher, special, special, 1, 0);
-
-		return;
-	}
-	else if ((special->flags & MF_ENEMY) && !(special->flags & MF_MISSILE))
-	{
-		////////////////////////////////////////////////////////
-		/////ENEMIES!!//////////////////////////////////////////
-		////////////////////////////////////////////////////////
-		if (special->type == MT_GSNAPPER && !(((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| player->powers[pw_invulnerability] || player->powers[pw_super] || elementalpierce)
-		&& toucher->z < special->z + special->height && toucher->z + toucher->height > special->z
-		&& !(player->powers[pw_shield] & SH_PROTECTSPIKE))
-		{
-			// Can only hit snapper from above
-			P_DamageMobj(toucher, special, special, 1, DMG_SPIKE);
-		}
-		else if (special->type == MT_SHARP
-		&& ((special->state == &states[special->info->xdeathstate]) || (toucher->z > special->z + special->height/2))
-		&& !(player->powers[pw_shield] & SH_PROTECTSPIKE))
-		{
-			if (player->pflags & PF_BOUNCING)
+			 case MT_BIGMINE:
 			{
-				toucher->momz = -toucher->momz;
-				P_DoAbilityBounce(player, false);
+				special->momx = toucher->momx/3;
+				special->momy = toucher->momy/3;
+				special->momz = toucher->momz/3;
+				toucher->momx /= -8;
+				toucher->momy /= -8;
+				toucher->momz /= -8;
+				special->flags &= ~MF_SPECIAL;
+				if (special->info->activesound)
+					S_StartSound(special, special->info->activesound);
+				P_SetTarget(&special->tracer, toucher);
+				player->homing = 0;
+				return;
 			}
-			else // Cannot hit sharp from above or when red and angry
-				P_DamageMobj(toucher, special, special, 1, DMG_SPIKE);
+			case MT_GSNAPPER:
+				if (!elementalpierce
+				&& toucher->z < special->z + special->height
+				&& toucher->z + toucher->height > special->z
+				&& P_DamageMobj(toucher, special, special, 1, DMG_SPIKE))
+					return; // Can only hit snapper from above
+				break;
+
+			case MT_SPINCUSHION:
+				if (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0)
+				{
+					if (player->pflags & PF_BOUNCING)
+					{
+						toucher->momz = -toucher->momz;
+						P_DoAbilityBounce(player, false);
+						return;
+					}
+					else if (P_DamageMobj(toucher, special, special, 1, DMG_SPIKE))
+						return; // Cannot hit sharp from above
+				}
+				break;
+			case MT_FANG:
+				if (!player->powers[pw_flashing]
+				&& !(player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+				&& !(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+				{
+					if ((special->state == &states[S_FANG_BOUNCE3]
+					  || special->state == &states[S_FANG_BOUNCE4]
+					  || special->state == &states[S_FANG_PINCHBOUNCE3]
+					  || special->state == &states[S_FANG_PINCHBOUNCE4])
+					&& P_MobjFlip(special)*((special->z + special->height/2) - (toucher->z - toucher->height/2)) > 0)
+					{
+						P_DamageMobj(toucher, special, special, 1, 0);
+						P_SetTarget(&special->tracer, toucher);
+
+						if (special->state == &states[S_FANG_PINCHBOUNCE3]
+						 || special->state == &states[S_FANG_PINCHBOUNCE4])
+							P_SetMobjState(special, S_FANG_PINCHPATHINGSTART2);
+						else
+						{
+							var1 = var2 = 4;
+							A_Boss5ExtraRepeat(special);
+							P_SetMobjState(special, S_FANG_PATHINGCONT2); //S_FANG_PATHINGCONT1 if you want him to drop a bomb on the player
+						}
+						if (special->eflags & MFE_VERTICALFLIP)
+							special->z = toucher->z - special->height;
+						else
+							special->z = toucher->z + toucher->height;
+						return;
+					}
+				}
+				break;
+			default:
+				break;
 		}
-		else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
-		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
-		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
-		|| player->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
+
+		if (P_PlayerCanDamage(player, special)) // Do you possess the ability to subdue the object?
 		{
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
@@ -448,19 +465,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			if (player->pflags & PF_BOUNCING)
 				P_DoAbilityBounce(player, false);
-
-			P_DamageMobj(special, toucher, toucher, 1, 0);
-		}
-		else if (((toucher->z < special->z && !(toucher->eflags & MFE_VERTICALFLIP))
-		|| (toucher->z + toucher->height > special->z + special->height && (toucher->eflags & MFE_VERTICALFLIP))) // Flame is bad at logic - JTE
-		&& player->charability == CA_FLY
-		&& (player->powers[pw_tailsfly]
-		|| toucher->state-states == S_PLAY_FLY_TIRED)) // Tails can shred stuff with his propeller.
-		{
-			if (P_MobjFlip(toucher)*toucher->momz < 0)
-				toucher->momz = -toucher->momz/2;
-
+			if (special->info->spawnhealth > 1) // Multi-hit? Bounce back!
+			{
+				toucher->momx = -toucher->momx;
+				toucher->momy = -toucher->momy;
+				if (player->charability == CA_FLY && player->panim == PA_ABILITY)
+					toucher->momz = -toucher->momz/2;
+			}
 			P_DamageMobj(special, toucher, toucher, 1, 0);
+			if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+				P_TwinSpinRejuvenate(player, player->thokitem);
 		}
 		else
 			P_DamageMobj(toucher, special, special, 1, 0);
@@ -490,42 +504,46 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			/* FALLTHRU */
 		case MT_RING:
 		case MT_FLINGRING:
-			if (!(P_CanPickupItem(player, false)))
+		case MT_COIN:
+		case MT_FLINGCOIN:
+		case MT_NIGHTSSTAR:
+			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
 				return;
 
 			special->momx = special->momy = special->momz = 0;
 			P_GivePlayerRings(player, 1);
 
-			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGRING)
+			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGRING && special->type != MT_FLINGCOIN)
 				P_DoNightsScore(player);
 			break;
-
-		case MT_COIN:
-		case MT_FLINGCOIN:
-			if (!(P_CanPickupItem(player, false)))
+		case MT_BLUESPHERE:
+		case MT_FLINGBLUESPHERE:
+		case MT_NIGHTSCHIP:
+		case MT_FLINGNIGHTSCHIP:
+			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
 				return;
 
-			special->momx = special->momy = 0;
-			P_GivePlayerRings(player, 1);
+			special->momx = special->momy = special->momz = 0;
+			P_GivePlayerSpheres(player, 1);
 
-			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGCOIN)
+			if (special->type == MT_BLUESPHERE)
+			{
+				special->destscale = ((player->powers[pw_carry] == CR_NIGHTSMODE) ? 4 : 2)*special->scale;
+				if (states[special->info->deathstate].tics > 0)
+					special->scalespeed = FixedDiv(FixedDiv(special->destscale, special->scale), states[special->info->deathstate].tics<<FRACBITS);
+				else
+					special->scalespeed = 4*FRACUNIT/5;
+			}
+
+			if (maptol & TOL_NIGHTS)
 				P_DoNightsScore(player);
 			break;
-		case MT_BLUEBALL:
-			if (!(P_CanPickupItem(player, false)))
+		case MT_BOMBSPHERE:
+			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
 				return;
 
-			P_GivePlayerRings(player, 1);
-
 			special->momx = special->momy = special->momz = 0;
-			special->destscale = FixedMul(8*FRACUNIT, special->scale);
-			if (states[special->info->deathstate].tics > 0)
-				special->scalespeed = FixedDiv(FixedDiv(special->destscale, special->scale), states[special->info->deathstate].tics<<FRACBITS);
-			else
-				special->scalespeed = 4*FRACUNIT/5;
-
-			if (maptol & TOL_NIGHTS)
-				P_DoNightsScore(player);
+			P_DamageMobj(toucher, special, special, 1, 0);
 			break;
 		case MT_AUTOPICKUP:
 		case MT_BOUNCEPICKUP:
@@ -577,25 +595,37 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		case MT_TOKEN:
 			if (player->bot)
 				return;
-			tokenlist += special->health;
 
 			P_AddPlayerScore(player, 1000);
 
-			if (!modeattacking) // score only there...
+			if (gametype != GT_COOP || modeattacking) // score only?
+			{
+				S_StartSound(toucher, sfx_chchng);
+				break;
+			}
+
+			tokenlist += special->health;
+
+			if (ALL7EMERALDS(emeralds)) // Got all 7
 			{
-				if (ALL7EMERALDS(emeralds)) // Got all 7
+				if (!(netgame || multiplayer))
 				{
-					if (!(netgame || multiplayer))
-					{
-						player->continues += 1;
-						players->gotcontinue = true;
-						if (P_IsLocalPlayer(player))
-							S_StartSound(NULL, sfx_s3kac);
-					}
+					player->continues += 1;
+					players->gotcontinue = true;
+					if (P_IsLocalPlayer(player))
+						S_StartSound(NULL, sfx_s3kac);
+					else
+						S_StartSound(toucher, sfx_chchng);
 				}
 				else
-					token++;
+					S_StartSound(toucher, sfx_chchng);
+			}
+			else
+			{
+				token++;
+				S_StartSound(toucher, sfx_token);
 			}
+
 			break;
 
 		// Emerald Hunt
@@ -619,7 +649,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 					players[i].exiting = (14*TICRATE)/5 + 1;
 				}
-				S_StartSound(NULL, sfx_lvpass);
+				//S_StartSound(NULL, sfx_lvpass);
 			}
 			break;
 
@@ -640,7 +670,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_DoMatchSuper(player);
 			}
 			else
+			{
 				emeralds |= special->info->speed;
+				stagefailed = false;
+			}
 
 			if (special->target && special->target->type == MT_EMERALDSPAWN)
 			{
@@ -750,45 +783,134 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // NiGHTS gameplay items and powerups //
 // ********************************** //
 		case MT_NIGHTSDRONE:
-			if (player->bot)
-				return;
-			if (player->exiting)
-				return;
-			if (player->bonustime)
 			{
-				if (G_IsSpecialStage(gamemap)) //After-mare bonus time/emerald reward in special stages.
+				boolean spec = G_IsSpecialStage(gamemap);
+				boolean cangiveemmy = false;
+				if (player->bot)
+					return;
+				if (player->exiting)
+					return;
+				if (player->bonustime)
+				{
+					if (spec) //After-mare bonus time/emerald reward in special stages.
+					{
+						// only allow the player with the emerald in-hand to leave.
+						if (toucher->tracer
+						&& toucher->tracer->type == MT_GOTEMERALD)
+						{}
+						else // Make sure that SOMEONE has the emerald, at least!
+						{
+							for (i = 0; i < MAXPLAYERS; i++)
+								if (playeringame[i] && players[i].playerstate == PST_LIVE
+								&& players[i].mo->tracer
+								&& players[i].mo->tracer->type == MT_GOTEMERALD)
+									return;
+							// Well no one has an emerald, so exit anyway!
+						}
+						cangiveemmy = true;
+						// Don't play Ideya sound in special stage mode
+					}
+					else
+						S_StartSound(toucher, special->info->activesound);
+				}
+				else //Initial transformation. Don't allow second chances in special stages!
+				{
+					if (player->powers[pw_carry] == CR_NIGHTSMODE)
+						return;
+
+					S_StartSound(toucher, sfx_supert);
+				}
+				P_SwitchSpheresBonusMode(false);
+				if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
+					P_SetTarget(&special->tracer, toucher);
+				P_SetTarget(&player->drone, special); // Mark the player as 'center into the drone'
+				P_NightserizePlayer(player, special->health); // Transform!
+				if (!spec)
 				{
-					// only allow the player with the emerald in-hand to leave.
-					if (toucher->tracer
-					&& toucher->tracer->type == MT_GOTEMERALD)
+					if (toucher->tracer) // Move the Ideya to an anchor!
 					{
+						mobj_t *orbittarget = special->target ? special->target : special;
+						mobj_t *hnext = orbittarget->hnext, *anchorpoint = NULL, *anchorpoint2 = NULL;
+						mobj_t *mo2;
+						thinker_t *th;
+
+						// The player might have two Ideyas: toucher->tracer and toucher->tracer->hnext
+						// so handle their anchorpoints accordingly.
+						// scan the thinkers to find the corresponding anchorpoint
+						for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+						{
+							if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+								continue;
+
+							mo2 = (mobj_t *)th;
+
+							if (mo2->type != MT_IDEYAANCHOR)
+								continue;
+
+							if (mo2->health == toucher->tracer->health) // do ideya numberes match?
+								anchorpoint = mo2;
+							else if (toucher->tracer->hnext && mo2->health == toucher->tracer->hnext->health)
+								anchorpoint2 = mo2;
+
+							if ((!toucher->tracer->hnext && anchorpoint)
+								|| (toucher->tracer->hnext && anchorpoint && anchorpoint2))
+								break;
+						}
+
+						if (anchorpoint)
+						{
+							toucher->tracer->flags |= MF_GRENADEBOUNCE; // custom radius factors
+							toucher->tracer->threshold = 8 << 20; // X factor 0, Y factor 0, Z factor 8
+						}
+
+						if (anchorpoint2)
+						{
+							toucher->tracer->hnext->flags |= MF_GRENADEBOUNCE; // custom radius factors
+							toucher->tracer->hnext->threshold = 8 << 20; // X factor 0, Y factor 0, Z factor 8
+						}
+
+						P_SetTarget(&orbittarget->hnext, toucher->tracer);
+						if (!orbittarget->hnext->hnext)
+							P_SetTarget(&orbittarget->hnext->hnext, hnext); // Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.
+						else
+							P_SetTarget(&orbittarget->hnext->hnext->target, anchorpoint2 ? anchorpoint2 : orbittarget);
+						P_SetTarget(&orbittarget->hnext->target, anchorpoint ? anchorpoint : orbittarget);
+						P_SetTarget(&toucher->tracer, NULL);
+
+						if (hnext)
+						{
+							orbittarget->hnext->extravalue1 = (angle_t)(hnext->extravalue1 - 72*ANG1);
+							if (orbittarget->hnext->extravalue1 > hnext->extravalue1)
+								orbittarget->hnext->extravalue1 -= (72*ANG1)/orbittarget->hnext->extravalue1;
+						}
 					}
-					else // Make sure that SOMEONE has the emerald, at least!
+					if (player->exiting) // ...then move it back?
 					{
-						for (i = 0; i < MAXPLAYERS; i++)
-							if (playeringame[i] && players[i].playerstate == PST_LIVE
-							&& players[i].mo->tracer
-							&& players[i].mo->tracer->type == MT_GOTEMERALD)
-								return;
-						// Well no one has an emerald, so exit anyway!
+						mobj_t *hnext = special->target ? special->target : special; // goalpost
+						while ((hnext = hnext->hnext))
+						{
+							hnext->flags &= ~MF_GRENADEBOUNCE;
+							hnext->threshold = 0;
+							P_SetTarget(&hnext->target, toucher);
+						}
 					}
-					P_GiveEmerald(false);
-					// Don't play Ideya sound in special stage mode
+					return;
 				}
-				else
-					S_StartSound(toucher, special->info->activesound);
-			}
-			else //Initial transformation. Don't allow second chances in special stages!
-			{
-				if (player->powers[pw_carry] == CR_NIGHTSMODE)
+
+				if (!cangiveemmy)
 					return;
 
-				S_StartSound(toucher, sfx_supert);
+				if (player->exiting)
+					P_GiveEmerald(false);
+				else if (player->mo->tracer && player->mare)
+				{
+					P_KillMobj(toucher->tracer, NULL, NULL, 0); // No emerald for you just yet!
+					S_StartSound(NULL, sfx_ghosty);
+					special->flags2 |= MF2_DONTDRAW;
+				}
+
+				return;
 			}
-			if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
-				P_SetTarget(&special->tracer, toucher);
-			P_NightserizePlayer(player, special->health); // Transform!
-			return;
 		case MT_NIGHTSLOOPHELPER:
 			// One second delay
 			if (special->fuse < toucher->fuse - TICRATE)
@@ -809,9 +931,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				count = 1;
 
 				// scan the remaining thinkers
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -859,9 +981,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 				// Now we RE-scan all the thinkers to find close objects to pull
 				// in from the paraloop. Isn't this just so efficient?
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -893,14 +1015,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						}
 					}
 
-					if (!(mo2->type == MT_NIGHTSWING || mo2->type == MT_RING || mo2->type == MT_COIN
-					   || mo2->type == MT_BLUEBALL
-					   || ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL))))
+					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))))
 						continue;
 
 					// Yay! The thing's in reach! Pull it in!
 					mo2->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
 					mo2->flags2 |= MF2_NIGHTSPULL;
+					// New NiGHTS attract speed dummied out because the older behavior
+					// is exploited as a mechanic. Uncomment to enable.
+					mo2->movefactor = 0; // 32*FRACUNIT; // initialize the NightsItemChase timer
 					P_SetTarget(&mo2->tracer, toucher);
 				}
 			}
@@ -913,6 +1039,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->powers[pw_carry] == CR_NIGHTSMODE && !toucher->target)
 				return;
 
+			if (toucher->tracer && toucher->tracer->health > 0)
+				return; // Don't have multiple ideya, unless it's the first one given (health = 0)
+
 			if (player->mare != special->threshold) // wrong mare
 				return;
 
@@ -920,16 +1049,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 
 			if (G_IsSpecialStage(gamemap) && !player->exiting)
-			{ // In special stages, share rings. Everyone gives up theirs to the player who touched the capsule
+			{ // In special stages, share spheres. Everyone gives up theirs to the player who touched the capsule
 				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && (&players[i] != player) && players[i].rings > 0)
+					if (playeringame[i] && (&players[i] != player) && players[i].spheres > 0)
 					{
-						player->rings += players[i].rings;
-						players[i].rings = 0;
+						player->spheres += players[i].spheres;
+						players[i].spheres = 0;
 					}
 			}
 
-			if (player->rings <= 0 || player->exiting)
+			if (player->spheres <= 0 || player->exiting)
 				return;
 
 			// Mark the player as 'pull into the capsule'
@@ -957,13 +1086,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						player->flyangle = special->threshold;
 
 					player->speed = FixedMul(special->info->speed, special->scale);
-					// Potentially causes axis transfer failures.
-					// Also rarely worked properly anyway.
-					//P_UnsetThingPosition(player->mo);
-					//player->mo->x = special->x;
-					//player->mo->y = special->y;
-					//P_SetThingPosition(player->mo);
-					toucher->z = special->z+(special->height/4);
+					P_SetTarget(&player->mo->hnext, special); // Reference bumper for position correction on next tic
 				}
 				else // More like a spring
 				{
@@ -1086,6 +1209,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				player->nightstime += special->info->speed;
 				player->startedtime += special->info->speed;
+				player->lapstartedtime += special->info->speed;
 				P_RestoreMusic(player);
 			}
 			else
@@ -1095,6 +1219,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					{
 						players[i].nightstime += special->info->speed;
 						players[i].startedtime += special->info->speed;
+						players[i].lapstartedtime += special->info->speed;
 						P_RestoreMusic(&players[i]);
 					}
 				if (special->info->deathsound != sfx_None)
@@ -1115,7 +1240,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (!G_IsSpecialStage(gamemap))
 			{
 				player->powers[pw_nights_linkfreeze] = (UINT16)special->info->speed;
-				player->linktimer = 2*TICRATE;
+				player->linktimer = nightslinktics;
 			}
 			else
 			{
@@ -1123,7 +1248,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 					{
 						players[i].powers[pw_nights_linkfreeze] += (UINT16)special->info->speed;
-						players[i].linktimer = 2*TICRATE;
+						players[i].linktimer = nightslinktics;
 					}
 				if (special->info->deathsound != sfx_None)
 					S_StartSound(NULL, special->info->deathsound);
@@ -1137,17 +1262,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Link Freeze"));
 			}
 			break;
-		case MT_NIGHTSWING:
-			if (G_IsSpecialStage(gamemap) && useNightsSS)
-			{ // Pseudo-ring.
-				S_StartSound(toucher, special->info->painsound);
-				player->totalring++;
-			}
-			else
-				S_StartSound(toucher, special->info->activesound);
-
-			P_DoNightsScore(player);
-			break;
 		case MT_HOOPCOLLIDE:
 			// This produces a kind of 'domino effect' with the hoop's pieces.
 			for (; special->hprev != NULL; special = special->hprev); // Move to the first sprite in the hoop
@@ -1237,21 +1351,22 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->bot)
 					return;
 
-				junk.tag = 649;
+				junk.tag = LE_AXE;
 				EV_DoElevator(&junk, bridgeFall, false);
 
 				// scan the remaining thinkers to find koopa
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
-					if (mo2->type == MT_KOOPA)
-					{
-						mo2->momz = 5*FRACUNIT;
-						break;
-					}
+
+					if (mo2->type != MT_KOOPA)
+						continue;
+
+					mo2->momz = 5*FRACUNIT;
+					break;
 				}
 			}
 			break;
@@ -1334,33 +1449,29 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			P_ClearStarPost(special->health);
 
-			// Find all starposts in the level with this value.
+			// Find all starposts in the level with this value - INCLUDING this one!
+			if (!(netgame && circuitmap && player != &players[consoleplayer]))
 			{
 				thinker_t *th;
 				mobj_t *mo2;
 
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-					continue;
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+						continue;
 
 					mo2 = (mobj_t *)th;
 
-					if (mo2 == special)
+					if (mo2->type != MT_STARPOST)
+						continue;
+					if (mo2->health != special->health)
 						continue;
 
-					if (mo2->type == MT_STARPOST && mo2->health == special->health)
-					{
-						if (!(netgame && circuitmap && player != &players[consoleplayer]))
-							P_SetMobjState(mo2, mo2->info->painstate);
-					}
+					P_SetMobjState(mo2, mo2->info->painstate);
 				}
 			}
 
 			S_StartSound(toucher, special->info->painsound);
-
-			if (!(netgame && circuitmap && player != &players[consoleplayer]))
-				P_SetMobjState(special, special->info->painstate);
 			return;
 
 		case MT_FAKEMOBILE:
@@ -1423,23 +1534,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 		case MT_EGGSHIELD:
 			{
-				fixed_t touchx, touchy, touchspeed;
-				angle_t angle;
-
-				if (P_AproxDistance(toucher->x-special->x, toucher->y-special->y) >
-					P_AproxDistance((toucher->x-toucher->momx)-special->x, (toucher->y-toucher->momy)-special->y))
-				{
-					touchx = toucher->x + toucher->momx;
-					touchy = toucher->y + toucher->momy;
-				}
-				else
-				{
-					touchx = toucher->x;
-					touchy = toucher->y;
-				}
-
-				angle = R_PointToAngle2(special->x, special->y, touchx, touchy) - special->angle;
-				touchspeed = P_AproxDistance(toucher->momx, toucher->momy);
+				angle_t angle = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->angle;
+				fixed_t touchspeed = P_AproxDistance(toucher->momx, toucher->momy);
+				if (touchspeed < special->scale)
+					touchspeed = special->scale;
 
 				// Blocked by the shield?
 				if (!(angle > ANGLE_90 && angle < ANGLE_270))
@@ -1456,13 +1554,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 					// Play a bounce sound?
 					S_StartSound(toucher, special->info->painsound);
-					return;
+
+					// experimental bounce
+					if (special->target)
+						special->target->extravalue1 = -special->target->info->speed;
 				}
-				else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-						|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
-						|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
-						|| (player->pflags & (PF_SPINNING|PF_GLIDING))
-						|| player->powers[pw_invulnerability] || player->powers[pw_super]) // Do you possess the ability to subdue the object?
+				else
 				{
 					// Shatter the shield!
 					toucher->momx = -toucher->momx/2;
@@ -1473,6 +1570,45 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			return;
 
+		case MT_EGGROBO1:
+			if (special->state == &states[special->info->deathstate])
+				return;
+			if (P_PlayerInPain(player))
+				return;
+
+			P_SetMobjState(special, special->info->meleestate);
+			special->angle = special->movedir;
+			special->momx = special->momy = 0;
+
+			// Buenos Dias Mandy
+			P_SetPlayerMobjState(toucher, S_PLAY_STUN);
+			player->pflags &= ~PF_APPLYAUTOBRAKE;
+			player->drawangle = special->angle + ANGLE_180;
+			P_InstaThrust(toucher, special->angle, FixedMul(3*special->info->speed, special->scale/2));
+			toucher->z += P_MobjFlip(toucher);
+			if (toucher->eflags & MFE_UNDERWATER) // unlikely.
+				P_SetObjectMomZ(toucher, FixedDiv(10511*FRACUNIT,2600*FRACUNIT), false);
+			else
+				P_SetObjectMomZ(toucher, FixedDiv(69*FRACUNIT,10*FRACUNIT), false);
+			if (P_IsLocalPlayer(player))
+			{
+				quake.intensity = 9*FRACUNIT;
+				quake.time = TICRATE/2;
+				quake.epicenter = NULL;
+			}
+
+#if 0 // camera redirection - deemed unnecessary
+			toucher->angle = special->angle;
+			if (player == &players[consoleplayer])
+				localangle = toucher->angle;
+			else if (player == &players[secondarydisplayplayer])
+				localangle2 = toucher->angle;
+#endif
+
+			S_StartSound(toucher, special->info->attacksound); // home run
+
+			return;
+
 		case MT_BIGTUMBLEWEED:
 		case MT_LITTLETUMBLEWEED:
 			if (toucher->momx || toucher->momy)
@@ -1487,10 +1623,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_SetMobjState(special, special->info->seestate);
 			}
 			return;
-		case MT_SMALLMACECHAIN:
-		case MT_BIGMACECHAIN:
-			// Is this the last link in the chain?
-			if (toucher->momz > 0 || !(special->flags2 & MF2_AMBUSH)
+		case MT_SMALLGRABCHAIN:
+		case MT_BIGGRABCHAIN:
+			if (P_MobjFlip(toucher)*toucher->momz > 0
 				|| (player->powers[pw_carry]))
 				return;
 
@@ -1503,10 +1638,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->powers[pw_flashing])
 				return;
 
-			if (special->movefactor && special->tracer && (angle_t)special->tracer->health != ANGLE_90 && (angle_t)special->tracer->health != ANGLE_270)
+			if (special->movefactor && special->tracer && special->tracer->angle != ANGLE_90 && special->tracer->angle != ANGLE_270)
 			{ // I don't expect you to understand this, Mr Bond...
-				angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->threshold;
-				if ((special->movefactor > 0) == ((angle_t)special->tracer->health > ANGLE_90 && (angle_t)special->tracer->health < ANGLE_270))
+				angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->angle;
+				if ((special->movefactor > 0) == (special->tracer->angle > ANGLE_90 && special->tracer->angle < ANGLE_270))
 					ang += ANGLE_180;
 				if (ang < ANGLE_180)
 					return; // I expect you to die.
@@ -1528,20 +1663,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			player->pflags |= PF_JUMPSTASIS;
 
 			return;
-		case MT_BIGMINE:
-		case MT_BIGAIRMINE:
-			// Spawn explosion!
-			P_SpawnMobj(special->x, special->y, special->z, special->info->mass);
-			P_RadiusAttack(special, special, special->info->damage);
-			S_StartSound(special, special->info->deathsound);
-			P_SetMobjState(special, special->info->deathstate);
-			return;
-		case MT_SPECIALSPIKEBALL:
-			if (!useNightsSS && G_IsSpecialStage(gamemap)) // Only for old special stages
-				P_SpecialStageDamage(player, special, NULL);
-			else
-				P_DamageMobj(toucher, special, special, 1, 0);
-			return;
 		case MT_EGGMOBILE2_POGO:
 			// sanity checks
 			if (!special->target || !special->target->health)
@@ -1579,7 +1700,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			// Eaten by player!
 			if ((!player->bot) && (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1))
+			{
+				player->powers[pw_underwater] = underwatertics + 1;
 				P_RestoreMusic(player);
+			}
 
 			if (player->powers[pw_underwater] < underwatertics + 1)
 				player->powers[pw_underwater] = underwatertics + 1;
@@ -1610,6 +1734,74 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			return;
 
+		case MT_CANARIVORE_GAS:
+			// if player and gas touch, attach gas to player (overriding any gas that already attached) and apply slowdown effect
+			special->flags |= MF_NOGRAVITY|MF_NOCLIPHEIGHT;
+			P_UnsetThingPosition(special);
+			special->x = toucher->x - toucher->momx/2;
+			special->y = toucher->y - toucher->momy/2;
+			special->z = toucher->z - toucher->momz/2;
+			P_SetThingPosition(special);
+			toucher->momx = FixedMul(toucher->momx, 50*FRACUNIT/51);
+			toucher->momy = FixedMul(toucher->momy, 50*FRACUNIT/51);
+			special->momx = toucher->momx;
+			special->momy = toucher->momy;
+			special->momz = toucher->momz;
+			return;
+
+		case MT_MINECARTSPAWNER:
+			if (!special->fuse || player->powers[pw_carry] != CR_MINECART)
+			{
+				mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
+				P_SetTarget(&mcart->target, toucher);
+				mcart->angle = toucher->angle = player->drawangle = special->angle;
+				mcart->friction = FRACUNIT;
+
+				P_ResetPlayer(player);
+				player->pflags |= PF_JUMPDOWN;
+				player->powers[pw_carry] = CR_MINECART;
+				toucher->player->pflags &= ~PF_APPLYAUTOBRAKE;
+				P_SetTarget(&toucher->tracer, mcart);
+				toucher->momx = toucher->momy = toucher->momz = 0;
+
+				special->fuse = 3*TICRATE;
+				special->flags2 |= MF2_DONTDRAW;
+			}
+			return;
+
+		case MT_MINECARTEND:
+			if (player->powers[pw_carry] == CR_MINECART && toucher->tracer && !P_MobjWasRemoved(toucher->tracer) && toucher->tracer->health)
+			{
+				fixed_t maxz = max(toucher->z, special->z + 35*special->scale);
+
+				toucher->momx = toucher->tracer->momx/2;
+				toucher->momy = toucher->tracer->momy/2;
+				toucher->momz = toucher->tracer->momz + P_AproxDistance(toucher->tracer->momx, toucher->tracer->momy)/2;
+				P_ResetPlayer(player);
+				player->pflags &= ~PF_APPLYAUTOBRAKE;
+				P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+				P_SetTarget(&toucher->tracer->target, NULL);
+				P_KillMobj(toucher->tracer, toucher, special, 0);
+				P_SetTarget(&toucher->tracer, NULL);
+				player->powers[pw_carry] = CR_NONE;
+				P_UnsetThingPosition(toucher);
+				toucher->x = special->x;
+				toucher->y = special->y;
+				toucher->z = maxz;
+				P_SetThingPosition(toucher);
+			}
+			return;
+
+		case MT_MINECARTSWITCHPOINT:
+			if (player->powers[pw_carry] == CR_MINECART && toucher->tracer && !P_MobjWasRemoved(toucher->tracer) && toucher->tracer->health)
+			{
+				mobjflag2_t destambush = special->flags2 & MF2_AMBUSH;
+				angle_t angdiff = toucher->tracer->angle - special->angle;
+				if (angdiff > ANGLE_90 && angdiff < ANGLE_270)
+					destambush ^= MF2_AMBUSH;
+				toucher->tracer->flags2 = (toucher->tracer->flags2 & ~MF2_AMBUSH) | destambush;
+			}
+			return;
 		default: // SOC or script pickup
 			if (player->bot)
 				return;
@@ -1661,6 +1853,10 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 
 	deadtarget = (player->mo->health <= 0);
 
+	// Don't log every hazard hit if they don't want us to.
+	if (!deadtarget && !cv_hazardlog.value)
+		return;
+
 	// Target's name
 	snprintf(targetname, sizeof(targetname), "%s%s%s",
 	         CTFTEAMCODE(player),
@@ -1764,7 +1960,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 		switch (damagetype)
 		{
 			case DMG_WATER:
-				str = M_GetText("%s was %s by chemical water.\n");
+				str = M_GetText("%s was %s by dangerous water.\n");
 				break;
 			case DMG_FIRE:
 				str = M_GetText("%s was %s by molten lava.\n");
@@ -1812,10 +2008,6 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 	if (!str) // Should not happen! Unless we missed catching something above.
 		return;
 
-	// Don't log every hazard hit if they don't want us to.
-	if (!deadtarget && !cv_hazardlog.value)
-		return;
-
 	if (deathonly)
 	{
 		if (!deadtarget)
@@ -2110,8 +2302,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	if (inflictor && (inflictor->type == MT_SHELL || inflictor->type == MT_FIREBALL))
 		P_SetTarget(&target->tracer, inflictor);
 
-	if (!useNightsSS && G_IsSpecialStage(gamemap) && target->player && sstimer > 6)
-		sstimer = 6; // Just let P_Ticker take care of the rest.
+	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && target->player && target->player->nightstime > 6)
+		target->player->nightstime = 6; // Just let P_Ticker take care of the rest.
 
 	if (target->flags & (MF_ENEMY|MF_BOSS))
 		target->momx = target->momy = target->momz = 0;
@@ -2120,7 +2312,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		target->flags |= MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT; // Don't drop Tails 03-08-2000
 
 	if (target->flags2 & MF2_NIGHTSPULL)
+	{
 		P_SetTarget(&target->tracer, NULL);
+		target->movefactor = 0; // reset NightsItemChase timer
+	}
 
 	// dead target is no more shootable
 	target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SPECIAL);
@@ -2179,28 +2374,34 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 					// to make people want to actually dash towards/paraloop enemies
 					if (++source->player->linkcount > source->player->maxlink)
 						source->player->maxlink = source->player->linkcount;
-					source->player->linktimer = 2*TICRATE;
+					source->player->linktimer = nightslinktics;
 				}
 			}
 			else
 			{
 				if (target->flags & MF_BOSS)
 					score = 1000;
-				else if ((target->flags & MF_ENEMY) && !(target->flags & MF_MISSILE))
+				else if ((target->flags & MF_ENEMY) && !(target->flags & MF_MISSILE) && target->info->spawnhealth)
 				{
+					UINT8 locscoreadd = source->player->scoreadd + target->info->spawnhealth;
 					mobj_t *scoremobj;
 					UINT32 scorestate = mobjinfo[MT_SCORE].spawnstate;
 
 					scoremobj = P_SpawnMobj(target->x, target->y, target->z + (target->height / 2), MT_SCORE);
 
-					// On ground? No chain starts.
-					if (!source->player->powers[pw_invulnerability] && P_IsObjectOnGround(source))
+					// More Sonic-like point system
+					if (!mariomode) switch (locscoreadd)
 					{
-						source->player->scoreadd = 0;
-						score = 100;
+						case 1:  score = 100;   break;
+						case 2:  score = 200;   scorestate += 1; break;
+						case 3:  score = 500;   scorestate += 2; break;
+						case 4: case 5: case 6: case 7: case 8: case 9:
+						case 10: case 11: case 12: case 13: case 14:
+						         score = 1000;  scorestate += 3; break;
+						default: score = 10000; scorestate += 4; break;
 					}
 					// Mario Mode has Mario-like chain point values
-					else if (mariomode) switch (++source->player->scoreadd)
+					else switch (locscoreadd)
 					{
 						case 1: score = 100;  break;
 						case 2: score = 200;  scorestate += 1; break;
@@ -2220,19 +2421,12 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 							scorestate += 10;
 							break;
 					}
-					// More Sonic-like point system
-					else switch (++source->player->scoreadd)
-					{
-						case 1:  score = 100;   break;
-						case 2:  score = 200;   scorestate += 1; break;
-						case 3:  score = 500;   scorestate += 2; break;
-						case 4: case 5: case 6: case 7: case 8: case 9:
-						case 10: case 11: case 12: case 13: case 14:
-						         score = 1000;  scorestate += 3; break;
-						default: score = 10000; scorestate += 4; break;
-					}
 
 					P_SetMobjState(scoremobj, scorestate);
+
+					// On ground? No chain starts.
+					if (source->player->powers[pw_invulnerability] || !P_IsObjectOnGround(source))
+						source->player->scoreadd = locscoreadd;
 				}
 			}
 
@@ -2250,7 +2444,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 
 		if ((target->player->lives <= 1) && (netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value == 0))
 			;
-		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap) && (target->player->lives != 0x7f)
+		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap) && (target->player->lives != INFLIVES)
 		 && G_GametypeUsesLives())
 		{
 			target->player->lives -= 1; // Lose a life Tails 03-11-2000
@@ -2276,10 +2470,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 					gameovermus = true;
 
 				if (gameovermus)
-				{
-					S_StopMusic(); // Stop the Music! Tails 03-14-2000
-					S_ChangeMusicInternal("_gover", false); // Yousa dead now, Okieday? Tails 03-14-2000
-				}
+					P_PlayJingle(target->player, JT_GOVER); // Yousa dead now, Okieday? Tails 03-14-2000
 
 				if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
 				{
@@ -2347,78 +2538,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	if (source && target && target->player && source->player)
 		P_PlayVictorySound(source); // Killer laughs at you. LAUGHS! BWAHAHAHA!
 
-#ifdef OLDANIMALSPAWNING
-	// Drop stuff.
-	// This determines the kind of object spawned
-	// during the death frame of a thing.
-	if (!mariomode // Don't show birds, etc. in Mario Mode Tails 12-23-2001
-	&& target->flags & MF_ENEMY)
-	{
-		mobjtype_t item;
-		INT32 prandom;
-
-		switch (target->type)
-		{
-			case MT_REDCRAWLA:
-			case MT_GOLDBUZZ:
-			case MT_SKIM:
-			case MT_UNIDUS:
-				item = MT_FLICKY_02/*MT_BUNNY*/;
-				break;
-
-			case MT_BLUECRAWLA:
-			case MT_JETTBOMBER:
-			case MT_GFZFISH:
-				item = MT_FLICKY_01/*MT_BIRD*/;
-				break;
-
-			case MT_JETTGUNNER:
-			case MT_CRAWLACOMMANDER:
-			case MT_REDBUZZ:
-			case MT_DETON:
-				item = MT_FLICKY_12/*MT_MOUSE*/;
-				break;
-
-			case MT_GSNAPPER:
-			case MT_EGGGUARD:
-			case MT_SPRINGSHELL:
-				item = MT_FLICKY_11/*MT_COW*/;
-				break;
-
-			case MT_MINUS:
-			case MT_VULTURE:
-			case MT_POINTY:
-			case MT_YELLOWSHELL:
-				item = MT_FLICKY_03/*MT_CHICKEN*/;
-				break;
-
-			case MT_AQUABUZZ:
-				item = MT_FLICKY_01/*MT_REDBIRD*/;
-				break;
-
-			default:
-				if (target->info->doomednum)
-					prandom = target->info->doomednum%5; // "Random" animal for new enemies.
-				else
-					prandom = P_RandomKey(5); // No placable object, just use a random number.
-
-				switch(prandom)
-				{
-					default: item = MT_FLICKY_02/*MT_BUNNY*/; break;
-					case 1: item = MT_FLICKY_01/*MT_BIRD*/; break;
-					case 2: item = MT_FLICKY_12/*MT_MOUSE*/; break;
-					case 3: item = MT_FLICKY_11/*MT_COW*/; break;
-					case 4: item = MT_FLICKY_03/*MT_CHICKEN*/; break;
-				}
-				break;
-		}
-
-		mo = P_SpawnMobj(target->x, target->y, target->z + (target->height / 2) - FixedMul(mobjinfo[item].height / 2, target->scale), item);
-		mo->destscale = target->scale;
-		P_SetScale(mo, mo->destscale);
-	}
-	else
-#endif
 	// Other death animation effects
 	switch(target->type)
 	{
@@ -2432,11 +2551,114 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			target->fuse = target->info->damage;
 			break;
 
+		case MT_BUBBLEBUZZ:
+			if (inflictor && inflictor->player // did a player kill you? Spawn relative to the player so they're bound to get it
+			&& P_AproxDistance(inflictor->x - target->x, inflictor->y - target->y) <= inflictor->radius + target->radius + FixedMul(8*FRACUNIT, inflictor->scale) // close enough?
+			&& inflictor->z <= target->z + target->height + FixedMul(8*FRACUNIT, inflictor->scale)
+			&& inflictor->z + inflictor->height >= target->z - FixedMul(8*FRACUNIT, inflictor->scale))
+				mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE);
+			else
+				mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE);
+			mo->destscale = target->scale;
+			P_SetScale(mo, mo->destscale);
+			break;
+
+		case MT_YELLOWSHELL:
+			P_SpawnMobjFromMobj(target, 0, 0, 0, MT_YELLOWSPRING);
+			break;
+
+		case MT_CRAWLACOMMANDER:
+			target->momx = target->momy = target->momz = 0;
+			break;
+
+		case MT_CRUSHSTACEAN:
+			if (target->tracer)
+			{
+				mobj_t *chain = target->tracer->target, *chainnext;
+				while (chain)
+				{
+					chainnext = chain->target;
+					P_RemoveMobj(chain);
+					chain = chainnext;
+				}
+				S_StopSound(target->tracer);
+				P_KillMobj(target->tracer, inflictor, source, damagetype);
+			}
+			break;
+
+		case MT_EGGSHIELD:
+			P_SetObjectMomZ(target, 4*target->scale, false);
+			P_InstaThrust(target, target->angle, 3*target->scale);
+			target->flags = (target->flags|MF_NOCLIPHEIGHT) & ~MF_NOGRAVITY;
+			break;
+
+		case MT_EGGMOBILE3:
+			{
+				thinker_t *th;
+				UINT32 i = 0; // to check how many clones we've removed
+
+				// scan the thinkers to make sure all the old pinch dummies are gone on death
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+				{
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+						continue;
+
+					mo = (mobj_t *)th;
+					if (mo->type != (mobjtype_t)target->info->mass)
+						continue;
+					if (mo->tracer != target)
+						continue;
+
+					P_KillMobj(mo, inflictor, source, damagetype);
+					mo->destscale = mo->scale/8;
+					mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE);
+					mo->momz = mo->info->speed;
+					mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
+					if (++i == 2) // we've already removed 2 of these, let's stop now
+						break;
+					else
+						S_StartSound(mo, mo->info->deathsound); // done once to prevent sound stacking
+				}
+			}
+			break;
+
+		case MT_BIGMINE:
+			if (inflictor)
+			{
+				fixed_t dx = target->x - inflictor->x, dy = target->y - inflictor->y, dz = target->z - inflictor->z;
+				fixed_t dm = FixedHypot(dz, FixedHypot(dy, dx));
+				target->momx = FixedDiv(FixedDiv(dx, dm), dm)*512;
+				target->momy = FixedDiv(FixedDiv(dy, dm), dm)*512;
+				target->momz = FixedDiv(FixedDiv(dz, dm), dm)*512;
+			}
+			if (source)
+				P_SetTarget(&target->tracer, source);
+			break;
+
+		case MT_BLASTEXECUTOR:
+			if (target->spawnpoint)
+				P_LinedefExecute(target->spawnpoint->angle, (source ? source : inflictor), target->subsector->sector);
+			break;
+
+		case MT_SPINBOBERT:
+			if (target->hnext)
+				P_KillMobj(target->hnext, inflictor, source, damagetype);
+			if (target->hprev)
+				P_KillMobj(target->hprev, inflictor, source, damagetype);
+			break;
+
 		case MT_EGGTRAP:
 			// Time for birdies! Yaaaaaaaay!
 			target->fuse = TICRATE*2;
 			break;
 
+		case MT_MINECART:
+			A_Scream(target);
+			target->momx = target->momy = target->momz = 0;
+			if (target->target && target->target->health)
+				P_KillMobj(target->target, target, source, 0);
+			break;
+
 		case MT_PLAYER:
 			{
 				target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player)
@@ -2465,57 +2687,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			break;
 	}
 
-	// Enemy drops that ALWAYS occur regardless of mode
-	if (target->type == MT_AQUABUZZ) // Additionally spawns breathable bubble for players to get
-	{
-		if (inflictor && inflictor->player // did a player kill you? Spawn relative to the player so he's bound to get it
-		&& P_AproxDistance(inflictor->x - target->x, inflictor->y - target->y) <= inflictor->radius + target->radius + FixedMul(8*FRACUNIT, inflictor->scale) // close enough?
-		&& inflictor->z <= target->z + target->height + FixedMul(8*FRACUNIT, inflictor->scale)
-		&& inflictor->z + inflictor->height >= target->z - FixedMul(8*FRACUNIT, inflictor->scale))
-			mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE);
-		else
-			mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE);
-		mo->destscale = target->scale;
-		P_SetScale(mo, mo->destscale);
-	}
-	else if (target->type == MT_YELLOWSHELL) // Spawns a spring that falls to the ground
-	{
-		mobjtype_t spawnspring = MT_YELLOWSPRING;
-		fixed_t spawnheight = target->z;
-		if (!(target->eflags & MFE_VERTICALFLIP))
-			spawnheight += target->height;
-
-		mo = P_SpawnMobj(target->x, target->y, spawnheight, spawnspring);
-		mo->destscale = target->scale;
-		P_SetScale(mo, mo->destscale);
-
-		if (target->flags2 & MF2_OBJECTFLIP)
-			mo->flags2 |= MF2_OBJECTFLIP;
-	}
-
-	if (target->type == MT_EGGMOBILE3)
-	{
-		thinker_t *th;
-		UINT32 i = 0; // to check how many clones we've removed
-
-		// scan the thinkers to make sure all the old pinch dummies are gone on death
-		// this can happen if the boss was hurt earlier than expected
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
-
-			mo = (mobj_t *)th;
-			if (mo->type == (mobjtype_t)target->info->mass && mo->tracer == target)
-			{
-				P_RemoveMobj(mo);
-				i++;
-			}
-			if (i == 2) // we've already removed 2 of these, let's stop now
-				break;
-		}
-	}
-
+	// Final state setting - do something instead of P_SetMobjState;
 	if (target->type == MT_SPIKE && target->info->deathstate != S_NULL)
 	{
 		const angle_t ang = ((inflictor) ? inflictor->angle : 0) + ANGLE_90;
@@ -2694,6 +2866,8 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 	player_t *player = target->player;
 	tic_t oldnightstime = player->nightstime;
 
+	(void)source; // unused
+
 	if (!player->powers[pw_flashing])
 	{
 		angle_t fa;
@@ -2707,20 +2881,10 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 			player->drillmeter -= 5*20;
 		else
 		{
-			if (source && source->player)
-			{
-				if (player->nightstime > 20*TICRATE)
-					player->nightstime -= 20*TICRATE;
-				else
-					player->nightstime = 1;
-			}
+			if (player->nightstime > 5*TICRATE)
+				player->nightstime -= 5*TICRATE;
 			else
-			{
-				if (player->nightstime > 5*TICRATE)
-					player->nightstime -= 5*TICRATE;
-				else
-					player->nightstime = 1;
-			}
+				player->nightstime = 1;
 		}
 
 		if (player->pflags & PF_TRANSFERTOCLOSEST)
@@ -2743,13 +2907,23 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		if (oldnightstime > 10*TICRATE
 			&& player->nightstime < 10*TICRATE)
 		{
-			//S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
-			S_ChangeMusicInternal("_drown",false);
+			if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+#ifdef _WIN32
+				// win32 MIDI volume hack means we cannot fade down the music
+				&& S_MusicType() != MU_MID
+#endif
+			)
+			{
+				S_FadeMusic(0, 10*MUSICRATE);
+				S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
+			}
+			else
+				P_PlayJingle(player, ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? JT_NIGHTSTIMEOUT : JT_SSTIMEOUT);
 		}
 	}
 }
 
-static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
+static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
 	player_t *player = target->player;
 	(void)damage; //unused parm
@@ -2758,26 +2932,47 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 	if (player->powers[pw_flashing] || player->powers[pw_invulnerability])
 		return false;
 
-	// Ignore IT players shooting each other, unless friendlyfire is on.
-	if ((player->pflags & PF_TAGIT && !(cv_friendlyfire.value &&
-		source && source->player && source->player->pflags & PF_TAGIT)))
-		return false;
-
 	// Don't allow any damage before the round starts.
 	if (leveltime <= hidetime * TICRATE)
 		return false;
 
+	// Ignore IT players shooting each other, unless friendlyfire is on.
+	if ((player->pflags & PF_TAGIT && !((cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) &&
+		source && source->player && source->player->pflags & PF_TAGIT)))
+	{
+		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+		{
+			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+			{
+				P_SwitchShield(player, SH_PINK);
+				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+			}
+		}
+		return false;
+	}
+
 	// Don't allow players on the same team to hurt one another,
 	// unless cv_friendlyfire is on.
-	if (!cv_friendlyfire.value && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
+	if (!(cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
 	{
-		if (!(inflictor->flags & MF_FIRE))
+		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+		{
+			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+			{
+				P_SwitchShield(player, SH_PINK);
+				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+			}
+		}
+		else if (!(inflictor->flags & MF_FIRE))
 			P_GivePlayerRings(player, 1);
 		if (inflictor->flags2 & MF2_BOUNCERING)
 			inflictor->fuse = 0; // bounce ring disappears at -1 not 0
 		return false;
 	}
 
+	if (inflictor->type == MT_LHRT)
+		return false;
+
 	// The tag occurs so long as you aren't shooting another tagger with friendlyfire on.
 	if (source->player->pflags & PF_TAGIT && !(player->pflags & PF_TAGIT))
 	{
@@ -2809,43 +3004,74 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 		return true;
 	}
 
-	if (player->rings > 0) // Ring loss
+	if (player->powers[pw_carry] == CR_NIGHTSFALL)
+	{
+		if (player->spheres > 0)
+		{
+			P_PlayRinglossSound(target);
+			P_PlayerRingBurst(player, player->spheres);
+			player->spheres = 0;
+		}
+	}
+	else if (player->rings > 0) // Ring loss
 	{
 		P_PlayRinglossSound(target);
 		P_PlayerRingBurst(player, player->rings);
+		player->rings = 0;
 	}
 	else // Death
 	{
 		P_PlayDeathSound(target);
 		P_PlayVictorySound(source); // Killer laughs at you! LAUGHS! BWAHAHAHHAHAA!!
 	}
-
-	player->rings = 0;
 	return true;
 }
 
-static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
+static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
 	player_t *player = target->player;
 
-	// You can't kill yourself, idiot...
-	if (source == target)
-		return false;
+	if (!(damagetype & DMG_CANHURTSELF))
+	{
+		// You can't kill yourself, idiot...
+		if (source == target)
+			return false;
 
-	// In COOP/RACE/CHAOS, you can't hurt other players unless cv_friendlyfire is on
-	if (!cv_friendlyfire.value && (G_PlatformGametype()))
-		return false;
+		// In COOP/RACE, you can't hurt other players unless cv_friendlyfire is on
+		if (!cv_friendlyfire.value && (G_PlatformGametype()))
+		{
+			if (gametype == GT_COOP && inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK)) // co-op only
+			{
+				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+				{
+					P_SwitchShield(player, SH_PINK);
+					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+				}
+			}
+			return false;
+		}
+	}
 
 	// Tag handling
 	if (G_TagGametype())
-		return P_TagDamage(target, inflictor, source, damage);
+		return P_TagDamage(target, inflictor, source, damage, damagetype);
+	else if (damagetype & DMG_CANHURTSELF)
+		return true;
 	else if (G_GametypeHasTeams()) // CTF + Team Match
 	{
 		// Don't allow players on the same team to hurt one another,
 		// unless cv_friendlyfire is on.
 		if (!cv_friendlyfire.value && target->player->ctfteam == source->player->ctfteam)
 		{
-			if (!(inflictor->flags & MF_FIRE))
+			if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+			{
+				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+				{
+					P_SwitchShield(player, SH_PINK);
+					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+				}
+			}
+			else if (!(inflictor->flags & MF_FIRE))
 				P_GivePlayerRings(target->player, 1);
 			if (inflictor->flags2 & MF2_BOUNCERING)
 				inflictor->fuse = 0; // bounce ring disappears at -1 not 0
@@ -2854,6 +3080,9 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 		}
 	}
 
+	if (inflictor->type == MT_LHRT)
+		return false;
+
 	// Add pity.
 	if (!player->powers[pw_flashing] && !player->powers[pw_invulnerability] && !player->powers[pw_super]
 	&& source->player->score > player->score)
@@ -2886,6 +3115,9 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 
 	P_ResetPlayer(player);
 
+	if (!player->spectator)
+		player->mo->flags2 &= ~MF2_DONTDRAW;
+
 	P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
 	if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
 	{
@@ -2914,7 +3146,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 	}
 }
 
-static inline void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage)
+static void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
 	fixed_t fallbackspeed;
 	angle_t ang;
@@ -3030,7 +3262,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 	}
 }
 
-static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
+static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype, boolean dospheres)
 {
 	P_DoPlayerPain(player, source, inflictor);
 
@@ -3060,20 +3292,32 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 	// Ring loss sound plays despite hitting spikes
 	P_PlayRinglossSound(player->mo); // Ringledingle!
 	P_PlayerRingBurst(player, damage);
-	player->rings -= damage;
-	if (player->rings < 0)
-		player->rings = 0;
+
+	if (dospheres)
+	{
+		player->spheres -= damage;
+		if (player->spheres < 0)
+			player->spheres = 0;
+	}
+	else
+	{
+		player->rings -= damage;
+		if (player->rings < 0)
+			player->rings = 0;
+	}
 }
 
 //
 // P_SpecialStageDamage
 //
 // Do old special stage-style damaging
-// Removes 10 rings from the player, or knocks off their shield if they have one.
+// Removes 5 seconds from the player, or knocks off their shield if they have one.
 // If they don't have anything, just knock the player back anyway (this doesn't kill them).
 //
 void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 {
+	tic_t oldnightstime = player->nightstime;
+
 	if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super])
 		return;
 
@@ -3084,17 +3328,29 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 	}
 	else
 	{
-		P_PlayRinglossSound(player->mo);
-		if (player->rings >= 10)
-			player->rings -= 10;
+		S_StartSound(player->mo, sfx_nghurt);
+		if (player->nightstime > 5*TICRATE)
+			player->nightstime -= 5*TICRATE;
 		else
-			player->rings = 0;
+			player->nightstime = 0;
 	}
 
 	P_DoPlayerPain(player, inflictor, source);
 
 	if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
 		P_PlayerFlagBurst(player, false);
+
+	if (oldnightstime > 10*TICRATE
+		&& player->nightstime < 10*TICRATE)
+	{
+		if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+		{
+			S_FadeMusic(0, 10*MUSICRATE);
+			S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
+		}
+		else
+			S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false);
+	}
 }
 
 /** Damages an object, which may or may not be a player.
@@ -3179,36 +3435,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			return false;
 	}
 
-	// Special case for Crawla Commander
-	if (target->type == MT_CRAWLACOMMANDER)
-	{
-		if (!force && target->fuse) // Invincible
-			return false;
-
-#ifdef HAVE_BLUA
-		if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
-			return true;
-#endif
-
-		if (target->health > 1)
-		{
-			if (target->info->painsound)
-				S_StartSound(target, target->info->painsound);
-
-			target->fuse = TICRATE/2;
-			target->flags2 |= MF2_FRET;
-		}
-		else
-		{
-			target->flags |= MF_NOGRAVITY;
-			target->fuse = 0;
-		}
-
-		target->momx = target->momy = target->momz = 0;
-
-		P_InstaThrust(target, target->angle-ANGLE_180, FixedMul(5*FRACUNIT, target->scale));
-	}
-	else if (target->flags & MF_BOSS)
+	if (target->flags & (MF_ENEMY|MF_BOSS))
 	{
 		if (!force && target->flags2 & MF2_FRET) // Currently flashing from being hit
 			return false;
@@ -3221,13 +3448,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		if (target->health > 1)
 			target->flags2 |= MF2_FRET;
 	}
-#ifdef HAVE_BLUA
-	else if (target->flags & MF_ENEMY)
-	{
-		if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
-			return true;
-	}
-#endif
 
 	player = target->player;
 
@@ -3280,6 +3500,12 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			return true;
 		}
 
+		if (G_IsSpecialStage(gamemap))
+		{
+			P_SpecialStageDamage(player, inflictor, source);
+			return true;
+		}
+
 		if (!force && inflictor && inflictor->flags & MF_FIRE)
 		{
 			if (player->powers[pw_shield] & SH_PROTECTFIRE)
@@ -3292,7 +3518,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		// Player hits another player
 		if (!force && source && source->player)
 		{
-			if (!P_PlayerHitsPlayer(target, inflictor, source, damage))
+			if (!P_PlayerHitsPlayer(target, inflictor, source, damage, damagetype))
 				return false;
 		}
 
@@ -3300,7 +3526,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		if (damagetype & DMG_DEATHMASK)
 		{
 			P_KillPlayer(player, source, damage);
-			player->rings = 0;
+			player->rings = player->spheres = 0;
 		}
 		else if (metalrecording)
 		{
@@ -3319,12 +3545,10 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 				return true;
 			}
 		}
-		else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] // ignore bouncing & such in invulnerability
-			|| player->powers[pw_super])
+		else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super]) // ignore bouncing & such in invulnerability
 		{
-			if (force || (inflictor && (inflictor->flags & MF_MISSILE)
-				&& (inflictor->flags2 & MF2_SUPERFIRE)
-				&& player->powers[pw_super]))
+			if (force
+			|| (inflictor && inflictor->flags & MF_MISSILE && inflictor->flags2 & MF2_SUPERFIRE)) // Super Sonic is stunned!
 			{
 #ifdef HAVE_BLUA
 				if (!LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
@@ -3332,8 +3556,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 					P_SuperDamage(player, inflictor, source, damage);
 				return true;
 			}
-			else
-				return false;
+			return false;
 		}
 #ifdef HAVE_BLUA
 		else if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
@@ -3344,10 +3567,17 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			P_ShieldDamage(player, inflictor, source, damage, damagetype);
 			damage = 0;
 		}
+		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
+		{
+			// always damage so we can recoil upon losing points
+			damage = player->spheres;
+			P_RingDamage(player, inflictor, source, damage, damagetype, true);
+			damage = 0;
+		}
 		else if (player->rings > 0) // No shield but have rings.
 		{
 			damage = player->rings;
-			P_RingDamage(player, inflictor, source, damage, damagetype);
+			P_RingDamage(player, inflictor, source, damage, damagetype, false);
 			damage = 0;
 		}
 		// To reduce griefing potential, don't allow players to be killed
@@ -3365,8 +3595,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			P_KillPlayer(player, source, damage);
 		}
 
-		P_HitDeathMessages(player, inflictor, source, damagetype);
-
 		P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
 	}
 
@@ -3381,6 +3609,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 	else
 		target->health -= damage;
 
+	if (player)
+		P_HitDeathMessages(player, inflictor, source, damagetype);
+
 	if (source && source->player && target)
 		G_GhostAddHit(target);
 
@@ -3392,20 +3623,14 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 
 	if (player)
 		P_ResetPlayer(target->player);
+	else if ((target->type == MT_EGGMOBILE2) // egg slimer
+	&& (target->health < target->info->damage)) // in pinch phase
+		P_SetMobjState(target, target->info->meleestate); // go to pinch pain state
 	else
-		switch (target->type)
-		{
-		case MT_EGGMOBILE2: // egg slimer
-			if (target->health < target->info->damage) // in pinch phase
-			{
-				P_SetMobjState(target, target->info->meleestate); // go to pinch pain state
-				break;
-			}
-			/* FALLTHRU */
-		default:
-			P_SetMobjState(target, target->info->painstate);
-			break;
-		}
+		P_SetMobjState(target, target->info->painstate);
+
+	if (target->type == MT_HIVEELEMENTAL)
+		target->extravalue1 += 3;
 
 	target->reactiontime = 0; // we're awake now...
 
@@ -3430,16 +3655,17 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 {
 	INT32 i;
 	mobj_t *mo;
-	angle_t fa;
+	angle_t fa, va;
 	fixed_t ns;
 	fixed_t z;
+	boolean nightsreplace = ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap));
 
 	// Better safe than sorry.
 	if (!player)
 		return;
 
 	// If no health, don't spawn ring!
-	if (player->rings <= 0)
+	if (((maptol & TOL_NIGHTS) && player->spheres <= 0) || (!(maptol & TOL_NIGHTS) && player->rings <= 0))
 		num_rings = 0;
 
 	if (num_rings > 32 && player->powers[pw_carry] != CR_NIGHTSFALL)
@@ -3451,11 +3677,18 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 	// Spill weapons first
 	P_PlayerWeaponPanelOrAmmoBurst(player);
 
+	if (abs(player->mo->momx) > player->mo->scale || abs(player->mo->momy) > player->mo->scale)
+		va = R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0)>>ANGLETOFINESHIFT;
+	else
+		va = player->mo->angle>>ANGLETOFINESHIFT;
+
 	for (i = 0; i < num_rings; i++)
 	{
 		INT32 objType = mobjinfo[MT_RING].reactiontime;
 		if (mariomode)
 			objType = mobjinfo[MT_COIN].reactiontime;
+		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
+			objType = mobjinfo[(nightsreplace ? MT_NIGHTSCHIP : MT_BLUESPHERE)].reactiontime;
 
 		z = player->mo->z;
 		if (player->mo->eflags & MFE_VERTICALFLIP)
@@ -3470,7 +3703,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		P_SetScale(mo, player->mo->scale);
 
 		// Angle offset by player angle, then slightly offset by amount of rings
-		fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT) - ((num_rings-1)*FINEANGLES/32)) & FINEMASK;
+		fa = ((i*FINEANGLES/16) + va - ((num_rings-1)*FINEANGLES/32)) & FINEMASK;
 
 		// Make rings spill out around the player in 16 directions like SA, but spill like Sonic 2.
 		// Technically a non-SA way of spilling rings. They just so happen to be a little similar.
@@ -3484,6 +3717,9 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 
 			P_SetObjectMomZ(mo, 8*FRACUNIT, false);
 			mo->fuse = 20*TICRATE; // Adjust fuse for NiGHTS
+
+			// Toggle bonus time colors
+			P_SetMobjState(mo, (player->bonustime ? mo->info->raisestate : mo->info->spawnstate));
 		}
 		else
 		{
@@ -3514,9 +3750,6 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		}
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 			mo->momz *= -1;
-
-		if (P_IsObjectOnGround(player->mo))
-			player->powers[pw_carry] = CR_NONE;
 	}
 
 	player->losstime += 10*TICRATE;
diff --git a/src/p_lights.c b/src/p_lights.c
index 8aa2eedca49c41f877faf74dbb01f0bae23b15d3..8459d9ea044d066e5ba02c37b2717d549849b9ce 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -23,7 +23,7 @@
   *
   * \param sector The sector to remove effects from.
   */
-static void P_RemoveLighting(sector_t *sector)
+void P_RemoveLighting(sector_t *sector)
 {
 	if (sector->lightingdata)
 	{
@@ -76,7 +76,7 @@ fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxse
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	flick = Z_Calloc(sizeof (*flick), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flick->thinker);
+	P_AddThinker(THINK_MAIN, &flick->thinker);
 
 	flick->thinker.function.acp1 = (actionf_p1)T_FireFlicker;
 	flick->sector = maxsector;
@@ -155,7 +155,7 @@ void P_SpawnLightningFlash(sector_t *sector)
 
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->thinker.function.acp1 = (actionf_p1)T_LightningFlash;
 	flash->sector = sector;
@@ -214,7 +214,7 @@ strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector,
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->sector = maxsector;
 	flash->darktime = darktime;
@@ -289,7 +289,7 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector,
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	g = Z_Calloc(sizeof (*g), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&g->thinker);
+	P_AddThinker(THINK_MAIN, &g->thinker);
 
 	g->sector = maxsector;
 	g->minlight = minsector->lightlevel;
@@ -322,39 +322,70 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector,
 	return g;
 }
 
-/** Fades all the lights in sectors with a particular tag to a new
+/** Fades all the lights in specified sector to a new
   * value.
   *
-  * \param tag       Tag to look for sectors by.
+  * \param sector    Target sector
   * \param destvalue The final light value in these sectors.
-  * \param speed     Speed of the fade; the change to the ligh
+  * \param speed     If tic-based: total duration of effect.
+  *                  If speed-based: Speed of the fade; the change to the ligh
   *                  level in each sector per tic.
-  * \todo Calculate speed better so that it is possible to specify
-  *       the time for completion of the fade, and all lights fade
-  *       in this time regardless of initial values.
+  * \param ticbased  Use a specific duration for the fade, defined by speed
   * \sa T_LightFade
   */
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
+void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean ticbased)
 {
-	INT32 i;
 	lightlevel_t *ll;
-	sector_t *sector;
 
-	// search all sectors for ones with tag
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0 ;)
+	P_RemoveLighting(sector); // remove the old lighting effect first
+
+	if ((ticbased && !speed) || sector->lightlevel == destvalue) // set immediately
 	{
-		sector = &sectors[i];
+		sector->lightlevel = destvalue;
+		return;
+	}
+
+	ll = Z_Calloc(sizeof (*ll), PU_LEVSPEC, NULL);
+	ll->thinker.function.acp1 = (actionf_p1)T_LightFade;
+	sector->lightingdata = ll; // set it to the lightlevel_t
+
+	P_AddThinker(THINK_MAIN, &ll->thinker); // add thinker
 
-		P_RemoveLighting(sector); // remove the old lighting effect first
-		ll = Z_Calloc(sizeof (*ll), PU_LEVSPEC, NULL);
-		ll->thinker.function.acp1 = (actionf_p1)T_LightFade;
-		sector->lightingdata = ll; // set it to the lightlevel_t
+	ll->sector = sector;
+	ll->sourcelevel = sector->lightlevel;
+	ll->destlevel = destvalue;
 
-		P_AddThinker(&ll->thinker); // add thinker
+	ll->fixedcurlevel = sector->lightlevel<<FRACBITS;
 
-		ll->sector = sector;
-		ll->destlevel = destvalue;
-		ll->speed = speed;
+	if (ticbased)
+	{
+		// Speed means duration.
+		ll->timer = abs(speed);
+		ll->fixedpertic = FixedDiv((destvalue<<FRACBITS) - ll->fixedcurlevel, speed<<FRACBITS);
+	}
+	else
+	{
+		// Speed means increment per tic (literally speed).
+		ll->timer = FixedDiv((destvalue<<FRACBITS) - ll->fixedcurlevel, speed<<FRACBITS)>>FRACBITS;
+		ll->fixedpertic = speed<<FRACBITS;
+	}
+}
+
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force)
+{
+	INT32 i;
+	// search all sectors for ones with tag
+	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0 ;)
+	{
+		if (!force && ticbased // always let speed fader execute
+			&& sectors[i].lightingdata
+			&& ((lightlevel_t*)sectors[i].lightingdata)->thinker.function.acp1 == (actionf_p1)T_LightFade)
+			// && ((lightlevel_t*)sectors[i].lightingdata)->timer > 2)
+		{
+			CONS_Debug(DBG_GAMELOGIC, "Line type 420 Executor: Fade light thinker already exists, timer: %d\n", ((lightlevel_t*)sectors[i].lightingdata)->timer);
+			continue;
+		}
+		P_FadeLightBySector(&sectors[i], destvalue, speed, ticbased);
 	}
 }
 
@@ -365,30 +396,13 @@ void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed)
   */
 void T_LightFade(lightlevel_t *ll)
 {
-	if (ll->sector->lightlevel < ll->destlevel)
+	if (--ll->timer <= 0)
 	{
-		// increase the lightlevel
-		if (ll->sector->lightlevel + ll->speed >= ll->destlevel)
-		{
-			// stop changing light level
-			ll->sector->lightlevel = (INT16)ll->destlevel; // set to dest lightlevel
-
-			P_RemoveLighting(ll->sector); // clear lightingdata, remove thinker
-		}
-		else
-			ll->sector->lightlevel = (INT16)(ll->sector->lightlevel + (INT16)ll->speed); // move lightlevel
+		ll->sector->lightlevel = ll->destlevel; // set to dest lightlevel
+		P_RemoveLighting(ll->sector); // clear lightingdata, remove thinker
+		return;
 	}
-	else
-	{
-		// decrease lightlevel
-		if (ll->sector->lightlevel - ll->speed <= ll->destlevel)
-		{
-			// stop changing light level
-			ll->sector->lightlevel = (INT16)ll->destlevel; // set to dest lightlevel
 
-			P_RemoveLighting(ll->sector); // clear lightingdata, remove thinker
-		}
-		else
-			ll->sector->lightlevel = (INT16)(ll->sector->lightlevel - (INT16)ll->speed); // move lightlevel
-	}
+	ll->fixedcurlevel = ll->fixedcurlevel + ll->fixedpertic;
+	ll->sector->lightlevel = (ll->fixedcurlevel)>>FRACBITS;
 }
diff --git a/src/p_local.h b/src/p_local.h
index 54ae37ed24d241f8ddf17ab00377211a562ff61e..3a0146e541fc9f3737239df62a3139db3f7a3258 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -39,9 +39,6 @@
 // Convenience macro to fix issue with collision along bottom/left edges of blockmap -Red
 #define BMBOUNDFIX(xl, xh, yl, yh) {if (xl > xh) xl = 0; if (yl > yh) yl = 0;}
 
-// player radius used only in am_map.c
-#define PLAYERRADIUS (16*FRACUNIT)
-
 // MAXRADIUS is for precalculated sector block boxes
 // the spider demon is larger,
 // but we do not have any moving sectors nearby
@@ -64,15 +61,21 @@
 #define P_GetPlayerHeight(player) FixedMul(player->height, player->mo->scale)
 #define P_GetPlayerSpinHeight(player) FixedMul(player->spinheight, player->mo->scale)
 
-//
-// P_TICK
-//
-
-// both the head and tail of the thinker list
-extern thinker_t thinkercap;
+typedef enum
+{
+	THINK_POLYOBJ,
+	THINK_MAIN,
+	THINK_MOBJ,
+#ifdef ESLOPE
+	THINK_DYNSLOPE,
+#endif
+	THINK_PRECIP,
+	NUM_THINKERLISTS
+} thinklistnum_t; /**< Thinker lists. */
+extern thinker_t thlist[];
 
 void P_InitThinkers(void);
-void P_AddThinker(thinker_t *thinker);
+void P_AddThinker(const thinklistnum_t n, thinker_t *thinker);
 void P_RemoveThinker(thinker_t *thinker);
 
 //
@@ -131,6 +134,7 @@ pflags_t P_GetJumpFlags(player_t *player);
 boolean P_PlayerInPain(player_t *player);
 void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor);
 void P_ResetPlayer(player_t *player);
+boolean P_PlayerCanDamage(player_t *player, mobj_t *thing);
 boolean P_IsLocalPlayer(player_t *player);
 
 boolean P_IsObjectInGoop(mobj_t *mo);
@@ -138,7 +142,7 @@ boolean P_IsObjectOnGround(mobj_t *mo);
 boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec);
 boolean P_InSpaceSector(mobj_t *mo);
 boolean P_InQuicksand(mobj_t *mo);
-boolean P_PlayerHitFloor(player_t *player);
+boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff);
 
 void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative);
 void P_RestoreMusic(player_t *player);
@@ -146,6 +150,7 @@ void P_SpawnShieldOrb(player_t *player);
 void P_SwitchShield(player_t *player, UINT16 shieldtype);
 mobj_t *P_SpawnGhostMobj(mobj_t *mobj);
 void P_GivePlayerRings(player_t *player, INT32 num_rings);
+void P_GivePlayerSpheres(player_t *player, INT32 num_spheres);
 void P_GivePlayerLives(player_t *player, INT32 numlives);
 void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound);
 UINT8 P_GetNextEmerald(void);
@@ -160,6 +165,7 @@ boolean P_AutoPause(void);
 void P_DoJumpShield(player_t *player);
 void P_DoBubbleBounce(player_t *player);
 void P_DoAbilityBounce(player_t *player, boolean changemomz);
+void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type);
 void P_BlackOw(player_t *player);
 void P_ElementalFire(player_t *player, boolean cropcircle);
 
@@ -176,7 +182,7 @@ void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
 mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
-void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
+boolean P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
 void P_DoJump(player_t *player, boolean soundandstate);
 #if 0
@@ -202,6 +208,47 @@ boolean P_GetLives(player_t *player);
 boolean P_SpectatorJoinGame(player_t *player);
 void P_RestoreMultiMusic(player_t *player);
 
+/// ------------------------
+/// Jingle stuff
+/// ------------------------
+
+typedef enum
+{
+	JT_NONE,   // Null state
+	JT_OTHER,  // Other state
+	JT_MASTER, // Main level music
+	JT_1UP, // Extra life
+	JT_SHOES,  // Speed shoes
+	JT_INV, // Invincibility
+	JT_MINV, // Mario Invincibility
+	JT_DROWN,  // Drowning
+	JT_SUPER,  // Super Sonic
+	JT_GOVER, // Game Over
+	JT_NIGHTSTIMEOUT, // NiGHTS Time Out (10 seconds)
+	JT_SSTIMEOUT, // NiGHTS Special Stage Time Out (10 seconds)
+
+	// these are not jingles
+	// JT_LCLEAR, // Level Clear
+	// JT_RACENT, // Multiplayer Intermission
+	// JT_CONTSC, // Continue
+
+	NUMJINGLES
+} jingletype_t;
+
+typedef struct
+{
+	char musname[7];
+	boolean looping;
+} jingle_t;
+
+extern jingle_t jingleinfo[NUMJINGLES];
+
+#define JINGLEPOSTFADE 1000
+
+void P_PlayJingle(player_t *player, jingletype_t jingletype);
+boolean P_EvaluateMusicStatus(UINT16 status);
+void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status);
+
 //
 // P_MOBJ
 //
@@ -231,6 +278,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
 boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
 void P_RunShields(void);
 void P_RunOverlays(void);
+void P_HandleMinecartSegments(mobj_t *mobj);
 void P_MobjThinker(mobj_t *mobj);
 boolean P_RailThinker(mobj_t *mobj);
 void P_PushableThinker(mobj_t *mobj);
@@ -256,6 +304,7 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f
 boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover);
 boolean P_CheckDeathPitCollide(mobj_t *mo);
 boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover);
+void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype);
 
 mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type);
 
@@ -280,6 +329,8 @@ mobj_t *P_GetClosestAxis(mobj_t *source);
 
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover);
 
+void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot);
+
 void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration);
 #define PAL_WHITE    1
 #define PAL_MIXUP    2
@@ -307,6 +358,8 @@ void P_NewChaseDir(mobj_t *actor);
 boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed_t dist);
 
 mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz, boolean lookforplayers);
+void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo);
+#define P_IsFlickyCenter(type) (type > MT_FLICKY_01 && type < MT_SEED && (type - MT_FLICKY_01) % 2 ? 1 : 0)
 void P_InternalFlickyBubble(mobj_t *actor);
 void P_InternalFlickyFly(mobj_t *actor, fixed_t flyspeed, fixed_t targetdist, fixed_t chasez);
 void P_InternalFlickyHop(mobj_t *actor, fixed_t momz, fixed_t momh, angle_t angle);
@@ -320,6 +373,7 @@ void P_InternalFlickyHop(mobj_t *actor, fixed_t momz, fixed_t momh, angle_t angl
 extern boolean floatok;
 extern fixed_t tmfloorz;
 extern fixed_t tmceilingz;
+extern ffloor_t *tmfloorrover, *tmceilingrover;
 extern mobj_t *tmfloorthing, *tmhitthing, *tmthing;
 extern camera_t *mapcampointer;
 extern fixed_t tmx;
@@ -360,7 +414,7 @@ void P_DelPrecipSeclist(mprecipsecnode_t *node);
 void P_CreateSecNodeList(mobj_t *thing, fixed_t x, fixed_t y);
 void P_Initsecnode(void);
 
-void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist);
+void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 damagetype);
 
 fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height);
 boolean PIT_PushableMoved(mobj_t *thing);
@@ -410,6 +464,8 @@ typedef struct BasicFF_s
 #define DMG_DEATHPIT   0x80+3
 #define DMG_CRUSHED    0x80+4
 #define DMG_SPECTATOR  0x80+5
+// Masks
+#define DMG_CANHURTSELF 0x40 // Flag - can hurt self/team indirectly, such as through mines
 #define DMG_DEATHMASK  DMG_INSTAKILL // if bit 7 is set, this is a death type instead of a damage type
 
 void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period);
diff --git a/src/p_map.c b/src/p_map.c
index 6d1760596520f13043d8e6048800fd264d127ffe..6beca92f14c2a5614971b21e4925b3c57d1f705e 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -52,6 +52,7 @@ fixed_t tmfloorz, tmceilingz;
 static fixed_t tmdropoffz, tmdrpoffceilz; // drop-off floor/ceiling heights
 mobj_t *tmfloorthing; // the thing corresponding to tmfloorz or NULL if tmfloorz is from a sector
 mobj_t *tmhitthing; // the solid thing you bumped into (for collisions)
+ffloor_t *tmfloorrover, *tmceilingrover;
 #ifdef ESLOPE
 pslope_t *tmfloorslope, *tmceilingslope;
 #endif
@@ -101,6 +102,8 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
 
 	thing->floorz = tmfloorz;
 	thing->ceilingz = tmceilingz;
+	thing->floorrover = tmfloorrover;
+	thing->ceilingrover = tmceilingrover;
 
 	return true;
 }
@@ -109,74 +112,213 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
 //                       MOVEMENT ITERATOR FUNCTIONS
 // =========================================================================
 
+// P_DoSpring
+//
+// MF_SPRING does some weird, mildly hacky stuff sometimes.
+//   mass = vertical speed
+//   damage = horizontal speed
+//   raisestate = state to change spring to on collision
+//   reactiontime = number of times it can give 10 points (0 is standard)
+//   painchance = spring mode:
+//       0 = standard vanilla spring behaviour
+//     Positive spring modes are minor variants of vanilla spring behaviour.
+//       1 = launch players in jump
+//       2 = don't modify player at all, just add momentum
+//     Negative spring modes are mildly-related gimmicks with customisation.
+//      -1 = pinball bumper
+//     Any other spring mode defaults to standard vanilla spring behaviour,
+//     ****** but forward compatibility is not guaranteed for these. ******
+//
+
 boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 {
-	INT32 pflags;
-	fixed_t offx, offy;
 	fixed_t vertispeed = spring->info->mass;
 	fixed_t horizspeed = spring->info->damage;
-	UINT8 secondjump;
+	boolean final = false;
+	UINT8 strong = 0;
 
-	if (object->eflags & MFE_SPRUNG) // Object was already sprung this tic
+	// Object was already sprung this tic
+	if (object->eflags & MFE_SPRUNG)
 		return false;
 
 	// Spectators don't trigger springs.
 	if (object->player && object->player->spectator)
 		return false;
 
-	if (object->player && (object->player->powers[pw_carry] == CR_NIGHTSMODE))
-	{
-		/*Someone want to make these work like bumpers?*/
+	// "Even in Death" is a song from Volume 8, not a command.
+	if (!spring->health || !object->health)
 		return false;
-	}
 
-	if (object->player
-	&& ((object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
-	|| (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)))
+	if (object->player)
 	{
-		S_StartSound(object, sfx_s3k8b);
-		horizspeed = FixedMul(horizspeed, (4*FRACUNIT)/3);
-		vertispeed = FixedMul(vertispeed, (6*FRACUNIT)/5); // aprox square root of above
+		if (object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
+			strong = 1;
+		else if (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)
+			strong = 2;
 	}
 
-	object->eflags |= MFE_SPRUNG; // apply this flag asap!
-	spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify
-
-	if ((horizspeed && vertispeed) || (object->player && object->player->homing)) // Mimic SA
+	if (spring->info->painchance == -1) // Pinball bumper mode.
 	{
-		object->momx = object->momy = 0;
-		P_TryMove(object, spring->x, spring->y, true);
+		// The first of the entirely different spring modes!
+		// Some of the attributes mean different things here.
+		//   mass = default strength (can be controlled by mapthing's spawnangle)
+		//   damage = unused
+		angle_t horizangle, vertiangle;
+
+		if (!vertispeed)
+			return false;
+
+		if (object->player && object->player->homing) // Sonic Heroes and Shadow the Hedgehog are the only games to contain homing-attackable bumpers!
+		{
+			horizangle = 0;
+			vertiangle = ((object->eflags & MFE_VERTICALFLIP) ? ANGLE_270 : ANGLE_90) >> ANGLETOFINESHIFT;
+			object->player->pflags &= ~PF_THOKKED;
+			if (spring->eflags & MFE_VERTICALFLIP)
+				object->z = spring->z - object->height - 1;
+			else
+				object->z = spring->z + spring->height + 1;
+		}
+		else
+		{
+			horizangle = R_PointToAngle2(spring->x, spring->y, object->x, object->y);
+			vertiangle = (R_PointToAngle2(
+							0,
+							spring->z + spring->height/2,
+							FixedHypot(object->x - spring->x, object->y - spring->y),
+							object->z + object->height/2)
+								>> ANGLETOFINESHIFT) & FINEMASK;
+		}
+
+		if (spring->spawnpoint && spring->spawnpoint->angle > 0)
+			vertispeed = (spring->spawnpoint->angle<<(FRACBITS-1))/5;
+		vertispeed = FixedMul(vertispeed, FixedMul(object->scale, spring->scale));
+
+		if (object->player)
+		{
+			fixed_t playervelocity;
+
+			if (strong)
+				vertispeed <<= 1;
+
+			if (!(object->player->pflags & PF_THOKKED) && !(object->player->homing)
+			&& ((playervelocity = FixedDiv(9*FixedHypot(object->player->speed, object->momz), 10<<FRACBITS)) > vertispeed))
+				vertispeed = playervelocity;
+
+			if (object->player->powers[pw_carry] == CR_NIGHTSMODE) // THIS has NiGHTS support, at least...
+			{
+				angle_t nightsangle = 0;
+
+				if (object->player->bumpertime >= TICRATE/4)
+					return false;
+
+				if ((object->player->pflags & PF_TRANSFERTOCLOSEST) && object->player->axis1 && object->player->axis2)
+				{
+					nightsangle = R_PointToAngle2(object->player->axis1->x, object->player->axis1->y, object->player->axis2->x, object->player->axis2->y);
+					nightsangle += ANGLE_90;
+				}
+				else if (object->target)
+				{
+					if (object->target->flags2 & MF2_AMBUSH)
+						nightsangle = R_PointToAngle2(object->target->x, object->target->y, object->x, object->y);
+					else
+						nightsangle = R_PointToAngle2(object->x, object->y, object->target->x, object->target->y);
+				}
+
+				object->player->flyangle = AngleFixed(R_PointToAngle2(
+					0,
+					spring->z + spring->height/2,
+					FixedMul(
+						FINESINE(((nightsangle - horizangle) >> ANGLETOFINESHIFT) & FINEMASK),
+						FixedHypot(object->x - spring->x, object->y - spring->y)),
+					object->z + object->height/2))>>FRACBITS;
+
+				object->player->bumpertime = TICRATE/2;
+			}
+			else
+			{
+				INT32 pflags = object->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING); // Not identical to below...
+				UINT8 secondjump = object->player->secondjump;
+				if (object->player->pflags & PF_GLIDING)
+					P_SetPlayerMobjState(object, S_PLAY_FALL);
+				P_ResetPlayer(object->player);
+				object->player->pflags |= pflags;
+				object->player->secondjump = secondjump;
+			}
+		}
+
+		if (!P_IsObjectOnGround(object)) // prevents uncurling when spinning due to "landing"
+			object->momz = FixedMul(vertispeed, FINESINE(vertiangle));
+		P_InstaThrust(object, horizangle, FixedMul(vertispeed, FINECOSINE(vertiangle)));
+
+		object->eflags |= MFE_SPRUNG; // apply this flag asap!
+
+		goto springstate;
 	}
 
+	// Does nothing?
+	if (!vertispeed && !horizspeed)
+		return false;
+
+#ifdef ESLOPE
+	object->standingslope = NULL; // Okay, now we know it's not going to be relevant - no launching off at silly angles for you.
+#endif
+
 	if (spring->eflags & MFE_VERTICALFLIP)
 		vertispeed *= -1;
 
-	if (vertispeed > 0)
-		object->z = spring->z + spring->height + 1;
-	else if (vertispeed < 0)
-		object->z = spring->z - object->height - 1;
-	else
+	if (object->player && (object->player->powers[pw_carry] == CR_NIGHTSMODE))
 	{
-		// Horizontal springs teleport you in FRONT of them.
-		object->momx = object->momy = 0;
+		/*Someone want to make these work like bumpers?*/
+		return false;
+	}
 
-		// Overestimate the distance to position you at
-		offx = P_ReturnThrustX(spring, spring->angle, (spring->radius + object->radius + 1) * 2);
-		offy = P_ReturnThrustY(spring, spring->angle, (spring->radius + object->radius + 1) * 2);
+	if (strong)
+	{
+		if (horizspeed)
+			horizspeed = FixedMul(horizspeed, (4*FRACUNIT)/3);
+		if (vertispeed)
+			vertispeed = FixedMul(vertispeed, (6*FRACUNIT)/5); // aprox square root of above
+	}
 
-		// Make it square by clipping
-		if (offx > (spring->radius + object->radius + 1))
-			offx = spring->radius + object->radius + 1;
-		else if (offx < -(spring->radius + object->radius + 1))
-			offx = -(spring->radius + object->radius + 1);
+	object->eflags |= MFE_SPRUNG; // apply this flag asap!
+	spring->flags &= ~(MF_SPRING|MF_SPECIAL); // De-solidify
 
-		if (offy > (spring->radius + object->radius + 1))
-			offy = spring->radius + object->radius + 1;
-		else if (offy < -(spring->radius + object->radius + 1))
-			offy = -(spring->radius + object->radius + 1);
+	if (spring->info->painchance != 2)
+	{
+		if ((horizspeed && vertispeed) || (object->player && object->player->homing)) // Mimic SA
+		{
+			object->momx = object->momy = 0;
+			P_TryMove(object, spring->x, spring->y, true);
+		}
 
-		// Set position!
-		P_TryMove(object, spring->x + offx, spring->y + offy, true);
+		if (vertispeed > 0)
+			object->z = spring->z + spring->height + 1;
+		else if (vertispeed < 0)
+			object->z = spring->z - object->height - 1;
+		else
+		{
+			fixed_t offx, offy;
+			// Horizontal springs teleport you in FRONT of them.
+			object->momx = object->momy = 0;
+
+			// Overestimate the distance to position you at
+			offx = P_ReturnThrustX(spring, spring->angle, (spring->radius + object->radius + 1) * 2);
+			offy = P_ReturnThrustY(spring, spring->angle, (spring->radius + object->radius + 1) * 2);
+
+			// Make it square by clipping
+			if (offx > (spring->radius + object->radius + 1))
+				offx = spring->radius + object->radius + 1;
+			else if (offx < -(spring->radius + object->radius + 1))
+				offx = -(spring->radius + object->radius + 1);
+
+			if (offy > (spring->radius + object->radius + 1))
+				offy = spring->radius + object->radius + 1;
+			else if (offy < -(spring->radius + object->radius + 1))
+				offy = -(spring->radius + object->radius + 1);
+
+			// Set position!
+			P_TryMove(object, spring->x + offx, spring->y + offy, true);
+		}
 	}
 
 	if (vertispeed)
@@ -186,19 +328,21 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		P_InstaThrustEvenIn2D(object, spring->angle, FixedMul(horizspeed,FixedSqrt(FixedMul(object->scale, spring->scale))));
 
 	// Re-solidify
-	spring->flags |= (spring->info->flags & (MF_SPECIAL|MF_SOLID));
-
-	P_SetMobjState(spring, spring->info->raisestate);
+	spring->flags |= (spring->info->flags & (MF_SPRING|MF_SPECIAL));
 
 	if (object->player)
 	{
+		INT32 pflags;
+		UINT8 secondjump;
+		boolean washoming;
+
 		if (spring->flags & MF_ENEMY) // Spring shells
 			P_SetTarget(&spring->target, object);
 
 		if (horizspeed)
 		{
 			object->player->drawangle = spring->angle;
-			if (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0)
+			if (vertispeed || (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0))
 			{
 				object->angle = spring->angle;
 
@@ -212,19 +356,28 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			}
 		}
 
-		pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_SHIELDABILITY|PF_BOUNCING); // I still need these.
+		pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING); // I still need these.
 		secondjump = object->player->secondjump;
+		washoming = object->player->homing;
+		if (object->player->pflags & PF_GLIDING)
+			P_SetPlayerMobjState(object, S_PLAY_FALL);
 		P_ResetPlayer(object->player);
 
-		if (spring->info->painchance)
+		if (spring->info->painchance == 1) // For all those ancient, SOC'd abilities.
 		{
 			object->player->pflags |= P_GetJumpFlags(object->player);
 			P_SetPlayerMobjState(object, S_PLAY_JUMP);
 		}
-		else if (!vertispeed || (pflags & PF_BOUNCING)) // horizontal spring or bouncing
+		else if ((spring->info->painchance == 2) || (pflags & PF_BOUNCING)) // Adding momentum only.
 		{
-			if ((pflags & PF_BOUNCING)
-			|| (pflags & (PF_JUMPED|PF_SPINNING) && (object->player->panim == PA_ROLL || object->player->panim == PA_JUMP || object->player->panim == PA_FALL)))
+			object->player->pflags |= (pflags &~ PF_STARTJUMP);
+			object->player->secondjump = secondjump;
+			if (washoming)
+				object->player->pflags &= ~PF_THOKKED;
+		}
+		else if (!vertispeed)
+		{
+			if (pflags & (PF_JUMPED|PF_SPINNING))
 			{
 				object->player->pflags |= pflags;
 				object->player->secondjump = secondjump;
@@ -239,10 +392,31 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	}
 
 #ifdef ESLOPE
-	object->standingslope = NULL; // Okay, now we know it's not going to be relevant - no launching off at silly angles for you.
+	object->standingslope = NULL; // And again.
 #endif
 
-	return true;
+	final = true;
+
+springstate:
+	if ((statenum_t)(spring->state-states) < spring->info->raisestate)
+	{
+		P_SetMobjState(spring, spring->info->raisestate);
+		if (object->player && spring->reactiontime && !(spring->info->flags & MF_ENEMY))
+		{
+			if (object->player->powers[pw_carry] != CR_NIGHTSMODE) // don't make graphic in NiGHTS
+				P_SetMobjState(P_SpawnMobj(spring->x, spring->y, spring->z + (spring->height/2), MT_SCORE), mobjinfo[MT_SCORE].spawnstate+11);
+			P_AddPlayerScore(object->player, 10);
+			spring->reactiontime--;
+		}
+
+		if (strong)
+		{
+			P_TwinSpinRejuvenate(object->player, (strong == 1 ? object->player->thokitem : object->player->revitem));
+			S_StartSound(object, sfx_sprong); // strong spring. sprong.
+		}
+	}
+
+	return final;
 }
 
 static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
@@ -392,13 +566,50 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	}
 }
 
+// Boss 5 post-defeat comedy
+static void P_SlapStick(mobj_t *fang, mobj_t *pole)
+{
+	fixed_t momx1, momx2, momy1, momy2;
+
+#define dist 3
+	momx1 = pole->momx/dist;
+	momy1 = pole->momy/dist;
+	momx2 = fang->momx/dist;
+	momy2 = fang->momy/dist;
+
+	pole->tracer->momx = momx1 + (dist-1)*momx2;
+	pole->tracer->momy = momy1 + (dist-1)*momy2;
+	fang->momx = (dist-1)*momx1 + momx2;
+	fang->momy = (dist-1)*momy1 + momy2;
+#undef dist
+
+	P_SetMobjState(pole, pole->info->deathstate);
+
+	P_SetObjectMomZ(pole->tracer, 6*FRACUNIT, false);
+	pole->tracer->flags &= ~(MF_NOGRAVITY|MF_NOCLIP);
+	pole->tracer->movedir = ANGLE_67h;
+	if ((R_PointToAngle(fang->x - pole->tracer->x, fang->y - pole->tracer->y) - pole->angle) > ANGLE_180)
+		pole->tracer->movedir = InvAngle(pole->tracer->movedir);
+
+	P_SetObjectMomZ(fang, 14*FRACUNIT, false);
+	fang->flags |= MF_NOGRAVITY|MF_NOCLIP;
+	P_SetMobjState(fang, fang->info->xdeathstate);
+
+	pole->tracer->tics = pole->tics = fang->tics;
+
+	var1 = var2 = 0;
+	A_Scream(pole->tracer);
+	S_StartSound(fang, sfx_altdi1);
+
+	P_SetTarget(&pole->tracer, NULL);
+}
+
 //
 // PIT_CheckThing
 //
 static boolean PIT_CheckThing(mobj_t *thing)
 {
 	fixed_t blockdist;
-	boolean iwassprung = false;
 
 	// don't clip against self
 	if (thing == tmthing)
@@ -514,7 +725,28 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE)))
+	// vectorise metal - done in a special case as at this point neither has the right flags for touching
+	if (thing->type == MT_METALSONIC_BATTLE
+	&& (tmthing->flags & MF_MISSILE)
+	&& tmthing->target != thing
+	&& thing->state == &states[thing->info->spawnstate])
+	{
+		blockdist = thing->radius + tmthing->radius;
+
+		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
+			return true; // didn't hit it
+
+		if (tmthing->z > thing->z + thing->height)
+			return true; // overhead
+		if (tmthing->z + tmthing->height < thing->z)
+			return true; // underneath
+
+		thing->flags2 |= MF2_CLASSICPUSH;
+
+		return true;
+	}
+
+	if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING)))
 		return true;
 
 	// Don't collide with your buddies while NiGHTS-flying.
@@ -622,6 +854,145 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	}
 #endif
 
+	if (tmthing->type == MT_MINECART)
+	{
+		//height check
+		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !(thing->health))
+			return true;
+
+		if (thing->type == MT_TNTBARREL)
+			P_KillMobj(thing, tmthing, tmthing->target, 0);
+		else if ((thing->flags & MF_MONITOR) || (thing->flags & MF_ENEMY))
+		{
+			P_KillMobj(thing, tmthing, tmthing->target, 0);
+			if (tmthing->momz*P_MobjFlip(tmthing) < 0)
+				tmthing->momz = abs(tmthing->momz)*P_MobjFlip(tmthing);
+		}
+	}
+
+	if (thing->type == MT_SALOONDOOR && tmthing->player)
+	{
+		if (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer) && tmthing->tracer->health)
+		{
+			fixed_t dx = tmthing->tracer->momx;
+			fixed_t dy = tmthing->tracer->momy;
+			fixed_t dm = min(FixedHypot(dx, dy), 16*FRACUNIT);
+			angle_t ang = R_PointToAngle2(0, 0, dx, dy) - thing->angle;
+			fixed_t s = FINESINE((ang >> ANGLETOFINESHIFT) & FINEMASK);
+			S_StartSound(tmthing, thing->info->activesound);
+			thing->extravalue2 += 2*FixedMul(s, dm)/3;
+			return true;
+		}
+	}
+
+	if (thing->type == MT_TNTBARREL && tmthing->player)
+	{
+		if (tmthing->momz < 0)
+		{
+			if (tmthing->z + tmthing->momz > thing->z + thing->height)
+				return true;
+		}
+		else
+		{
+			if (tmthing->z > thing->z + thing->height)
+				return true;
+		}
+
+		if (tmthing->momz > 0)
+		{
+			if (tmthing->z + tmthing->height + tmthing->momz < thing->z)
+				return true;
+		}
+		else
+		{
+			if (tmthing->z + tmthing->height < thing->z)
+				return true;
+		}
+
+		if ((tmthing->player->pflags & (PF_SPINNING | PF_GLIDING))
+			|| ((tmthing->player->pflags & PF_JUMPED)
+				&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
+					|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
+			|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
+			|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
+				&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height / 2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))
+			|| (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (tmthing->player->pflags & PF_SHIELDABILITY)))
+			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
+	}
+
+	if (thing->type == MT_VULTURE && tmthing->type == MT_VULTURE)
+	{
+		fixed_t dx = thing->x - tmthing->x;
+		fixed_t dy = thing->y - tmthing->y;
+		fixed_t dz = thing->z - tmthing->z;
+		fixed_t dm = FixedHypot(dz, FixedHypot(dx, dy));
+		thing->momx += FixedDiv(dx, dm);
+		thing->momy += FixedDiv(dy, dm);
+		thing->momz += FixedDiv(dz, dm);
+	}
+
+	if (tmthing->type == MT_FANG && thing->type == MT_FSGNB)
+	{
+		if (thing->z > tmthing->z + tmthing->height)
+			return true; // overhead
+		if (thing->z + thing->height < tmthing->z)
+			return true; // underneath
+		if (!thing->tracer)
+			return true;
+		P_SlapStick(tmthing, thing);
+		// no return value was used in the original prototype script at this point,
+		// so I'm assuming we fall back on the solid code to determine how it all ends?
+		// -- Monster Iestyn
+	}
+
+	// Billiards mines!
+	if (thing->type == MT_BIGMINE)
+	{
+		if (tmthing->type == MT_BIGMINE)
+		{
+			if (!tmthing->momx && !tmthing->momy)
+				return true;
+			if ((statenum_t)(thing->state-states) >= thing->info->meleestate)
+				return true;
+			if (thing->z > tmthing->z + tmthing->height)
+				return true; // overhead
+			if (thing->z + thing->height < tmthing->z)
+				return true; // underneath
+
+			thing->momx = tmthing->momx/3;
+			thing->momy = tmthing->momy/3;
+			thing->momz = tmthing->momz/3;
+			tmthing->momx /= -8;
+			tmthing->momy /= -8;
+			tmthing->momz /= -8;
+			if (thing->info->activesound)
+				S_StartSound(thing, thing->info->activesound);
+			P_SetMobjState(thing, thing->info->meleestate);
+			P_SetTarget(&thing->tracer, tmthing->tracer);
+			return true;
+		}
+		else if (tmthing->type == MT_CRUSHCLAW)
+		{
+			if (tmthing->extravalue1 <= 0)
+				return true;
+			if ((statenum_t)(thing->state-states) >= thing->info->meleestate)
+				return true;
+			if (thing->z > tmthing->z + tmthing->height)
+				return true; // overhead
+			if (thing->z + thing->height < tmthing->z)
+				return true; // underneath
+
+			thing->momx = P_ReturnThrustX(tmthing, tmthing->angle, 2*tmthing->extravalue1*tmthing->scale/3);
+			thing->momy = P_ReturnThrustY(tmthing, tmthing->angle, 2*tmthing->extravalue1*tmthing->scale/3);
+			if (thing->info->activesound)
+				S_StartSound(thing, thing->info->activesound);
+			P_SetMobjState(thing, thing->info->meleestate);
+			if (tmthing->tracer)
+				P_SetTarget(&thing->tracer, tmthing->tracer->target);
+			return false;
+		}
+	}
+
 	// 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)
 	{
@@ -639,35 +1010,37 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	if (thing->flags & MF_PAIN)
+	if (thing->flags & MF_PAIN && tmthing->player)
 	{ // Player touches painful thing sitting on the floor
 		// see if it went over / under
 		if (thing->z > tmthing->z + tmthing->height)
 			return true; // overhead
 		if (thing->z + thing->height < tmthing->z)
 			return true; // underneath
-		if (tmthing->player && tmthing->flags & MF_SHOOTABLE && thing->health > 0)
+		if (tmthing->flags & MF_SHOOTABLE && thing->health > 0)
 		{
-			UINT8 damagetype = 0;
-			if (thing->flags & MF_FIRE) // BURN!
+			UINT8 damagetype = (thing->info->mass & 0xFF);
+			if (!damagetype && thing->flags & MF_FIRE) // BURN!
 				damagetype = DMG_FIRE;
-			P_DamageMobj(tmthing, thing, thing, 1, damagetype);
+			if (P_DamageMobj(tmthing, thing, thing, 1, damagetype) && (damagetype = (thing->info->mass>>8)))
+				S_StartSound(thing, damagetype);
 		}
 		return true;
 	}
-	else if (tmthing->flags & MF_PAIN)
+	else if (tmthing->flags & MF_PAIN && thing->player)
 	{ // Painful thing splats player in the face
 		// 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->player && thing->flags & MF_SHOOTABLE && tmthing->health > 0)
+		if (thing->flags & MF_SHOOTABLE && tmthing->health > 0)
 		{
-			UINT8 damagetype = 0;
-			if (tmthing->flags & MF_FIRE) // BURN!
+			UINT8 damagetype = (tmthing->info->mass & 0xFF);
+			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
 				damagetype = DMG_FIRE;
-			P_DamageMobj(thing, tmthing, tmthing, 1, damagetype);
+			if (P_DamageMobj(thing, tmthing, tmthing, 1, damagetype) && (damagetype = (tmthing->info->mass>>8)))
+				S_StartSound(tmthing, damagetype);
 		}
 		return true;
 	}
@@ -714,6 +1087,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		else if (tmz > thzh - sprarea && tmz < thzh) // Don't damage people springing up / down
 			return true;
 	}
+
 	// missiles can hit other things
 	if (tmthing->flags & MF_MISSILE || tmthing->type == MT_SHELL)
 	{
@@ -768,30 +1142,11 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 		if (thing->type == MT_EGGSHIELD)
 		{
-			fixed_t touchx, touchy;
-			angle_t angle;
+			angle_t angle = (R_PointToAngle2(thing->x, thing->y, tmthing->x - tmthing->momx, tmthing->y - tmthing->momy) - thing->angle) - ANGLE_90;
 
-			if (P_AproxDistance(tmthing->x-thing->x, tmthing->y-thing->y) >
-				P_AproxDistance((tmthing->x-tmthing->momx)-thing->x, (tmthing->y-tmthing->momy)-thing->y))
-			{
-				touchx = tmthing->x + tmthing->momx;
-				touchy = tmthing->y + tmthing->momy;
-			}
-			else
-			{
-				touchx = tmthing->x;
-				touchy = tmthing->y;
-			}
-
-			angle = R_PointToAngle2(thing->x, thing->y, touchx, touchy) - thing->angle;
-
-			if (!(angle > ANGLE_90 && angle < ANGLE_270)) // hit front of shield, didn't destroy it
-				return false;
-			else // hit shield from behind, shield is destroyed!
-			{
+			if (angle < ANGLE_180) // hit shield from behind, shield is destroyed!
 				P_KillMobj(thing, tmthing, tmthing, 0);
-				return false;
-			}
+			return false;
 		}
 
 		// damage / explode
@@ -834,8 +1189,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			tmthing->y = thing->y;
 			P_SetThingPosition(tmthing);
 		}
-		else if (!(tmthing->type == MT_SHELL && thing->player)) // player collision handled in touchspecial
-			P_DamageMobj(thing, tmthing, tmthing->target, 1, 0);
+		else if (!(tmthing->type == MT_SHELL && thing->player)) // player collision handled in touchspecial for shell
+		{
+			UINT8 damagetype = tmthing->info->mass;
+			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
+				damagetype = DMG_FIRE;
+			P_DamageMobj(thing, tmthing, tmthing->target, 1, damagetype);
+		}
 
 		// don't traverse any more
 
@@ -905,26 +1265,39 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		P_SetTarget(&thing->target, tmthing);
 	}
 
-	// Respawn rings and items
+	// NiGHTS lap logic
 	if ((tmthing->type == MT_NIGHTSDRONE || thing->type == MT_NIGHTSDRONE)
 	 && (tmthing->player || thing->player))
 	{
 		mobj_t *droneobj = (tmthing->type == MT_NIGHTSDRONE) ? tmthing : thing;
 		player_t *pl = (droneobj == thing) ? tmthing->player : thing->player;
 
-		// Must be in bonus time, and must be NiGHTS, must wait about a second
+		// Must be NiGHTS, must wait about a second
 		// must be flying in the SAME DIRECTION as the last time you came through.
 		// not (your direction) xor (stored direction)
 		// In other words, you can't u-turn and respawn rings near the drone.
-		if (pl->bonustime && (pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
-		   !(pl->anotherflyangle >= 90 &&   pl->anotherflyangle <= 270)
-		^ (droneobj->extravalue1 >= 90 && droneobj->extravalue1 <= 270)
+		if ((pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
+		   !(pl->flyangle > 90 &&   pl->flyangle < 270)
+		^ (droneobj->extravalue1 > 90 && droneobj->extravalue1 < 270)
 		))
 		{
-			// Reload all the fancy ring stuff!
-			P_ReloadRings();
+			pl->marelap++;
+			pl->totalmarelap++;
+			pl->lapbegunat = leveltime;
+			pl->lapstartedtime = pl->nightstime;
+
+			if (pl->bonustime)
+			{
+				pl->marebonuslap++;
+				pl->totalmarebonuslap++;
+
+				// Respawn rings and items
+				P_ReloadRings();
+			}
+
+			P_RunNightsLapExecutors(pl->mo);
 		}
-		droneobj->extravalue1 = pl->anotherflyangle;
+		droneobj->extravalue1 = pl->flyangle;
 		droneobj->extravalue2 = (INT32)leveltime + TICRATE;
 	}
 
@@ -1030,15 +1403,28 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	if (tmthing->flags & MF_PUSHABLE)
 	{
 		if (thing->type == MT_FAN || thing->type == MT_STEAM)
+		{
 			P_DoFanAndGasJet(thing, tmthing);
+			return true;
+		}
 		else if (thing->flags & MF_SPRING)
 		{
 			if ( thing->z <= tmthing->z + tmthing->height
 			&& tmthing->z <= thing->z + thing->height)
-				iwassprung = P_DoSpring(thing, tmthing);
+				if (P_DoSpring(thing, tmthing))
+					return false;
+			return true;
 		}
 	}
 
+	// thanks to sal for solidenemies dot lua
+	if (thing->flags & (MF_ENEMY|MF_BOSS) && tmthing->flags & (MF_ENEMY|MF_BOSS))
+	{
+		if ((thing->z + thing->height >= tmthing->z)
+		&& (tmthing->z + tmthing->height >= thing->z))
+			return false;
+	}
+
 	// Damage other players when invincible
 	if (tmthing->player && thing->player
 	// Make sure they aren't able to damage you ANYWHERE along the Z axis, you have to be TOUCHING the person.
@@ -1103,7 +1489,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			{
 				// Objects kill you if it falls from above.
 				if (thing != tmthing->target)
-					P_DamageMobj(thing, tmthing, tmthing->target, 1, DMG_INSTAKILL);
+					P_DamageMobj(thing, tmthing, tmthing->target, 1, DMG_CRUSHED);
 
 				tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
 				// The tmthing->target allows the pusher of the object
@@ -1122,45 +1508,43 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 		if (thing->type == MT_FAN || thing->type == MT_STEAM)
 			P_DoFanAndGasJet(thing, tmthing);
-		else if (thing->flags & MF_SPRING)
+		else if (thing->flags & MF_SPRING && tmthing->player->powers[pw_carry] != CR_MINECART)
 		{
 			if ( thing->z <= tmthing->z + tmthing->height
 			&& tmthing->z <= thing->z + thing->height)
-				iwassprung = P_DoSpring(thing, tmthing);
+				if (P_DoSpring(thing, tmthing))
+					return false;
+			return true;
 		}
-		// Are you touching the side of the object you're interacting with?
-		else if (thing->z - FixedMul(FRACUNIT, thing->scale) <= tmthing->z + tmthing->height
-			&& thing->z + thing->height + FixedMul(FRACUNIT, thing->scale) >= tmthing->z)
+		// Monitor?
+		else if (thing->flags & MF_MONITOR
+		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2))
+		&& (!(thing->flags & MF_SOLID) || P_PlayerCanDamage(tmthing->player, thing)))
 		{
-			// 0 = none, 1 = elemental pierce, 2 = bubble bounce
-			UINT8 elementalpierce = (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (tmthing->player->pflags & PF_SHIELDABILITY)
-			? (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
-			: 0);
-			if (thing->flags & MF_MONITOR
-				&& (tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
-				|| ((tmthing->player->pflags & PF_JUMPED)
-					&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
-					|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
-				|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-				|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
-					&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))
-				|| elementalpierce))
+			if (thing->z - thing->scale <= tmthing->z + tmthing->height
+			&& thing->z + thing->height + thing->scale >= tmthing->z)
 			{
 				player_t *player = tmthing->player;
+				// 0 = none, 1 = elemental pierce, 2 = bubble bounce
+				UINT8 elementalpierce = (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY)
+				? (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
+				: 0);
 				SINT8 flipval = P_MobjFlip(thing); // Save this value in case monitor gets removed.
 				fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
 				fixed_t *z = &tmthing->z; // aau.
-				P_DamageMobj(thing, tmthing, tmthing, 1, 0); // break the monitor
 				// Going down? Then bounce back up.
-				if ((P_MobjWasRemoved(thing) // Monitor was removed
-					|| !thing->health) // or otherwise popped
+				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...
 				{
 					if (elementalpierce == 2)
 						P_DoBubbleBounce(player);
 					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+					{
 						*momz = -*momz; // 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 == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
 				{
@@ -1171,30 +1555,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				else
 					*z -= *momz; // to ensure proper collision.
 			}
+
+			return true;
 		}
 	}
 
-	if (!(tmthing->player) && (thing->player))
+	if ((!tmthing->player) && (thing->player))
 		; // no solid thing should ever be able to step up onto a player
-	else if (thing->flags & MF_SPRING && (tmthing->player || tmthing->flags & MF_PUSHABLE))
-	{
-		if (iwassprung) // this spring caused you to gain MFE_SPRUNG just now...
-			return false; // "cancel" P_TryMove via blocking so you keep your current position
-	}
-	else if (tmthing->flags & MF_SPRING && (thing->flags & MF_PUSHABLE))
-		; // Fix a few nasty spring-jumping bugs that happen sometimes.
-	// Monitors are not treated as solid to players who are jumping, spinning or gliding,
-	// unless it's a CTF team monitor and you're on the wrong team
-	else if (thing->flags & MF_MONITOR && tmthing->player
-	&& (tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
-		|| ((tmthing->player->pflags & PF_JUMPED)
-			&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
-			|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
-		|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-		|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
-			&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0)))
-	&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2)))
-		;
 	// z checking at last
 	// Treat noclip things as non-solid!
 	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
@@ -1212,6 +1579,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				if (thing->z + thing->height > tmfloorz)
 				{
 					tmfloorz = thing->z + thing->height;
+					tmfloorrover = NULL;
 #ifdef ESLOPE
 					tmfloorslope = NULL;
 #endif
@@ -1221,19 +1589,18 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			topz = thing->z - thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways
 
-			if (thing->flags & MF_SPRING)
-				;
 			// block only when jumping not high enough,
 			// (dont climb max. 24units while already in air)
 			// since return false doesn't handle momentum properly,
 			// we lie to P_TryMove() so it's always too high
-			else if (tmthing->player && tmthing->z + tmthing->height > topz
+			if (tmthing->player && tmthing->z + tmthing->height > topz
 				&& tmthing->z + tmthing->height < tmthing->ceilingz)
 			{
-				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->flags2 & MF2_STANDONME)) // Gold monitor hack...
+				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->info->flags & MF_MONITOR)) // Gold monitor hack...
 					return false;
 
 				tmfloorz = tmceilingz = topz; // block while in air
+				tmceilingrover = NULL;
 #ifdef ESLOPE
 				tmceilingslope = NULL;
 #endif
@@ -1242,6 +1609,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			else if (topz < tmceilingz && tmthing->z <= thing->z+thing->height)
 			{
 				tmceilingz = topz;
+				tmceilingrover = NULL;
 #ifdef ESLOPE
 				tmceilingslope = NULL;
 #endif
@@ -1258,6 +1626,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				if (thing->z < tmceilingz)
 				{
 					tmceilingz = thing->z;
+					tmceilingrover = NULL;
 #ifdef ESLOPE
 					tmceilingslope = NULL;
 #endif
@@ -1267,19 +1636,18 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			topz = thing->z + thing->height + thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways
 
-			if (thing->flags & MF_SPRING)
-				;
 			// block only when jumping not high enough,
 			// (dont climb max. 24units while already in air)
 			// since return false doesn't handle momentum properly,
 			// we lie to P_TryMove() so it's always too high
-			else if (tmthing->player && tmthing->z < topz
+			if (tmthing->player && tmthing->z < topz
 				&& tmthing->z > tmthing->floorz)
 			{
-				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->flags2 & MF2_STANDONME)) // Gold monitor hack...
+				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->info->flags & MF_MONITOR)) // Gold monitor hack...
 					return false;
 
 				tmfloorz = tmceilingz = topz; // block while in air
+				tmfloorrover = NULL;
 #ifdef ESLOPE
 				tmfloorslope = NULL;
 #endif
@@ -1288,6 +1656,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			else if (topz > tmfloorz && tmthing->z+tmthing->height >= thing->z)
 			{
 				tmfloorz = topz;
+				tmfloorrover = NULL;
 #ifdef ESLOPE
 				tmfloorslope = NULL;
 #endif
@@ -1439,6 +1808,7 @@ static boolean PIT_CheckLine(line_t *ld)
 	{
 		tmceilingz = opentop;
 		ceilingline = ld;
+		tmceilingrover = openceilingrover;
 #ifdef ESLOPE
 		tmceilingslope = opentopslope;
 #endif
@@ -1447,6 +1817,7 @@ static boolean PIT_CheckLine(line_t *ld)
 	if (openbottom > tmfloorz)
 	{
 		tmfloorz = openbottom;
+		tmfloorrover = openfloorrover;
 #ifdef ESLOPE
 		tmfloorslope = openbottomslope;
 #endif
@@ -1528,6 +1899,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 	// will adjust them.
 	tmfloorz = tmdropoffz = P_GetFloorZ(thing, newsubsec->sector, x, y, NULL); //newsubsec->sector->floorheight;
 	tmceilingz = P_GetCeilingZ(thing, newsubsec->sector, x, y, NULL); //newsubsec->sector->ceilingheight;
+	tmfloorrover = NULL;
+	tmceilingrover = NULL;
 #ifdef ESLOPE
 	tmfloorslope = newsubsec->sector->f_slope;
 	tmceilingslope = newsubsec->sector->c_slope;
@@ -1571,6 +1944,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 					{
 						if (tmfloorz < topheight - sinklevel) {
 							tmfloorz = topheight - sinklevel;
+							tmfloorrover = rover;
 #ifdef ESLOPE
 							tmfloorslope = *rover->t_slope;
 #endif
@@ -1580,6 +1954,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 					{
 						if (tmceilingz > bottomheight + sinklevel) {
 							tmceilingz = bottomheight + sinklevel;
+							tmceilingrover = rover;
 #ifdef ESLOPE
 							tmceilingslope = *rover->b_slope;
 #endif
@@ -1604,6 +1979,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				{
 					if (tmfloorz < thing->z) {
 						tmfloorz = thing->z;
+						tmfloorrover = rover;
 #ifdef ESLOPE
 						tmfloorslope = NULL;
 #endif
@@ -1622,6 +1998,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				&& !(rover->flags & FF_REVERSEPLATFORM))
 			{
 				tmfloorz = tmdropoffz = topheight;
+				tmfloorrover = rover;
 #ifdef ESLOPE
 				tmfloorslope = *rover->t_slope;
 #endif
@@ -1631,6 +2008,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				&& !(thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE)))
 			{
 				tmceilingz = tmdrpoffceilz = bottomheight;
+				tmceilingrover = rover;
 #ifdef ESLOPE
 				tmceilingslope = *rover->b_slope;
 #endif
@@ -1711,6 +2089,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 #ifdef ESLOPE
 							tmfloorslope = NULL;
 #endif
+							tmfloorrover = NULL;
 						}
 
 						if (polybottom < tmceilingz && abs(delta1) >= abs(delta2)) {
@@ -1718,6 +2097,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 #ifdef ESLOPE
 							tmceilingslope = NULL;
 #endif
+							tmceilingrover = NULL;
 						}
 					}
 					plink = (polymaplink_t *)(plink->link.next);
@@ -1964,18 +2344,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 	return true;
 }
 
-//
-// CheckMissileImpact
-//
-static void CheckMissileImpact(mobj_t *mobj)
-{
-	if (!(mobj->flags & MF_MISSILE) || !mobj->target)
-		return;
-
-	if (!mobj->target->player)
-		return;
-}
-
 // The highest the camera will "step up" onto another floor.
 #define MAXCAMERASTEPMOVE MAXSTEPMOVE
 
@@ -2127,6 +2495,8 @@ boolean PIT_PushableMoved(mobj_t *thing)
 		mobj_t *oldthing = tmthing;
 		line_t *oldceilline = ceilingline;
 		line_t *oldblockline = blockingline;
+		ffloor_t *oldflrrover = tmfloorrover;
+		ffloor_t *oldceilrover = tmceilingrover;
 #ifdef ESLOPE
 		pslope_t *oldfslope = tmfloorslope;
 		pslope_t *oldcslope = tmceilingslope;
@@ -2143,6 +2513,8 @@ boolean PIT_PushableMoved(mobj_t *thing)
 		P_SetTarget(&tmthing, oldthing);
 		ceilingline = oldceilline;
 		blockingline = oldblockline;
+		tmfloorrover = oldflrrover;
+		tmceilingrover = oldceilrover;
 #ifdef ESLOPE
 		tmfloorslope = oldfslope;
 		tmceilingslope = oldcslope;
@@ -2196,11 +2568,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 		}
 
 		if (!P_CheckPosition(thing, tryx, tryy))
-		{
-			if (!P_MobjWasRemoved(thing))
-				CheckMissileImpact(thing);
 			return false; // solid wall or thing
-		}
 
 		if (!(thing->flags & MF_NOCLIP))
 		{
@@ -2226,7 +2594,6 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 
 			if (tmceilingz - tmfloorz < thing->height)
 			{
-				CheckMissileImpact(thing);
 				if (tmfloorthing)
 					tmhitthing = tmfloorthing;
 				return false; // doesn't fit
@@ -2237,16 +2604,10 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			if (thing->eflags & MFE_VERTICALFLIP)
 			{
 				if (thing->z < tmfloorz)
-				{
-					CheckMissileImpact(thing);
 					return false; // mobj must raise itself to fit
-				}
 			}
 			else if (tmceilingz < thingtop)
-			{
-				CheckMissileImpact(thing);
 				return false; // mobj must lower itself to fit
-			}
 
 			// Ramp test
 			if (maxstep > 0 && !(
@@ -2264,6 +2625,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 					if (thingtop == thing->ceilingz && tmceilingz > thingtop && tmceilingz - thingtop <= maxstep)
 					{
 						thing->z = (thing->ceilingz = thingtop = tmceilingz) - thing->height;
+						thing->ceilingrover = tmceilingrover;
 						thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 					}
 #ifdef ESLOPE
@@ -2271,6 +2633,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 					else if (tmceilingslope && tmceilingz < thingtop && thingtop - tmceilingz <= maxstep)
 					{
 						thing->z = (thing->ceilingz = thingtop = tmceilingz) - thing->height;
+						thing->ceilingrover = tmceilingrover;
 						thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 					}
 #endif
@@ -2278,6 +2641,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 				else if (thing->z == thing->floorz && tmfloorz < thing->z && thing->z - tmfloorz <= maxstep)
 				{
 					thing->z = thing->floorz = tmfloorz;
+					thing->floorrover = tmfloorrover;
 					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 				}
 #ifdef ESLOPE
@@ -2285,6 +2649,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 				else if (tmfloorslope && tmfloorz > thing->z && tmfloorz - thing->z <= maxstep)
 				{
 					thing->z = thing->floorz = tmfloorz;
+					thing->floorrover = tmfloorrover;
 					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 				}
 #endif
@@ -2294,7 +2659,6 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			{
 				if (thingtop - tmceilingz > maxstep)
 				{
-					CheckMissileImpact(thing);
 					if (tmfloorthing)
 						tmhitthing = tmfloorthing;
 					return false; // too big a step up
@@ -2302,18 +2666,11 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			}
 			else if (tmfloorz - thing->z > maxstep)
 			{
-				CheckMissileImpact(thing);
 				if (tmfloorthing)
 					tmhitthing = tmfloorthing;
 				return false; // too big a step up
 			}
 
-			if (tmfloorz > thing->z)
-			{
-				if (thing->flags & MF_MISSILE)
-					CheckMissileImpact(thing);
-			}
-
 			if (!allowdropoff && !(thing->flags & MF_FLOAT) && thing->type != MT_SKIM && !tmfloorthing)
 			{
 				if (thing->eflags & MFE_VERTICALFLIP)
@@ -2356,6 +2713,8 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 
 	thing->floorz = tmfloorz;
 	thing->ceilingz = tmceilingz;
+	thing->floorrover = tmfloorrover;
+	thing->ceilingrover = tmceilingrover;
 
 #ifdef ESLOPE
 	if (!(thing->flags & MF_NOCLIPHEIGHT))
@@ -2436,6 +2795,8 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 
 	thing->floorz = tmfloorz;
 	thing->ceilingz = tmceilingz;
+	thing->floorrover = tmfloorrover;
+	thing->ceilingrover = tmceilingrover;
 	thing->x = x;
 	thing->y = y;
 
@@ -2461,8 +2822,11 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 static boolean P_ThingHeightClip(mobj_t *thing)
 {
 	boolean floormoved;
-	fixed_t oldfloorz = thing->floorz;
+	fixed_t oldfloorz = thing->floorz, oldz = thing->z;
+	ffloor_t *oldfloorrover = thing->floorrover;
+	ffloor_t *oldceilingrover = thing->ceilingrover;
 	boolean onfloor = P_IsObjectOnGround(thing);//(thing->z <= thing->floorz);
+	ffloor_t *rover = NULL;
 
 	if (thing->flags & MF_NOCLIPHEIGHT)
 		return true;
@@ -2477,6 +2841,8 @@ static boolean P_ThingHeightClip(mobj_t *thing)
 
 	thing->floorz = tmfloorz;
 	thing->ceilingz = tmceilingz;
+	thing->floorrover = tmfloorrover;
+	thing->ceilingrover = tmceilingrover;
 
 	// Ugly hack?!?! As long as just ceilingz is the lowest,
 	// you'll still get crushed, right?
@@ -2485,16 +2851,23 @@ static boolean P_ThingHeightClip(mobj_t *thing)
 
 	if (onfloor && !(thing->flags & MF_NOGRAVITY) && floormoved)
 	{
-		if (thing->eflags & MFE_VERTICALFLIP)
-			thing->pmomz = thing->ceilingz - (thing->z + thing->height);
-		else
-			thing->pmomz = thing->floorz - thing->z;
-		thing->eflags |= MFE_APPLYPMOMZ;
+		rover = (thing->eflags & MFE_VERTICALFLIP) ? oldceilingrover : oldfloorrover;
 
-		if (thing->eflags & MFE_VERTICALFLIP)
-			thing->z = thing->ceilingz - thing->height;
-		else
-			thing->z = thing->floorz;
+		// Match the Thing's old floorz to an FOF and check for FF_EXISTS
+		// If ~FF_EXISTS, don't set mobj Z.
+		if (!rover || ((rover->flags & FF_EXISTS) && (rover->flags & FF_SOLID)))
+		{
+			if (thing->eflags & MFE_VERTICALFLIP)
+				thing->pmomz = thing->ceilingz - (thing->z + thing->height);
+			else
+				thing->pmomz = thing->floorz - thing->z;
+			thing->eflags |= MFE_APPLYPMOMZ;
+
+			if (thing->eflags & MFE_VERTICALFLIP)
+				thing->z = thing->ceilingz - thing->height;
+			else
+				thing->z = thing->floorz;
+		}
 	}
 	else if (!tmfloorthing)
 	{
@@ -2508,6 +2881,12 @@ static boolean P_ThingHeightClip(mobj_t *thing)
 			thing->z = thing->ceilingz - thing->height;
 	}
 
+	if (thing->z != oldz)
+	{
+		if (thing->player)
+			P_PlayerHitFloor(thing->player, false);
+	}
+
 	// debug: be sure it falls to the floor
 	thing->eflags &= ~MFE_ONGROUND;
 
@@ -3498,6 +3877,7 @@ bounceback:
 static fixed_t bombdamage;
 static mobj_t *bombsource;
 static mobj_t *bombspot;
+static UINT8 bombdamagetype;
 
 //
 // PIT_RadiusAttack
@@ -3508,17 +3888,13 @@ static boolean PIT_RadiusAttack(mobj_t *thing)
 {
 	fixed_t dx, dy, dz, dist;
 
-	if (thing == bombspot // ignore the bomb itself (Deton fix)
-	|| (bombsource && thing->type == bombsource->type)) // ignore the type of guys who dropped the bomb (Jetty-Syn Bomber or Skim can bomb eachother, but not themselves.)
+	if (thing == bombspot) // ignore the bomb itself (Deton fix)
 		return true;
 
-	if (!(thing->flags & MF_SHOOTABLE))
+	if ((thing->flags & (MF_MONITOR|MF_SHOOTABLE)) != MF_SHOOTABLE)
 		return true;
 
-	if (thing->flags & MF_BOSS)
-		return true;
-
-	if (thing->flags & MF_MONITOR)
+	 if (bombsource && thing->type == bombsource->type && !(bombdamagetype & DMG_CANHURTSELF)) // ignore the type of guys who dropped the bomb (Jetty-Syn Bomber or Skim can bomb eachother, but not themselves.)
 		return true;
 
 	dx = abs(thing->x - bombspot->x);
@@ -3542,7 +3918,7 @@ static boolean PIT_RadiusAttack(mobj_t *thing)
 
 	if (P_CheckSight(thing, bombspot))
 	{	// must be in direct path
-		P_DamageMobj(thing, bombspot, bombsource, 1, 0); // Tails 01-11-2001
+		P_DamageMobj(thing, bombspot, bombsource, 1, bombdamagetype); // Tails 01-11-2001
 	}
 
 	return true;
@@ -3552,7 +3928,7 @@ static boolean PIT_RadiusAttack(mobj_t *thing)
 // P_RadiusAttack
 // Source is the creature that caused the explosion at spot.
 //
-void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist)
+void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 damagetype)
 {
 	INT32 x, y;
 	INT32 xl, xh, yl, yh;
@@ -3569,6 +3945,7 @@ void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist)
 	bombspot = spot;
 	bombsource = source;
 	bombdamage = FixedMul(damagedist, spot->scale);
+	bombdamagetype = damagetype;
 
 	for (y = yl; y <= yh; y++)
 		for (x = xl; x <= xh; x++)
@@ -3597,6 +3974,8 @@ static boolean nofit;
 static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 {
 	mobj_t *killer = NULL;
+	//If a thing is both pushable and vulnerable, it doesn't block the crusher because it gets killed.
+	boolean immunepushable = ((thing->flags & (MF_PUSHABLE | MF_SHOOTABLE)) == MF_PUSHABLE);
 
 	if (P_ThingHeightClip(thing))
 	{
@@ -3615,7 +3994,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 	// just be blocked by another object - check if it's really a ceiling!
 	if (thing->z + thing->height > thing->ceilingz && thing->z <= thing->ceilingz)
 	{
-		if (thing->flags & MF_PUSHABLE && thing->z + thing->height > thing->subsector->sector->ceilingheight)
+		if (immunepushable && thing->z + thing->height > thing->subsector->sector->ceilingheight)
 		{
 			//Thing is a pushable and blocks the moving ceiling
 			nofit = true;
@@ -3623,7 +4002,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 		}
 
 		//Check FOFs in the sector
-		if (thing->subsector->sector->ffloors && (realcrush || thing->flags & MF_PUSHABLE))
+		if (thing->subsector->sector->ffloors && (realcrush || immunepushable))
 		{
 			ffloor_t *rover;
 			fixed_t topheight, bottomheight;
@@ -3650,7 +4029,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 				delta2 = thingtop - (bottomheight + topheight)/2;
 				if (bottomheight <= thing->ceilingz && abs(delta1) >= abs(delta2))
 				{
-					if (thing->flags & MF_PUSHABLE)
+					if (immunepushable)
 					{
 						//FOF is blocked by pushable
 						nofit = true;
@@ -3662,7 +4041,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 						thinker_t *think;
 						elevator_t *crumbler;
 
-						for (think = thinkercap.next; think != &thinkercap; think = think->next)
+						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 						{
 							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
 								continue;
@@ -3708,6 +4087,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 boolean P_CheckSector(sector_t *sector, boolean crunch)
 {
 	msecnode_t *n;
+	size_t i;
 
 	nofit = false;
 	crushchange = crunch;
@@ -3722,9 +4102,57 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 
 
 	// 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)
+		{
+			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
+			{
+				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 (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+							continue;
+
+						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
+
+							if (!P_MobjTouchingPolyobj(po, mo))
+								continue;
+
+							if (!PIT_ChangeSector(mo, false))
+							{
+								nofit = true;
+								return nofit;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
 	if (sector->numattached)
 	{
-		size_t i;
 		sector_t *sec;
 		for (i = 0; i < sector->numattached; i++)
 		{
@@ -3784,9 +4212,53 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 	} while (n); // repeat from scratch until all things left are marked valid
 
 	// Nothing blocked us, so lets crush for real!
+
+	// 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)
+		{
+			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
+			{
+				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 (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+							continue;
+
+						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
+
+							if (!P_MobjTouchingPolyobj(po, mo))
+								continue;
+
+							PIT_ChangeSector(mo, true);
+							return nofit;
+						}
+					}
+				}
+			}
+		}
+	}
 	if (sector->numattached)
 	{
-		size_t i;
 		sector_t *sec;
 		for (i = 0; i < sector->numattached; i++)
 		{
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 46b033386410c2119d1af74292e7684ecfac5e36..45fc82fbfe2b0b97e9ee27b7491d8de0edebd6fc 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -311,6 +311,7 @@ fixed_t opentop, openbottom, openrange, lowfloor, highceiling;
 #ifdef ESLOPE
 pslope_t *opentopslope, *openbottomslope;
 #endif
+ffloor_t *openfloorrover, *openceilingrover;
 
 // P_CameraLineOpening
 // P_LineOpening, but for camera
@@ -418,10 +419,6 @@ void P_CameraLineOpening(line_t *linedef)
 		if (front->ffloors || back->ffloors)
 		{
 			ffloor_t *rover;
-			fixed_t highestceiling = highceiling;
-			fixed_t lowestceiling = opentop;
-			fixed_t highestfloor = openbottom;
-			fixed_t lowestfloor = lowfloor;
 			fixed_t delta1, delta2;
 
 			// Check for frontsector's fake floors
@@ -437,15 +434,15 @@ void P_CameraLineOpening(line_t *linedef)
 
 					delta1 = abs(mapcampointer->z - (bottomheight + ((topheight - bottomheight)/2)));
 					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
-					if (bottomheight < lowestceiling && delta1 >= delta2)
-						lowestceiling = bottomheight;
-					else if (bottomheight < highestceiling && delta1 >= delta2)
-						highestceiling = bottomheight;
-
-					if (topheight > highestfloor && delta1 < delta2)
-						highestfloor = topheight;
-					else if (topheight > lowestfloor && delta1 < delta2)
-						lowestfloor = topheight;
+					if (bottomheight < opentop && delta1 >= delta2)
+						opentop = bottomheight;
+					else if (bottomheight < highceiling && delta1 >= delta2)
+						highceiling = bottomheight;
+
+					if (topheight > openbottom && delta1 < delta2)
+						openbottom = topheight;
+					else if (topheight > lowfloor && delta1 < delta2)
+						lowfloor = topheight;
 				}
 
 			// Check for backsectors fake floors
@@ -461,28 +458,16 @@ void P_CameraLineOpening(line_t *linedef)
 
 					delta1 = abs(mapcampointer->z - (bottomheight + ((topheight - bottomheight)/2)));
 					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
-					if (bottomheight < lowestceiling && delta1 >= delta2)
-						lowestceiling = bottomheight;
-					else if (bottomheight < highestceiling && delta1 >= delta2)
-						highestceiling = bottomheight;
-
-					if (topheight > highestfloor && delta1 < delta2)
-						highestfloor = topheight;
-					else if (topheight > lowestfloor && delta1 < delta2)
-						lowestfloor = topheight;
+					if (bottomheight < opentop && delta1 >= delta2)
+						opentop = bottomheight;
+					else if (bottomheight < highceiling && delta1 >= delta2)
+						highceiling = bottomheight;
+
+					if (topheight > openbottom && delta1 < delta2)
+						openbottom = topheight;
+					else if (topheight > lowfloor && delta1 < delta2)
+						lowfloor = topheight;
 				}
-
-			if (highestceiling < highceiling)
-				highceiling = highestceiling;
-
-			if (highestfloor > openbottom)
-				openbottom = highestfloor;
-
-			if (lowestceiling < opentop)
-				opentop = lowestceiling;
-
-			if (lowestfloor > lowfloor)
-				lowfloor = lowestfloor;
 		}
 		openrange = opentop - openbottom;
 		return;
@@ -500,23 +485,27 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 		return;
 	}
 
-	// Treat polyobjects kind of like 3D Floors
+	front = linedef->frontsector;
+	back = linedef->backsector;
+
+	I_Assert(front != NULL);
+	I_Assert(back != NULL);
+
+	openfloorrover = openceilingrover = NULL;
 #ifdef POLYOBJECTS
-	if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT))
+	if (linedef->polyobj)
 	{
-		front = linedef->frontsector;
-		back = linedef->frontsector;
+		// set these defaults so that polyobjects don't interfere with collision above or below them
+		opentop = INT32_MAX;
+		openbottom = INT32_MIN;
+		highceiling = INT32_MIN;
+		lowfloor = INT32_MAX;
+#ifdef ESLOPE
+		opentopslope = openbottomslope = NULL;
+#endif
 	}
 	else
 #endif
-	{
-		front = linedef->frontsector;
-		back = linedef->backsector;
-	}
-
-	I_Assert(front != NULL);
-	I_Assert(back != NULL);
-
 	{ // Set open and high/low values here
 		fixed_t frontheight, backheight;
 
@@ -622,25 +611,49 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				}
 			}
 		}
-
-		// Check for fake floors in the sector.
-		if (front->ffloors || back->ffloors
 #ifdef POLYOBJECTS
-		    || linedef->polyobj
+		if (linedef->polyobj)
+		{
+			// Treat polyobj's backsector like a 3D Floor
+			if (linedef->polyobj->flags & POF_TESTHEIGHT)
+			{
+				const sector_t *polysec = linedef->backsector;
+				fixed_t polytop, polybottom;
+				fixed_t delta1, delta2;
+
+				if (linedef->polyobj->flags & POF_CLIPPLANES)
+				{
+					polytop = polysec->ceilingheight;
+					polybottom = polysec->floorheight;
+				}
+				else
+				{
+					polytop = INT32_MAX;
+					polybottom = INT32_MIN;
+				}
+
+				delta1 = abs(mobj->z - (polybottom + ((polytop - polybottom)/2)));
+				delta2 = abs(thingtop - (polybottom + ((polytop - polybottom)/2)));
+
+				if (polybottom < opentop && delta1 >= delta2)
+					opentop = polybottom;
+				else if (polybottom < highceiling && delta1 >= delta2)
+					highceiling = polybottom;
+
+				if (polytop > openbottom && delta1 < delta2)
+					openbottom = polytop;
+				else if (polytop > lowfloor && delta1 < delta2)
+					lowfloor = polytop;
+			}
+			// otherwise don't do anything special, pretend there's nothing else there
+		}
+		else
 #endif
-		   )
+		// Check for fake floors in the sector.
+		if (front->ffloors || back->ffloors)
 		{
 			ffloor_t *rover;
-
-			fixed_t highestceiling = highceiling;
-			fixed_t lowestceiling = opentop;
-			fixed_t highestfloor = openbottom;
-			fixed_t lowestfloor = lowfloor;
 			fixed_t delta1, delta2;
-#ifdef ESLOPE
-			pslope_t *ceilingslope = opentopslope;
-			pslope_t *floorslope = openbottomslope;
-#endif
 
 			// Check for frontsector's fake floors
 			for (rover = front->ffloors; rover; rover = rover->next)
@@ -663,26 +676,28 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 
 				if (delta1 >= delta2 && !(rover->flags & FF_PLATFORM)) // thing is below FOF
 				{
-					if (bottomheight < lowestceiling) {
-						lowestceiling = bottomheight;
+					if (bottomheight < opentop) {
+						opentop = bottomheight;
 #ifdef ESLOPE
-						ceilingslope = *rover->b_slope;
+						opentopslope = *rover->b_slope;
 #endif
+						openceilingrover = rover;
 					}
-					else if (bottomheight < highestceiling)
-						highestceiling = bottomheight;
+					else if (bottomheight < highceiling)
+						highceiling = bottomheight;
 				}
 
 				if (delta1 < delta2 && !(rover->flags & FF_REVERSEPLATFORM)) // thing is above FOF
 				{
-					if (topheight > highestfloor) {
-						highestfloor = topheight;
+					if (topheight > openbottom) {
+						openbottom = topheight;
 #ifdef ESLOPE
-						floorslope = *rover->t_slope;
+						openbottomslope = *rover->t_slope;
 #endif
+						openfloorrover = rover;
 					}
-					else if (topheight > lowestfloor)
-						lowestfloor = topheight;
+					else if (topheight > lowfloor)
+						lowfloor = topheight;
 				}
 			}
 
@@ -707,75 +722,30 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 
 				if (delta1 >= delta2 && !(rover->flags & FF_PLATFORM)) // thing is below FOF
 				{
-					if (bottomheight < lowestceiling) {
-						lowestceiling = bottomheight;
+					if (bottomheight < opentop) {
+						opentop = bottomheight;
 #ifdef ESLOPE
-						ceilingslope = *rover->b_slope;
+						opentopslope = *rover->b_slope;
 #endif
+						openceilingrover = rover;
 					}
-					else if (bottomheight < highestceiling)
-						highestceiling = bottomheight;
+					else if (bottomheight < highceiling)
+						highceiling = bottomheight;
 				}
 
 				if (delta1 < delta2 && !(rover->flags & FF_REVERSEPLATFORM)) // thing is above FOF
 				{
-					if (topheight > highestfloor) {
-						highestfloor = topheight;
+					if (topheight > openbottom) {
+						openbottom = topheight;
 #ifdef ESLOPE
-						floorslope = *rover->t_slope;
+						openbottomslope = *rover->t_slope;
 #endif
+						openfloorrover = rover;
 					}
-					else if (topheight > lowestfloor)
-						lowestfloor = topheight;
+					else if (topheight > lowfloor)
+						lowfloor = topheight;
 				}
 			}
-
-#ifdef POLYOBJECTS
-			// Treat polyobj's backsector like a 3D Floor
-			if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT))
-			{
-				const sector_t *polysec = linedef->backsector;
-
-				delta1 = abs(mobj->z - (polysec->floorheight + ((polysec->ceilingheight - polysec->floorheight)/2)));
-				delta2 = abs(thingtop - (polysec->floorheight + ((polysec->ceilingheight - polysec->floorheight)/2)));
-				if (polysec->floorheight < lowestceiling && delta1 >= delta2) {
-					lowestceiling = polysec->floorheight;
-#ifdef ESLOPE
-					ceilingslope = NULL;
-#endif
-				}
-				else if (polysec->floorheight < highestceiling && delta1 >= delta2)
-					highestceiling = polysec->floorheight;
-
-				if (polysec->ceilingheight > highestfloor && delta1 < delta2) {
-					highestfloor = polysec->ceilingheight;
-#ifdef ESLOPE
-					floorslope = NULL;
-#endif
-				}
-				else if (polysec->ceilingheight > lowestfloor && delta1 < delta2)
-					lowestfloor = polysec->ceilingheight;
-			}
-#endif
-			if (highestceiling < highceiling)
-				highceiling = highestceiling;
-
-			if (highestfloor > openbottom) {
-				openbottom = highestfloor;
-#ifdef ESLOPE
-				openbottomslope = floorslope;
-#endif
-			}
-
-			if (lowestceiling < opentop) {
-				opentop = lowestceiling;
-#ifdef ESLOPE
-				opentopslope = ceilingslope;
-#endif
-			}
-
-			if (lowestfloor > lowfloor)
-				lowfloor = lowestfloor;
 		}
 	}
 
@@ -1093,7 +1063,10 @@ boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
 	{
 		P_SetTarget(&bnext, mobj->bnext); // We want to note our reference to bnext here incase it is MF_NOTHINK and gets removed!
 		if (!func(mobj))
+		{
+			P_SetTarget(&bnext, NULL);
 			return false;
+		}
 		if (P_MobjWasRemoved(tmthing) // func just popped our tmthing, cannot continue.
 		|| (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue.
 		{
diff --git a/src/p_maputl.h b/src/p_maputl.h
index 3d74e927b3ba44c76585c513dcbd941369c76377..5042817c54ec8a13112626a75ea330ab3c612549 100644
--- a/src/p_maputl.h
+++ b/src/p_maputl.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -58,6 +58,7 @@ extern fixed_t opentop, openbottom, openrange, lowfloor, highceiling;
 #ifdef ESLOPE
 extern pslope_t *opentopslope, *openbottomslope;
 #endif
+extern ffloor_t *openfloorrover, *openceilingrover;
 
 void P_LineOpening(line_t *plinedef, mobj_t *mobj);
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8695d57e4098898e48a24530f718818e861b2bb8..5482f1a5df31dc8c9b61076d6543df5db693900a 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -35,6 +35,7 @@
 #include "p_slopes.h"
 #endif
 #include "f_finale.h"
+#include "m_cond.h"
 
 static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
 consvar_t cv_movebob = {"movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -280,6 +281,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_FALL;
 		break;
 	case S_PLAY_FLY:
+	case S_PLAY_FLY_TIRED:
 	case S_PLAY_SWIM:
 	case S_PLAY_GLIDE:
 	case S_PLAY_BOUNCE:
@@ -712,10 +714,10 @@ void P_EmeraldManager(void)
 		spawnpoints[i] = NULL;
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
@@ -877,7 +879,7 @@ void P_ExplodeMissile(mobj_t *mo)
 
 	if (mo->type == MT_DETON)
 	{
-		P_RadiusAttack(mo, mo, 96*FRACUNIT);
+		P_RadiusAttack(mo, mo, 96*FRACUNIT, 0);
 
 		explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
 		P_SetScale(explodemo, mo->scale);
@@ -1508,8 +1510,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 	if (mo->player)
 	{
 		if ((mo->player->pflags & PF_GLIDING)
-		|| (mo->player->charability == CA_FLY && (mo->player->powers[pw_tailsfly]
-			|| mo->state-states == S_PLAY_FLY_TIRED)))
+		|| (mo->player->charability == CA_FLY && mo->player->panim == PA_ABILITY))
 			gravityadd = gravityadd/3; // less gravity while flying/gliding
 		if (mo->player->climbing || (mo->player->powers[pw_carry] == CR_NIGHTSMODE))
 			gravityadd = 0;
@@ -1539,6 +1540,8 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 			{
 				case MT_FLINGRING:
 				case MT_FLINGCOIN:
+				case MT_FLINGBLUESPHERE:
+				case MT_FLINGNIGHTSCHIP:
 				case MT_FLINGEMERALD:
 				case MT_BOUNCERING:
 				case MT_RAILRING:
@@ -1781,7 +1784,7 @@ static void P_PushableCheckBustables(mobj_t *mo)
 							continue;
 					}
 
-					EV_CrumbleChain(node->m_sector, rover);
+					EV_CrumbleChain(NULL, rover); // node->m_sector
 
 					// Run a linedef executor??
 					if (rover->master->flags & ML_EFFECT5)
@@ -2133,7 +2136,7 @@ void P_XYMovement(mobj_t *mo)
 	if (mo->flags & MF_NOCLIPHEIGHT)
 		return; // no frictions for objects that can pass through floors
 
-	if (mo->flags & MF_MISSILE || mo->flags2 & MF2_SKULLFLY || mo->type == MT_SHELL || mo->type == MT_VULTURE)
+	if (mo->flags & MF_MISSILE || mo->flags2 & MF2_SKULLFLY || mo->type == MT_SHELL || mo->type == MT_VULTURE || mo->type == MT_PENGUINATOR)
 		return; // no friction for missiles ever
 
 	if (player && player->homing) // no friction for homing
@@ -2200,7 +2203,7 @@ static void P_SceneryXYMovement(mobj_t *mo)
 // 1 - forces false check for water (rings)
 // 2 - forces false check for water + different quicksand behaviour (scenery)
 //
-static void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype)
+void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype)
 {
 	ffloor_t *rover;
 	fixed_t delta1, delta2, thingtop;
@@ -2404,6 +2407,7 @@ boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover)
 static boolean P_ZMovement(mobj_t *mo)
 {
 	fixed_t dist, delta;
+	boolean onground;
 
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
@@ -2421,13 +2425,14 @@ static boolean P_ZMovement(mobj_t *mo)
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
+	onground = P_IsObjectOnGround(mo);
 
 #ifdef ESLOPE
 	if (mo->standingslope)
 	{
 		if (mo->flags & MF_NOCLIPHEIGHT)
 			mo->standingslope = NULL;
-		else if (!P_IsObjectOnGround(mo))
+		else if (!onground)
 			P_SlopeLaunch(mo);
 	}
 #endif
@@ -2513,11 +2518,16 @@ static boolean P_ZMovement(mobj_t *mo)
 
 		case MT_RING: // Ignore still rings
 		case MT_COIN:
-		case MT_BLUEBALL:
+		case MT_BLUESPHERE:
+		case MT_BOMBSPHERE:
+		case MT_NIGHTSCHIP:
+		case MT_NIGHTSSTAR:
 		case MT_REDTEAMRING:
 		case MT_BLUETEAMRING:
 		case MT_FLINGRING:
 		case MT_FLINGCOIN:
+		case MT_FLINGBLUESPHERE:
+		case MT_FLINGNIGHTSCHIP:
 		case MT_FLINGEMERALD:
 			// Remove flinged stuff from death pits.
 			if (P_CheckDeathPitCollide(mo))
@@ -2550,10 +2560,6 @@ static boolean P_ZMovement(mobj_t *mo)
 			if (!(mo->momx || mo->momy || mo->momz))
 				return true;
 			break;
-		case MT_NIGHTSWING:
-			if (!(mo->momx || mo->momy || mo->momz))
-				return true;
-			break;
 		case MT_FLAMEJET:
 		case MT_VERTICALFLAMEJET:
 			if (!(mo->flags & MF_BOUNCE))
@@ -2572,23 +2578,22 @@ static boolean P_ZMovement(mobj_t *mo)
 			break;
 	}
 
-	if (P_CheckDeathPitCollide(mo))
+	if (!mo->player && P_CheckDeathPitCollide(mo))
 	{
-		if (mo->flags & MF_PUSHABLE)
-		{
-			// Remove other pushable items from death pits.
-			P_RemoveMobj(mo);
-			return false;
-		}
-		else if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS)
+		if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
 		{
-			// Kill enemies and bosses that fall into death pits.
+			// Kill enemies, bosses and minecarts that fall into death pits.
 			if (mo->health)
 			{
 				P_KillMobj(mo, NULL, NULL, 0);
 				return false;
 			}
 		}
+		else
+		{
+			P_RemoveMobj(mo);
+			return false;
+		}
 	}
 
 	if (P_MobjFlip(mo)*mo->momz < 0
@@ -2701,9 +2706,7 @@ static boolean P_ZMovement(mobj_t *mo)
 
 		if (P_MobjFlip(mo)*mom.z < 0) // falling
 		{
-			if (!tmfloorthing || tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-			|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER)
-				mo->eflags |= MFE_JUSTHITFLOOR;
+			mo->eflags |= MFE_JUSTHITFLOOR;
 
 			if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something
 				mom.z = -mom.z;
@@ -2711,6 +2714,8 @@ static boolean P_ZMovement(mobj_t *mo)
 			// Flingrings bounce
 			if (mo->type == MT_FLINGRING
 				|| mo->type == MT_FLINGCOIN
+				|| mo->type == MT_FLINGBLUESPHERE
+				|| mo->type == MT_FLINGNIGHTSCHIP
 				|| P_WeaponOrPanel(mo->type)
 				|| mo->type == MT_FLINGEMERALD
 				|| mo->type == MT_BIGTUMBLEWEED
@@ -2764,7 +2769,7 @@ static boolean P_ZMovement(mobj_t *mo)
 				else if (mo->type == MT_FALLINGROCK)
 				{
 					if (P_MobjFlip(mo)*mom.z > FixedMul(2*FRACUNIT, mo->scale))
-						S_StartSound(mo, mo->info->activesound + P_RandomKey(mo->info->mass));
+						S_StartSound(mo, mo->info->activesound + P_RandomKey(mo->info->reactiontime));
 
 					mom.z /= 2; // Rocks not so bouncy
 
@@ -2783,14 +2788,11 @@ static boolean P_ZMovement(mobj_t *mo)
 						mom.z = 0;
 				}
 			}
-			else if (tmfloorthing && (tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-			|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER))
-				mom.z = tmfloorthing->momz;
-			else if (!tmfloorthing)
-				mom.z = 0;
+			else
+				mom.z = (tmfloorthing ? tmfloorthing->momz : 0);
+
 		}
-		else if (tmfloorthing && (tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-		|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER))
+		else if (tmfloorthing)
 			mom.z = tmfloorthing->momz;
 
 #ifdef ESLOPE
@@ -2876,6 +2878,8 @@ static boolean P_ZMovement(mobj_t *mo)
 
 static void P_PlayerZMovement(mobj_t *mo)
 {
+	boolean onground;
+
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
@@ -2909,6 +2913,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 	}
 
 	mo->z += mo->momz;
+	onground = P_IsObjectOnGround(mo);
 
 	// Have player fall through floor?
 	if (mo->player->playerstate == PST_DEAD
@@ -2920,13 +2925,13 @@ static void P_PlayerZMovement(mobj_t *mo)
 	{
 		if (mo->flags & MF_NOCLIPHEIGHT)
 			mo->standingslope = NULL;
-		else if (!P_IsObjectOnGround(mo))
+		else if (!onground)
 			P_SlopeLaunch(mo);
 	}
 #endif
 
 	// clip movement
-	if (P_IsObjectOnGround(mo) && !(mo->flags & MF_NOCLIPHEIGHT))
+	if (onground && !(mo->flags & MF_NOCLIPHEIGHT))
 	{
 		if (mo->eflags & MFE_VERTICALFLIP)
 			mo->z = mo->ceilingz - mo->height;
@@ -2968,100 +2973,89 @@ static void P_PlayerZMovement(mobj_t *mo)
 			if (P_MobjFlip(mo)*mo->momz < -FixedMul(8*FRACUNIT, mo->scale))
 				mo->player->deltaviewheight = (P_MobjFlip(mo)*mo->momz)>>3; // make sure momz is negative
 
-			if (!tmfloorthing || tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-				|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER) // Spin Attack
+			mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
+
 			{
-				mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
+#ifdef POLYOBJECTS
+				// Check if we're on a polyobject
+				// that triggers a linedef executor.
+				msecnode_t *node;
+				boolean stopmovecut = false;
 
-				if (mo->eflags & MFE_JUSTHITFLOOR)
+				for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 				{
-#ifdef POLYOBJECTS
-					// Check if we're on a polyobject
-					// that triggers a linedef executor.
-					msecnode_t *node;
-					boolean stopmovecut = false;
+					sector_t *sec = node->m_sector;
+					subsector_t *newsubsec;
+					size_t i;
 
-					for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+					for (i = 0; i < numsubsectors; i++)
 					{
-						sector_t *sec = node->m_sector;
-						subsector_t *newsubsec;
-						size_t i;
+						newsubsec = &subsectors[i];
 
-						for (i = 0; i < numsubsectors; i++)
-						{
-							newsubsec = &subsectors[i];
+						if (newsubsec->sector != sec)
+							continue;
 
-							if (newsubsec->sector != sec)
-								continue;
+						if (newsubsec->polyList)
+						{
+							polyobj_t *po = newsubsec->polyList;
+							sector_t *polysec;
 
-							if (newsubsec->polyList)
+							while(po)
 							{
-								polyobj_t *po = newsubsec->polyList;
-								sector_t *polysec;
-
-								while(po)
+								if (!P_MobjInsidePolyobj(po, mo) || !(po->flags & POF_SOLID))
 								{
-									if (!P_MobjInsidePolyobj(po, mo) || !(po->flags & POF_SOLID))
-									{
-										po = (polyobj_t *)(po->link.next);
-										continue;
-									}
-
-									// We're inside it! Yess...
-									polysec = po->lines[0]->backsector;
-
-									// Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red
-									if ((mo->z == polysec->ceilingheight || mo->z+mo->height == polysec->floorheight) && po->thinker)
-										stopmovecut = true;
+									po = (polyobj_t *)(po->link.next);
+									continue;
+								}
 
-									if (!(po->flags & POF_LDEXEC))
-									{
-										po = (polyobj_t *)(po->link.next);
-										continue;
-									}
+								// We're inside it! Yess...
+								polysec = po->lines[0]->backsector;
 
-									if (mo->z == polysec->ceilingheight)
-									{
-										// We're landing on a PO, so check for
-										// a linedef executor.
-										// Trigger tags are 32000 + the PO's ID number.
-										P_LinedefExecute((INT16)(32000 + po->id), mo, NULL);
-									}
+								// Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red
+								if ((mo->z == polysec->ceilingheight || mo->z+mo->height == polysec->floorheight) && po->thinker)
+									stopmovecut = true;
 
+								if (!(po->flags & POF_LDEXEC))
+								{
 									po = (polyobj_t *)(po->link.next);
+									continue;
+								}
+
+								if (mo->z == polysec->ceilingheight)
+								{
+									// We're landing on a PO, so check for
+									// a linedef executor.
+									// Trigger tags are 32000 + the PO's ID number.
+									P_LinedefExecute((INT16)(32000 + po->id), mo, NULL);
 								}
+
+								po = (polyobj_t *)(po->link.next);
 							}
 						}
 					}
+				}
 
-					if (!stopmovecut)
+			if (!stopmovecut)
 #endif
 
-					// Cut momentum in half when you hit the ground and
-					// aren't pressing any controls.
-					if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
-					{
-						mo->momx >>= 1;
-						mo->momy >>= 1;
-					}
+				// Cut momentum in half when you hit the ground and
+				// aren't pressing any controls.
+				if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
+				{
+					mo->momx >>= 1;
+					mo->momy >>= 1;
 				}
-
-				clipmomz = P_PlayerHitFloor(mo->player);
 			}
+
+			clipmomz = P_PlayerHitFloor(mo->player, true);
+
 			if (!(mo->player->pflags & PF_SPINNING) && mo->player->powers[pw_carry] != CR_NIGHTSMODE)
 				mo->player->pflags &= ~PF_STARTDASH;
 
 			if (clipmomz)
-			{
-				if (tmfloorthing && (tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-				|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER))
-					mo->momz = tmfloorthing->momz;
-				else if (!tmfloorthing)
-					mo->momz = 0;
-			}
+				mo->momz = (tmfloorthing ? tmfloorthing->momz : 0);
 		}
-		else if (tmfloorthing && (tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-		|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER))
+		else if (tmfloorthing)
 			mo->momz = tmfloorthing->momz;
 	}
 	else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
@@ -3135,7 +3129,7 @@ nightsdone:
 						{
 							// DO THE MARIO!
 							if (rover->flags & FF_SHATTERBOTTOM) // Brick block!
-								EV_CrumbleChain(node->m_sector, rover);
+								EV_CrumbleChain(NULL, rover); // node->m_sector
 							else // Question block!
 								EV_MarioBlock(rover, node->m_sector, mo);
 						}
@@ -3239,19 +3233,14 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 				P_RemoveMobj(mo);
 				return false;
 			}
-
 		default:
 			break;
 	}
 
-	// Fix for any silly pushables like the egg statues that are also scenery for some reason -- Monster Iestyn
 	if (P_CheckDeathPitCollide(mo))
 	{
-		if (mo->flags & MF_PUSHABLE)
-		{
-			P_RemoveMobj(mo);
-			return false;
-		}
+		P_RemoveMobj(mo);
+		return false;
 	}
 
 	// clip movement
@@ -3266,12 +3255,9 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 
 		if (P_MobjFlip(mo)*mo->momz < 0) // falling
 		{
-			if (!tmfloorthing || tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-				|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER)
-					mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
+			mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
 
-			if (tmfloorthing && (tmfloorthing->flags & (MF_PUSHABLE|MF_MONITOR)
-			|| tmfloorthing->flags2 & MF2_STANDONME || tmfloorthing->type == MT_PLAYER))
+			if (tmfloorthing)
 				mo->momz = tmfloorthing->momz;
 			else if (!tmfloorthing)
 				mo->momz = 0;
@@ -3410,11 +3396,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 		if (!((p->powers[pw_super]) || (p->powers[pw_invulnerability])))
 		{
 			boolean electric = !!(p->powers[pw_shield] & SH_PROTECTELECTRIC);
-#define SH_OP (SH_PROTECTFIRE|SH_PROTECTWATER|SH_PROTECTELECTRIC)
-			if ((p->powers[pw_shield] & SH_OP) == SH_OP) // No.
-				P_KillMobj(mobj, NULL, NULL, DMG_INSTAKILL);
-#undef SH_OP
-			else if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER)))
+			if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER)))
 			{ // Water removes electric and non-water fire shields...
 				P_FlashPal(p,
 				electric
@@ -3713,10 +3695,10 @@ void P_DestroyRobots(void)
 	mobj_t *mo;
 	thinker_t *think;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 		if (mo->health <= 0 || !(mo->flags & (MF_ENEMY|MF_BOSS)))
@@ -3744,14 +3726,15 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 
 	if (player->pflags & PF_FLIPCAM && !(player->powers[pw_carry] == CR_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP)
 		postimg = postimg_flip;
-	else if (player->awayviewtics)
+	else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist
 	{
 		camera_t dummycam;
 		dummycam.subsector = player->awayviewmobj->subsector;
 		dummycam.x = player->awayviewmobj->x;
 		dummycam.y = player->awayviewmobj->y;
 		dummycam.z = player->awayviewmobj->z;
-		dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT
+		//dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT
+		dummycam.height = 0;			 // Why? Remote viewpoint cameras have no height.
 		// Are we in water?
 		if (P_CameraCheckWater(&dummycam))
 			postimg = postimg_water;
@@ -4074,7 +4057,8 @@ void P_RecalcPrecipInSector(sector_t *sector)
 //
 void P_NullPrecipThinker(precipmobj_t *mobj)
 {
-	(void)mobj;
+	//(void)mobj;
+	mobj->precipflags &= ~PCF_THUNK;
 }
 
 void P_SnowThinker(precipmobj_t *mobj)
@@ -4094,35 +4078,37 @@ void P_RainThinker(precipmobj_t *mobj)
 	{
 		// cycle through states,
 		// calling action functions at transitions
-		if (mobj->tics > 0 && --mobj->tics == 0)
-		{
-			// you can cycle through multiple states in a tic
-			if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate))
-				return; // freed itself
-		}
+		if (mobj->tics <= 0)
+			return;
+
+		if (--mobj->tics)
+			return;
+
+		if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate))
+			return;
+
+		if (mobj->state != &states[S_RAINRETURN])
+			return;
+
+		mobj->z = mobj->ceilingz;
+		P_SetPrecipMobjState(mobj, S_RAIN1);
 
-		if (mobj->state == &states[S_RAINRETURN])
-		{
-			mobj->z = mobj->ceilingz;
-			P_SetPrecipMobjState(mobj, S_RAIN1);
-		}
 		return;
 	}
 
 	// adjust height
-	mobj->z += mobj->momz;
+	if ((mobj->z += mobj->momz) > mobj->floorz)
+		return;
 
-	if (mobj->z <= mobj->floorz)
+	// no splashes on sky or bottomless pits
+	if (mobj->precipflags & PCF_PIT)
 	{
-		// no splashes on sky or bottomless pits
-		if (mobj->precipflags & PCF_PIT)
-			mobj->z = mobj->ceilingz;
-		else
-		{
-			mobj->z = mobj->floorz;
-			P_SetPrecipMobjState(mobj, S_SPLASH1);
-		}
+		mobj->z = mobj->ceilingz;
+		return;
 	}
+
+	mobj->z = mobj->floorz;
+	P_SetPrecipMobjState(mobj, S_SPLASH1);
 }
 
 static void P_RingThinker(mobj_t *mobj)
@@ -4394,6 +4380,8 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	}
 
 	if (mobj->health <= 0)
+		return;
+	/*
 	{
 		mobj->movecount = 0;
 		mobj->reactiontime = 0;
@@ -4406,157 +4394,110 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			mobj->momz = mobj->info->speed;
 			return;
 		}
-	}
+		else
+		{
+			mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
+			mobj->flags |= MF_NOCLIPHEIGHT;
+			mobj->threshold = -1;
+			return;
+		}
+	}*/
 
-	if (mobj->reactiontime) // Shock mode
+	if (mobj->reactiontime) // At the bottom of the water
 	{
 		UINT32 i;
+		SINT8 curpath = mobj->threshold;
+
+		// Choose one of the paths you're not already on
+		mobj->threshold = P_RandomKey(8-1);
+		if (mobj->threshold >= curpath)
+			mobj->threshold++;
 
 		if (mobj->state != &states[mobj->info->spawnstate])
 			P_SetMobjState(mobj, mobj->info->spawnstate);
 
 		mobj->reactiontime--;
-		if (!mobj->reactiontime)
-		{
-			ffloor_t *rover;
-
-			// Shock the water
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-
-				if (!players[i].mo)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
-
-				if (players[i].mo->eflags & MFE_UNDERWATER)
-					P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
-			}
-
-			// Make the water flash
-			for (i = 0; i < numsectors; i++)
-			{
-				if (!sectors[i].ffloors)
-					continue;
-
-				for (rover = sectors[i].ffloors; rover; rover = rover->next)
-				{
-					if (!(rover->flags & FF_EXISTS))
-						continue;
-
-					if (!(rover->flags & FF_SWIMMABLE))
-						continue;
-
-					P_SpawnLightningFlash(rover->master->frontsector);
-					break;
-				}
-			}
-
-			if ((UINT32)mobj->extravalue1 + TICRATE*2 < leveltime)
-			{
-				mobj->extravalue1 = (INT32)leveltime;
-				S_StartSound(0, sfx_buzz1);
-			}
-
-			// If in the center, check to make sure
-			// none of the players are in the water
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-
-				if (!players[i].mo || players[i].bot)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
-
-				if (players[i].mo->eflags & MFE_UNDERWATER)
-				{ // Stay put
-					mobj->reactiontime = 2*TICRATE;
-					return;
-				}
-			}
-		}
 
 		if (!mobj->reactiontime && mobj->health <= mobj->info->damage)
 		{ // Spawn pinch dummies from the center when we're leaving it.
 			thinker_t *th;
 			mobj_t *mo2;
 			mobj_t *dummy;
-			SINT8 way = mobj->threshold - 1; // 0 through 4.
-			SINT8 way2;
+			SINT8 way0 = mobj->threshold; // 0 through 4.
+			SINT8 way1, way2;
 
 			i = 0; // reset i to 0 so we can check how many clones we've removed
 
 			// scan the thinkers to make sure all the old pinch dummies are gone before making new ones
 			// this can happen if the boss was hurt earlier than expected
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
-				if (mo2->type == (mobjtype_t)mobj->info->mass && mo2->tracer == mobj)
-				{
-					P_RemoveMobj(mo2);
-					i++;
-				}
-				if (i == 2) // we've already removed 2 of these, let's stop now
+				if (mo2->type != (mobjtype_t)mobj->info->mass)
+					continue;
+				if (mo2->tracer != mobj)
+					continue;
+
+				P_RemoveMobj(mo2);
+				if (++i == 2) // we've already removed 2 of these, let's stop now
 					break;
 			}
 
-			way = (way + P_RandomRange(1,3)) % 5; // dummy 1 at one of the first three options after eggmobile
+			way1 = P_RandomKey(8-2);
+			if (way1 >= curpath)
+				way1++;
+			if (way1 >= way0)
+			{
+				way1++;
+				if (way1 == curpath)
+					way1++;
+			}
+
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
 			dummy->angle = mobj->angle;
-			dummy->threshold = way + 1;
-			dummy->tracer = mobj;
+			dummy->threshold = way1;
+			P_SetTarget(&dummy->tracer, mobj);
+			dummy->movefactor = mobj->movefactor;
+			dummy->cusval = mobj->cusval;
+
+			way2 = P_RandomKey(8-3);
+			if (way2 >= curpath)
+				way2++;
+			if (way2 >= way0)
+			{
+				way2++;
+				if (way2 == curpath)
+					way2++;
+			}
+			if (way2 >= way1)
+			{
+				way2++;
+				if (way2 == curpath || way2 == way0)
+					way2++;
+			}
 
-			do
-				way2 = (way + P_RandomRange(1,3)) % 5; // dummy 2 has to be careful,
-			while (way2 == mobj->threshold - 1); // to make sure it doesn't try to go the Eggman Way if dummy 1 rolled high.
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
 			dummy->angle = mobj->angle;
-			dummy->threshold = way2 + 1;
-			dummy->tracer = mobj;
+			dummy->threshold = way2;
+			P_SetTarget(&dummy->tracer, mobj);
+			dummy->movefactor = mobj->movefactor;
+			dummy->cusval = mobj->cusval;
 
-			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", mobj->threshold, way + 1, dummy->threshold);
-			P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
+			P_LinedefExecute(LE_PINCHPHASE+(mobj->cusval*LE_PARAMWIDTH), mobj, NULL);
 		}
 	}
 	else if (mobj->movecount) // Firing mode
 	{
-		UINT32 i;
-
 		// look for a new target
 		P_BossTargetPlayer(mobj, false);
 
 		if (!mobj->target || !mobj->target->player)
 			return;
 
-		// Are there any players underwater? If so, shock them!
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (!playeringame[i] || players[i].spectator)
-				continue;
-
-			if (!players[i].mo || players[i].bot)
-				continue;
-
-			if (players[i].mo->health <= 0)
-				continue;
-
-			if (players[i].mo->eflags & MFE_UNDERWATER)
-			{
-				mobj->movecount = 0;
-				P_SetMobjState(mobj, mobj->info->spawnstate);
-				return;
-			}
-		}
-
 		// Always face your target.
 		A_FaceTarget(mobj);
 
@@ -4571,9 +4512,7 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	}
 	else if (mobj->threshold >= 0) // Traveling mode
 	{
-		thinker_t *th;
-		mobj_t *mo2;
-		fixed_t dist, dist2;
+		fixed_t dist = 0;
 		fixed_t speed;
 
 		P_SetTarget(&mobj->target, NULL);
@@ -4582,97 +4521,112 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			&& !(mobj->flags2 & MF2_FRET))
 			P_SetMobjState(mobj, mobj->info->spawnstate);
 
-		// scan the thinkers
-		// to find a point that matches
-		// the number
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		if (!(mobj->flags2 & MF2_STRONGBOX))
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
+			thinker_t *th;
+			mobj_t *mo2;
 
-			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && mo2->spawnpoint->angle == mobj->threshold)
+			P_SetTarget(&mobj->tracer, NULL);
+
+			// scan the thinkers
+			// to find a point that matches
+			// the number
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				P_SetTarget(&mobj->target, mo2);
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+				if (mo2->type != MT_BOSS3WAYPOINT)
+					continue;
+				if (!mo2->spawnpoint)
+					continue;
+				if (mo2->spawnpoint->angle != mobj->threshold)
+					continue;
+				if (mo2->spawnpoint->extrainfo != mobj->cusval)
+					continue;
+
+				P_SetTarget(&mobj->tracer, mo2);
 				break;
 			}
 		}
 
-		if (!mobj->target) // Should NEVER happen
+		if (!mobj->tracer) // Should NEVER happen
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d\n", mobj->threshold);
+			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d, %d\n", mobj->threshold, mobj->cusval);
 			return;
 		}
 
-		dist = P_AproxDistance(P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y), mobj->target->z - mobj->z);
-
-		if (dist < 1)
-			dist = 1;
-
 		if ((mobj->movedir) || (mobj->health <= mobj->info->damage))
 			speed = mobj->info->speed * 2;
 		else
 			speed = mobj->info->speed;
 
-		mobj->momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed);
-		mobj->momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed);
-		mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed);
+		if (mobj->tracer->x == mobj->x && mobj->tracer->y == mobj->y)
+		{
+			// apply ambush for old routing, otherwise whack a mole only
+			dist = P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z + mobj->movefactor - mobj->z);
 
-		if (mobj->momx != 0 || mobj->momy != 0)
-			mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
+			if (dist < 1)
+				dist = 1;
 
-		dist2 = P_AproxDistance(P_AproxDistance(mobj->target->x - (mobj->x + mobj->momx), mobj->target->y - (mobj->y + mobj->momy)), mobj->target->z - (mobj->z + mobj->momz));
+			mobj->momx = FixedMul(FixedDiv(mobj->tracer->x - mobj->x, dist), speed);
+			mobj->momy = FixedMul(FixedDiv(mobj->tracer->y - mobj->y, dist), speed);
+			mobj->momz = FixedMul(FixedDiv(mobj->tracer->z + mobj->movefactor - mobj->z, dist), speed);
 
-		if (dist2 < 1)
-			dist2 = 1;
+			if (mobj->momx != 0 || mobj->momy != 0)
+				mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
+		}
 
-		if ((dist >> FRACBITS) <= (dist2 >> FRACBITS))
+		if (dist <= speed)
 		{
-			// If further away, set XYZ of mobj to waypoint location
+			// If distance to point is less than travel in that frame, set XYZ of mobj to waypoint location
 			P_UnsetThingPosition(mobj);
-			mobj->x = mobj->target->x;
-			mobj->y = mobj->target->y;
-			mobj->z = mobj->target->z;
+			mobj->x = mobj->tracer->x;
+			mobj->y = mobj->tracer->y;
+			mobj->z = mobj->tracer->z + mobj->movefactor;
 			mobj->momx = mobj->momy = mobj->momz = 0;
 			P_SetThingPosition(mobj);
 
-			if (mobj->threshold == 0)
+			if (!mobj->movefactor) // to firing mode
 			{
-				mobj->reactiontime = 1; // Bzzt! Shock the water!
-				mobj->movedir = 0;
+				UINT8 i;
+				angle_t ang = 0;
+
+				mobj->movecount = mobj->health+1;
+				mobj->movefactor = -512*FRACUNIT;
 
-				if (mobj->health <= 0)
+				// shock the water!
+				for (i = 0; i < 64; i++)
 				{
-					mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
-					mobj->flags |= MF_NOCLIPHEIGHT;
-					mobj->threshold = -1;
-					return;
+					mobj_t *shock = P_SpawnMobjFromMobj(mobj, 0, 0, 4*FRACUNIT, MT_SHOCK);
+					P_SetTarget(&shock->target, mobj);
+					P_InstaThrust(shock, ang, shock->info->speed);
+					P_CheckMissileSpawn(shock);
+					ang += (ANGLE_MAX/64);
 				}
+				S_StartSound(mobj, sfx_fizzle);
 			}
-
-			// Set to next waypoint in sequence
-			if (mobj->target->spawnpoint)
+			else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
 			{
-				// From the center point, choose one of the five paths
-				if (mobj->target->spawnpoint->angle == 0)
-					mobj->threshold = P_RandomRange(1,5);
-				else
-					mobj->threshold = mobj->target->spawnpoint->extrainfo;
-
-				// If the deaf flag is set, go into firing mode
-				if (mobj->target->spawnpoint->options & MTF_AMBUSH)
-					mobj->movecount = mobj->health+1;
+				mobj->flags2 &= ~(MF2_STRONGBOX|MF2_CLASSICPUSH);
+				mobj->reactiontime = 1; // spawn pinch dummies
+				mobj->movedir = 0;
+			}
+			else // just shifted to another tube
+			{
+				mobj->flags2 |= MF2_STRONGBOX;
+				if (mobj->health > 0)
+					mobj->movefactor = 0;
 			}
-			else // This should never happen, as well
-				CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 waypoint has no spawnpoint associated with it.\n");
 		}
 	}
 }
 
 // Move Boss4's sectors by delta.
-static boolean P_Boss4MoveCage(fixed_t delta)
+static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 {
-	const UINT16 tag = 65534;
+	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
 	for (snum = sectors[tag%numsectors].firsttag; snum != -1; snum = sector->nexttag)
@@ -4692,7 +4646,7 @@ static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
 {
 	INT32 s;
 	mobj_t *base = mobj, *seg;
-	fixed_t dist, bz = mobj->watertop+(16<<FRACBITS);
+	fixed_t dist, bz = mobj->watertop+(8<<FRACBITS);
 	while ((base = base->tracer))
 	{
 		for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s)
@@ -4702,26 +4656,44 @@ static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
 }
 
 // Pull them closer.
-static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
+static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 {
 	INT32 s;
 	mobj_t *base = mobj, *seg;
-	fixed_t dist, bz = mobj->watertop+(16<<FRACBITS);
-	while ((base = base->tracer))
+	fixed_t originx, originy, workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
+
+	if (mobj->spawnpoint)
 	{
-		for (seg = base, dist = 112*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 132*FRACUNIT, --s)
+		originx = mobj->spawnpoint->x << FRACBITS;
+		originy = mobj->spawnpoint->y << FRACBITS;
+	}
+	else
+	{
+		originx = mobj->x;
+		originy = mobj->y;
+	}
+
+	dz /= 9;
+
+	while ((base = base->tracer)) // there are 10 per spoke, remember that
+	{
+		dx = (originx + P_ReturnThrustX(mobj, angle, (9*132)<<FRACBITS) - mobj->x)/9;
+		dy = (originy + P_ReturnThrustY(mobj, angle, (9*132)<<FRACBITS) - mobj->y)/9;
+		workx = mobj->x + P_ReturnThrustX(mobj, angle, (112)<<FRACBITS);
+		worky = mobj->y + P_ReturnThrustY(mobj, angle, (112)<<FRACBITS);
+		for (seg = base, s = 9; seg; seg = seg->hnext, --s)
 		{
-			seg->z = bz + FixedMul(fz, FixedDiv(s<<FRACBITS, 9<<FRACBITS));
-			P_TryMove(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), true);
+			seg->z = bz + (dz*(9-s));
+			P_TryMove(seg, workx + (dx*s), worky + (dy*s), true);
 		}
 		angle += ANGLE_MAX/3;
 	}
 }
 
 // Destroy cage FOFs.
-static void P_Boss4DestroyCage(void)
+static void P_Boss4DestroyCage(mobj_t *mobj)
 {
-	const UINT16 tag = 65534;
+	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum, next;
 	size_t a;
 	sector_t *sector, *rsec;
@@ -4784,9 +4756,11 @@ static void P_Boss4PopSpikeballs(mobj_t *mobj)
 //
 static void P_Boss4Thinker(mobj_t *mobj)
 {
+	fixed_t movespeed = 0;
+
 	if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
 	{
-		if (mobj->health > mobj->info->damage || mobj->movedir == 4)
+		if (mobj->flags2 & MF2_FRET && (mobj->health > mobj->info->damage))
 			mobj->flags2 &= ~MF2_FRET;
 		mobj->reactiontime = 0; // Drop the cage immediately.
 	}
@@ -4796,12 +4770,50 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	{
 		if (mobj->tracer) // need to clean up!
 		{
-			P_Boss4DestroyCage(); // Just in case pinch phase was skipped.
+			P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped.
 			P_Boss4PopSpikeballs(mobj);
 		}
 		return;
 	}
 
+	if (mobj->movedir) // only not during init
+	{
+		INT32 oldmovecount = mobj->movecount;
+		if (mobj->movedir == 3) // pinch start
+			movespeed = -(210<<(FRACBITS>>1));
+		else if (mobj->movedir > 3) // pinch
+		{
+			movespeed = 420<<(FRACBITS>>1);
+			movespeed += (420*(mobj->info->damage-mobj->health)<<(FRACBITS>>1));
+			if (mobj->movedir == 4)
+				movespeed = -movespeed;
+		}
+		else // normal
+		{
+			movespeed = 170<<(FRACBITS>>1);
+			movespeed += ((50*(mobj->info->spawnhealth-mobj->health))<<(FRACBITS>>1));
+			if (mobj->movedir == 2)
+				movespeed = -movespeed;
+			if (mobj->movefactor)
+				movespeed /= 2;
+			else if (mobj->threshold)
+			{
+				// 1 -> 1.5 second timer
+				INT32 maxtimer = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
+				if (maxtimer < 1)
+					maxtimer = 1;
+				maxtimer = ((mobj->threshold*movespeed)/(2*maxtimer));
+				movespeed -= maxtimer;
+			}
+		}
+
+		mobj->movecount += movespeed + 360*FRACUNIT;
+		mobj->movecount %= 360*FRACUNIT;
+
+		if (((oldmovecount>>FRACBITS)%120 >= 60) && !((mobj->movecount>>FRACBITS)%120 >= 60))
+			S_StartSound(NULL, sfx_mswing);
+	}
+
 	// movedir == battle stage:
 	//   0: initialization
 	//   1: phase 1 forward
@@ -4832,17 +4844,17 @@ static void P_Boss4Thinker(mobj_t *mobj)
 				P_SetTarget(&seg->target, mobj);
 				for (i = 0; i < 9; i++)
 				{
-					seg->hnext = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE);
-					seg->hnext->hprev = seg;
+					P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE));
+					P_SetTarget(&seg->hnext->hprev, seg);
 					seg = seg->hnext;
 				}
 			}
 			// Move the cage up to the sky.
 			mobj->movecount = 800*FRACUNIT;
-			if (!P_Boss4MoveCage(mobj->movecount))
+			if (!P_Boss4MoveCage(mobj, mobj->movecount))
 			{
 				mobj->movecount = 0;
-				mobj->threshold = 3*TICRATE;
+				//mobj->threshold = 3*TICRATE;
 				mobj->extravalue1 = 1;
 				mobj->movedir++; // We don't have a cage, just continue.
 			}
@@ -4854,17 +4866,13 @@ static void P_Boss4Thinker(mobj_t *mobj)
 			fixed_t oldz = mobj->movecount;
 			mobj->threshold -= 5*FRACUNIT;
 			mobj->movecount += mobj->threshold;
-			if (mobj->movecount < 0)
-				mobj->movecount = 0;
-			P_Boss4MoveCage(mobj->movecount - oldz);
-			P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
-			if (mobj->movecount == 0)
+			if (mobj->movecount <= 0)
 			{
-				mobj->threshold = 3*TICRATE;
-				mobj->extravalue1 = 1;
-				P_LinedefExecute(LE_BOSS4DROP, mobj, NULL);
+				mobj->movecount = 0;
 				mobj->movedir++; // Initialization complete, next phase!
 			}
+			P_Boss4MoveCage(mobj, mobj->movecount - oldz);
+			P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
 		}
 		return;
 	}
@@ -4878,17 +4886,18 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	case 3:
 	{
 		fixed_t z;
-		if (mobj->z < mobj->watertop+(512<<FRACBITS))
+		if (mobj->z < mobj->watertop+(400<<FRACBITS))
 			mobj->momz = 8*FRACUNIT;
 		else
 		{
-			mobj->momz = 0;
+			mobj->momz = mobj->movefactor = 0;
+			mobj->threshold = 1110<<FRACBITS;
+			S_StartSound(NULL, sfx_s3k60);
 			mobj->movedir++;
 		}
-		mobj->movecount += 400<<(FRACBITS>>1);
-		mobj->movecount %= 360*FRACUNIT;
+
 		z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2;
-		if (z < 0) // We haven't risen high enough to pull the spikeballs along yet
+		if (z < (8<<FRACBITS)) // We haven't risen high enough to pull the spikeballs along yet
 			P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet.
 		else
 			P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z);
@@ -4896,18 +4905,32 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	}
 	// Pinch phase!
 	case 4:
+	case 5:
 	{
-		if (mobj->z < (mobj->watertop + ((512+128*(mobj->info->damage-mobj->health))<<FRACBITS)))
-			mobj->momz = 8*FRACUNIT;
-		else
-			mobj->momz = 0;
-		mobj->movecount += (800+800*(mobj->info->damage-mobj->health))<<(FRACBITS>>1);
-		mobj->movecount %= 360*FRACUNIT;
+		mobj->angle -= FixedAngle(movespeed/8);
+
+		if (mobj->movefactor != mobj->threshold)
+		{
+			if (mobj->threshold - mobj->movefactor < FRACUNIT)
+			{
+				mobj->movefactor = mobj->threshold;
+				mobj->flags2 &= ~MF2_FRET;
+			}
+			else
+				mobj->movefactor += (mobj->threshold - mobj->movefactor)/8;
+		}
+
+		if (mobj->spawnpoint)
+			P_TryMove(mobj,
+				(mobj->spawnpoint->x<<FRACBITS) - P_ReturnThrustX(mobj, mobj->angle, mobj->movefactor),
+				(mobj->spawnpoint->y<<FRACBITS) - P_ReturnThrustY(mobj, mobj->angle, mobj->movefactor),
+				true);
+
 		P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2);
 
 		if (!mobj->target || !mobj->target->health)
 			P_SupermanLook4Players(mobj);
-		A_FaceTarget(mobj);
+		//A_FaceTarget(mobj);
 		return;
 	}
 
@@ -4927,10 +4950,23 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	if (mobj->reactiontime == 1)
 	{
 		fixed_t oldz = mobj->movefactor;
-		mobj->movefactor += 8*FRACUNIT;
-		if (mobj->movefactor > 128*FRACUNIT)
-			mobj->movefactor = 128*FRACUNIT;
-		P_Boss4MoveCage(mobj->movefactor - oldz);
+		if (mobj->movefactor != 128*FRACUNIT)
+		{
+			if (mobj->movefactor < 128*FRACUNIT)
+			{
+				mobj->movefactor += 8*FRACUNIT;
+				if (!oldz)
+				{
+					// 5 -> 2.5 second timer
+					mobj->threshold = 5*TICRATE-(TICRATE*(mobj->info->spawnhealth-mobj->health)/2);
+					if (mobj->threshold < 1)
+						mobj->threshold = 1;
+				}
+			}
+			else
+				mobj->movefactor = 128*FRACUNIT;
+			P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
+		}
 	}
 	// Drop the cage!
 	else if (mobj->movefactor)
@@ -4939,64 +4975,38 @@ static void P_Boss4Thinker(mobj_t *mobj)
 		mobj->movefactor -= 4*FRACUNIT;
 		if (mobj->movefactor < 0)
 			mobj->movefactor = 0;
-		P_Boss4MoveCage(mobj->movefactor - oldz);
+		P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
 		if (!mobj->movefactor)
 		{
 			if (mobj->health <= mobj->info->damage)
 			{ // Proceed to pinch phase!
-				P_Boss4DestroyCage();
+				P_Boss4DestroyCage(mobj);
 				mobj->movedir = 3;
-				P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+				P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+				P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0);
+				var1 = 3;
+				A_BossJetFume(mobj);
 				return;
 			}
-			P_LinedefExecute(LE_BOSS4DROP, mobj, NULL);
+			P_LinedefExecute(LE_BOSS4DROP - (mobj->info->spawnhealth-mobj->health) + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+			// 1 -> 1.5 second timer
+			mobj->threshold = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
+			if (mobj->threshold < 1)
+				mobj->threshold = 1;
 		}
 	}
 
-	{
-		fixed_t movespeed = 170<<(FRACBITS>>1);
-		if (mobj->reactiontime == 2)
-			movespeed *= 3;
-		if (mobj->movedir == 2)
-			mobj->movecount -= movespeed;
-		else
-			mobj->movecount += movespeed;
-	}
-	mobj->movecount %= 360*FRACUNIT;
 	P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor);
 
 	// Check for attacks, always tick the timer even while animating!!
-	if (!(mobj->flags2 & MF2_FRET) // but pause for pain so we don't interrupt pinch phase, eep!
-	&& mobj->threshold-- == 0)
+	if (mobj->threshold)
 	{
-		// 5 -> 2.5 second timer
-		mobj->threshold = 5*TICRATE-(TICRATE/2)*(mobj->info->spawnhealth-mobj->health);
-		if (mobj->threshold < 1)
-			mobj->threshold = 1;
-
-		if (mobj->extravalue1-- == 0)
-		{
-			P_SetMobjState(mobj, mobj->info->raisestate);
-			mobj->extravalue1 = 3;
-		}
-		else
+		if (!(mobj->flags2 & MF2_FRET) && !(--mobj->threshold)) // but pause for pain so we don't interrupt pinch phase, eep!
 		{
 			if (mobj->reactiontime == 1) // Cage is raised?
+			{
+				P_SetMobjState(mobj, mobj->info->spawnstate);
 				mobj->reactiontime = 0; // Drop it!
-			switch(P_RandomKey(10))
-			{
-				// Telegraph Right (Speed Up!!)
-				case 1:
-				case 3:
-				case 4:
-				case 5:
-				case 6:
-					P_SetMobjState(mobj, mobj->info->missilestate);
-					break;
-				// Telegraph Left (Reverse Direction)
-				default:
-					P_SetMobjState(mobj, mobj->info->meleestate);
-					break;
 			}
 		}
 	}
@@ -5008,13 +5018,11 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	// Map allows us to get killed despite cage being down?
 	if (mobj->health <= mobj->info->damage)
 	{ // Proceed to pinch phase!
-		P_Boss4DestroyCage();
-		// spawn jet's flame now you're flying upwards
-		// tracer is already used, so if this ever gets reached again we've got problems
+		P_Boss4DestroyCage(mobj);
+		mobj->movedir = 3;
+		P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
 		var1 = 3;
 		A_BossJetFume(mobj);
-		mobj->movedir = 3;
-		P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
 		return;
 	}
 
@@ -5025,7 +5033,48 @@ static void P_Boss4Thinker(mobj_t *mobj)
 }
 
 //
-// AI for Black Eggman
+// AI for the fifth boss.
+//
+static void P_Boss5Thinker(mobj_t *mobj)
+{
+	if (!mobj->health)
+	{
+		if (mobj->state == &states[mobj->info->xdeathstate])
+			mobj->momz -= (2*FRACUNIT)/3;
+		else if (mobj->tracer && P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y) < 2*mobj->radius)
+			mobj->flags &= ~MF_NOCLIP;
+	}
+	else
+	{
+		if (mobj->flags2 & MF2_FRET && (leveltime & 1)
+		&& mobj->state != &states[S_FANG_PAIN1] && mobj->state != &states[S_FANG_PAIN2])
+			mobj->flags2 |= MF2_DONTDRAW;
+		else
+			mobj->flags2 &= ~MF2_DONTDRAW;
+	}
+
+	if (mobj->state == &states[S_FANG_BOUNCE3]
+	||  mobj->state == &states[S_FANG_BOUNCE4]
+	||  mobj->state == &states[S_FANG_PINCHBOUNCE3]
+	||  mobj->state == &states[S_FANG_PINCHBOUNCE4])
+	{
+		if (P_MobjFlip(mobj)*mobj->momz > 0
+		&& abs(mobj->momx) < FRACUNIT/2 && abs(mobj->momy) < FRACUNIT/2
+		&& !P_IsObjectOnGround(mobj))
+		{
+			mobj_t *prevtarget = mobj->target;
+			P_SetTarget(&mobj->target, NULL);
+			var1 = var2 = 0;
+			A_DoNPCPain(mobj);
+			P_SetTarget(&mobj->target, prevtarget);
+			P_SetMobjState(mobj, S_FANG_WALLHIT);
+			mobj->extravalue2++;
+		}
+	}
+}
+
+//
+// AI for Black Eggman
 // Note: You CANNOT have more than ONE Black Eggman
 // in a level! Just don't try it!
 //
@@ -5187,6 +5236,7 @@ static void P_Boss7Thinker(mobj_t *mobj)
 		INT32 i;
 		boolean foundgoop = false;
 		INT32 closestNum;
+		UINT8 extrainfo = (mobj->spawnpoint ? mobj->spawnpoint->extrainfo : 0);
 
 		// Looks for players in goop. If you find one, try to jump on him.
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -5206,23 +5256,29 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				closestdist = INT32_MAX; // Just in case...
 
 				// Find waypoint he is closest to
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
-					if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint)
-					{
-						dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
+					if (mo2->type != MT_BOSS3WAYPOINT)
+						continue;
+					if (!mo2->spawnpoint)
+						continue;
+					if (mo2->spawnpoint->extrainfo != extrainfo)
+						continue;
+					if (mobj->health <= mobj->info->damage && !(mo2->spawnpoint->options & 7))
+						continue; // don't jump to center
 
-						if (closestNum == -1 || dist < closestdist)
-						{
-							closestNum = (mo2->spawnpoint->options & 7);
-							closestdist = dist;
-							foundgoop = true;
-						}
-					}
+					dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
+
+					if (!(closestNum == -1 || dist < closestdist))
+						continue;
+
+					closestNum = (mo2->spawnpoint->options & 7);
+					closestdist = dist;
+					foundgoop = true;
 				}
 				waypointNum = closestNum;
 				break;
@@ -5231,17 +5287,14 @@ static void P_Boss7Thinker(mobj_t *mobj)
 
 		if (!foundgoop)
 		{
-			if (mobj->z > 1056*FRACUNIT)
-				waypointNum = 0;
-			else
+			// Don't jump to the center when health is low.
+			// Force the player to beat you with missiles.
+			if (mobj->z <= 1056*FRACUNIT || mobj->health <= mobj->info->damage)
 				waypointNum = 1 + P_RandomKey(4);
+			else
+				waypointNum = 0;
 		}
 
-		// Don't jump to the center when health is low.
-		// Force the player to beat you with missiles.
-		if (mobj->health <= mobj->info->damage && waypointNum == 0)
-			waypointNum = 1 + P_RandomKey(4);
-
 		if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT
 			&& mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum)
 		{
@@ -5250,28 +5303,31 @@ static void P_Boss7Thinker(mobj_t *mobj)
 			else
 				waypointNum--;
 
-			waypointNum %= 5;
-
-			if (waypointNum < 0)
-				waypointNum = 0;
+			if (mobj->health <= mobj->info->damage)
+				waypointNum = ((waypointNum + 3) % 4) + 1; // plus four to avoid modulo being negative, minus one to avoid waypoint #0
+			else
+				waypointNum = ((waypointNum + 5) % 5);
 		}
 
-		if (waypointNum == 0 && mobj->health <= mobj->info->damage)
-			waypointNum = 1 + (P_RandomFixed() & 1);
-
 		// scan the thinkers to find
 		// the waypoint to use
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && (mo2->spawnpoint->options & 7) == waypointNum)
-			{
-				hitspot = mo2;
-				break;
-			}
+			if (mo2->type != MT_BOSS3WAYPOINT)
+				continue;
+			if (!mo2->spawnpoint)
+				continue;
+			if ((mo2->spawnpoint->options & 7) != waypointNum)
+				continue;
+			if (mo2->spawnpoint->extrainfo != extrainfo)
+				continue;
+
+			hitspot = mo2;
+			break;
 		}
 
 		if (hitspot == NULL)
@@ -5368,7 +5424,8 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				if (mobj->info->activesound)\
 					S_StartSound(mobj, mobj->info->activesound);\
 				if (mobj->info->painchance)\
-					P_SetMobjState(mobj, mobj->info->painchance)
+					P_SetMobjState(mobj, mobj->info->painchance);\
+				mobj->flags2 &= ~MF2_INVERTAIMABLE;\
 
 // Metal Sonic battle boss
 // You CAN put multiple Metal Sonics in a single map
@@ -5393,19 +5450,19 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 		// Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map.
 		// Build a hoop linked list of 'em!
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
 			if (mo2->type == MT_BOSS9GATHERPOINT)
 			{
 				if (last)
-					last->hnext = mo2;
+					P_SetTarget(&last->hnext, mo2);
 				else
-					mobj->hnext = mo2;
-				mo2->hprev = last;
+					P_SetTarget(&mobj->hnext, mo2);
+				P_SetTarget(&mo2->hprev, last);
 				last = mo2;
 			}
 		}
@@ -5457,27 +5514,17 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 	// AI goes here.
 	{
-		boolean danger = true;
 		angle_t angle;
-		if (mobj->threshold)
+		if (mobj->threshold || mobj->movecount)
 			mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER
 		else
 			mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position
 
-		if (mobj->movecount == 2) {
+		if (mobj->movecount == 2)
+		{
 			mobj_t *spawner;
 			fixed_t dist = 0;
-			angle = 0x06000000*leveltime;
-
-			// Alter your energy bubble's size/position
-			if (mobj->health > 3) {
-				mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
-				P_SetScale(mobj->tracer, mobj->tracer->destscale);
-				P_TeleportMove(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2);
-				mobj->tracer->momx = mobj->momx;
-				mobj->tracer->momy = mobj->momy;
-				mobj->tracer->momz = mobj->momz;
-			}
+			angle = 0x06000000*leveltime; // wtf?
 
 			// Face your target
 			P_BossTargetPlayer(mobj, true);
@@ -5488,27 +5535,150 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			else
 				mobj->angle -= InvAngle(angle)/8;
 
+			// Alter your energy bubble's size/position
+			if (mobj->health > 3)
+			{
+				mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
+				P_SetScale(mobj->tracer, mobj->tracer->destscale);
+			}
+			else
+				mobj->tracer->frame &= ~FF_TRANSMASK; // this causes a flicker but honestly i like it this way
+			P_TeleportMove(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2);
+			mobj->tracer->momx = mobj->momx;
+			mobj->tracer->momy = mobj->momy;
+			mobj->tracer->momz = mobj->momz;
+
+				// Firin' mah lazors - INDICATOR
+				if (mobj->fuse > TICRATE/2)
+				{
+					tic_t shoottime, worktime, calctime;
+					shoottime = (TICRATE/((mobj->extravalue1 == 3) ? 8 : 4));
+					shoottime += (shoottime>>1);
+					worktime = shoottime*(mobj->threshold/2);
+					calctime = mobj->fuse-(TICRATE/2);
+
+					if (calctime <= worktime && (calctime % shoottime == 0))
+					{
+						mobj_t *missile;
+
+						missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER);
+						S_StopSound(missile);
+						if (mobj->extravalue1 >= 2)
+							P_SetScale(missile, FRACUNIT>>1);
+						missile->destscale = missile->scale>>1;
+						missile->fuse = TICRATE/2;
+						missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
+						missile->z -= missile->height/2;
+						missile->momx *= -1;
+						missile->momy *= -1;
+						missile->momz *= -1;
+
+						if (mobj->extravalue1 == 2)
+						{
+							UINT8 i;
+							mobj_t *spread;
+							for (i = 0; i < 5; i++)
+							{
+								if (i == 2)
+									continue;
+								spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
+								spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
+								P_InstaThrust(spread,spread->angle,-spread->info->speed);
+								spread->momz = missile->momz;
+								P_SetScale(spread, missile->scale);
+								spread->destscale = missile->destscale;
+								spread->scalespeed = missile->scalespeed;
+								spread->fuse = missile->fuse;
+								P_UnsetThingPosition(spread);
+								spread->x -= spread->fuse*spread->momx;
+								spread->y -= spread->fuse*spread->momy;
+								spread->z -= spread->fuse*spread->momz;
+								P_SetThingPosition(spread);
+							}
+							P_InstaThrust(missile,missile->angle,-missile->info->speed);
+						}
+						else if (mobj->extravalue1 >= 3)
+						{
+							UINT8 i;
+							mobj_t *spread;
+							mobj->target->z -= (4*missile->height);
+							for (i = 0; i < 5; i++)
+							{
+								if (i != 2)
+								{
+									spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+									P_SetScale(spread, missile->scale);
+									spread->destscale = missile->destscale;
+									spread->fuse = missile->fuse;
+									spread->z -= spread->height/2;
+									spread->momx *= -1;
+									spread->momy *= -1;
+									spread->momz *= -1;
+									P_UnsetThingPosition(spread);
+									spread->x -= spread->fuse*spread->momx;
+									spread->y -= spread->fuse*spread->momy;
+									spread->z -= spread->fuse*spread->momz;
+									P_SetThingPosition(spread);
+								}
+								mobj->target->z += missile->height*2;
+							}
+							mobj->target->z -= (6*missile->height);
+						}
+
+						P_UnsetThingPosition(missile);
+						missile->x -= missile->fuse*missile->momx;
+						missile->y -= missile->fuse*missile->momy;
+						missile->z -= missile->fuse*missile->momz;
+						P_SetThingPosition(missile);
+
+						S_StartSound(mobj, sfx_s3kb3);
+					}
+				}
+
+			// up...
+			mobj->z += mobj->height/2;
+
 			// Spawn energy particles
-			for (spawner = mobj->hnext; spawner; spawner = spawner->hnext) {
+			for (spawner = mobj->hnext; spawner; spawner = spawner->hnext)
+			{
 				dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y);
 				if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
 					break;
 			}
-			if (spawner) {
+			if (spawner)
+			{
 				mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
-				if (mobj->health > mobj->info->damage)
-					missile->momz = FixedDiv(missile->momz, 7*FRACUNIT/5);
+
 				if (dist == 0)
 					missile->fuse = 0;
 				else
 					missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
+
 				if (missile->fuse > mobj->fuse)
 					P_RemoveMobj(missile);
+
+				if (mobj->health > mobj->info->damage)
+				{
+					P_SetScale(missile, FRACUNIT/2);
+					missile->color = SKINCOLOR_GOLD; // sonic cd electric power
+				}
+				else
+				{
+					P_SetScale(missile, FRACUNIT/4);
+					missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
+				}
+				missile->destscale = missile->scale*2;
+				missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
+				missile->colorized = true;
 			}
+
+			// ...then down. easier than changing the missile's momz after-the-fact
+			mobj->z -= mobj->height/2;
 		}
 
 		// Pre-threshold reactiontime stuff for attack phases
-		if (mobj->reactiontime && mobj->movecount == 3) {
+		if (mobj->reactiontime && mobj->movecount == 3)
+		{
 			mobj->reactiontime--;
 
 			if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase
@@ -5529,13 +5699,15 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		}
 
 		// threshold is used for attacks/maneuvers.
-		if (mobj->threshold) {
+		if (mobj->threshold && mobj->movecount != 2) {
 			fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, mobj->info->spawnhealth<<FRACBITS));
-			int tries = 0;
+			UINT8 tries = 0;
 
 			// Firin' mah lazors
-			if (mobj->movecount == 3 && mobj->movedir == 1) {
-				if (!(mobj->threshold&1)) {
+			if (mobj->movecount == 3 && mobj->movedir == 1)
+			{
+				if (!(mobj->threshold & 1))
+				{
 					mobj_t *missile;
 					if (mobj->info->seesound)
 						S_StartSound(mobj, mobj->info->seesound);
@@ -5547,18 +5719,20 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 					A_FaceTarget(mobj);
 					missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed);
-					if (mobj->extravalue1 == 2 || mobj->extravalue1 == 3) {
+					if (mobj->extravalue1 >= 2)
+					{
 						missile->destscale = FRACUNIT>>1;
 						P_SetScale(missile, missile->destscale);
 					}
 					missile->fuse = 3*TICRATE;
 					missile->z -= missile->height/2;
 
-					if (mobj->extravalue1 == 2) {
-						int i;
+					if (mobj->extravalue1 == 2)
+					{
+						UINT8 i;
 						mobj_t *spread;
-						missile->flags |= MF_MISSILE;
-						for (i = 0; i < 5; i++) {
+						for (i = 0; i < 5; i++)
+						{
 							if (i == 2)
 								continue;
 							spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
@@ -5567,11 +5741,32 @@ static void P_Boss9Thinker(mobj_t *mobj)
 							spread->momz = missile->momz;
 							spread->destscale = FRACUNIT>>1;
 							P_SetScale(spread, spread->destscale);
-							spread->fuse = 3*TICRATE;
+							spread->fuse = missile->fuse;
+						}
+						P_InstaThrust(missile,missile->angle,missile->info->speed);
+					}
+					else if (mobj->extravalue1 >= 3)
+					{
+						UINT8 i;
+						mobj_t *spread;
+						mobj->target->z -= (2*missile->height);
+						for (i = 0; i < 5; i++)
+						{
+							if (i != 2)
+							{
+								spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+								spread->destscale = FRACUNIT>>1;
+								P_SetScale(spread, spread->destscale);
+								spread->fuse = missile->fuse;
+								spread->z -= spread->height/2;
+							}
+							mobj->target->z += missile->height;
 						}
-						missile->flags &= ~MF_MISSILE;
+						mobj->target->z -= (3*missile->height);
 					}
-				} else {
+				}
+				else
+				{
 					P_SetMobjState(mobj, mobj->state->nextstate);
 					if (mobj->extravalue1 == 3)
 						mobj->reactiontime = TICRATE/8;
@@ -5585,7 +5780,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			P_SpawnGhostMobj(mobj);
 
 			// Pinball attack!
-			if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2)) {
+			if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2))
+			{
 				if ((statenum_t)(mobj->state-states) != mobj->info->seestate)
 					P_SetMobjState(mobj, mobj->info->seestate);
 				if (mobj->movedir == 0) // mobj health == 1
@@ -5594,7 +5790,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT);
 				else // mobj health == 2
 					P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT);
-				if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true)) { // Hit a wall? Find a direction to bounce
+				if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
+				{ // Hit a wall? Find a direction to bounce
 					mobj->threshold--;
 					P_SetMobjState(mobj, mobj->state->nextstate);
 					if (!mobj->threshold) { // failed bounce!
@@ -5607,11 +5804,15 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj->movecount = 0;
 						P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
 						P_SetMobjState(mobj, mobj->info->meleestate);
-					} else if (!(mobj->threshold%4)) { // We've decided to lock onto the player this bounce.
+					}
+					else if (!(mobj->threshold%4))
+					{ // We've decided to lock onto the player this bounce.
 						S_StartSound(mobj, sfx_s3k5a);
 						mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
 						mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time
-					} else { // No homing, just use P_BounceMove
+					}
+					else
+					{ // No homing, just use P_BounceMove
 						S_StartSound(mobj, sfx_s3kaa); // make the bounces distinct...
 						P_BounceMove(mobj);
 						mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy);
@@ -5625,7 +5826,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			// Vector form dodge!
 			mobj->angle += mobj->movedir;
 			P_InstaThrust(mobj, mobj->angle, -speed);
-			while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16) {
+			while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16)
+			{
 				S_StartSound(mobj, sfx_mspogo);
 				P_BounceMove(mobj);
 				mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
@@ -5682,7 +5884,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		if (mobj->flags2 & MF2_FRET)
 			return;
 
-		if (mobj->state == &states[mobj->info->raisestate])
+		if (mobj->movecount == 1 || mobj->movecount == 2)
 		{ // Charging energy
 			if (mobj->momx != 0 || mobj->momy != 0) { // Apply the air breaks
 				if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT)
@@ -5690,11 +5892,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				else
 					P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8);
 			}
-			return;
+			if (mobj->state == states+mobj->info->raisestate)
+				return;
 		}
 
 		if (mobj->fuse == 0)
 		{
+			mobj->flags2 &= ~MF2_INVERTAIMABLE;
 			// It's time to attack! What are we gonna do?!
 			switch(mobj->movecount)
 			{
@@ -5702,6 +5906,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			default:
 				// Fly up and prepare for an attack!
 				// We have to charge up first, so let's go up into the air
+				S_StartSound(mobj, sfx_beflap);
 				P_SetMobjState(mobj, mobj->info->raisestate);
 				if (mobj->floorz >= mobj->target->floorz)
 					mobj->watertop = mobj->floorz + 256*FRACUNIT;
@@ -5709,33 +5914,70 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					mobj->watertop = mobj->target->floorz + 256*FRACUNIT;
 				break;
 
-			case 1: {
+			case 1:
 				// Okay, we're up? Good, time to gather energy...
 				if (mobj->health > mobj->info->damage)
 				{ // No more bubble if we're broken (pinch phase)
 					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
 					P_SetTarget(&mobj->tracer, shield);
 					P_SetTarget(&shield->target, mobj);
+
+					// Attack 2: Energy shot!
+					switch (mobj->health)
+					{
+						case 8: // shoot once
+						default:
+							mobj->extravalue1 = 0;
+							mobj->threshold = 2;
+							break;
+						case 7: // spread shot (vertical)
+							mobj->extravalue1 = 4;
+							mobj->threshold = 2;
+							break;
+						case 6: // three shots
+							mobj->extravalue1 = 1;
+							mobj->threshold = 3*2;
+							break;
+						case 5: // spread shot (horizontal)
+							mobj->extravalue1 = 2;
+							mobj->threshold = 2;
+							break;
+						case 4: // machine gun
+							mobj->extravalue1 = 3;
+							mobj->threshold = 5*2;
+							break;
+					}
 				}
 				else
-					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+				{
+					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
+					P_SetTarget(&mobj->tracer, shield);
+					P_SetTarget(&shield->target, mobj);
+					shield->height -= 20*FRACUNIT; // different offset...
+					shield->color = SKINCOLOR_MAGENTA;
+					shield->colorized = true;
+					P_SetMobjState(shield, S_FIRS1);
+					//P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
+				}
 				mobj->fuse = 4*TICRATE;
 				mobj->flags |= MF_PAIN;
 				if (mobj->info->attacksound)
 					S_StartSound(mobj, mobj->info->attacksound);
 				A_FaceTarget(mobj);
+
 				break;
-			}
 
 			case 2:
+			{
 				// We're all charged and ready now! Unleash the fury!!
-				if (mobj->health > mobj->info->damage)
+				mobj_t *removemobj = mobj->tracer;
+				S_StopSound(mobj);
+				P_SetTarget(&mobj->tracer, mobj->hnext);
+				P_RemoveMobj(removemobj);
+				if (mobj->health <= mobj->info->damage)
 				{
-					mobj_t *removemobj = mobj->tracer;
-					P_SetTarget(&mobj->tracer, mobj->hnext);
-					P_RemoveMobj(removemobj);
-				}
-				if (mobj->health <= mobj->info->damage) {
+					mobj_t *whoosh;
+
 					// Attack 1: Pinball dash!
 					if (mobj->health == 1)
 						mobj->movedir = 0;
@@ -5750,35 +5992,26 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj->threshold = 24; // bounce 24 times
 					mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
 					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
-				} else {
+
+					whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
+					whoosh->frame = FF_FULLBRIGHT;
+					whoosh->sprite = SPR_ARMA;
+					whoosh->destscale = whoosh->scale<<1;
+					whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
+					whoosh->height = 38*whoosh->scale;
+					whoosh->fuse = 10;
+					whoosh->color = SKINCOLOR_MAGENTA;
+					whoosh->colorized = true;
+					whoosh->flags |= MF_NOCLIPHEIGHT;
+				}
+				else
+				{
 					// Attack 2: Energy shot!
 					mobj->movedir = 1;
-
-					if (mobj->health >= 8)
-						mobj->extravalue1 = 0;
-					else if (mobj->health >= 5)
-						mobj->extravalue1 = 2;
-					else if (mobj->health >= 4)
-						mobj->extravalue1 = 1;
-					else
-						mobj->extravalue1 = 3;
-
-					switch(mobj->extravalue1) {
-					case 0: // shoot once
-					case 2: // spread-shot
-					default:
-						mobj->threshold = 2;
-						break;
-					case 1: // shoot 3 times
-						mobj->threshold = 3*2;
-						break;
-					case 3: // shoot like a goddamn machinegun
-						mobj->threshold = 8*2;
-						break;
-					}
+					// looking for the number of things to fire? that's done in case 1 now
 				}
 				break;
-
+			}
 			case 3:
 				// Return to idle.
 				mobj->watertop = mobj->target->floorz + 32*FRACUNIT;
@@ -5809,38 +6042,44 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				mobj->angle -= InvAngle(angle)/8;
 			//A_FaceTarget(mobj);
 
-			// Check if we're being attacked
-			if (!(mobj->target->player->pflags & (PF_JUMPED|PF_SPINNING)
-			|| mobj->target->player->powers[pw_tailsfly]
-			|| mobj->target->player->powers[pw_invulnerability]
-			|| mobj->target->player->powers[pw_super]))
-				danger = false;
-			if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
-				danger = false;
-			if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
-				danger = false;
-			if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
-				danger = false;
-			if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
-				danger = false;
-			if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
-				danger = false;
-			if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
-				danger = false;
-			if (danger) {
-				// An incoming attack is detected! What should we do?!
-				// Go into vector form!
-				vectorise;
-				return;
-			}
+			if (mobj->flags2 & MF2_CLASSICPUSH)
+				mobj->flags2 &= ~MF2_CLASSICPUSH; // a missile caught us in PIT_CheckThing!
+			else
+			{
+				// Check if we're being attacked
+				if (!mobj->target || !mobj->target->player || !P_PlayerCanDamage(mobj->target->player, mobj))
+					goto nodanger;
+				if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
+					goto nodanger;
+				if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
+					goto nodanger;
+				if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
+					goto nodanger;
+				if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
+					goto nodanger;
+				if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
+					goto nodanger;
+				if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
+					goto nodanger;
+			}
+
+			// An incoming attack is detected! What should we do?!
+			// Go into vector form!
+			vectorise;
+			return;
+nodanger:
+
+			mobj->flags2 |= MF2_INVERTAIMABLE;
 
 			// Move normally: Approach the player using normal thrust and simulated friction.
 			dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y);
 			P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8);
-			if (dist < 64*FRACUNIT)
+			if (dist < 64*FRACUNIT && !(mobj->target->player && mobj->target->player->homing))
 				P_Thrust(mobj, mobj->angle, -4*FRACUNIT);
 			else if (dist > 180*FRACUNIT)
 				P_Thrust(mobj, mobj->angle, FRACUNIT);
+			else
+				P_Thrust(mobj, mobj->angle + ANGLE_90, FINECOSINE((((angle_t)(leveltime*ANG1))>>ANGLETOFINESHIFT) & FINEMASK)>>1);
 			mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going.
 		}
 	}
@@ -5859,9 +6098,9 @@ mobj_t *P_GetClosestAxis(mobj_t *source)
 	fixed_t dist1, dist2 = 0;
 
 	// scan the thinkers to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -6117,9 +6356,11 @@ void P_SetScale(mobj_t *mobj, fixed_t newscale)
 void P_Attract(mobj_t *source, mobj_t *dest, boolean nightsgrab) // Home in on your target
 {
 	fixed_t dist, ndist, speedmul;
+	angle_t vangle;
 	fixed_t tx = dest->x;
 	fixed_t ty = dest->y;
 	fixed_t tz = dest->z + (dest->height/2); // Aim for center
+	fixed_t xydist = P_AproxDistance(tx - source->x, ty - source->y);
 
 	if (!dest || dest->health <= 0 || !dest->player || !source->tracer)
 		return;
@@ -6128,19 +6369,40 @@ void P_Attract(mobj_t *source, mobj_t *dest, boolean nightsgrab) // Home in on y
 	source->angle = R_PointToAngle2(source->x, source->y, tx, ty);
 
 	// change slope
-	dist = P_AproxDistance(P_AproxDistance(tx - source->x, ty - source->y), tz - source->z);
+	dist = P_AproxDistance(xydist, tz - source->z);
 
 	if (dist < 1)
 		dist = 1;
 
-	if (nightsgrab)
-		speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(8*FRACUNIT, source->scale);
+	if (nightsgrab && source->movefactor)
+	{
+		source->movefactor += FRACUNIT/2;
+
+		if (dist < source->movefactor)
+		{
+			source->momx = source->momy = source->momz = 0;
+			P_TeleportMove(source, tx, ty, tz);
+		}
+		else
+		{
+			vangle = R_PointToAngle2(source->z, 0, tz, xydist);
+
+			source->momx = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINECOSINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
+			source->momy = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINESINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
+			source->momz = FixedMul(FINECOSINE(vangle >> ANGLETOFINESHIFT), source->movefactor);
+		}
+	}
 	else
-		speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(source->info->speed, source->scale);
+	{
+		if (nightsgrab)
+			speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(8*FRACUNIT, source->scale);
+		else
+			speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(source->info->speed, source->scale);
 
-	source->momx = FixedMul(FixedDiv(tx - source->x, dist), speedmul);
-	source->momy = FixedMul(FixedDiv(ty - source->y, dist), speedmul);
-	source->momz = FixedMul(FixedDiv(tz - source->z, dist), speedmul);
+		source->momx = FixedMul(FixedDiv(tx - source->x, dist), speedmul);
+		source->momy = FixedMul(FixedDiv(ty - source->y, dist), speedmul);
+		source->momz = FixedMul(FixedDiv(tz - source->z, dist), speedmul);
+	}
 
 	// Instead of just unsetting NOCLIP like an idiot, let's check the distance to our target.
 	ndist = P_AproxDistance(P_AproxDistance(tx - (source->x+source->momx),
@@ -6165,6 +6427,7 @@ static void P_NightsItemChase(mobj_t *thing)
 	{
 		P_SetTarget(&thing->tracer, NULL);
 		thing->flags2 &= ~MF2_NIGHTSPULL;
+		thing->movefactor = 0;
 		return;
 	}
 
@@ -6176,124 +6439,218 @@ static void P_NightsItemChase(mobj_t *thing)
 
 //
 // P_MaceRotate
-// Spins an object around its target, or, swings it from side to side.
+// Spins a hnext-chain of objects around its centerpoint, side to side or periodically.
 //
-static void P_MaceRotate(mobj_t *mobj)
+void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot)
 {
-	TVector v;
+	TVector unit_lengthways, unit_sideways, pos_lengthways, pos_sideways;
 	TVector *res;
-	fixed_t radius, dist;
+	fixed_t radius, dist, zstore;
 	angle_t fa;
-	INT32 prevswing;
-	boolean donetwice = false;
+	boolean dosound = false;
+	mobj_t *mobj = center->hnext, *hnext = NULL;
 
-	// Tracer was removed.
-	if (!mobj->health)
-		return;
-	else if (!mobj->tracer)
+	INT32 lastthreshold = -1; // needs to never be equal at start of loop
+	fixed_t lastfriction = INT32_MIN; // ditto; almost certainly never, but...
+
+	INT32 rot;
+	INT32 prevrot;
+
+	dist = pos_sideways[0] = pos_sideways[1] = pos_sideways[2] = pos_sideways[3] = unit_sideways[3] =\
+	 pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
+
+	while (mobj)
 	{
-		P_KillMobj(mobj, NULL, NULL, 0);
-		return;
-	}
+		if (P_MobjWasRemoved(mobj) || !mobj->health)
+		{
+			mobj = mobj->hnext;
+			continue;
+		}
 
-	mobj->momx = mobj->momy = mobj->momz = 0;
+		mobj->momx = mobj->momy = mobj->momz = 0;
 
-	prevswing = mobj->threshold;
-	mobj->threshold += mobj->tracer->lastlook;
-	mobj->threshold &= FINEMASK;
+		if (mobj->threshold != lastthreshold
+		|| mobj->friction != lastfriction)
+		{
+			rot = (baserot + mobj->threshold) & FINEMASK;
+			prevrot = (baseprevrot + mobj->threshold) & FINEMASK;
 
-	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+			pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
 
-	// Radius of the link's rotation.
-	radius = FixedMul(dist * mobj->movecount, mobj->tracer->scale) + mobj->tracer->extravalue1;
+			dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+			dist = ((center->scale == FRACUNIT) ? dist : FixedMul(dist, center->scale));
 
-maceretry:
+			fa = (FixedAngle(center->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
+			radius = FixedMul(dist, FINECOSINE(fa));
+			unit_lengthways[1] = -FixedMul(dist, FINESINE(fa));
+			unit_lengthways[3] = FRACUNIT;
 
-	fa = (FixedAngle(mobj->tracer->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
-	radius = FixedMul(FINECOSINE(fa), radius);
-	v[1] = -FixedMul(FINESINE(fa), radius)
-	+ FixedMul(dist * mobj->movefactor, mobj->tracer->scale);
-	v[3] = FRACUNIT;
+			// Swinging Chain.
+			if (center->flags2 & MF2_STRONGBOX)
+			{
+				fixed_t swingmag = FixedMul(FINECOSINE(rot), center->lastlook << FRACBITS);
+				fixed_t prevswingmag = FINECOSINE(prevrot);
 
-	// Swinging Chain.
-	if (mobj->tracer->flags2 & MF2_STRONGBOX)
-	{
-		fixed_t swingmagnitude = FixedMul(FINECOSINE(mobj->threshold), mobj->tracer->lastlook << FRACBITS);
-		prevswing = FINECOSINE(prevswing);
+				if ((prevswingmag > 0) != (swingmag > 0)) // just passed its lowest point
+					dosound = true;
 
-		if (!donetwice
-		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
-		&& ((prevswing > 0) != (swingmagnitude > 0))) // just passed its lowest point
-			S_StartSound(mobj, mobj->info->activesound);
+				fa = ((FixedAngle(swingmag) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
 
-		fa = ((FixedAngle(swingmagnitude) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
+				unit_lengthways[0] = FixedMul(FINESINE(fa), -radius);
+				unit_lengthways[2] = FixedMul(FINECOSINE(fa), -radius);
+			}
+			// Rotating Chain.
+			else
+			{
+				angle_t prevfa = (prevrot + mobj->friction) & FINEMASK;
+				fa = (rot + mobj->friction) & FINEMASK;
 
-		v[0] = FixedMul(FINESINE(fa), -radius);
-		v[2] = FixedMul(FINECOSINE(fa), -radius);
-	}
-	// Rotating Chain.
-	else
-	{
-		prevswing = (prevswing + mobj->friction) & FINEMASK;
-		fa = (mobj->threshold + mobj->friction) & FINEMASK;
+				if (!(prevfa > (FINEMASK/2)) && (fa > (FINEMASK/2))) // completed a full swing
+					dosound = true;
+
+				unit_lengthways[0] = FixedMul(FINECOSINE(fa), radius);
+				unit_lengthways[2] = FixedMul(FINESINE(fa), radius);
+			}
 
-		if (!donetwice
-		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
-		&& (!(prevswing > (FINEMASK/2)) && (fa > (FINEMASK/2)))) // completed a full swing
+			// Calculate the angle matrixes for the link.
+			res = VectorMatrixMultiply(unit_lengthways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
+			M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
+			res = VectorMatrixMultiply(unit_lengthways, *RotateZMatrix(center->angle));
+			M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
+
+			lastthreshold = mobj->threshold;
+			lastfriction = mobj->friction;
+		}
+
+		if (dosound && (mobj->flags2 & MF2_BOSSNOTRAP))
+		{
 			S_StartSound(mobj, mobj->info->activesound);
+			dosound = false;
+		}
 
-		v[0] = FixedMul(FINECOSINE(fa), radius);
-		v[2] = FixedMul(FINESINE(fa), radius);
-	}
+		if (pos_sideways[3] != mobj->movefactor)
+		{
+			if (!unit_sideways[3])
+			{
+				unit_sideways[1] = dist;
+				unit_sideways[0] = unit_sideways[2] = 0;
+				unit_sideways[3] = FRACUNIT;
 
-	// Calculate the angle matrixes for the link.
-	res = VectorMatrixMultiply(v, *RotateXMatrix(mobj->tracer->threshold << ANGLETOFINESHIFT));
-	M_Memcpy(&v, res, sizeof(v));
-	res = VectorMatrixMultiply(v, *RotateZMatrix(mobj->tracer->health << ANGLETOFINESHIFT));
-	M_Memcpy(&v, res, sizeof(v));
+				res = VectorMatrixMultiply(unit_sideways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
+				M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
+				res = VectorMatrixMultiply(unit_sideways, *RotateZMatrix(center->angle));
+				M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
+			}
 
-	// Cut the height to align the link with the axis.
-	if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN)
-		v[2] -= P_MobjFlip(mobj)*mobj->height/4;
-	else
-		v[2] -= P_MobjFlip(mobj)*mobj->height/2;
+			if (pos_sideways[3] > mobj->movefactor)
+			{
+				do
+				{
+					pos_sideways[0] -= unit_sideways[0];
+					pos_sideways[1] -= unit_sideways[1];
+					pos_sideways[2] -= unit_sideways[2];
+				}
+				while ((--pos_sideways[3]) != mobj->movefactor);
+			}
+			else
+			{
+				do
+				{
+					pos_sideways[0] += unit_sideways[0];
+					pos_sideways[1] += unit_sideways[1];
+					pos_sideways[2] += unit_sideways[2];
+				}
+				while ((++pos_sideways[3]) != mobj->movefactor);
+			}
+		}
 
-	P_UnsetThingPosition(mobj);
+		hnext = mobj->hnext; // just in case the mobj is removed
+
+		if (pos_lengthways[3] > mobj->movecount)
+		{
+			do
+			{
+				pos_lengthways[0] -= unit_lengthways[0];
+				pos_lengthways[1] -= unit_lengthways[1];
+				pos_lengthways[2] -= unit_lengthways[2];
+			}
+			while ((--pos_lengthways[3]) != mobj->movecount);
+		}
+		else if (pos_lengthways[3] < mobj->movecount)
+		{
+			do
+			{
+				pos_lengthways[0] += unit_lengthways[0];
+				pos_lengthways[1] += unit_lengthways[1];
+				pos_lengthways[2] += unit_lengthways[2];
+			}
+			while ((++pos_lengthways[3]) != mobj->movecount);
+		}
 
-	// Add on the appropriate distances to the center's co-ordinates.
-	mobj->x = mobj->tracer->x + v[0];
-	mobj->y = mobj->tracer->y + v[1];
-	mobj->z = mobj->tracer->z + v[2];
+		P_UnsetThingPosition(mobj);
 
-	P_SetThingPosition(mobj);
+		mobj->x = center->x;
+		mobj->y = center->y;
+		mobj->z = center->z;
 
-	if (donetwice || P_MobjWasRemoved(mobj))
-		return;
+		// Add on the appropriate distances to the center's co-ordinates.
+		if (pos_lengthways[3])
+		{
+			mobj->x += pos_lengthways[0];
+			mobj->y += pos_lengthways[1];
+			zstore = pos_lengthways[2] + pos_sideways[2];
+		}
+		else
+			zstore = pos_sideways[2];
 
-	if (mobj->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT))
-		return;
+		mobj->x += pos_sideways[0];
+		mobj->y += pos_sideways[1];
 
-	if ((fa = ((mobj->tracer->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
-		return;
+		// Cut the height to align the link with the axis.
+		if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN || mobj->type == MT_SMALLGRABCHAIN || mobj->type == MT_BIGGRABCHAIN)
+			zstore -= P_MobjFlip(mobj)*mobj->height/4;
+		else
+			zstore -= P_MobjFlip(mobj)*mobj->height/2;
 
-	if (mobj->subsector->sector->ffloors)
-		P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
+		mobj->z += zstore;
 
-	// Variable reuse
-	if (mobj->floorz > mobj->z)
-		dist = (mobj->floorz - mobj->tracer->z);
-	else if (mobj->ceilingz < mobj->z)
-		dist = (mobj->ceilingz - mobj->tracer->z);
-	else
-		return;
+#if 0 // toaster's testing flashie!
+		if (!(mobj->movecount & 1) && !(leveltime & TICRATE)) // I had a brainfart and the flashing isn't exactly what I expected it to be, but it's actually much more useful.
+			mobj->flags2 ^= MF2_DONTDRAW;
+#endif
 
-	if ((dist = FixedDiv(dist, v[2])) > FRACUNIT)
-		return;
+		P_SetThingPosition(mobj);
+
+#if 0 // toaster's height-clipping dealie!
+		if (!pos_lengthways[3] || P_MobjWasRemoved(mobj) || (mobj->flags & MF_NOCLIPHEIGHT))
+			goto cont;
+
+		if ((fa = ((center->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
+			goto cont;
+
+		if (mobj->subsector->sector->ffloors)
+			P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
+
+		if (mobj->floorz > mobj->z)
+			zstore = (mobj->floorz - zstore);
+		else if (mobj->ceilingz < mobj->z)
+			zstore = (mobj->ceilingz - mobj->height - zstore);
+		else
+			goto cont;
+
+		zstore = FixedDiv(zstore, dist); // Still needs work... scaling factor is wrong!
+
+		P_UnsetThingPosition(mobj);
+
+		mobj->x -= FixedMul(unit_lengthways[0], zstore);
+		mobj->y -= FixedMul(unit_lengthways[1], zstore);
 
-	radius = FixedMul(radius, dist);
-	donetwice = true;
-	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
-	goto maceretry;
+		P_SetThingPosition(mobj);
+
+cont:
+#endif
+		mobj = hnext;
+	}
 }
 
 static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
@@ -6442,7 +6799,7 @@ void P_RunOverlays(void)
 		{
 			angle_t viewingangle;
 
-			if (players[displayplayer].awayviewtics)
+			if (players[displayplayer].awayviewtics && players[displayplayer].awayviewmobj != NULL && !P_MobjWasRemoved(players[displayplayer].awayviewmobj))
 				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y);
 			else if (!camera.chase && players[displayplayer].mo)
 				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y);
@@ -6515,12 +6872,14 @@ static void P_RemoveOverlay(mobj_t *thing)
 {
 	mobj_t *mo;
 	for (mo = overlaycap; mo; mo = mo->hnext)
-		if (mo->hnext == thing)
-		{
-			P_SetTarget(&mo->hnext, thing->hnext);
-			P_SetTarget(&thing->hnext, NULL);
-			return;
-		}
+	{
+		if (mo->hnext != thing)
+			continue;
+
+		P_SetTarget(&mo->hnext, thing->hnext);
+		P_SetTarget(&thing->hnext, NULL);
+		return;
+	}
 }
 
 void A_BossDeath(mobj_t *mo);
@@ -6580,6 +6939,67 @@ static void P_KoopaThinker(mobj_t *koopa)
 	}
 }
 
+// Spawns and chains the minecart sides.
+static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode)
+{
+	fixed_t x = mobj->x;
+	fixed_t y = mobj->y;
+	fixed_t z = mobj->z;
+	mobj_t *prevseg = mobj;
+	mobj_t *seg;
+	UINT8 i;
+
+	for (i = 0; i < 4; i++)
+	{
+		seg = P_SpawnMobj(x, y, z, MT_MINECARTSEG);
+		P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i));
+		if (i >= 2)
+			seg->extravalue1 = (i == 2) ? -18 : 18;
+		else
+		{
+			seg->extravalue2 = (i == 0) ? 24 : -24;
+			seg->cusval = -90;
+		}
+		if (!mode)
+			seg->frame &= ~FF_ANIMATE;
+		P_SetTarget(&prevseg->tracer, seg);
+		prevseg = seg;
+	}
+}
+
+// Updates the chained segments.
+static void P_UpdateMinecartSegments(mobj_t *mobj)
+{
+	mobj_t *seg = mobj->tracer;
+	fixed_t x = mobj->x;
+	fixed_t y = mobj->y;
+	fixed_t z = mobj->z;
+	angle_t ang = mobj->angle;
+	angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t c = FINECOSINE(fa);
+	fixed_t s = FINESINE(fa);
+	INT32 dx, dy;
+	INT32 sang;
+
+	while (seg)
+	{
+		dx = seg->extravalue1;
+		dy = seg->extravalue2;
+		sang = seg->cusval;
+		P_TeleportMove(seg, x + s*dx + c*dy, y - c*dx + s*dy, z);
+		seg->angle = ang + FixedAngle(FRACUNIT*sang);
+		seg->flags2 = (seg->flags2 & ~MF2_DONTDRAW) | (mobj->flags2 & MF2_DONTDRAW);
+		seg = seg->tracer;
+	}
+}
+
+void P_HandleMinecartSegments(mobj_t *mobj)
+{
+	if (!mobj->tracer)
+		P_SpawnMinecartSegments(mobj, (mobj->type == MT_MINECART));
+	P_UpdateMinecartSegments(mobj);
+}
+
 //
 // P_MobjThinker
 //
@@ -6591,11 +7011,18 @@ void P_MobjThinker(mobj_t *mobj)
 	if (mobj->flags & MF_NOTHINK)
 		return;
 
+	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
+		return;
+
 	// Remove dead target/tracer.
 	if (mobj->target && P_MobjWasRemoved(mobj->target))
 		P_SetTarget(&mobj->target, NULL);
 	if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
 		P_SetTarget(&mobj->tracer, NULL);
+	if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
+		P_SetTarget(&mobj->hnext, NULL);
+	if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
+		P_SetTarget(&mobj->hprev, NULL);
 
 	mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
 
@@ -6664,13 +7091,6 @@ void P_MobjThinker(mobj_t *mobj)
 		}
 	}
 
-	if (mobj->flags2 & MF2_MACEROTATE)
-	{
-		P_MaceRotate(mobj);
-		if (P_MobjWasRemoved(mobj))
-			return;
-	}
-
 	// Special thinker for scenery objects
 	if (mobj->flags & MF_SCENERY)
 	{
@@ -6687,6 +7107,75 @@ void P_MobjThinker(mobj_t *mobj)
 
 		switch (mobj->type)
 		{
+			case MT_MACEPOINT:
+			case MT_CHAINMACEPOINT:
+			case MT_SPRINGBALLPOINT:
+			case MT_CHAINPOINT:
+			case MT_FIREBARPOINT:
+			case MT_CUSTOMMACEPOINT:
+			case MT_HIDDEN_SLING:
+				{
+					angle_t oldmovedir = mobj->movedir;
+
+					// Always update movedir to prevent desyncing (in the traditional sense, not the netplay sense).
+					mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
+
+					// If too far away and not deliberately spitting in the face of optimisation, don't think!
+					if (!(mobj->flags2 & MF2_BOSSNOTRAP))
+					{
+						UINT8 i;
+						// Quick! Look through players! Don't move unless a player is relatively close by.
+						// The below is selected based on CEZ2's first room. I promise you it is a coincidence that it looks like the weed number.
+						for (i = 0; i < MAXPLAYERS; ++i)
+							if (playeringame[i] && players[i].mo
+							 && P_AproxDistance(P_AproxDistance(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y), mobj->z - players[i].mo->z) < (4200<<FRACBITS))
+								break; // Stop looking.
+						if (i == MAXPLAYERS)
+						{
+							if (!(mobj->flags2 & MF2_BEYONDTHEGRAVE))
+							{
+								mobj_t *ref = mobj;
+
+								// stop/hide all your babies
+								while ((ref = ref->hnext))
+								{
+									ref->eflags = (((ref->flags & MF_NOTHINK) ? 0 : 1)
+										| ((ref->flags & MF_NOCLIPTHING) ? 0 : 2)
+										| ((ref->flags2 & MF2_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
+									ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
+									ref->flags2 |= MF2_DONTDRAW;
+									ref->momx = ref->momy = ref->momz = 0;
+								}
+
+								mobj->flags2 |= MF2_BEYONDTHEGRAVE;
+							}
+
+							break; // don't make bubble!
+						}
+						else if (mobj->flags2 & MF2_BEYONDTHEGRAVE)
+						{
+							mobj_t *ref = mobj;
+
+							// start/show all your babies
+							while ((ref = ref->hnext))
+							{
+								if (ref->eflags & 1)
+									ref->flags &= ~MF_NOTHINK;
+								if (ref->eflags & 2)
+									ref->flags &= ~MF_NOCLIPTHING;
+								if (ref->eflags & 4)
+									ref->flags2 &= ~MF2_DONTDRAW;
+								ref->eflags = 0; // le sign
+							}
+
+							mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
+						}
+					}
+
+					// Okay, time to MOVE
+					P_MaceRotate(mobj, mobj->movedir, oldmovedir);
+				}
+				break;
 			case MT_HOOP:
 				if (mobj->fuse > 1)
 					P_MoveHoop(mobj);
@@ -6894,6 +7383,9 @@ void P_MobjThinker(mobj_t *mobj)
 					P_RemoveMobj(mobj);
 					return;
 				}
+
+				mobj->flags2 &= ~MF2_DONTDRAW;
+
 				mobj->x = mobj->target->x;
 				mobj->y = mobj->target->y;
 
@@ -6907,6 +7399,17 @@ void P_MobjThinker(mobj_t *mobj)
 				else
 					mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
 				break;
+			case MT_LOCKONINF:
+				if (!(mobj->flags2 & MF2_STRONGBOX))
+				{
+					mobj->threshold = mobj->z;
+					mobj->flags2 |= MF2_STRONGBOX;
+				}
+				if (!(mobj->eflags & MFE_VERTICALFLIP))
+					mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+				else
+					mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+				break;
 			case MT_DROWNNUMBERS:
 				if (!mobj->target)
 				{
@@ -7008,6 +7511,34 @@ void P_MobjThinker(mobj_t *mobj)
 					S_StartSound(flame, sfx_fire);
 				}
 				break;
+			case MT_FLICKY_01_CENTER:
+			case MT_FLICKY_02_CENTER:
+			case MT_FLICKY_03_CENTER:
+			case MT_FLICKY_04_CENTER:
+			case MT_FLICKY_05_CENTER:
+			case MT_FLICKY_06_CENTER:
+			case MT_FLICKY_07_CENTER:
+			case MT_FLICKY_08_CENTER:
+			case MT_FLICKY_09_CENTER:
+			case MT_FLICKY_10_CENTER:
+			case MT_FLICKY_11_CENTER:
+			case MT_FLICKY_12_CENTER:
+			case MT_FLICKY_13_CENTER:
+			case MT_FLICKY_14_CENTER:
+			case MT_FLICKY_15_CENTER:
+			case MT_FLICKY_16_CENTER:
+			case MT_SECRETFLICKY_01_CENTER:
+			case MT_SECRETFLICKY_02_CENTER:
+				if (mobj->tracer && (mobj->flags & MF_NOCLIPTHING)
+					&& (mobj->flags & MF_GRENADEBOUNCE))
+					// for now: only do this bounce routine if flicky is in-place. \todo allow in all movements
+				{
+					if (!(mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z <= mobj->tracer->floorz)
+						mobj->tracer->momz = 7*FRACUNIT;
+					else if ((mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z >= mobj->tracer->ceilingz - mobj->tracer->height)
+						mobj->tracer->momz = -7*FRACUNIT;
+				}
+				break;
 			case MT_SEED:
 				if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
 					mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
@@ -7028,6 +7559,8 @@ void P_MobjThinker(mobj_t *mobj)
 			case MT_ROCKCRUMBLE14:
 			case MT_ROCKCRUMBLE15:
 			case MT_ROCKCRUMBLE16:
+			case MT_WOODDEBRIS:
+			case MT_BRICKDEBRIS:
 				if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
 					&& mobj->state != &states[mobj->info->deathstate])
 				{
@@ -7035,6 +7568,63 @@ void P_MobjThinker(mobj_t *mobj)
 					return;
 				}
 				break;
+			case MT_PARTICLEGEN:
+				if (!mobj->lastlook)
+					return;
+
+				if (!mobj->threshold)
+					return;
+
+				if (--mobj->fuse <= 0)
+				{
+					INT32 i = 0;
+					mobj_t *spawn;
+					fixed_t bottomheight, topheight;
+					INT32 type = mobj->threshold, line = mobj->cvmem;
+
+					mobj->fuse = (tic_t)mobj->reactiontime;
+
+					bottomheight = lines[line].frontsector->floorheight;
+					topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
+
+					if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
+					{
+						if (mobj->movefactor && (topheight > bottomheight))
+							mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
+						else
+							mobj->health = 0;
+
+						mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
+					}
+
+					if (!mobj->health)
+						return;
+
+					for (i = 0; i < mobj->lastlook; i++)
+					{
+						spawn = P_SpawnMobj(
+							mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle>>ANGLETOFINESHIFT)),
+							mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle>>ANGLETOFINESHIFT)),
+							mobj->z,
+							(mobjtype_t)mobj->threshold);
+						P_SetScale(spawn, mobj->scale);
+						spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
+						spawn->destscale = spawn->scale/100;
+						spawn->scalespeed = spawn->scale/mobj->health;
+						spawn->tics = (tic_t)mobj->health;
+						spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
+						spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
+
+						mobj->angle += mobj->movedir;
+					}
+
+					mobj->angle += (angle_t)mobj->movecount;
+				}
+				break;
+			case MT_FSGNA:
+				if (mobj->movedir)
+					mobj->angle += mobj->movedir;
+				break;
 			default:
 				if (mobj->fuse)
 				{ // Scenery object fuse! Very basic!
@@ -7124,6 +7714,9 @@ void P_MobjThinker(mobj_t *mobj)
 			case MT_EGGMOBILE4:
 				P_Boss4Thinker(mobj);
 				break;
+			case MT_FANG:
+				P_Boss5Thinker(mobj);
+				break;
 			case MT_BLACKEGGMAN:
 				P_Boss7Thinker(mobj);
 				break;
@@ -7142,7 +7735,7 @@ void P_MobjThinker(mobj_t *mobj)
 	else if (mobj->health <= 0) // Dead things think differently than the living.
 		switch (mobj->type)
 		{
-		case MT_BLUEBALL:
+		case MT_BLUESPHERE:
 			if ((mobj->tics>>2)+1 > 0 && (mobj->tics>>2)+1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
 				mobj->frame = (NUMTRANSMAPS-((mobj->tics>>2)+1))<<FF_TRANSSHIFT;
 			else // tr_trans60 otherwise
@@ -7155,6 +7748,37 @@ void P_MobjThinker(mobj_t *mobj)
 				return;
 			}
 			break;
+		case MT_FAKEMOBILE:
+			if (mobj->scale == mobj->destscale)
+			{
+				if (!mobj->fuse)
+				{
+					S_StartSound(mobj, sfx_s3k77);
+					mobj->flags2 |= MF2_DONTDRAW;
+					mobj->fuse = TICRATE;
+				}
+				return;
+			}
+			if (!mobj->reactiontime)
+			{
+				if (P_RandomChance(FRACUNIT/2))
+					mobj->movefactor = FRACUNIT;
+				else
+					mobj->movefactor = -FRACUNIT;
+				if (P_RandomChance(FRACUNIT/2))
+					mobj->movedir = ANG20;
+				else
+					mobj->movedir = -ANG20;
+				mobj->reactiontime = 5;
+			}
+			mobj->momz += mobj->movefactor;
+			mobj->angle += mobj->movedir;
+			P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
+			mobj->reactiontime--;
+			break;
+		case MT_EGGSHIELD:
+			mobj->flags2 ^= MF2_DONTDRAW;
+			break;
 		case MT_EGGTRAP: // Egg Capsule animal release
 			if (mobj->fuse > 0 && mobj->fuse < 2*TICRATE-(TICRATE/7)
 				&& (mobj->fuse & 3))
@@ -7197,588 +7821,1206 @@ void P_MobjThinker(mobj_t *mobj)
 			break;
 		case MT_PLAYER:
 			/// \todo Have the player's dead body completely finish its animation even if they've already respawned.
-			if (!(mobj->flags2 & MF2_DONTDRAW))
-			{
-				if (!mobj->fuse)
-				{ // Go away.
-					/// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
-					mobj->momz = 0;
-					if (mobj->player)
-						mobj->flags2 |= MF2_DONTDRAW;
-					else // safe to remove, nobody's going to complain!
-					{
-						P_RemoveMobj(mobj);
-						return;
-					}
-				}
-				else // Apply gravity to fall downwards.
-				{
-					if (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
-					{
-							fixed_t r = mobj->radius>>FRACBITS;
-							mobj_t *explosion = P_SpawnMobj(
-								mobj->x + (P_RandomRange(r, -r)<<FRACBITS),
-								mobj->y + (P_RandomRange(r, -r)<<FRACBITS),
-								mobj->z + (P_RandomKey(mobj->height>>FRACBITS)<<FRACBITS),
-								MT_BOSSEXPLODE);
-							S_StartSound(explosion, sfx_cybdth);
-					}
-					if (mobj->movedir == DMG_DROWNED)
-						P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning
-					else
-						P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
-				}
-			}
-			break;
-		default:
-			break;
-		}
-	else switch (mobj->type)
-	{
-		case MT_WALLSPIKEBASE:
-			if (!mobj->target) {
-				P_RemoveMobj(mobj);
-				return;
-			}
-			mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
-#if 0
-			if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
-			{
-				mobj_t *target = mobj->target; // shortcut
-				const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
-				P_UnsetThingPosition(mobj);
-				mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
-				mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
-				P_SetThingPosition(mobj);
-				mobj->angle = target->angle + ANGLE_90;
-			}
-#endif
-			break;
-		case MT_FALLINGROCK:
-			// Despawn rocks here in case zmovement code can't do so (blame slopes)
-			if (!mobj->momx && !mobj->momy && !mobj->momz
-			&& ((mobj->eflags & MFE_VERTICALFLIP) ?
-				  mobj->z + mobj->height >= mobj->ceilingz
-				: mobj->z <= mobj->floorz))
-			{
-				P_RemoveMobj(mobj);
-				return;
-			}
-			P_MobjCheckWater(mobj);
-			break;
-		case MT_EMERALDSPAWN:
-			if (mobj->threshold)
-			{
-				mobj->threshold--;
-
-				if (!mobj->threshold && !mobj->target && mobj->reactiontime)
+			if (!mobj->fuse)
+			{ // Go away.
+			  /// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
+				mobj->momz = 0;
+				if (mobj->player)
+					mobj->flags2 |= MF2_DONTDRAW;
+				else // safe to remove, nobody's going to complain!
 				{
-					mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
-					emerald->threshold = 42;
-					P_SetTarget(&mobj->target, emerald);
-					P_SetTarget(&emerald->target, mobj);
+					P_RemoveMobj(mobj);
+					return;
 				}
 			}
-			break;
-		case MT_AQUABUZZ:
-			P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
-			/* FALLTHRU */
-		case MT_BIGAIRMINE:
+			else // Apply gravity to fall downwards.
 			{
-				if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
-					&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16)
+				if (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
 				{
-					// Home in on the target.
-					P_HomingAttack(mobj, mobj->tracer);
-
-					if (mobj->z < mobj->floorz)
-						mobj->z = mobj->floorz;
-
-					if (leveltime % mobj->info->painchance == 0)
-						S_StartSound(mobj, mobj->info->activesound);
+					fixed_t r = mobj->radius >> FRACBITS;
+					mobj_t *explosion = P_SpawnMobj(
+						mobj->x + (P_RandomRange(r, -r) << FRACBITS),
+						mobj->y + (P_RandomRange(r, -r) << FRACBITS),
+						mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
+						MT_BOSSEXPLODE);
+					S_StartSound(explosion, sfx_cybdth);
 				}
+				if (mobj->movedir == DMG_DROWNED)
+					P_SetObjectMomZ(mobj, -FRACUNIT / 2, true); // slower fall from drowning
 				else
-				{
-					// Try to find a player
-					P_LookForPlayers(mobj, true, true, mobj->radius * 16);
-					mobj->momx >>= 1;
-					mobj->momy >>= 1;
-					mobj->momz >>= 1;
-				}
+					P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true);
 			}
 			break;
-		case MT_BIGMINE:
-			{
-				if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
-					&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16)
-				{
-					P_MobjCheckWater(mobj);
+		default:
+			break;
+		}
+	else
+	{
+		if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
+			mobj->flags2 &= ~MF2_FRET;
 
-					// Home in on the target.
-					P_HomingAttack(mobj, mobj->tracer);
+		// Angle-to-tracer to trigger a linedef exec
+		// See Linedef Exec 457 (Track mobj angle to point)
+		if ((mobj->eflags & MFE_TRACERANGLE) && mobj->tracer && mobj->extravalue2)
+		{
+			// mobj->lastlook - Don't disable behavior after first failure
+			// mobj->extravalue1 - Angle tolerance
+			// mobj->extravalue2 - Exec tag upon failure
+			// mobj->cvval - Allowable failure delay
+			// mobj->cvmem - Failure timer
 
-					// Don't let it go out of water
-					if (mobj->z + mobj->height > mobj->watertop)
-						mobj->z = mobj->watertop - mobj->height;
+			angle_t ang = mobj->angle - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
 
-					if (mobj->z < mobj->floorz)
-						mobj->z = mobj->floorz;
+			// \todo account for distance between mobj and tracer
+			// Because closer mobjs can be facing beyond the angle tolerance
+			// yet tracer is still in the camera view
 
-					if (leveltime % mobj->info->painchance == 0)
-						S_StartSound(mobj, mobj->info->activesound);
-				}
+			// failure state: mobj is not facing tracer
+			// Reasaonable defaults: ANGLE_67h, ANGLE_292h
+			if (ang >= (angle_t)mobj->extravalue1 && ang <= ANGLE_MAX - (angle_t)mobj->extravalue1)
+			{
+				if (mobj->cvmem)
+					mobj->cvmem--;
 				else
 				{
-					// Try to find a player
-					P_LookForPlayers(mobj, true, true, mobj->radius * 16);
-					mobj->momx >>= 1;
-					mobj->momy >>= 1;
-					mobj->momz >>= 1;
-				}
-			}
-			break;
-		case MT_CHAINPOINT:
-		case MT_CHAINMACEPOINT:
-			if (leveltime & 1)
-			{
-				if (mobj->lastlook > mobj->movecount)
-					mobj->lastlook--;
-/*
-				if (mobj->threshold > mobj->movefactor)
-					mobj->threshold -= FRACUNIT;
-				else if (mobj->threshold < mobj->movefactor)
-					mobj->threshold += FRACUNIT;*/
-			}
-			break;
-		case MT_EGGCAPSULE:
-			if (!mobj->reactiontime)
-			{
-				// Target nearest player on your mare.
-				// (You can make it float up/down by adding MF_FLOAT,
-				//  but beware level design pitfalls.)
-				fixed_t shortest = 1024*FRACUNIT;
-				INT32 i;
-				P_SetTarget(&mobj->target, NULL);
-				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].mo
-					&& players[i].mare == mobj->threshold && players[i].rings > 0)
+					INT32 exectag = mobj->extravalue2; // remember this before we erase the values
+
+					if (mobj->lastlook)
+						mobj->cvmem = mobj->cusval; // reset timer for next failure
+					else
 					{
-						fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
-						if (dist < shortest)
-						{
-							P_SetTarget(&mobj->target, players[i].mo);
-							shortest = dist;
-						}
+						// disable after first failure
+						mobj->eflags &= ~MFE_TRACERANGLE;
+						mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
 					}
+
+					P_LinedefExecute(exectag, mobj, NULL);
+				}
 			}
-			break;
-		case MT_EGGMOBILE2_POGO:
-			if (!mobj->target
-			|| !mobj->target->health
-			|| mobj->target->state == &states[mobj->target->info->spawnstate]
-			|| mobj->target->state == &states[mobj->target->info->raisestate])
-			{
-				P_RemoveMobj(mobj);
-				return;
-			}
-			P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
-			break;
-		case MT_HAMMER:
-			if (mobj->z <= mobj->floorz)
-			{
-				P_RemoveMobj(mobj);
-				return;
-			}
-			break;
-		case MT_KOOPA:
-			P_KoopaThinker(mobj);
-			break;
-		case MT_REDRING:
-			if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
-				&& mobj->flags & MF_MISSILE)
-			{
-				P_ExplodeMissile(mobj);
-				return;
-			}
-			break;
-		case MT_BOSSFLYPOINT:
-			return;
-		case MT_NIGHTSCORE:
-			mobj->color = (UINT8)(leveltime % SKINCOLOR_WHITE);
-			break;
-		case MT_JETFUME1:
-			{
-				fixed_t jetx, jety;
+			else
+				mobj->cvmem = mobj->cusval; // reset failure timer
+		}
 
-				if (!mobj->target // if you have no target
-				|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-				{ // then remove yourself as well!
+		switch (mobj->type)
+		{
+			case MT_WALLSPIKEBASE:
+				if (!mobj->target) {
 					P_RemoveMobj(mobj);
 					return;
 				}
-
-				jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
-				jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
-
-				if (mobj->fuse == 56) // First one
+				mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
+#if 0
+				if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
 				{
+					mobj_t *target = mobj->target; // shortcut
+					const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
 					P_UnsetThingPosition(mobj);
-					mobj->x = jetx;
-					mobj->y = jety;
-					if (mobj->target->eflags & MFE_VERTICALFLIP)
-						mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
-					else
-						mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
+					mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
+					mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
 					P_SetThingPosition(mobj);
+					mobj->angle = target->angle + ANGLE_90;
 				}
-				else if (mobj->fuse == 57)
+#endif
+				break;
+			case MT_FALLINGROCK:
+				// Despawn rocks here in case zmovement code can't do so (blame slopes)
+				if (!mobj->momx && !mobj->momy && !mobj->momz
+				&& ((mobj->eflags & MFE_VERTICALFLIP) ?
+					  mobj->z + mobj->height >= mobj->ceilingz
+					: mobj->z <= mobj->floorz))
 				{
-					P_UnsetThingPosition(mobj);
-					mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-					mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-					if (mobj->target->eflags & MFE_VERTICALFLIP)
-						mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
-					else
-						mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
+					P_RemoveMobj(mobj);
+					return;
 				}
-				else if (mobj->fuse == 58)
+				P_MobjCheckWater(mobj);
+				break;
+			case MT_ARROW:
+				if (mobj->flags & MF_MISSILE)
 				{
-					P_UnsetThingPosition(mobj);
-					mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-					mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-					if (mobj->target->eflags & MFE_VERTICALFLIP)
-						mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
+					// Calculate the angle of movement.
+					/*
+						   momz
+						 / |
+					   /   |
+					 /     |
+					0------dist(momx,momy)
+					*/
+
+					fixed_t dist = P_AproxDistance(mobj->momx, mobj->momy);
+					angle_t angle = R_PointToAngle2(0, 0, dist, mobj->momz);
+
+					if (angle > ANG20 && angle <= ANGLE_180)
+						mobj->frame = 2;
+					else if (angle < ANG340 && angle > ANGLE_180)
+						mobj->frame = 0;
 					else
-						mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
+						mobj->frame = 1;
+
+					if (!(mobj->extravalue1) && (mobj->momz < 0))
+					{
+						mobj->extravalue1 = 1;
+						S_StartSound(mobj, mobj->info->activesound);
+					}
+					if (leveltime & 1)
+					{
+						mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
+						dust->tics = 18;
+						dust->scalespeed = 4096;
+						dust->destscale = FRACUNIT/32;
+					}
 				}
-				else if (mobj->fuse == 59)
+				else
+					mobj->flags2 ^= MF2_DONTDRAW;
+				break;
+			case MT_EMERALDSPAWN:
+				if (mobj->threshold)
 				{
-					jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
-					jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
-					P_UnsetThingPosition(mobj);
-					mobj->x = jetx;
-					mobj->y = jety;
-					if (mobj->target->eflags & MFE_VERTICALFLIP)
-						mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
-					else
-						mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
-				}
-				mobj->fuse++;
-			}
-			break;
-		case MT_PROPELLER:
-			{
-				fixed_t jetx, jety;
+					mobj->threshold--;
 
-				if (!mobj->target // if you have no target
-				|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-				{ // then remove yourself as well!
-					P_RemoveMobj(mobj);
-					return;
+					if (!mobj->threshold && !mobj->target && mobj->reactiontime)
+					{
+						mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
+						emerald->threshold = 42;
+						P_SetTarget(&mobj->target, emerald);
+						P_SetTarget(&emerald->target, mobj);
+					}
 				}
+				break;
+			case MT_BUBBLEBUZZ:
+				mobj->eflags |= MFE_UNDERWATER; //P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
+				{
+					if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
+						&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16)
+					{
+						// Home in on the target.
+						P_HomingAttack(mobj, mobj->tracer);
 
-				jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
-				jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
+						if (mobj->z < mobj->floorz)
+							mobj->z = mobj->floorz;
 
+						if (leveltime % mobj->info->painchance == 0)
+							S_StartSound(mobj, mobj->info->activesound);
+					}
+					else
+					{
+						// Try to find a player
+						P_LookForPlayers(mobj, true, true, mobj->radius * 16);
+						mobj->momx >>= 1;
+						mobj->momy >>= 1;
+						mobj->momz >>= 1;
+					}
+				}
+				break;
+			case MT_BUMBLEBORE:
+				{
+					statenum_t st = mobj->state-states;
+					if (st == S_BUMBLEBORE_FLY1 || st == S_BUMBLEBORE_FLY2)
+					{
+						if (!mobj->target)
+							P_SetMobjState(mobj, mobj->info->spawnstate);
+						else if (P_MobjFlip(mobj)*((mobj->z + (mobj->height>>1)) - (mobj->target->z + (mobj->target->height>>1))) > 0
+							&& R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) <= 32*FRACUNIT)
+						{
+							mobj->momx >>= 1;
+							mobj->momy >>= 1;
+							if (++mobj->movefactor == 4)
+							{
+								S_StartSound(mobj, mobj->info->seesound);
+								mobj->momx = mobj->momy = mobj->momz = 0;
+								mobj->flags = (mobj->flags|MF_PAIN) & ~MF_NOGRAVITY;
+								P_SetMobjState(mobj, mobj->info->meleestate);
+							}
+						}
+						else
+							mobj->movefactor = 0;
+					}
+					else if (st == S_BUMBLEBORE_RAISE || st == S_BUMBLEBORE_FALL2) // no _FALL1 because it's an 0-tic
+					{
+						if (P_IsObjectOnGround(mobj))
+						{
+							S_StopSound(mobj);
+							S_StartSound(mobj, mobj->info->attacksound);
+							mobj->flags = (mobj->flags|MF_NOGRAVITY) & ~MF_PAIN;
+							mobj->momx = mobj->momy = mobj->momz = 0;
+							P_SetMobjState(mobj, mobj->info->painstate);
+						}
+						else
+						{
+							mobj->angle += ANGLE_22h;
+							mobj->frame = mobj->state->frame + ((mobj->tics & 2)>>1);
+						}
+					}
+					else if (st == S_BUMBLEBORE_STUCK2 && mobj->tics < TICRATE)
+						mobj->frame = mobj->state->frame + ((mobj->tics & 2)>>1);
+				}
+				break;
+			case MT_BIGMINE:
+				mobj->extravalue1 += 3;
+				mobj->extravalue1 %= 360;
 				P_UnsetThingPosition(mobj);
-				mobj->x = jetx;
-				mobj->y = jety;
-				mobj->z = mobj->target->z + FixedMul(17*FRACUNIT, mobj->target->scale);
-				mobj->angle = mobj->target->angle - ANGLE_180;
-				mobj->floorz = mobj->z;
-				mobj->ceilingz = mobj->z+mobj->height;
+				mobj->z += FINESINE(mobj->extravalue1*(FINEMASK+1)/360);
 				P_SetThingPosition(mobj);
-			}
-			break;
-		case MT_JETFLAME:
-			{
-				if (!mobj->target // if you have no target
-				|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-				{ // then remove yourself as well!
+				break;
+			case MT_FLAME:
+				if (mobj->flags2 & MF2_BOSSNOTRAP)
+				{
+					if (!mobj->target || P_MobjWasRemoved(mobj->target))
+					{
+						if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
+							P_RemoveMobj(mobj->tracer);
+						P_RemoveMobj(mobj);
+						return;
+					}
+					mobj->z = mobj->target->z + mobj->target->momz;
+					if (!(mobj->eflags & MFE_VERTICALFLIP))
+						mobj->z += mobj->target->height;
+				}
+				if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
+				{
+					mobj->tracer->z = mobj->z + P_MobjFlip(mobj)*20*mobj->scale;
+					if (mobj->eflags & MFE_VERTICALFLIP)
+						mobj->tracer->z += mobj->height;
+				}
+				break;
+			case MT_WAVINGFLAG1:
+			case MT_WAVINGFLAG2:
+				{
+					fixed_t base = (leveltime<<(FRACBITS+1));
+					mobj_t *seg = mobj->tracer, *prev = mobj;
+					mobj->movedir = mobj->angle
+						+ ((((FINESINE((FixedAngle(base<<1)>>ANGLETOFINESHIFT) & FINEMASK)
+							+ FINESINE((FixedAngle(base<<4)>>ANGLETOFINESHIFT) & FINEMASK))>>1)
+						+ FINESINE((FixedAngle(base*9)>>ANGLETOFINESHIFT) & FINEMASK)
+						+ FINECOSINE(((FixedAngle(base*9))>>ANGLETOFINESHIFT) & FINEMASK))<<12); //*2^12
+					while (seg)
+					{
+						seg->movedir = seg->angle;
+						seg->angle = prev->movedir;
+						P_UnsetThingPosition(seg);
+						seg->x = prev->x + P_ReturnThrustX(prev, prev->angle, prev->radius);
+						seg->y = prev->y + P_ReturnThrustY(prev, prev->angle, prev->radius);
+						seg->z = prev->z + prev->height - (seg->scale>>1);
+						P_SetThingPosition(seg);
+						prev = seg;
+						seg = seg->tracer;
+					}
+				}
+				break;
+			case MT_SPINCUSHION:
+				if (mobj->target && mobj->state-states >= S_SPINCUSHION_AIM1 && mobj->state-states <= S_SPINCUSHION_AIM5)
+					mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+				break;
+			case MT_CRUSHCLAW:
+				if (mobj->state-states == S_CRUSHCLAW_STAY && mobj->target)
+				{
+					mobj_t *chain = mobj->target->target;
+					SINT8 sign = ((mobj->tics & 1) ? mobj->tics : -(SINT8)(mobj->tics));
+					while (chain)
+					{
+						chain->z = chain->watertop + sign*mobj->scale;
+						sign = -sign;
+						chain = chain->target;
+					}
+				}
+				break;
+			case MT_SMASHINGSPIKEBALL:
+				mobj->momx = mobj->momy = 0;
+				if (mobj->state-states == S_SMASHSPIKE_FALL && P_IsObjectOnGround(mobj))
+				{
+					P_SetMobjState(mobj, S_SMASHSPIKE_STOMP1);
+					S_StartSound(mobj, sfx_spsmsh);
+				}
+				else if (mobj->state-states == S_SMASHSPIKE_RISE2 && P_MobjFlip(mobj)*(mobj->z - mobj->movecount) >= 0)
+				{
+					mobj->momz = 0;
+					P_SetMobjState(mobj, S_SMASHSPIKE_FLOAT);
+				}
+				break;
+			case MT_HANGSTER:
+				{
+					statenum_t st =  mobj->state-states;
+					//ghost image trail when flying down
+					if (st == S_HANGSTER_SWOOP1 || st == S_HANGSTER_SWOOP2)
+					{
+						P_SpawnGhostMobj(mobj);
+						//curve when in line with target, otherwise curve to avoid crashing into floor
+						if ((mobj->z - mobj->floorz <= 80*FRACUNIT) || (mobj->target && (mobj->z - mobj->target->z <= 80*FRACUNIT)))
+							P_SetMobjState(mobj, (st = S_HANGSTER_ARC1));
+					}
+
+					//swoop arc movement stuff
+					if (st == S_HANGSTER_ARC1)
+					{
+						A_FaceTarget(mobj);
+						P_Thrust(mobj, mobj->angle, 1*FRACUNIT);
+					}
+					else if (st == S_HANGSTER_ARC2)
+						P_Thrust(mobj, mobj->angle, 2*FRACUNIT);
+					else if (st == S_HANGSTER_ARC3)
+						P_Thrust(mobj, mobj->angle, 4*FRACUNIT);
+					//if movement has stopped while flying (like hitting a wall), fly up immediately
+					else if (st == S_HANGSTER_FLY1 && !mobj->momx && !mobj->momy)
+					{
+						mobj->extravalue1 = 0;
+						P_SetMobjState(mobj, S_HANGSTER_ARCUP1);
+					}
+					//after swooping back up, check for ceiling
+					else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
+						P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
+
+					//should you roost on a ceiling with F_SKY1 as its flat, disappear forever
+					if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
+					&& mobj->subsector->sector->ceilingpic == skyflatnum
+					&& mobj->subsector->sector->ceilingheight == mobj->ceilingz)
+					{
+						P_RemoveMobj(mobj);
+						return;
+					}
+				}
+				break;
+			case MT_LHRT:
+				mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
+				mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
+				break;
+			case MT_EGGCAPSULE:
+				if (!mobj->reactiontime)
+				{
+					// Target nearest player on your mare.
+					// (You can make it float up/down by adding MF_FLOAT,
+					//  but beware level design pitfalls.)
+					fixed_t shortest = 1024*FRACUNIT;
+					INT32 i;
+					P_SetTarget(&mobj->target, NULL);
+					for (i = 0; i < MAXPLAYERS; i++)
+						if (playeringame[i] && players[i].mo
+						&& players[i].mare == mobj->threshold && players[i].spheres > 0)
+						{
+							fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
+							if (dist < shortest)
+							{
+								P_SetTarget(&mobj->target, players[i].mo);
+								shortest = dist;
+							}
+						}
+				}
+				break;
+			case MT_EGGMOBILE2_POGO:
+				if (!mobj->target
+				|| !mobj->target->health
+				|| mobj->target->state == &states[mobj->target->info->spawnstate]
+				|| mobj->target->state == &states[mobj->target->info->raisestate])
+				{
+					P_RemoveMobj(mobj);
+					return;
+				}
+				P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
+				break;
+			case MT_HAMMER:
+				if (mobj->z <= mobj->floorz)
+				{
 					P_RemoveMobj(mobj);
 					return;
 				}
+				break;
+			case MT_KOOPA:
+				P_KoopaThinker(mobj);
+				break;
+			case MT_REDRING:
+				if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
+					&& mobj->flags & MF_MISSILE)
+				{
+					P_ExplodeMissile(mobj);
+					return;
+				}
+				break;
+			case MT_BOSSFLYPOINT:
+				return;
+			case MT_NIGHTSCORE:
+				mobj->color = (UINT8)(leveltime % SKINCOLOR_WHITE);
+				break;
+			case MT_JETFUME1:
+				{
+					fixed_t jetx, jety;
 
-				P_UnsetThingPosition(mobj);
-				mobj->x = mobj->target->x;
-				mobj->y = mobj->target->y;
-				mobj->z = mobj->target->z - FixedMul(50*FRACUNIT, mobj->target->scale);
-				mobj->floorz = mobj->z;
-				mobj->ceilingz = mobj->z+mobj->height;
-				P_SetThingPosition(mobj);
-			}
-			break;
-		case MT_NIGHTSDRONE:
-			if (mobj->state >= &states[S_NIGHTSDRONE_SPARKLING1] && mobj->state <= &states[S_NIGHTSDRONE_SPARKLING16])
-			{
-				mobj->flags2 &= ~MF2_DONTDRAW;
-				mobj->z = mobj->floorz + mobj->height + (mobj->spawnpoint->options >> ZSHIFT) * FRACUNIT;
-				mobj->angle = 0;
+					if (!mobj->target // if you have no target
+					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+					{ // then remove yourself as well!
+						P_RemoveMobj(mobj);
+						return;
+					}
 
-				if (!mobj->target)
+					jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
+					jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
+
+					if (mobj->fuse == 56) // First one
+					{
+						P_UnsetThingPosition(mobj);
+						mobj->x = jetx;
+						mobj->y = jety;
+						if (mobj->target->eflags & MFE_VERTICALFLIP)
+							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
+						else
+							mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
+						mobj->floorz = mobj->z;
+						mobj->ceilingz = mobj->z+mobj->height;
+						P_SetThingPosition(mobj);
+					}
+					else if (mobj->fuse == 57)
+					{
+						P_UnsetThingPosition(mobj);
+						mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+						mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+						if (mobj->target->eflags & MFE_VERTICALFLIP)
+							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
+						else
+							mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
+						mobj->floorz = mobj->z;
+						mobj->ceilingz = mobj->z+mobj->height;
+						P_SetThingPosition(mobj);
+					}
+					else if (mobj->fuse == 58)
+					{
+						P_UnsetThingPosition(mobj);
+						mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+						mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+						if (mobj->target->eflags & MFE_VERTICALFLIP)
+							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
+						else
+							mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
+						mobj->floorz = mobj->z;
+						mobj->ceilingz = mobj->z+mobj->height;
+						P_SetThingPosition(mobj);
+					}
+					else if (mobj->fuse == 59)
+					{
+						jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
+						jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
+						P_UnsetThingPosition(mobj);
+						mobj->x = jetx;
+						mobj->y = jety;
+						if (mobj->target->eflags & MFE_VERTICALFLIP)
+							mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
+						else
+							mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
+						mobj->floorz = mobj->z;
+						mobj->ceilingz = mobj->z+mobj->height;
+						P_SetThingPosition(mobj);
+					}
+					mobj->fuse++;
+				}
+				break;
+			case MT_PROPELLER:
+				{
+					fixed_t jetx, jety;
+
+					if (!mobj->target // if you have no target
+					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+					{ // then remove yourself as well!
+						P_RemoveMobj(mobj);
+						return;
+					}
+
+					jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
+					jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
+
+					P_UnsetThingPosition(mobj);
+					mobj->x = jetx;
+					mobj->y = jety;
+					mobj->z = mobj->target->z + FixedMul(17*FRACUNIT, mobj->target->scale);
+					mobj->angle = mobj->target->angle - ANGLE_180;
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_SetThingPosition(mobj);
+				}
+				break;
+			case MT_JETFLAME:
 				{
-					mobj_t *goalpost = P_SpawnMobj(mobj->x, mobj->y, mobj->z + FRACUNIT, MT_NIGHTSGOAL);
-					CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
-					goalpost->angle = mobj->angle;
-					P_SetTarget(&mobj->target, goalpost);
+					if (!mobj->target // if you have no target
+					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+					{ // then remove yourself as well!
+						P_RemoveMobj(mobj);
+						return;
+					}
+
+					P_UnsetThingPosition(mobj);
+					mobj->x = mobj->target->x;
+					mobj->y = mobj->target->y;
+					mobj->z = mobj->target->z - 50*mobj->target->scale;
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_SetThingPosition(mobj);
 				}
+				break;
+			case MT_EGGROBO1:
+#define SPECTATORRADIUS (96*mobj->scale)
+				{
+					if (!(mobj->flags2 & MF2_STRONGBOX))
+					{
+						mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
+						mobj->cvmem = mobj->y; // ditto
+						mobj->movedir = mobj->angle;
+						mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
+						if (mobj->threshold < 0)
+							mobj->threshold += (mobj->ceilingz - mobj->height);
+						else
+							mobj->threshold += mobj->floorz;
+						var1 = 4;
+						A_BossJetFume(mobj);
+						mobj->flags2 |= MF2_STRONGBOX;
+					}
 
-				if (G_IsSpecialStage(gamemap))
-				{ // Never show the NiGHTS drone in special stages. Check ANYONE for bonustime.
-					INT32 i;
-					boolean bonustime = false;
-					for (i = 0; i < MAXPLAYERS; i++)
-						if (playeringame[i] && players[i].bonustime)
+					if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
+					{
+						if (mobj->movecount)
 						{
-							bonustime = true;
-							break;
+							if (!(--mobj->movecount))
+								S_StartSound(mobj, mobj->info->deathsound);
+						}
+						else
+						{
+							mobj->momz += P_MobjFlip(mobj)*mobj->scale;
+							if (mobj->momz > 0)
+							{
+								if (mobj->z + mobj->momz > mobj->ceilingz + (1000<<FRACBITS))
+								{
+									P_RemoveMobj(mobj);
+									return;
+								}
+							}
+							else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000<<FRACBITS))
+							{
+								P_RemoveMobj(mobj);
+								return;
+							}
 						}
-					if (!bonustime)
+					}
+					else
 					{
-						mobj->flags &= ~MF_NOGRAVITY;
-						P_SetMobjState(mobj, S_NIGHTSDRONE1);
-						mobj->flags2 |= MF2_DONTDRAW;
+						mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK), 8*mobj->scale);
+						if (mobj->state != &states[mobj->info->meleestate])
+						{
+							boolean didmove = false;
+
+							if (mobj->state == &states[mobj->info->spawnstate])
+							{
+								UINT8 i;
+								fixed_t dist = INT32_MAX;
+
+								for (i = 0; i < MAXPLAYERS; i++)
+								{
+									fixed_t compdist;
+									if (!playeringame[i])
+										continue;
+									if (players[i].spectator)
+										continue;
+									if (!players[i].mo)
+										continue;
+									if (!players[i].mo->health)
+										continue;
+									if (P_PlayerInPain(&players[i]))
+										continue;
+									if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
+										continue;
+									if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
+										continue;
+									compdist = P_AproxDistance(
+										players[i].mo->x + players[i].mo->momx - mobj->cusval,
+										players[i].mo->y + players[i].mo->momy - mobj->cvmem);
+									if (compdist >= dist)
+										continue;
+									dist = compdist;
+									P_SetTarget(&mobj->target, players[i].mo);
+								}
+
+								if (dist < (SPECTATORRADIUS<<1))
+								{
+									didmove = true;
+									mobj->frame = 3 + ((leveltime & 2)>>1);
+									mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+
+									if (P_AproxDistance(
+										mobj->x - mobj->cusval,
+										mobj->y - mobj->cvmem)
+										< mobj->scale)
+										S_StartSound(mobj, mobj->info->seesound);
+
+									P_TeleportMove(mobj,
+										(15*(mobj->x>>4)) + (mobj->cusval>>4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS>>4),
+										(15*(mobj->y>>4)) + (mobj->cvmem>>4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS>>4),
+										mobj->z);
+								}
+								else
+								{
+									angle_t diff = (mobj->movedir - mobj->angle);
+									if (diff > ANGLE_180)
+										diff = InvAngle(InvAngle(diff)/8);
+									else
+										diff /= 8;
+									mobj->angle += diff;
+
+									dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK);
+
+									if (abs(dist) < FRACUNIT/2)
+										mobj->frame = 0;
+									else
+										mobj->frame = (dist > 0) ? 1 : 2;
+								}
+							}
+
+							if (!didmove)
+							{
+								if (P_AproxDistance(
+										mobj->x - mobj->cusval,
+										mobj->y - mobj->cvmem)
+										< mobj->scale)
+									P_TeleportMove(mobj,
+										mobj->cusval,
+										mobj->cvmem,
+										mobj->z);
+								else
+									P_TeleportMove(mobj,
+										(15*(mobj->x>>4)) + (mobj->cusval>>4),
+										(15*(mobj->y>>4)) + (mobj->cvmem>>4),
+										mobj->z);
+							}
+						}
+					}
+				}
+				break;
+#undef SPECTATORRADIUS
+			case MT_EGGROBO1JET:
+				{
+					if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
+					|| (mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+					{ // then remove yourself as well!
+						P_RemoveMobj(mobj);
+						return;
 					}
+
+					mobj->flags2 ^= MF2_DONTDRAW;
+
+					P_UnsetThingPosition(mobj);
+					mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
+					mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
+					mobj->z = mobj->target->z;
+					if (mobj->target->eflags & MFE_VERTICALFLIP)
+						mobj->z += (mobj->target->height - mobj->height);
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_SetThingPosition(mobj);
 				}
-				else if (mobj->tracer && mobj->tracer->player)
+				break;
+			case MT_NIGHTSDRONE:
 				{
-					if (!(mobj->tracer->player->powers[pw_carry] == CR_NIGHTSMODE))
+					// variable setup
+					mobj_t *goalpost = NULL;
+					mobj_t *sparkle = NULL;
+					mobj_t *droneman = NULL;
+
+					boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
+					boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+					boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
+					boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+					boolean flipchanged = false;
+
+					fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
+
+					if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
 					{
-						mobj->flags &= ~MF_NOGRAVITY;
-						mobj->flags2 &= ~MF2_DONTDRAW;
-						P_SetMobjState(mobj, S_NIGHTSDRONE1);
+						goalpost = mobj->target;
+						if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
+							sparkle = goalpost->target;
+						if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
+							droneman = goalpost->tracer;
 					}
-					else if (!mobj->tracer->player->bonustime)
+
+					if (!goalpost || !sparkle || !droneman)
+						break;
+
+					// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
+					droneboxmandiff = max(mobj->height - droneman->height, 0);
+					dronemangoaldiff = max(droneman->height - goalpost->height, 0);
+
+					if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
 					{
-						mobj->flags &= ~MF_NOGRAVITY;
-						P_SetMobjState(mobj, S_NIGHTSDRONE1);
+						goalpost->eflags |= MFE_VERTICALFLIP;
+						goalpost->flags2 |= MF2_OBJECTFLIP;
+						sparkle->eflags |= MFE_VERTICALFLIP;
+						sparkle->flags2 |= MF2_OBJECTFLIP;
+						droneman->eflags |= MFE_VERTICALFLIP;
+						droneman->flags2 |= MF2_OBJECTFLIP;
+						flipchanged = true;
+					}
+					else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
+					{
+						goalpost->eflags &= ~MFE_VERTICALFLIP;
+						goalpost->flags2 &= ~MF2_OBJECTFLIP;
+						sparkle->eflags &= ~MFE_VERTICALFLIP;
+						sparkle->flags2 &= ~MF2_OBJECTFLIP;
+						droneman->eflags &= ~MFE_VERTICALFLIP;
+						droneman->flags2 &= ~MF2_OBJECTFLIP;
+						flipchanged = true;
 					}
-				}
-			}
-			else
-			{
-				if (G_IsSpecialStage(gamemap))
-				{ // Never show the NiGHTS drone in special stages. Check ANYONE for bonustime.
-					INT32 i;
 
-					boolean bonustime = false;
-					for (i = 0; i < MAXPLAYERS; i++)
-						if (playeringame[i] && players[i].bonustime)
+					if (goalpost->destscale != mobj->destscale
+					    || goalpost->movefactor != mobj->z
+						|| goalpost->friction != mobj->height
+						|| flipchanged
+						|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE)))
+					{
+						goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
+
+						// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
+						if (!flip)
 						{
-							bonustime = true;
-							break;
+							if (topaligned) // Align droneman to top of hitbox
+							{
+								dronemanoffset = droneboxmandiff;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
+							else if (middlealigned) // Align droneman to center of hitbox
+							{
+								dronemanoffset = droneboxmandiff / 2;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
+							else if (bottomoffsetted)
+							{
+								dronemanoffset = 24*FRACUNIT;
+								goaloffset = dronemangoaldiff + dronemanoffset;
+							}
+							else
+							{
+								dronemanoffset = 0;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
+
+							sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
 						}
+						else
+						{
+							if (topaligned) // Align droneman to top of hitbox
+							{
+								dronemanoffset = 0;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
+							else if (middlealigned) // Align droneman to center of hitbox
+							{
+								dronemanoffset = droneboxmandiff / 2;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
+							else if (bottomoffsetted)
+							{
+								dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+								goaloffset = dronemangoaldiff + dronemanoffset;
+							}
+							else
+							{
+								dronemanoffset = droneboxmandiff;
+								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+							}
 
-					if (bonustime)
-					{
-						P_SetMobjState(mobj, S_NIGHTSDRONE_SPARKLING1);
-						mobj->flags |= MF_NOGRAVITY;
+							sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
+						}
+
+						P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
+						P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
+						if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
+						{
+							P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
+							goalpost->movefactor = mobj->z;
+							goalpost->friction = mobj->height;
+						}
+						goalpost->threshold = mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE);
 					}
 					else
 					{
-						if (mobj->target)
+						if (goalpost->x != mobj->x || goalpost->y != mobj->y)
 						{
-							CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
-							P_RemoveMobj(mobj->target);
-							P_SetTarget(&mobj->target, NULL);
+							P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
+							P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
 						}
-						mobj->flags2 |= MF2_DONTDRAW;
+
+						if (droneman->x != mobj->x || droneman->y != mobj->y)
+							P_TeleportMove(droneman, mobj->x, mobj->y,
+							               droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
 					}
-				}
-				else if (mobj->tracer && mobj->tracer->player)
-				{
-					if (mobj->target)
+
+					// now toggle states!
+					// GOAL mode?
+					if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
 					{
-						CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
-						P_RemoveMobj(mobj->target);
-						P_SetTarget(&mobj->target, NULL);
-					}
+						INT32 i;
+						boolean bonustime = false;
 
-					if (mobj->tracer->player->powers[pw_carry] == CR_NIGHTSMODE)
+						for (i = 0; i < MAXPLAYERS; i++)
+							if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
+							{
+								bonustime = true;
+								break;
+							}
+
+						if (!bonustime)
+						{
+							CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
+							if (goalpost && goalpost->state != &states[S_INVISIBLE])
+								P_SetMobjState(goalpost, S_INVISIBLE);
+							if (sparkle && sparkle->state != &states[S_INVISIBLE])
+								P_SetMobjState(sparkle, S_INVISIBLE);
+						}
+					}
+					// Invisible/bouncing mode.
+					else
 					{
-						if (mobj->tracer->player->bonustime)
+						INT32 i;
+						boolean bonustime = false;
+						fixed_t zcomp;
+
+						// Bouncy bouncy!
+						if (!flip)
 						{
-							P_SetMobjState(mobj, S_NIGHTSDRONE_SPARKLING1);
-							mobj->flags |= MF_NOGRAVITY;
+							if (topaligned)
+								zcomp = droneboxmandiff + mobj->z;
+							else if (middlealigned)
+								zcomp = (droneboxmandiff / 2) + mobj->z;
+							else if (bottomoffsetted)
+								zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
+							else
+								zcomp = mobj->z;
 						}
 						else
-							mobj->flags2 |= MF2_DONTDRAW;
+						{
+							if (topaligned)
+								zcomp = mobj->z;
+							else if (middlealigned)
+								zcomp = (droneboxmandiff / 2) + mobj->z;
+							else if (bottomoffsetted)
+								zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+							else
+								zcomp = mobj->z + droneboxmandiff;
+						}
+
+						droneman->angle += ANG10;
+						if (!flip && droneman->z <= zcomp)
+							droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
+						else if (flip && droneman->z >= zcomp)
+							droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
+
+						// state switching logic
+						for (i = 0; i < MAXPLAYERS; i++)
+							if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
+							{
+								bonustime = true;
+								break;
+							}
+
+						if (bonustime)
+						{
+							CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
+							if (!(droneman->flags2 & MF2_DONTDRAW))
+								droneman->flags2 |= MF2_DONTDRAW;
+							if (goalpost->state == &states[S_INVISIBLE])
+								P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
+							if (sparkle->state == &states[S_INVISIBLE])
+								P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
+						}
+						else if (!G_IsSpecialStage(gamemap))
+						{
+							for (i = 0; i < MAXPLAYERS; i++)
+								if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
+								{
+									bonustime = true; // variable reuse
+									break;
+								}
+
+							if (bonustime)
+							{
+								// show droneman if at least one player is non-nights
+								if (goalpost->state != &states[S_INVISIBLE])
+									P_SetMobjState(goalpost, S_INVISIBLE);
+								if (sparkle->state != &states[S_INVISIBLE])
+									P_SetMobjState(sparkle, S_INVISIBLE);
+								if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
+									P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
+								if (droneman->flags2 & MF2_DONTDRAW)
+									droneman->flags2 &= ~MF2_DONTDRAW;
+							}
+							else
+							{
+								// else, hide it
+								if (!(droneman->flags2 & MF2_DONTDRAW))
+									droneman->flags2 |= MF2_DONTDRAW;
+							}
+						}
 					}
-					else // Not NiGHTS
-						mobj->flags2 &= ~MF2_DONTDRAW;
 				}
-				mobj->angle += ANG10;
-				if (mobj->z <= mobj->floorz)
-					mobj->momz = 5*FRACUNIT;
-			}
-			break;
-		case MT_PLAYER:
-			if (mobj->player)
-				P_PlayerMobjThinker(mobj);
-			return;
-		case MT_SKIM:
-			// check mobj against possible water content, before movement code
-			P_MobjCheckWater(mobj);
+				break;
+			case MT_PLAYER:
+				if (mobj->player)
+					P_PlayerMobjThinker(mobj);
+				return;
+			case MT_SKIM:
+				// check mobj against possible water content, before movement code
+				P_MobjCheckWater(mobj);
 
-			// Keep Skim at water surface
-			if (mobj->z <= mobj->watertop)
-			{
-				mobj->flags |= MF_NOGRAVITY;
-				if (mobj->z < mobj->watertop)
+				// Keep Skim at water surface
+				if (mobj->z <= mobj->watertop)
 				{
-					if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
+					mobj->flags |= MF_NOGRAVITY;
+					if (mobj->z < mobj->watertop)
+					{
+						if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
+							mobj->z = mobj->watertop;
+						else
+							mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
+					}
+				}
+				else
+				{
+					mobj->flags &= ~MF_NOGRAVITY;
+					if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
 						mobj->z = mobj->watertop;
-					else
-						mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
 				}
-			}
-			else
-			{
-				mobj->flags &= ~MF_NOGRAVITY;
-				if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
-					mobj->z = mobj->watertop;
-			}
-			break;
-		case MT_RING:
-		case MT_COIN:
-		case MT_BLUEBALL:
-		case MT_REDTEAMRING:
-		case MT_BLUETEAMRING:
-			// No need to check water. Who cares?
-			P_RingThinker(mobj);
-			if (mobj->flags2 & MF2_NIGHTSPULL)
-				P_NightsItemChase(mobj);
-			else
-				A_AttractChase(mobj);
-			return;
-		// Flung items
-		case MT_FLINGRING:
-		case MT_FLINGCOIN:
-			if (mobj->flags2 & MF2_NIGHTSPULL)
-				P_NightsItemChase(mobj);
-			else
-				A_AttractChase(mobj);
-			break;
-		case MT_NIGHTSWING:
-			if (mobj->flags2 & MF2_NIGHTSPULL)
-				P_NightsItemChase(mobj);
-			break;
-		case MT_EMBLEM:
-			if (mobj->flags2 & MF2_NIGHTSPULL)
-				P_NightsItemChase(mobj);
-			break;
-		case MT_SHELL:
-			if (mobj->threshold && mobj->threshold != TICRATE)
-				mobj->threshold--;
-
-			if (mobj->threshold >= TICRATE)
-			{
-				mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
-				P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
-			}
-			break;
-		case MT_TURRET:
-			P_MobjCheckWater(mobj);
-			P_CheckPosition(mobj, mobj->x, mobj->y);
-			if (P_MobjWasRemoved(mobj))
+				break;
+			case MT_RING:
+			case MT_COIN:
+			case MT_BLUESPHERE:
+			case MT_BOMBSPHERE:
+			case MT_NIGHTSCHIP:
+			case MT_NIGHTSSTAR:
+			case MT_REDTEAMRING:
+			case MT_BLUETEAMRING:
+				// No need to check water. Who cares?
+				P_RingThinker(mobj);
+				if (mobj->flags2 & MF2_NIGHTSPULL)
+					P_NightsItemChase(mobj);
+				else
+					A_AttractChase(mobj);
 				return;
-			mobj->floorz = tmfloorz;
-			mobj->ceilingz = tmceilingz;
+			// Flung items
+			case MT_FLINGRING:
+			case MT_FLINGCOIN:
+			case MT_FLINGBLUESPHERE:
+			case MT_FLINGNIGHTSCHIP:
+				if (mobj->flags2 & MF2_NIGHTSPULL)
+					P_NightsItemChase(mobj);
+				else
+					A_AttractChase(mobj);
+				break;
+			case MT_EMBLEM:
+				if (mobj->flags2 & MF2_NIGHTSPULL)
+					P_NightsItemChase(mobj);
+				break;
+			case MT_SHELL:
+				if (mobj->threshold && mobj->threshold != TICRATE)
+					mobj->threshold--;
 
-			if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
-			{
-				P_SetMobjState(mobj, mobj->info->deathstate);
-				mobj->health = 0;
-				mobj->flags2 &= ~MF2_FIRING;
-			}
-			else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
-			{
-				INT32 i,j;
-				fixed_t ns;
-				fixed_t x,y,z;
-				mobj_t *mo2;
+				if (mobj->threshold >= TICRATE)
+				{
+					mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
+					P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
+				}
+				break;
+			case MT_TURRET:
+				P_MobjCheckWater(mobj);
+				P_CheckPosition(mobj, mobj->x, mobj->y);
+				if (P_MobjWasRemoved(mobj))
+					return;
+				mobj->floorz = tmfloorz;
+				mobj->ceilingz = tmceilingz;
+				mobj->floorrover = tmfloorrover;
+				mobj->ceilingrover = tmceilingrover;
 
-				z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
-				for (j = 0; j < 2; j++)
+				if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
+				{
+					P_SetMobjState(mobj, mobj->info->deathstate);
+					mobj->health = 0;
+					mobj->flags2 &= ~MF2_FIRING;
+				}
+				else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
 				{
-					for (i = 0; i < 32; i++)
+					INT32 i,j;
+					fixed_t ns;
+					fixed_t x,y,z;
+					mobj_t *mo2;
+
+					z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
+					for (j = 0; j < 2; j++)
 					{
-						const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
-						ns = FixedMul(64 * FRACUNIT, mobj->scale);
-						x = mobj->x + FixedMul(FINESINE(fa),ns);
-						y = mobj->y + FixedMul(FINECOSINE(fa),ns);
-
-						mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
-						ns = FixedMul(16 * FRACUNIT, mobj->scale);
-						mo2->momx = FixedMul(FINESINE(fa),ns);
-						mo2->momy = FixedMul(FINECOSINE(fa),ns);
+						for (i = 0; i < 32; i++)
+						{
+							const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
+							ns = FixedMul(64 * FRACUNIT, mobj->scale);
+							x = mobj->x + FixedMul(FINESINE(fa),ns);
+							y = mobj->y + FixedMul(FINECOSINE(fa),ns);
+
+							mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+							ns = FixedMul(16 * FRACUNIT, mobj->scale);
+							mo2->momx = FixedMul(FINESINE(fa),ns);
+							mo2->momy = FixedMul(FINECOSINE(fa),ns);
+						}
+						z -= FixedMul(32*FRACUNIT, mobj->scale);
 					}
-					z -= FixedMul(32*FRACUNIT, mobj->scale);
+					P_SetMobjState(mobj, mobj->info->deathstate);
+					mobj->health = 0;
+					mobj->flags2 &= ~MF2_FIRING;
 				}
-				P_SetMobjState(mobj, mobj->info->deathstate);
-				mobj->health = 0;
-				mobj->flags2 &= ~MF2_FIRING;
-			}
-			break;
-		case MT_BLUEFLAG:
-		case MT_REDFLAG:
-			{
-				sector_t *sec2;
-				sec2 = P_ThingOnSpecial3DFloor(mobj);
-				if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
-					mobj->fuse = 1; // Return to base.
 				break;
-			}
-		case MT_CANNONBALL:
+			case MT_BLUEFLAG:
+			case MT_REDFLAG:
+				{
+					sector_t *sec2;
+					sec2 = P_ThingOnSpecial3DFloor(mobj);
+					if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
+						mobj->fuse = 1; // Return to base.
+					break;
+				}
+			case MT_CANNONBALL:
 #ifdef FLOORSPLATS
-			R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
-				mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
+				R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
+					mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
 #endif
-			break;
-		case MT_SPINDUST: // Spindash dust
-				mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
-				mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
-				//mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
-				if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
+				break;
+			case MT_SPINDUST: // Spindash dust
+					mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
+					mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
+					//mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
+					if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
+					{
+						P_MobjCheckWater(mobj);
+						if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
+							&& mobj->z+mobj->height >= mobj->watertop - 5*FRACUNIT)
+							mobj->flags2 |= MF2_DONTDRAW;
+					}
+				break;
+			case MT_TRAINDUSTSPAWNER:
+				if (leveltime % 5 == 0) {
+					mobj_t *traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
+					traindust->flags = MF_SCENERY;
+					P_SetMobjState(traindust, S_TRAINDUST);
+					traindust->frame = P_RandomRange(0, 8) | FF_TRANS90;
+					traindust->angle = mobj->angle;
+					traindust->tics = TICRATE * 4;
+					traindust->destscale = FRACUNIT * 64;
+					traindust->scalespeed = FRACUNIT / 24;
+					P_SetScale(traindust, FRACUNIT * 6);
+				}
+				break;
+			case MT_TRAINSTEAMSPAWNER:
+				if (leveltime % 5 == 0) {
+					mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom() / 2, mobj->y + FRACUNIT*P_SignedRandom() / 2, mobj->z, MT_PARTICLE);
+					P_SetMobjState(steam, S_TRAINSTEAM);
+					steam->frame = P_RandomRange(0, 1) | FF_TRANS90;
+					steam->tics = TICRATE * 8;
+					steam->destscale = FRACUNIT * 64;
+					steam->scalespeed = FRACUNIT / 8;
+					P_SetScale(steam, FRACUNIT * 16);
+					steam->momx = P_SignedRandom() * 32;
+					steam->momy = -64 * FRACUNIT;
+					steam->momz = 2 * FRACUNIT;
+				}
+				break;
+			case MT_CANARIVORE_GAS:
 				{
-					P_MobjCheckWater(mobj);
-					if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
-						&& mobj->z+mobj->height >= mobj->watertop - 5*FRACUNIT)
-						mobj->flags2 |= MF2_DONTDRAW;
+					fixed_t momz;
+
+					if (mobj->flags2 & MF2_AMBUSH)
+					{
+						mobj->momx = FixedMul(mobj->momx, 50 * FRACUNIT / 51);
+						mobj->momy = FixedMul(mobj->momy, 50 * FRACUNIT / 51);
+						break;
+					}
+
+					if (mobj->eflags & MFE_VERTICALFLIP)
+					{
+						if ((mobj->z + mobj->height + mobj->momz) <= mobj->ceilingz)
+							break;
+					}
+					else
+					{
+						if ((mobj->z + mobj->momz) >= mobj->floorz)
+							break;
+					}
+
+					momz = abs(mobj->momz);
+					if (R_PointToDist2(0, 0, mobj->momx, mobj->momy) < momz)
+						P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), momz);
+					mobj->flags2 |= MF2_AMBUSH;
+					break;
 				}
-			break;
-		case MT_SPINFIRE:
-			if (mobj->flags & MF_NOGRAVITY)
-			{
-				if (mobj->eflags & MFE_VERTICALFLIP)
-					mobj->z = mobj->ceilingz - mobj->height;
-				else
-					mobj->z = mobj->floorz;
-			}
-			/* FALLTHRU */
-		default:
-			// check mobj against possible water content, before movement code
-			P_MobjCheckWater(mobj);
+			case MT_SALOONDOOR:
+				{
+					fixed_t x = mobj->tracer->x;
+					fixed_t y = mobj->tracer->y;
+					fixed_t z = mobj->tracer->z;
+					angle_t oang = FixedAngle(mobj->extravalue1);
+					angle_t fa = (oang >> ANGLETOFINESHIFT) & FINEMASK;
+					fixed_t c0 = -96*FINECOSINE(fa);
+					fixed_t s0 = -96*FINESINE(fa);
+					angle_t fma;
+					fixed_t c, s;
+					angle_t angdiff;
+
+					// Adjust angular speed
+					fixed_t da = AngleFixed(mobj->angle - oang);
+					if (da > 180*FRACUNIT)
+						da -= 360*FRACUNIT;
+					mobj->extravalue2 = 8*(mobj->extravalue2 - da/32)/9;
+
+					// Update angle
+					mobj->angle += FixedAngle(mobj->extravalue2);
+
+					angdiff = mobj->angle - FixedAngle(mobj->extravalue1);
+					if (angdiff > (ANGLE_90 - ANG2) && angdiff < ANGLE_180)
+					{
+						mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_90 - ANG2);
+						mobj->extravalue2 /= 2;
+					}
+					else if (angdiff < (ANGLE_270 + ANG2) && angdiff >= ANGLE_180)
+					{
+						mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_270 + ANG2);
+						mobj->extravalue2 /= 2;
+					}
 
-			// Extinguish fire objects in water
-			if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
-				&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
-			{
-				P_KillMobj(mobj, NULL, NULL, 0);
-				return;
-			}
-			break;
+					// Update position
+					fma = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
+					c = 48*FINECOSINE(fma);
+					s = 48*FINESINE(fma);
+					P_TeleportMove(mobj, x + c0 + c, y + s0 + s, z);
+					break;
+				}
+			case MT_MINECARTSPAWNER:
+				P_HandleMinecartSegments(mobj);
+				if (!mobj->fuse || mobj->fuse > TICRATE)
+					break;
+				if (mobj->fuse == 2)
+				{
+					mobj->fuse = 0;
+					break;
+				}
+				mobj->flags2 ^= MF2_DONTDRAW;
+				break;
+			case MT_SPINFIRE:
+				if (mobj->flags & MF_NOGRAVITY)
+				{
+					if (mobj->eflags & MFE_VERTICALFLIP)
+						mobj->z = mobj->ceilingz - mobj->height;
+					else
+						mobj->z = mobj->floorz;
+				}
+				/* FALLTHRU */
+			default:
+				// check mobj against possible water content, before movement code
+				P_MobjCheckWater(mobj);
+
+				// Extinguish fire objects in water
+				if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+					&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
+				{
+					P_KillMobj(mobj, NULL, NULL, 0);
+					return;
+				}
+				break;
+		}
 	}
 	if (P_MobjWasRemoved(mobj))
 		return;
@@ -7795,7 +9037,7 @@ void P_MobjThinker(mobj_t *mobj)
 		{
 			mobj_t *missile;
 
-			if (mobj->target->player && mobj->target->player->nightstime)
+			if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
 			{
 				fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
 
@@ -7833,6 +9075,10 @@ void P_MobjThinker(mobj_t *mobj)
 	// Check fuse
 	if (mobj->fuse)
 	{
+
+		if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
+			mobj->flags2 ^= MF2_DONTDRAW;
+
 		mobj->fuse--;
 		if (!mobj->fuse)
 		{
@@ -7892,6 +9138,9 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 				case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
 					P_SetMobjState(mobj, mobj->info->deathstate);
 					break;
+				case MT_LHRT:
+					P_KillMobj(mobj, NULL, NULL, 0);
+					break;
 				case MT_BLUEFLAG:
 				case MT_REDFLAG:
 					if (mobj->spawnpoint)
@@ -7927,6 +9176,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 							// Assumedly in splitscreen players will be on opposing teams
 							if (players[consoleplayer].ctfteam == 1 || splitscreen)
 								S_StartSound(NULL, sfx_hoop1);
+							else if (players[consoleplayer].ctfteam == 2)
+								S_StartSound(NULL, sfx_hoop3);
 
 							redflag = flagmo;
 						}
@@ -7938,6 +9189,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 							// Assumedly in splitscreen players will be on opposing teams
 							if (players[consoleplayer].ctfteam == 2 || splitscreen)
 								S_StartSound(NULL, sfx_hoop1);
+							else if (players[consoleplayer].ctfteam == 1)
+								S_StartSound(NULL, sfx_hoop3);
 
 							blueflag = flagmo;
 						}
@@ -7955,6 +9208,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 				case MT_WALLSPIKE:
 					P_SetMobjState(mobj, mobj->state->nextstate);
 					mobj->fuse = mobj->info->speed;
+					if (mobj->spawnpoint)
+						mobj->fuse += (mobj->spawnpoint->angle/360);
 					break;
 				case MT_NIGHTSCORE:
 					P_RemoveMobj(mobj);
@@ -8003,6 +9258,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 #ifdef ESLOPE // Sliding physics for slidey mobjs!
 	if (mobj->type == MT_FLINGRING
 		|| mobj->type == MT_FLINGCOIN
+		|| mobj->type == MT_FLINGBLUESPHERE
+		|| mobj->type == MT_FLINGNIGHTSCHIP
 		|| P_WeaponOrPanel(mobj->type)
 		|| mobj->type == MT_FLINGEMERALD
 		|| mobj->type == MT_BIGTUMBLEWEED
@@ -8126,6 +9383,16 @@ void P_PushableThinker(mobj_t *mobj)
 	if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
 		P_TryMove(mobj, mobj->x, mobj->y, true);
 
+	if (mobj->type == MT_MINECART && mobj->health)
+	{
+		// If player is ded, remove this minecart
+		if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health || !mobj->target->player || mobj->target->player->powers[pw_carry] != CR_MINECART)
+		{
+			P_KillMobj(mobj, NULL, NULL, 0);
+			return;
+		}
+	}
+
 	if (mobj->fuse == 1) // it would explode in the MobjThinker code
 	{
 		mobj_t *spawnmo;
@@ -8208,6 +9475,8 @@ void P_SceneryThinker(mobj_t *mobj)
 			return;
 		mobj->floorz = tmfloorz;
 		mobj->ceilingz = tmceilingz;
+		mobj->floorrover = tmfloorrover;
+		mobj->ceilingrover = tmceilingrover;
 	}
 	else
 	{
@@ -8228,6 +9497,7 @@ void P_SceneryThinker(mobj_t *mobj)
 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 {
 	const mobjinfo_t *info = &mobjinfo[type];
+	SINT8 sc = -1;
 	state_t *st;
 	mobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
 
@@ -8243,7 +9513,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	mobj->height = info->height;
 	mobj->flags = info->flags;
 
-	mobj->health = info->spawnhealth;
+	mobj->health = (info->spawnhealth ? info->spawnhealth : 1);
 
 	mobj->reactiontime = info->reactiontime;
 
@@ -8290,6 +9560,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 #endif
 				mobj->subsector->sector->ceilingheight;
 
+	mobj->floorrover = NULL;
+	mobj->ceilingrover = NULL;
+
 	// Tells MobjCheckWater that the water height was not set.
 	mobj->watertop = INT32_MAX;
 
@@ -8335,8 +9608,11 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_ALTVIEWMAN:
 			if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
 			break;
+		case MT_LOCKONINF:
+			P_SetScale(mobj, (mobj->destscale = 3*mobj->scale));
+			break;
 		case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
-			mobj->fuse = mobj->info->mass;
+			mobj->fuse = mobj->info->painchance;
 			break;
 		case MT_BLACKEGGMAN:
 			{
@@ -8346,20 +9622,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 				P_SetTarget(&spawn->target, mobj);
 			}
 			break;
-		case MT_BLACKEGGMAN_HELPER:
-			// Collision helper can be stood on but not pushed
-			mobj->flags2 |= MF2_STANDONME;
-			break;
-		case MT_WALLSPIKE:
-		case MT_SPIKE:
-			mobj->flags2 |= MF2_STANDONME;
-			break;
-		case MT_GFZTREE:
-		case MT_GFZBERRYTREE:
-		case MT_GFZCHERRYTREE:
-		case MT_LAMPPOST1:
-		case MT_LAMPPOST2:
-			mobj->flags2 |= MF2_STANDONME;
+		case MT_FAKEMOBILE:
+		case MT_EGGSHIELD:
+			mobj->flags2 |= MF2_INVERTAIMABLE;
 			break;
 		case MT_DETON:
 			mobj->movedir = 0;
@@ -8374,48 +9639,102 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			}
 			break;
 		case MT_UNIDUS:
-		{
-			INT32 i;
-			mobj_t *ball;
-			// Spawn "damage" number of "painchance" spikeball mobjs
-			// threshold is the distance they should keep from the MT_UNIDUS (touching radius + ball painchance)
-			for (i = 0; i < mobj->info->damage; i++)
 			{
-				ball = P_SpawnMobj(x, y, z, mobj->info->painchance);
-				ball->destscale = mobj->scale;
-				P_SetScale(ball, mobj->scale);
-				P_SetTarget(&ball->target, mobj);
-				ball->movedir = FixedAngle(FixedMul(FixedDiv(i<<FRACBITS, mobj->info->damage<<FRACBITS), 360<<FRACBITS));
-				ball->threshold = ball->radius + mobj->radius + FixedMul(ball->info->painchance, ball->scale);
-
-				var1 = ball->state->var1, var2 = ball->state->var2;
-				ball->state->action.acp1(ball);
+				INT32 i;
+				mobj_t *ball;
+				// Spawn "damage" number of "painchance" spikeball mobjs
+				// threshold is the distance they should keep from the MT_UNIDUS (touching radius + ball painchance)
+				for (i = 0; i < mobj->info->damage; i++)
+				{
+					ball = P_SpawnMobj(x, y, z, mobj->info->painchance);
+					ball->destscale = mobj->scale;
+					P_SetScale(ball, mobj->scale);
+					P_SetTarget(&ball->target, mobj);
+					ball->movedir = FixedAngle(FixedMul(FixedDiv(i<<FRACBITS, mobj->info->damage<<FRACBITS), 360<<FRACBITS));
+					ball->threshold = ball->radius + mobj->radius + FixedMul(ball->info->painchance, ball->scale);
+
+					var1 = ball->state->var1, var2 = ball->state->var2;
+					ball->state->action.acp1(ball);
+				}
 			}
 			break;
-		}
 		case MT_POINTY:
-		{
-			INT32 q;
-			mobj_t *ball, *lastball = mobj;
+			{
+				INT32 q;
+				mobj_t *ball, *lastball = mobj;
 
-			for (q = 0; q < mobj->info->painchance; q++)
+				for (q = 0; q < mobj->info->painchance; q++)
+				{
+					ball = P_SpawnMobj(x, y, z, mobj->info->mass);
+					ball->destscale = mobj->scale;
+					P_SetScale(ball, mobj->scale);
+					P_SetTarget(&lastball->tracer, ball);
+					P_SetTarget(&ball->target, mobj);
+					lastball = ball;
+				}
+			}
+			break;
+		case MT_CRUSHSTACEAN:
+			{
+				mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CRUSHCLAW);
+				bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
+				P_SetTarget(&mobj->tracer, bigmeatyclaw);
+				P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				mobj->reactiontime >>= 1;
+			}
+			break;
+		case MT_BIGMINE:
+			mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS;
+			break;
+		case MT_WAVINGFLAG1:
+		case MT_WAVINGFLAG2:
 			{
-				ball = P_SpawnMobj(x, y, z, mobj->info->mass);
-				ball->destscale = mobj->scale;
-				P_SetScale(ball, mobj->scale);
-				P_SetTarget(&lastball->tracer, ball);
-				P_SetTarget(&ball->target, mobj);
-				lastball = ball;
+				mobj_t *prev = mobj, *cur;
+				UINT8 i;
+				for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version
+				{
+					cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));;
+					P_SetTarget(&prev->tracer, cur);
+					cur->extravalue1 = i;
+					prev = cur;
+				}
 			}
 			break;
-		}
 		case MT_EGGMOBILE2:
 			// Special condition for the 2nd boss.
 			mobj->watertop = mobj->info->speed;
 			break;
+		case MT_EGGMOBILE3:
+			mobj->movefactor = -512*FRACUNIT;
+			mobj->flags2 |= MF2_CLASSICPUSH;
+			break;
 		case MT_FLICKY_08:
 			mobj->color = (P_RandomChance(FRACUNIT/2) ? SKINCOLOR_RED : SKINCOLOR_AQUA);
 			break;
+		case MT_BALLOON:
+			mobj->color = SKINCOLOR_RED;
+			break;
+		case MT_EGGROBO1:
+			mobj->movecount = P_RandomKey(13);
+			mobj->color = SKINCOLOR_RUBY + P_RandomKey(MAXSKINCOLORS - SKINCOLOR_RUBY);
+			break;
+		case MT_HIVEELEMENTAL:
+			mobj->extravalue1 = 5;
+			break;
+		case MT_SMASHINGSPIKEBALL:
+			mobj->movecount = mobj->z;
+			break;
+		case MT_SPINBOBERT:
+			{
+				mobj_t *fire;
+				fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE1);
+				P_SetTarget(&fire->target, mobj);
+				P_SetTarget(&mobj->hnext, fire);
+				fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2);
+				P_SetTarget(&fire->target, mobj);
+				P_SetTarget(&mobj->hprev, fire);
+			}
+			break;
 		case MT_REDRING: // Make MT_REDRING red by default
 			mobj->color = skincolor_redring;
 			break;
@@ -8424,8 +9743,14 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_EXTRALARGEBUBBLE:
 			mobj->fuse += 30 * TICRATE;
 			break;
+		case MT_NIGHTSDRONE:
+			nummaprings = -1; // no perfect bonus, rings are free
+			break;
 		case MT_EGGCAPSULE:
-			mobj->extravalue1 = -1; // timer for how long a player has been at the capsule
+			mobj->reactiontime = 0;
+			mobj->extravalue1 = mobj->cvmem =\
+			 mobj->cusval = mobj->movecount =\
+			 mobj->lastlook = mobj->extravalue2 = -1;
 			break;
 		case MT_REDTEAMRING:
 			mobj->color = skincolor_redteam;
@@ -8435,14 +9760,60 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			break;
 		case MT_RING:
 		case MT_COIN:
-		case MT_BLUEBALL:
-			nummaprings++;
+		case MT_NIGHTSSTAR:
+			if (nummaprings >= 0)
+				nummaprings++;
+			break;
+		case MT_METALSONIC_BATTLE:
+		case MT_METALSONIC_RACE:
+			sc = 3;
+			break;
+		case MT_FANG:
+			sc = 4;
+			break;
+		case MT_CORK:
+			mobj->flags2 |= MF2_SUPERFIRE;
+			break;
+		case MT_FBOMB:
+			mobj->flags2 |= MF2_EXPLOSION;
+			break;
+		case MT_OILLAMP:
+			{
+				mobj_t* overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
+				P_SetTarget(&overlay->target, mobj);
+				P_SetMobjState(overlay, S_OILLAMPFLARE);
+				break;
+			}
+		case MT_TNTBARREL:
+			mobj->momx = 1; //stack hack
+			break;
+		case MT_MINECARTEND:
+			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
+			mobj->tracer->angle = mobj->angle + ANGLE_90;
+			break;
 		default:
 			break;
 	}
 
+	if (sc != -1)
+	{
+		UINT8 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i] || players[i].spectator)
+				continue;
+
+			if (players[i].skin == sc)
+			{
+				mobj->color = SKINCOLOR_SILVER;
+				mobj->colorized = true;
+				break;
+			}
+		}
+	}
+
 	if (!(mobj->flags & MF_NOTHINK))
-		P_AddThinker(&mobj->thinker);
+		P_AddThinker(THINK_MOBJ, &mobj->thinker);
 
 	// Call action functions when the state is set
 	if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
@@ -8510,11 +9881,14 @@ static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype
 #endif
 				mobj->subsector->sector->ceilingheight;
 
+	mobj->floorrover = NULL;
+	mobj->ceilingrover = NULL;
+
 	mobj->z = z;
 	mobj->momz = mobjinfo[type].speed;
 
 	mobj->thinker.function.acp1 = (actionf_p1)P_NullPrecipThinker;
-	P_AddThinker(&mobj->thinker);
+	P_AddThinker(THINK_PRECIP, &mobj->thinker);
 
 	CalculatePrecipFloor(mobj);
 
@@ -8531,14 +9905,15 @@ static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype
 static inline precipmobj_t *P_SpawnRainMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 {
 	precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
-	mo->thinker.function.acp1 = (actionf_p1)P_RainThinker;
+	mo->precipflags |= PCF_RAIN;
+	//mo->thinker.function.acp1 = (actionf_p1)P_RainThinker;
 	return mo;
 }
 
 static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 {
 	precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
-	mo->thinker.function.acp1 = (actionf_p1)P_SnowThinker;
+	//mo->thinker.function.acp1 = (actionf_p1)P_SnowThinker;
 	return mo;
 }
 
@@ -8570,7 +9945,7 @@ void P_RemoveMobj(mobj_t *mobj)
 	if (mobj->spawnpoint &&
 		(mobj->type == MT_RING
 		|| mobj->type == MT_COIN
-		|| mobj->type == MT_BLUEBALL
+		|| mobj->type == MT_NIGHTSSTAR
 		|| mobj->type == MT_REDTEAMRING
 		|| mobj->type == MT_BLUETEAMRING
 		|| P_WeaponOrPanel(mobj->type))
@@ -8590,7 +9965,7 @@ void P_RemoveMobj(mobj_t *mobj)
 	if (mobj->player && mobj->player->followmobj)
 	{
 		P_RemoveMobj(mobj->player->followmobj);
-		mobj->player->followmobj = NULL;
+		P_SetTarget(&mobj->player->followmobj, NULL);
 	}
 
 	mobj->health = 0; // Just because
@@ -8615,37 +9990,35 @@ void P_RemoveMobj(mobj_t *mobj)
 	// Remove any references to other mobjs.
 	P_SetTarget(&mobj->target, P_SetTarget(&mobj->tracer, NULL));
 
-	// free block
+	if (mobj->hnext && !P_MobjWasRemoved(mobj->hnext))
+		P_SetTarget(&mobj->hnext->hprev, mobj->hprev);
+	if (mobj->hprev && !P_MobjWasRemoved(mobj->hprev))
+		P_SetTarget(&mobj->hprev->hnext, mobj->hnext);
+
+	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
+
 	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
-	if (mobj->flags & MF_NOTHINK && !mobj->thinker.next)
+#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
+
+	// free block
+	if (!mobj->thinker.next)
 	{ // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through!
+		INT32 prevreferences;
 		if (!mobj->thinker.references)
 		{
-#ifdef SCRAMBLE_REMOVED
-			// Invalidate mobj_t data to cause crashes if accessed!
-			memset(mobj, 0xff, sizeof(mobj_t));
-#endif
-			Z_Free(mobj); // No refrences? Can be removed immediately! :D
-		}
-		else
-		{ // Add thinker just to delay removing it until refrences are gone.
-			mobj->flags &= ~MF_NOTHINK;
-			P_AddThinker((thinker_t *)mobj);
-#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
-			P_RemoveThinker((thinker_t *)mobj);
+			Z_Free(mobj); // No refrrences? Can be removed immediately! :D
+			return;
 		}
+
+		prevreferences = mobj->thinker.references;
+		P_AddThinker(THINK_MOBJ, (thinker_t *)mobj);
+		mobj->thinker.references = prevreferences;
 	}
-	else
-	{
-#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
-		P_RemoveThinker((thinker_t *)mobj);
-	}
+
+	P_RemoveThinker((thinker_t *)mobj);
 }
 
 // This does not need to be added to Lua.
@@ -8700,12 +10073,12 @@ consvar_t cv_flagtime = {"flagtime", "30", CV_NETVAR|CV_CHEAT, flagtime_cons_t,
 
 void P_SpawnPrecipitation(void)
 {
-	INT32 i, j, mrand;
+	INT32 i, mrand;
 	fixed_t basex, basey, x, y, height;
 	subsector_t *precipsector = NULL;
 	precipmobj_t *rainmo = NULL;
 
-	if (dedicated || !cv_precipdensity.value || curWeather == PRECIP_NONE)
+	if (dedicated || !(cv_drawdist_precip.value) || curWeather == PRECIP_NONE)
 		return;
 
 	// Use the blockmap to narrow down our placing patterns
@@ -8714,50 +10087,47 @@ void P_SpawnPrecipitation(void)
 		basex = bmaporgx + (i % bmapwidth) * MAPBLOCKSIZE;
 		basey = bmaporgy + (i / bmapwidth) * MAPBLOCKSIZE;
 
-		for (j = 0; j < cv_precipdensity.value; ++j)
-		{
-			x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
-			y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
-
-			precipsector = R_IsPointInSubsector(x, y);
+		x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
+		y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
 
-			// No sector? Stop wasting time,
-			// move on to the next entry in the blockmap
-			if (!precipsector)
-				break;
+		precipsector = R_IsPointInSubsector(x, y);
 
-			// Exists, but is too small for reasonable precipitation.
-			if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
-				continue;
+		// No sector? Stop wasting time,
+		// move on to the next entry in the blockmap
+		if (!precipsector)
+			continue;
 
-			// Don't set height yet...
-			height = precipsector->sector->ceilingheight;
+		// Exists, but is too small for reasonable precipitation.
+		if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
+			continue;
 
-			if (curWeather == PRECIP_SNOW)
-			{
-				// Not in a sector with visible sky -- exception for NiGHTS.
-				if (!(maptol & TOL_NIGHTS) && precipsector->sector->ceilingpic != skyflatnum)
-					continue;
+		// Don't set height yet...
+		height = precipsector->sector->ceilingheight;
 
-				rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
-				mrand = M_RandomByte();
-				if (mrand < 64)
-					P_SetPrecipMobjState(rainmo, S_SNOW3);
-				else if (mrand < 144)
-					P_SetPrecipMobjState(rainmo, S_SNOW2);
-			}
-			else // everything else.
-			{
-				// Not in a sector with visible sky.
-				if (precipsector->sector->ceilingpic != skyflatnum)
-					continue;
+		if (curWeather == PRECIP_SNOW)
+		{
+			// Not in a sector with visible sky -- exception for NiGHTS.
+			if (!(maptol & TOL_NIGHTS) && precipsector->sector->ceilingpic != skyflatnum)
+				continue;
 
-				rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
-			}
+			rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
+			mrand = M_RandomByte();
+			if (mrand < 64)
+				P_SetPrecipMobjState(rainmo, S_SNOW3);
+			else if (mrand < 144)
+				P_SetPrecipMobjState(rainmo, S_SNOW2);
+		}
+		else // everything else.
+		{
+			// Not in a sector with visible sky.
+			if (precipsector->sector->ceilingpic != skyflatnum)
+				continue;
 
-			// Randomly assign a height, now that floorz is set.
-			rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
+			rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
 		}
+
+		// Randomly assign a height, now that floorz is set.
+		rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
 	}
 
 	if (curWeather == PRECIP_BLANK)
@@ -8838,7 +10208,7 @@ void P_PrecipitationEffects(void)
 	if (!playeringame[displayplayer] || !players[displayplayer].mo)
 		return;
 
-	if (nosound || sound_disabled)
+	if (sound_disabled)
 		return; // Sound off? D'aw, no fun.
 
 	if (players[displayplayer].mo->subsector->sector->ceilingpic == skyflatnum)
@@ -8967,7 +10337,7 @@ void P_RespawnSpecials(void)
 #endif
 			ss->sector->ceilingheight) - (mthing->options >> ZSHIFT) * FRACUNIT;
 			if (mthing->options & MTF_AMBUSH
-			&& (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || P_WeaponOrPanel(i)))
+			&& (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || i == MT_NIGHTSSTAR || P_WeaponOrPanel(i)))
 				z -= 24*FRACUNIT;
 			z -= mobjinfo[i].height; // Don't forget the height!
 		}
@@ -8979,7 +10349,7 @@ void P_RespawnSpecials(void)
 #endif
 			ss->sector->floorheight) + (mthing->options >> ZSHIFT) * FRACUNIT;
 			if (mthing->options & MTF_AMBUSH
-			&& (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || P_WeaponOrPanel(i)))
+			&& (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || i == MT_NIGHTSSTAR || P_WeaponOrPanel(i)))
 				z += 24*FRACUNIT;
 		}
 
@@ -9016,7 +10386,7 @@ void P_SpawnPlayer(INT32 playernum)
 		p->spectator = p->outofcoop =
 		(((multiplayer || netgame) && gametype == GT_COOP) // only question status in coop
 		&& ((leveltime > 0
-		&& ((G_IsSpecialStage(gamemap) && useNightsSS) // late join special stage
+		&& ((G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS)) // late join special stage
 		|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
 		|| (((cv_cooplives.value == 1) || !P_GetLives(p)) && p->lives <= 0))); // game over and can't redistribute lives
 	}
@@ -9085,7 +10455,7 @@ void P_SpawnPlayer(INT32 playernum)
 	P_SetupStateAnimation(mobj, mobj->state);
 
 	mobj->health = 1;
-	p->rings = 0;
+	p->rings = p->spheres = 0;
 	p->playerstate = PST_LIVE;
 
 	p->bonustime = false;
@@ -9104,6 +10474,23 @@ void P_SpawnPlayer(INT32 playernum)
 	mobj->radius = FixedMul(skins[p->skin].radius, mobj->scale);
 	mobj->height = P_GetPlayerHeight(p);
 
+	if (!leveltime && ((maptol & TOL_NIGHTS) == TOL_NIGHTS) != (G_IsSpecialStage(gamemap))) // non-special NiGHTS stage or special non-NiGHTS stage
+	{
+		if (maptol & TOL_NIGHTS)
+		{
+			if (p == players) // this is totally the wrong place to do this aaargh.
+			{
+				mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD);
+				idya->health = 0; // for identification
+				P_SetTarget(&idya->target, mobj);
+				P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
+				P_SetTarget(&mobj->tracer, idya);
+			}
+		}
+		else if (sstimer)
+			p->nightstime = sstimer;
+	}
+
 	// Spawn with a pity shield if necessary.
 	P_DoPityCheck(p);
 }
@@ -9125,7 +10512,8 @@ void P_AfterPlayerSpawn(INT32 playernum)
 	else
 		p->viewz = p->mo->z + p->viewheight;
 
-	P_SetPlayerMobjState(p->mo, S_PLAY_STND);
+	if (p->powers[pw_carry] != CR_NIGHTSMODE)
+		P_SetPlayerMobjState(p->mo, S_PLAY_STND);
 	p->pflags &= ~PF_SPINNING;
 
 	if (playernum == consoleplayer)
@@ -9172,7 +10560,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 	{
 		x = mthing->x << FRACBITS;
 		y = mthing->y << FRACBITS;
-		angle = FixedAngle(mthing->angle*FRACUNIT);
+		angle = FixedAngle(mthing->angle<<FRACBITS);
 	}
 	//spawn at the origin as a desperation move if there is no mapthing
 
@@ -9301,6 +10689,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	mobj_t *mobj;
 	fixed_t x, y, z;
 	subsector_t *ss;
+	boolean doangle = true;
 
 	if (!mthing->type)
 		return; // Ignore type-0 things as NOPs
@@ -9361,19 +10750,21 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	else if (mthing->type == 750) // Slope vertex point (formerly chaos spawn)
 		return;
 
-	else if (mthing->type == 300 // Ring
-		|| mthing->type == 308 || mthing->type == 309 // Team Rings
-		|| mthing->type == 1706 // Nights Wing
-		|| (mthing->type >= 600 && mthing->type <= 609) // Placement patterns
-		|| mthing->type == 1705 || mthing->type == 1713 // NiGHTS Hoops
-		|| mthing->type == 1800) // Mario Coin
+	else if (mthing->type == mobjinfo[MT_RING].doomednum || mthing->type == mobjinfo[MT_COIN].doomednum
+	 || mthing->type == mobjinfo[MT_REDTEAMRING].doomednum || mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum
+	 || mthing->type == mobjinfo[MT_BLUESPHERE].doomednum || mthing->type == mobjinfo[MT_BOMBSPHERE].doomednum
+	 || (mthing->type >= 600 && mthing->type <= 609) // circles and diagonals
+	 || mthing->type == 1705 || mthing->type == 1713) // hoops
 	{
 		// Don't spawn hoops, wings, or rings yet!
 		return;
 	}
 
 	// check for players specially
-	if (mthing->type > 0 && mthing->type <= 32)
+#if MAXPLAYERS > 32
+You should think about modifying the deathmatch starts to take full advantage of this!
+#endif
+	if (mthing->type > 0 && mthing->type <= MAXPLAYERS)
 	{
 		// save spots for respawning in network games
 		if (!metalrecording)
@@ -9416,10 +10807,6 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			return;
 	}
 
-	if (!G_RingSlingerGametype() || !cv_specialrings.value)
-		if (P_WeaponOrPanel(i))
-			return; // Don't place weapons/panels in non-ringslinger modes
-
 	if (i == MT_EMERHUNT)
 	{
 		// Emerald Hunt is Coop only.
@@ -9453,6 +10840,10 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		if ((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS))
 			return;
 
+	if (!G_RingSlingerGametype() || !cv_specialrings.value)
+		if (P_WeaponOrPanel(i))
+			return; // Don't place weapons/panels in non-ringslinger modes
+
 	// Altering monitor spawns via cvars
 	// If MF_GRENADEBOUNCE is set in the monitor's info,
 	// skip this step. (Used for gold monitors)
@@ -9489,9 +10880,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 
 	if (gametype != GT_CTF) // CTF specific things
 	{
-		if (i == MT_BLUETEAMRING || i == MT_REDTEAMRING)
-			i = MT_RING;
-		else if (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX)
+		if (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX)
 			i = MT_RING_BOX;
 		else if (i == MT_BLUEFLAG || i == MT_REDFLAG)
 			return; // No flags in non-CTF modes!
@@ -9529,19 +10918,20 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	{
 		if (i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
 		 || i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
-		 || i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX)
-			return; // No shields in Ultimate mode
-
-		if (i == MT_RING_BOX && !G_IsSpecialStage(gamemap))
-			return; // No rings in Ultimate mode (except special stages)
+		 || i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX
+		 || i == MT_RING_BOX)
+			return; // No rings or shields in Ultimate mode
 
 		// Don't include the gold repeating boxes here please.
 		// They're likely facets of the level's design and therefore required to progress.
 	}
 
-	if (i == MT_TOKEN && (gametype != GT_COOP || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
+	if (i == MT_TOKEN && ((gametype != GT_COOP && gametype != GT_COMPETITION) || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
 		return; // you already got this token, or there are too many, or the gametype's not right
 
+	if (i == MT_EMBLEM && (netgame || multiplayer || (modifiedgame && !savemoddata))) // No cheating!!
+		return;
+
 	// Objectplace landing point
 	noreturns:
 
@@ -9558,7 +10948,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			ss->sector->floorheight) + ((mthing->options >> ZSHIFT) << FRACBITS);
 	else if (i == MT_AXIS || i == MT_AXISTRANSFER || i == MT_AXISTRANSFERLINE)
 		z = ONFLOORZ;
-	else if (i == MT_SPECIALSPIKEBALL || P_WeaponOrPanel(i) || i == MT_EMERALDSPAWN || i == MT_TOKEN)
+	else if (i == MT_SPIKEBALL || P_WeaponOrPanel(i) || i == MT_EMERALDSPAWN || i == MT_TOKEN || i == MT_EMBLEM)
 	{
 		if (mthing->options & MTF_OBJECTFLIP)
 		{
@@ -9660,12 +11050,78 @@ void P_SpawnMapThing(mapthing_t *mthing)
 #endif
 	switch(mobj->type)
 	{
+	case MT_EMBLEM:
+	{
+		INT32 j;
+		emblem_t *emblem = M_GetLevelEmblems(gamemap);
+		skincolors_t emcolor;
+
+		while (emblem)
+		{
+			if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == mthing->angle)
+				break;
+
+			emblem = M_GetLevelEmblems(-1);
+		}
+
+		if (!emblem)
+		{
+			CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
+			break;
+		}
+
+		j = emblem - emblemlocations;
+
+		I_Assert(emblemlocations[j].sprite >= 'A' && emblemlocations[j].sprite <= 'Z');
+		P_SetMobjState(mobj, mobj->info->spawnstate + (emblemlocations[j].sprite - 'A'));
+
+		mobj->health = j + 1;
+		emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
+		mobj->color = (UINT8)emcolor;
+
+		if (emblemlocations[j].collected
+			|| (emblemlocations[j].type == ET_SKIN && emblemlocations[j].var != players[0].skin))
+		{
+			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;
+				}
+			}
+		}
+		break;
+	}
 	case MT_SKYBOX:
 		if (mthing->options & MTF_OBJECTSPECIAL)
 			skyboxcenterpnts[mthing->extrainfo] = mobj;
 		else
 			skyboxviewpnts[mthing->extrainfo] = mobj;
 		break;
+	case MT_EGGSTATUE:
+		if (tutorialmode != (mthing->options & MTF_OBJECTSPECIAL))
+		{
+			mobj->color = SKINCOLOR_GOLD;
+			mobj->colorized = true;
+		}
+		break;
+	case MT_EGGMOBILE3:
+		mobj->cusval = mthing->extrainfo;
+		break;
 	case MT_FAN:
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
@@ -9683,6 +11139,58 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		else
 			mobj->health = FixedMul(ss->sector->ceilingheight-ss->sector->floorheight, 3*(FRACUNIT/4))>>FRACBITS;
 		break;
+	case MT_BALLOON:
+		if (mthing->angle > 0)
+			mobj->color = ((mthing->angle-1) % (MAXSKINCOLORS-1))+1;
+		break;
+#define makesoftwarecorona(mo, h) \
+			corona = P_SpawnMobjFromMobj(mo, 0, 0, h<<FRACBITS, MT_PARTICLE);\
+			corona->sprite = SPR_FLAM;\
+			corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);\
+			corona->tics = -1
+	case MT_FLAME:
+		if (mthing->options & MTF_EXTRA)
+		{
+			mobj_t *corona;
+			makesoftwarecorona(mobj, 20);
+			P_SetScale(corona, (corona->destscale = mobj->scale*3));
+			P_SetTarget(&mobj->tracer, corona);
+		}
+		break;
+	case MT_FLAMEHOLDER:
+		if (!(mthing->options & MTF_OBJECTSPECIAL)) // Spawn the fire
+		{
+			mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
+			P_SetTarget(&flame->target, mobj);
+			flame->flags2 |= MF2_BOSSNOTRAP;
+			if (mthing->options & MTF_EXTRA)
+			{
+				mobj_t *corona;
+				makesoftwarecorona(flame, 20);
+				P_SetScale(corona, (corona->destscale = flame->scale*3));
+				P_SetTarget(&flame->tracer, corona);
+			}
+		}
+		break;
+	case MT_CANDLE:
+	case MT_CANDLEPRICKET:
+		if (mthing->options & MTF_EXTRA)
+		{
+			mobj_t *corona;
+			makesoftwarecorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
+		}
+		break;
+#undef makesoftwarecorona
+	case MT_JACKO1:
+	case MT_JACKO2:
+	case MT_JACKO3:
+		if (!(mthing->options & MTF_EXTRA)) // take the torch out of the crafting recipe
+		{
+			mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
+			P_SetTarget(&overlay->target, mobj);
+			P_SetMobjState(overlay, mobj->info->raisestate);
+		}
+		break;
 	case MT_WATERDRIP:
 		if (mthing->angle)
 			mobj->tics = 3*TICRATE + mthing->angle;
@@ -9706,11 +11214,11 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	case MT_FIREBARPOINT:
 	case MT_CUSTOMMACEPOINT:
 	{
-		fixed_t mlength, mlengthset, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mnumspokesset, mpinch, mroll, mnumnospokes, mwidth, mmin, msound, radiusfactor;
+		fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
 		angle_t mspokeangle;
 		mobjtype_t chainlink, macetype, firsttype, linktype;
-		boolean mdoall = true;
-		mobj_t *spawnee;
+		boolean mdosound, mdocenter, mchainlike = false;
+		mobj_t *spawnee = NULL, *hprev = mobj;
 		mobjflag_t mflagsapply;
 		mobjflag2_t mflags2apply;
 		mobjeflag_t meflagsapply;
@@ -9741,14 +11249,17 @@ ML_NOCLIMB :
 	anything else - no functionality
 ML_EFFECT1 : Swings instead of spins
 ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
-ML_EFFECT3 : Spawn a bonus macetype at the hinge point
+ML_EFFECT3 : Spawn a bonus linktype at the hinge point
 ML_EFFECT4 : Don't clip inside the ground
+ML_EFFECT5 : Don't stop thinking when too far away
 */
 		mlength = abs(lines[line].dx >> FRACBITS);
 		mspeed = abs(lines[line].dy >> (FRACBITS - 4));
 		mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
-		if ((mmaxspeed = sides[lines[line].sidenum[0]].rowoffset >> (FRACBITS - 4)) < mspeed)
-			mmaxspeed = mspeed << 1;
+		if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset>>FRACBITS) < 0)
+			mminlength = 0;
+		else if (mminlength > mlength-1)
+			mminlength = mlength-1;
 		mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
 		myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
 
@@ -9767,30 +11278,29 @@ ML_EFFECT4 : Don't clip inside the ground
 			mpinch = mroll = mnumnospokes = mwidth = 0;
 
 		CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
-				"Length is %d\n"
+				"Length is %d (minus %d)\n"
 				"Speed is %d\n"
 				"Phase is %d\n"
 				"Yaw is %d\n"
 				"Pitch is %d\n"
-				"Max. speed is %d\n"
-				"No. of spokes is %d\n"
+				"No. of spokes is %d (%d antispokes)\n"
 				"Pinch is %d\n"
 				"Roll is %d\n"
-				"No. of antispokes is %d\n"
 				"Width is %d\n",
-				sizeu1(mthingi), mlength, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mpinch, mroll, mnumnospokes, mwidth);
+				sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
 
 		if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
 			mnumnospokes = mnumspokes/mnumnospokes;
 		else
-			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes - 1) : 0);
+			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
 
 		mobj->lastlook = mspeed;
 		mobj->movecount = mobj->lastlook;
-		mobj->health = (FixedAngle(myaw*FRACUNIT)>>ANGLETOFINESHIFT);
+		mobj->angle = FixedAngle(myaw*FRACUNIT);
+		doangle = false;
 		mobj->threshold = (FixedAngle(mpitch*FRACUNIT)>>ANGLETOFINESHIFT);
-		mobj->friction = mmaxspeed;
 		mobj->movefactor = mpinch;
+		mobj->movedir = 0;
 
 		// Mobjtype selection
 		switch(mobj->type)
@@ -9814,6 +11324,19 @@ ML_EFFECT4 : Don't clip inside the ground
 				else
 					chainlink = MT_NULL;
 				break;
+			case MT_CHAINPOINT:
+				if (mthing->options & MTF_AMBUSH)
+				{
+					macetype = MT_BIGGRABCHAIN;
+					chainlink = MT_BIGMACECHAIN;
+				}
+				else
+				{
+					macetype = MT_SMALLGRABCHAIN;
+					chainlink = MT_SMALLMACECHAIN;
+				}
+				mchainlike = true;
+				break;
 			default:
 				if (mthing->options & MTF_AMBUSH)
 				{
@@ -9828,20 +11351,18 @@ ML_EFFECT4 : Don't clip inside the ground
 				break;
 		}
 
-		if (!macetype)
+		if (!macetype && !chainlink)
 			break;
 
-		if (mobj->type != MT_CHAINPOINT)
-		{
-			firsttype = macetype;
-			mlength++;
-		}
-		else
+		if (mobj->type == MT_CHAINPOINT)
 		{
 			if (!mlength)
 				break;
-			firsttype = chainlink;
 		}
+		else
+			mlength++;
+
+		firsttype = macetype;
 
 		// Adjustable direction
 		if (lines[line].flags & ML_NOCLIMB)
@@ -9856,8 +11377,12 @@ ML_EFFECT4 : Don't clip inside the ground
 		else
 			mmin = mnumspokes;
 
+		// If over distance away, don't move UNLESS this flag is applied
+		if (lines[line].flags & ML_EFFECT5)
+			mobj->flags2 |= MF2_BOSSNOTRAP;
+
 		// Make the links the same type as the end - repeated below
-		if ((mobj->type != MT_CHAINPOINT) && ((lines[line].flags & ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
+		if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
 		{
 			linktype = macetype;
 			radiusfactor = 2; // Double the radius.
@@ -9865,35 +11390,41 @@ ML_EFFECT4 : Don't clip inside the ground
 		else
 			radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
 
+		if (!mchainlike)
+			mchainlike = (firsttype == chainlink);
+		widthfactor = (mchainlike ? 1 : 2);
+
 		mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT));
-		mflags2apply = (MF2_MACEROTATE|((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0));
+		mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
 		meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
 
-		msound = ((firsttype == chainlink) ? 0 : (mwidth & 1));
+		msound = (mchainlike ? 0 : (mwidth & 1));
 
 		// Quick and easy preparatory variable setting
 		mphase = (FixedAngle(mphase*FRACUNIT)>>ANGLETOFINESHIFT);
 		mroll = (FixedAngle(mroll*FRACUNIT)>>ANGLETOFINESHIFT);
 
-#define makemace(mobjtype, dist, moreflags2) P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
-				P_SetTarget(&spawnee->tracer, mobj);\
-				spawnee->threshold = mphase;\
-				spawnee->friction = mroll;\
-				spawnee->movefactor = mwidth;\
-				spawnee->movecount = dist;\
-				spawnee->angle = myaw;\
-				spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
-				spawnee->flags2 |= (mflags2apply|moreflags2);\
-				spawnee->eflags |= meflagsapply
-
-domaceagain:
-		mnumspokesset = mnumspokes;
+#define makemace(mobjtype, dist, moreflags2) {\
+	spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
+	P_SetTarget(&spawnee->tracer, mobj);\
+	spawnee->threshold = mphase;\
+	spawnee->friction = mroll;\
+	spawnee->movefactor = mwidthset;\
+	spawnee->movecount = dist;\
+	spawnee->angle = myaw;\
+	spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
+	spawnee->flags2 |= (mflags2apply|moreflags2);\
+	spawnee->eflags |= meflagsapply;\
+	P_SetTarget(&hprev->hnext, spawnee);\
+	P_SetTarget(&spawnee->hprev, hprev);\
+	hprev = spawnee;\
+}
 
-		if (mdoall && lines[line].flags & ML_EFFECT3) // Innermost mace/link
-			{ spawnee = makemace(macetype, 0, MF2_AMBUSH); }
+		mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
+		mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
 
 		// The actual spawning of spokes
-		while (mnumspokesset-- > 0)
+		while (mnumspokes-- > 0)
 		{
 			// Offsets
 			if (lines[line].flags & ML_EFFECT1) // Swinging
@@ -9901,14 +11432,15 @@ domaceagain:
 			else // Spinning
 				mphase = (mphase - mspokeangle) & FINEMASK;
 
-			if (mnumnospokes && !(mnumspokesset % mnumnospokes)) // Skipping a "missing" spoke
+			if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
 			{
 				if (mobj->type != MT_CHAINMACEPOINT)
 					continue;
 
-				firsttype = linktype = chainlink;
-				mlengthset = 1 + (mlength - 1)*radiusfactor;
-				radiusfactor = 1;
+				linktype = chainlink;
+				firsttype = ((mthing->options & MTF_AMBUSH) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
+				mmaxlength = 1 + (mlength - 1)*radiusfactor;
+				radiusfactor = widthfactor = 1;
 			}
 			else
 			{
@@ -9921,42 +11453,76 @@ domaceagain:
 						radiusfactor = 2;
 					}
 					else
-					{
-						linktype = chainlink;
 						radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
-					}
 
 					firsttype = macetype;
+					widthfactor = 2;
 				}
 
-				mlengthset = mlength;
+				mmaxlength = mlength;
+			}
+
+			mwidthset = mwidth;
+			mlengthset = mminlength;
+
+			if (mdocenter) // Innermost link
+				makemace(linktype, 0, 0);
+
+			// Out from the center...
+			if (linktype)
+			{
+				while ((++mlengthset) < mmaxlength)
+					makemace(linktype, radiusfactor*mlengthset, 0);
 			}
+			else
+				mlengthset = mmaxlength;
 
 			// Outermost mace/link
-			spawnee = makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
+			if (firsttype)
+				makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
 
-			if (mspeed && (mwidth == msound) && !(mthing->options & MTF_OBJECTSPECIAL) && mnumspokesset <= mmin) // Can it make a sound?
-				spawnee->flags2 |= MF2_BOSSNOTRAP;
+			if (!mwidth)
+			{
+				if (mdosound && mnumspokes <= mmin) // Can it make a sound?
+					spawnee->flags2 |= MF2_BOSSNOTRAP;
+			}
+			else
+			{
+				// Across the bar!
+				if (!firsttype)
+					mwidthset = -mwidth;
+				else if (mwidth > 0)
+				{
+					while ((mwidthset -= widthfactor) > -mwidth)
+					{
+						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+							spawnee->flags2 |= MF2_BOSSNOTRAP;
+					}
+				}
+				else
+				{
+					while ((mwidthset += widthfactor) < -mwidth)
+					{
+						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+							spawnee->flags2 |= MF2_BOSSNOTRAP;
+					}
+				}
+				mwidth = -mwidth;
 
-			if (!mdoall || !linktype)
-				continue;
+				// Outermost mace/link again!
+				if (firsttype)
+					makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
 
-			// The rest of the links
-			while (mlengthset > 0)
-				{ spawnee = makemace(linktype, radiusfactor*(mlengthset--), 0); }
-		}
+				// ...and then back into the center!
+				if (linktype)
+					while (mlengthset > mminlength)
+						makemace(linktype, radiusfactor*(mlengthset--), 0);
 
-		if (mwidth > 0)
-		{
-			mwidth *= -1;
-			goto domaceagain;
-		}
-		else if (mwidth != 0)
-		{
-			if ((mwidth = -(mwidth + ((firsttype == chainlink) ? 1 : 2))) < 0)
-				break;
-			mdoall = false;
-			goto domaceagain;
+				if (mdocenter) // Innermost link
+					makemace(linktype, 0, 0);
+			}
 		}
 
 #undef makemace
@@ -9965,8 +11531,8 @@ domaceagain:
 	}
 	case MT_PARTICLEGEN:
 	{
-		fixed_t radius, speed, bottomheight, topheight;
-		INT32 type, numdivisions, time, anglespeed, ticcount;
+		fixed_t radius, speed;
+		INT32 type, numdivisions, anglespeed, ticcount;
 		angle_t angledivision;
 		INT32 line;
 		const size_t mthingi = (size_t)(mthing - mapthings);
@@ -9985,13 +11551,9 @@ domaceagain:
 		else
 			type = (INT32)MT_PARTICLE;
 
-		speed = abs(sides[lines[line].sidenum[0]].textureoffset);
-		bottomheight = lines[line].frontsector->floorheight;
-		topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
-
 		if (!lines[line].backsector
 		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
-			ticcount = states[S_PARTICLEGEN].tics;
+			ticcount = 3;
 
 		numdivisions = (mthing->options >> ZSHIFT);
 
@@ -10009,18 +11571,9 @@ domaceagain:
 			angledivision = 0;
 		}
 
-		if ((speed) && (topheight > bottomheight))
-			time = (INT32)(FixedDiv((topheight - bottomheight), speed) >> FRACBITS);
-		else
-			time = 1; // There's no reasonable way to set it, so just show the object for one tic and move on.
-
+		speed = abs(sides[lines[line].sidenum[0]].textureoffset);
 		if (mthing->options & MTF_OBJECTFLIP)
-		{
-			mobj->z = topheight;
 			speed *= -1;
-		}
-		else
-			mobj->z = bottomheight;
 
 		CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
 				"Radius is %d\n"
@@ -10028,20 +11581,20 @@ domaceagain:
 				"Anglespeed is %d\n"
 				"Numdivisions is %d\n"
 				"Angledivision is %d\n"
-				"Time is %d\n"
 				"Type is %d\n"
 				"Tic seperation is %d\n",
-				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, time, type, ticcount);
+				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
 
 		mobj->angle = 0;
 		mobj->movefactor = speed;
 		mobj->lastlook = numdivisions;
 		mobj->movedir = angledivision*ANG1;
 		mobj->movecount = anglespeed*ANG1;
-		mobj->health = time;
 		mobj->friction = radius;
 		mobj->threshold = type;
 		mobj->reactiontime = ticcount;
+		mobj->cvmem = line;
+		mobj->watertop = mobj->waterbottom = 0;
 
 		break;
 	}
@@ -10060,24 +11613,142 @@ domaceagain:
 		// the bumper in 30 degree increments.
 		mobj->threshold = (mthing->options & 15) % 12; // It loops over, etc
 		P_SetMobjState(mobj, mobj->info->spawnstate+mobj->threshold);
-
-		// you can shut up now, OBJECTFLIP.  And all of the other options, for that matter.
-		mthing->options &= ~0xF;
 		break;
 	case MT_EGGCAPSULE:
 		if (mthing->angle <= 0)
 			mthing->angle = 20; // prevent 0 health
 
-		mobj->health = mthing->angle;
-		mobj->threshold = min(mthing->extrainfo, 7);
-		break;
-	case MT_TUBEWAYPOINT:
-		mobj->health = mthing->angle & 255;
-		mobj->threshold = mthing->angle >> 8;
+		mobj->health = mthing->angle;
+		mobj->threshold = min(mthing->extrainfo, 7);
+		break;
+	case MT_TUBEWAYPOINT:
+		mobj->health = mthing->angle & 255;
+		mobj->threshold = mthing->angle >> 8;
+		break;
+	case MT_IDEYAANCHOR:
+		mobj->health = mthing->extrainfo;
+		break;
+	case MT_NIGHTSDRONE:
+		{
+			boolean flip = mthing->options & MTF_OBJECTFLIP;
+			boolean topaligned = (mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
+			boolean middlealigned = (mthing->options & MTF_EXTRA) && !(mthing->options & MTF_OBJECTSPECIAL);
+			boolean bottomoffsetted = !(mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
+
+			INT16 timelimit = mthing->angle & 0xFFF;
+			fixed_t hitboxradius = ((mthing->angle & 0xF000) >> 12) * 32 * FRACUNIT;
+			fixed_t hitboxheight = mthing->extrainfo * 32 * FRACUNIT;
+			fixed_t oldheight = mobj->height;
+			fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
+
+			if (timelimit > 0)
+				mobj->health = timelimit;
+
+			if (hitboxradius > 0)
+				mobj->radius = hitboxradius;
+
+			if (hitboxheight > 0)
+				mobj->height = hitboxheight;
+			else
+				mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
+
+			droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
+			dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
+
+			if (flip && mobj->height != oldheight)
+				P_TeleportMove(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
+
+			if (!flip)
+			{
+				if (topaligned) // Align droneman to top of hitbox
+				{
+					dronemanoffset = droneboxmandiff;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+				else if (middlealigned) // Align droneman to center of hitbox
+				{
+					dronemanoffset = droneboxmandiff / 2;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+				else if (bottomoffsetted)
+				{
+					dronemanoffset = 24*FRACUNIT;
+					goaloffset = dronemangoaldiff + dronemanoffset;
+				}
+				else
+				{
+					dronemanoffset = 0;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+
+				sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
+			}
+			else
+			{
+				mobj->eflags |= MFE_VERTICALFLIP;
+				mobj->flags2 |= MF2_OBJECTFLIP;
+
+				if (topaligned) // Align droneman to top of hitbox
+				{
+					dronemanoffset = 0;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+				else if (middlealigned) // Align droneman to center of hitbox
+				{
+					dronemanoffset = droneboxmandiff / 2;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+				else if (bottomoffsetted)
+				{
+					dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+					goaloffset = dronemangoaldiff + dronemanoffset;
+				}
+				else
+				{
+					dronemanoffset = droneboxmandiff;
+					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
+				}
+
+				sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
+			}
+
+			// spawn visual elements
+			{
+				mobj_t *goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
+				mobj_t *sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
+				mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
+
+				P_SetTarget(&mobj->target, goalpost);
+				P_SetTarget(&goalpost->target, sparkle);
+				P_SetTarget(&goalpost->tracer, droneman);
+
+				// correct Z position
+				if (flip)
+				{
+					P_TeleportMove(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
+					P_TeleportMove(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
+					P_TeleportMove(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
+				}
+
+				// Remember position preference for later
+				mobj->flags &= ~(MF_SLIDEME | MF_GRENADEBOUNCE);
+				if (topaligned)
+					mobj->flags |= MF_SLIDEME;
+				else if (middlealigned)
+					mobj->flags |= MF_GRENADEBOUNCE;
+				else if (!bottomoffsetted)
+					mobj->flags |= MF_SLIDEME | MF_GRENADEBOUNCE;
+
+				// Remember old Z position and flags for correction detection
+				goalpost->movefactor = mobj->z;
+				goalpost->friction = mobj->height;
+				goalpost->threshold = mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE);
+			}
+		}
 		break;
-	case MT_NIGHTSDRONE:
-		if (mthing->angle > 0)
-			mobj->health = mthing->angle;
+	case MT_HIVEELEMENTAL:
+		if (mthing->extrainfo)
+			mobj->extravalue1 = mthing->extrainfo;
 		break;
 	case MT_TRAPGOYLE:
 	case MT_TRAPGOYLEUP:
@@ -10085,6 +11756,53 @@ domaceagain:
 	case MT_TRAPGOYLELONG:
 		if (mthing->angle >= 360)
 			mobj->tics += 7*(mthing->angle / 360) + 1; // starting delay
+		break;
+	case MT_DSZSTALAGMITE:
+	case MT_DSZ2STALAGMITE:
+	case MT_KELP:
+		if (mthing->options & MTF_OBJECTSPECIAL) { // make mobj twice as big as normal
+			P_SetScale(mobj, 2*mobj->scale); // not 2*FRACUNIT in case of something like the old ERZ3 mode
+			mobj->destscale = mobj->scale;
+		}
+		break;
+	case MT_THZTREE:
+		{ // Spawn the branches
+			angle_t mobjangle = FixedAngle((mthing->angle % 113)<<FRACBITS);
+			P_SpawnMobjFromMobj(mobj, 1*FRACUNIT,  0,          0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
+			P_SpawnMobjFromMobj(mobj, 0,           1*FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
+			P_SpawnMobjFromMobj(mobj, -1*FRACUNIT, 0,          0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
+		}
+		break;
+	case MT_CEZPOLE1:
+	case MT_CEZPOLE2:
+		{ // Spawn the banner
+			angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS);
+			P_SpawnMobjFromMobj(mobj,
+				P_ReturnThrustX(mobj, mobjangle, 4<<FRACBITS),
+				P_ReturnThrustY(mobj, mobjangle, 4<<FRACBITS),
+				0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
+		}
+			break;
+	case MT_HHZTREE_TOP:
+		{ // Spawn the branches
+			angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS) & (ANGLE_90-1);
+			mobj_t *leaf;
+#define doleaf(x, y) \
+			leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\
+			leaf->angle = mobjangle;\
+			P_SetMobjState(leaf, leaf->info->seestate);\
+			mobjangle += ANGLE_90
+			doleaf(1*FRACUNIT, 0);
+			doleaf(0, 1*FRACUNIT);
+			doleaf(-1*FRACUNIT, 0);
+			doleaf(0, -1*FRACUNIT);
+#undef doleaf
+		}
+		break;
+	case MT_SMASHINGSPIKEBALL:
+		if (mthing->angle > 0)
+			mobj->tics += mthing->angle;
+		break;
 	default:
 		break;
 	}
@@ -10117,9 +11835,6 @@ domaceagain:
 	}
 	else if (i == MT_TOKEN)
 	{
-		if (mthing->options & MTF_OBJECTSPECIAL) // Mario Block version
-			mobj->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
-
 		// We advanced tokenbits earlier due to the return check.
 		// Subtract 1 here for the correct value.
 		mobj->health = 1 << (tokenbits - 1);
@@ -10129,7 +11844,7 @@ domaceagain:
 		mobj_t *elecmobj;
 		elecmobj = P_SpawnMobj(x, y, z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
 		P_SetTarget(&elecmobj->target, mobj);
-		elecmobj->angle = FixedAngle(mthing->angle*FRACUNIT);;
+		elecmobj->angle = FixedAngle(mthing->angle<<FRACBITS);;
 		elecmobj->destscale = mobj->scale*2;
 		P_SetScale(elecmobj, elecmobj->destscale);
 	}
@@ -10141,9 +11856,9 @@ domaceagain:
 		mobj->health = (mthing->angle / 360) + 1;
 
 		// See if other starposts exist in this level that have the same value.
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -10167,7 +11882,9 @@ domaceagain:
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = mthing->angle + mobj->info->speed;
+			mobj->fuse = (16 - mthing->extrainfo) * (mthing->angle + mobj->info->speed) / 16;
+			if (mthing->options & MTF_EXTRA)
+				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
 		// Use per-thing collision for spikes if the deaf flag isn't checked.
 		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
@@ -10184,7 +11901,9 @@ domaceagain:
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = mobj->info->speed;
+			mobj->fuse = (16 - mthing->extrainfo) * ((mthing->angle/360) + mobj->info->speed) / 16;
+			if (mthing->options & MTF_EXTRA)
+				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
 		// Use per-thing collision for spikes if the deaf flag isn't checked.
 		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
@@ -10197,7 +11916,7 @@ domaceagain:
 
 		// spawn base
 		{
-			const angle_t mobjangle = FixedAngle(mthing->angle*FRACUNIT); // the mobj's own angle hasn't been set quite yet so...
+			const angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS); // the mobj's own angle hasn't been set quite yet so...
 			const fixed_t baseradius = mobj->radius - mobj->scale;
 			mobj_t *base = P_SpawnMobj(
 					mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
@@ -10212,7 +11931,7 @@ domaceagain:
 	}
 
 	//count 10 ring boxes into the number of rings equation too.
-	if (i == MT_RING_BOX)
+	if (i == MT_RING_BOX && nummaprings >= 0)
 		nummaprings += 10;
 
 	if (i == MT_BIGTUMBLEWEED || i == MT_LITTLETUMBLEWEED)
@@ -10264,7 +11983,15 @@ domaceagain:
 		}
 	}
 
-	mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+	if (doangle)
+		mobj->angle = FixedAngle(mthing->angle<<FRACBITS);
+
+	// ignore MTF_ flags and return early
+	if (i == MT_NIGHTSBUMPER)
+	{
+		mthing->mobj = mobj;
+		return;
+	}
 
 	if ((mthing->options & MTF_AMBUSH)
 	&& (mthing->options & MTF_OBJECTSPECIAL)
@@ -10277,6 +12004,15 @@ domaceagain:
 			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
 				mobj->angle += ANGLE_22h;
 
+			if (i == MT_YELLOWHORIZ || i == MT_REDHORIZ || i == MT_BLUEHORIZ)
+			{
+				if (mthing->options & MTF_OBJECTFLIP)
+					mobj->z -= 16*FRACUNIT;
+				else
+					mobj->z += 16*FRACUNIT;
+			}
+
+
 			if (mobj->flags & MF_NIGHTSITEM)
 			{
 				// Spawn already displayed
@@ -10285,10 +12021,7 @@ domaceagain:
 			}
 
 			if (mobj->flags & MF_PUSHABLE)
-			{
 				mobj->flags &= ~MF_PUSHABLE;
-				mobj->flags2 |= MF2_STANDONME;
-			}
 
 			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
 			{
@@ -10307,6 +12040,9 @@ domaceagain:
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
+			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
+				mobj->flags |= MF_NOGRAVITY;
+
 			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
 			{
 				// flag for strong/weak random boxes
@@ -10334,6 +12070,16 @@ domaceagain:
 		mobj->flags2 |= MF2_OBJECTFLIP;
 	}
 
+	// Extra functionality
+	if (mthing->options & MTF_EXTRA)
+	{
+		if (mobj->flags & MF_MONITOR && (mthing->angle & 16384))
+		{
+			// Store line exec tag to run upon popping
+			mobj->lastlook = (mthing->angle & 16383);
+		}
+	}
+
 	// Final set of not being able to draw nightsitems.
 	if (mobj->flags & MF_NIGHTSITEM)
 		mobj->flags2 |= MF2_DONTDRAW;
@@ -10341,14 +12087,16 @@ domaceagain:
 	mthing->mobj = mobj;
 }
 
-void P_SpawnHoopsAndRings(mapthing_t *mthing)
+void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 {
+	mobjtype_t ringthing = MT_RING;
 	mobj_t *mobj = NULL;
 	INT32 r, i;
 	fixed_t x, y, z, finalx, finaly, finalz;
 	sector_t *sec;
 	TVector v, *res;
 	angle_t closestangle, fa;
+	boolean nightsreplace = ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap));
 
 	x = mthing->x << FRACBITS;
 	y = mthing->y << FRACBITS;
@@ -10428,11 +12176,11 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			// Link all the sprites in the hoop together
 			if (nextmobj)
 			{
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 			}
 			else
-				mobj->hprev = mobj->hnext = NULL;
+				P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
 
 			nextmobj = mobj;
 		}
@@ -10458,9 +12206,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			mobj->z -= mobj->height/2;
 
 			// Link all the collision sprites together.
-			mobj->hnext = NULL;
-			mobj->hprev = nextmobj;
-			mobj->hprev->hnext = mobj;
+			P_SetTarget(&mobj->hnext, NULL);
+			P_SetTarget(&mobj->hprev, nextmobj);
+			P_SetTarget(&mobj->hprev->hnext, mobj);
 
 			nextmobj = mobj;
 		}
@@ -10485,9 +12233,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			mobj->z -= mobj->height/2;
 
 			// Link all the collision sprites together.
-			mobj->hnext = NULL;
-			mobj->hprev = nextmobj;
-			mobj->hprev->hnext = mobj;
+			P_SetTarget(&mobj->hnext, NULL);
+			P_SetTarget(&mobj->hprev, nextmobj);
+			P_SetTarget(&mobj->hprev->hnext, mobj);
 
 			nextmobj = mobj;
 		}
@@ -10570,11 +12318,11 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			// Link all the sprites in the hoop together
 			if (nextmobj)
 			{
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 			}
 			else
-				mobj->hprev = mobj->hnext = NULL;
+				P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
 
 			nextmobj = mobj;
 		}
@@ -10611,9 +12359,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 				mobj->z -= mobj->height/2;
 
 				// Link all the collision sprites together.
-				mobj->hnext = NULL;
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hnext, NULL);
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 
 				nextmobj = mobj;
 			}
@@ -10621,147 +12369,50 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 
 		return;
 	}
-	// Wing logo item.
-	else if (mthing->type == mobjinfo[MT_NIGHTSWING].doomednum)
-	{
-		z =
-#ifdef ESLOPE
-			sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
-#endif
-			sec->floorheight;
-		if (mthing->options >> ZSHIFT)
-			z += ((mthing->options >> ZSHIFT) << FRACBITS);
-
-		mthing->z = (INT16)(z>>FRACBITS);
-
-		mobj = P_SpawnMobj(x, y, z, MT_NIGHTSWING);
-		mobj->spawnpoint = mthing;
-
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			P_SetMobjState(mobj, mobj->info->meleestate);
-		else if (maptol & TOL_XMAS)
-			P_SetMobjState(mobj, mobj->info->seestate);
+	// ***
+	// Special placement patterns
+	// ***
 
-		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobj->flags2 |= MF2_AMBUSH;
-		mthing->mobj = mobj;
-	}
-	// All manners of rings and coins
-	else if (mthing->type == mobjinfo[MT_RING].doomednum || mthing->type == mobjinfo[MT_COIN].doomednum ||
-	         mthing->type == mobjinfo[MT_REDTEAMRING].doomednum || mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum)
+	// Vertical Rings - Stack of 5 (handles both red and yellow)
+	else if (mthing->type == 600 || mthing->type == 601)
 	{
-		mobjtype_t ringthing = MT_RING;
+		INT32 dist = 64*FRACUNIT;
+		if (mthing->type == 601)
+			dist = 128*FRACUNIT;
 
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
+		if (ultimatemode)
+			return; // No rings in Ultimate!
 
-		// Which ringthing to use
-		switch (mthing->type)
-		{
-			case 1800:
-				ringthing = MT_COIN;
-				break;
-			case 308: // No team rings in non-CTF
-				ringthing = (gametype == GT_CTF) ? MT_REDTEAMRING : MT_RING;
-				break;
-			case 309: // No team rings in non-CTF
-				ringthing = (gametype == GT_CTF) ? MT_BLUETEAMRING : MT_RING;
-				break;
-			default:
-				// Spawn rings as blue spheres in special stages, ala S3+K.
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					ringthing = MT_BLUEBALL;
-				break;
-		}
+		if (nightsreplace)
+			ringthing = MT_NIGHTSSTAR;
 
-		// Set proper height
 		if (mthing->options & MTF_OBJECTFLIP)
 		{
 			z = (
 #ifdef ESLOPE
-			sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
+				sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
 #endif
-			sec->ceilingheight) - mobjinfo[ringthing].height;
+				sec->ceilingheight) - mobjinfo[ringthing].height;
 			if (mthing->options >> ZSHIFT)
 				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
-		}
+			}
 		else
 		{
-			z =
+			z = (
 #ifdef ESLOPE
-			sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
+				sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
 #endif
-			sec->floorheight;
+				sec->floorheight);
 			if (mthing->options >> ZSHIFT)
 				z += ((mthing->options >> ZSHIFT) << FRACBITS);
 		}
 
-		if (mthing->options & MTF_AMBUSH) // Special flag for rings
-		{
-			if (mthing->options & MTF_OBJECTFLIP)
-				z -= 24*FRACUNIT;
-			else
-				z += 24*FRACUNIT;
-		}
-
-		mthing->z = (INT16)(z>>FRACBITS);
-
-		mobj = P_SpawnMobj(x, y, z, ringthing);
-		mobj->spawnpoint = mthing;
-
-		if (mthing->options & MTF_OBJECTFLIP)
-		{
-			mobj->eflags |= MFE_VERTICALFLIP;
-			mobj->flags2 |= MF2_OBJECTFLIP;
-		}
-
-		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobj->flags2 |= MF2_AMBUSH;
-		mthing->mobj = mobj;
-	}
-	// ***
-	// Special placement patterns
-	// ***
-
-	// Vertical Rings - Stack of 5 (handles both red and yellow)
-	else if (mthing->type == 600 || mthing->type == 601)
-	{
-		INT32 dist = 64*FRACUNIT;
-		mobjtype_t ringthing = MT_RING;
-		if (mthing->type == 601)
-			dist = 128*FRACUNIT;
-
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
-
-		// Spawn rings as blue spheres in special stages, ala S3+K.
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			ringthing = MT_BLUEBALL;
-
 		for (r = 1; r <= 5; r++)
 		{
 			if (mthing->options & MTF_OBJECTFLIP)
-			{
-				z = (
-#ifdef ESLOPE
-					sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
-#endif
-					sec->ceilingheight) - mobjinfo[ringthing].height - dist*r;
-				if (mthing->options >> ZSHIFT)
-					z -= ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				z -= dist;
 			else
-			{
-				z = (
-#ifdef ESLOPE
-					sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
-#endif
-					sec->floorheight) + dist*r;
-				if (mthing->options >> ZSHIFT)
-					z += ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				z += dist;
 
 			mobj = P_SpawnMobj(x, y, z, ringthing);
 
@@ -10774,52 +12425,57 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags2 |= MF2_AMBUSH;
+
+			if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
 	}
 	// Diagonal rings (handles both types)
 	else if (mthing->type == 602 || mthing->type == 603) // Diagonal rings (5)
 	{
-		angle_t angle = FixedAngle(mthing->angle*FRACUNIT);
-		mobjtype_t ringthing = MT_RING;
 		INT32 iterations = 5;
 		if (mthing->type == 603)
 			iterations = 10;
 
-		// No rings in Ultimate!
-		if (ultimatemode && !(G_IsSpecialStage(gamemap) || maptol & TOL_NIGHTS))
-			return;
+		if (ultimatemode)
+			return; // No rings in Ultimate!
 
-		// Spawn rings as blue spheres in special stages, ala S3+K.
-		if (G_IsSpecialStage(gamemap) && useNightsSS)
-			ringthing = MT_BLUEBALL;
+		if (nightsreplace)
+			ringthing = MT_NIGHTSSTAR;
 
-		angle >>= ANGLETOFINESHIFT;
+		closestangle = FixedAngle(mthing->angle*FRACUNIT);
+		fa = (closestangle >> ANGLETOFINESHIFT);
 
-		for (r = 1; r <= iterations; r++)
+		if (mthing->options & MTF_OBJECTFLIP)
 		{
-			x += FixedMul(64*FRACUNIT, FINECOSINE(angle));
-			y += FixedMul(64*FRACUNIT, FINESINE(angle));
-
-			if (mthing->options & MTF_OBJECTFLIP)
-			{
-				z = (
+			z = (
 #ifdef ESLOPE
-					sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
+				sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
 #endif
-					sec->ceilingheight) - mobjinfo[ringthing].height - 64*FRACUNIT*r;
-				if (mthing->options >> ZSHIFT)
-					z -= ((mthing->options >> ZSHIFT) << FRACBITS);
+				sec->ceilingheight) - mobjinfo[ringthing].height;
+			if (mthing->options >> ZSHIFT)
+				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
 			}
-			else
-			{
-				z = (
+		else
+		{
+			z = (
 #ifdef ESLOPE
-					sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
+				sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
 #endif
-					sec->floorheight) + 64*FRACUNIT*r;
-				if (mthing->options >> ZSHIFT)
-					z += ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				sec->floorheight);
+			if (mthing->options >> ZSHIFT)
+				z += ((mthing->options >> ZSHIFT) << FRACBITS);
+		}
+
+		for (r = 1; r <= iterations; r++)
+		{
+			x += FixedMul(64*FRACUNIT, FINECOSINE(fa));
+			y += FixedMul(64*FRACUNIT, FINESINE(fa));
+
+			if (mthing->options & MTF_OBJECTFLIP)
+				z -= 64*FRACUNIT;
+			else
+				z += 64*FRACUNIT;
 
 			mobj = P_SpawnMobj(x, y, z, ringthing);
 
@@ -10829,9 +12485,12 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
 
-			mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+			mobj->angle = closestangle;
 			if (mthing->options & MTF_AMBUSH)
 				mobj->flags2 |= MF2_AMBUSH;
+
+			if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
 	}
 	// Rings of items (all six of them)
@@ -10839,7 +12498,6 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 	{
 		INT32 numitems = 8;
 		INT32 size = 96*FRACUNIT;
-		mobjtype_t itemToSpawn = MT_NIGHTSWING;
 
 		if (mthing->type & 1)
 		{
@@ -10857,36 +12515,40 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 
 		closestangle = FixedAngle(mthing->angle*FRACUNIT);
 
+		switch (mthing->type)
+		{
+			case 604:
+			case 605:
+				if (ultimatemode)
+					return; // No rings in Ultimate!
+				if (nightsreplace)
+					ringthing = MT_NIGHTSSTAR;
+				break;
+			case 608:
+			case 609:
+				/*ringthing = (i & 1) ? MT_RING : MT_BLUESPHERE; -- i == 0 is bluesphere
+				break;*/
+			case 606:
+			case 607:
+				ringthing = (nightsreplace) ? MT_NIGHTSCHIP : MT_BLUESPHERE;
+				break;
+			default:
+				break;
+		}
+
 		// Create the hoop!
 		for (i = 0; i < numitems; i++)
 		{
-			switch (mthing->type)
-			{
-				case 604:
-				case 605:
-					itemToSpawn = MT_RING;
-					break;
-				case 608:
-				case 609:
-					itemToSpawn = (i & 1) ? MT_NIGHTSWING : MT_RING;
-					break;
-				case 606:
-				case 607:
-					itemToSpawn = MT_NIGHTSWING;
-					break;
-				default:
-					break;
-			}
-
-			// No rings in Ultimate!
-			if (itemToSpawn == MT_RING)
+			if (mthing->type == 608 || mthing->type == 609)
 			{
-				if (ultimatemode && !(G_IsSpecialStage(gamemap) || (maptol & TOL_NIGHTS)))
-					continue;
-
-				// Spawn rings as blue spheres in special stages, ala S3+K.
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					itemToSpawn = MT_BLUEBALL;
+				if (i & 1)
+				{
+					if (ultimatemode)
+						continue; // No rings in Ultimate!
+					ringthing = (nightsreplace) ? MT_NIGHTSSTAR : MT_RING;
+				}
+				else
+					ringthing = (nightsreplace) ? MT_NIGHTSCHIP : MT_BLUESPHERE;
 			}
 
 			fa = i*FINEANGLES/numitems;
@@ -10902,18 +12564,99 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 			finaly = y + v[1];
 			finalz = z + v[2];
 
-			mobj = P_SpawnMobj(finalx, finaly, finalz, itemToSpawn);
+			mobj = P_SpawnMobj(finalx, finaly, finalz, ringthing);
 			mobj->z -= mobj->height/2;
 
-			if (itemToSpawn == MT_NIGHTSWING)
+			if (mthing->options & MTF_OBJECTFLIP)
 			{
-				if (G_IsSpecialStage(gamemap) && useNightsSS)
-					P_SetMobjState(mobj, mobj->info->meleestate);
-				else if ((maptol & TOL_XMAS))
-					P_SetMobjState(mobj, mobj->info->seestate);
+				mobj->eflags |= MFE_VERTICALFLIP;
+				mobj->flags2 |= MF2_OBJECTFLIP;
 			}
+
+			mobj->angle = closestangle;
+			if (mthing->options & MTF_AMBUSH)
+				mobj->flags2 |= MF2_AMBUSH;
+
+			if (bonustime && (ringthing == MT_BLUESPHERE || ringthing == MT_NIGHTSCHIP))
+				P_SetMobjState(mobj, mobj->info->raisestate);
+			else if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+				P_SetMobjState(mobj, mobj->info->seestate);
 		}
-		return;
+	}
+	// All manners of rings and coins
+	else
+	{
+
+		// Which ringthing to use
+		if (mthing->type == mobjinfo[MT_BLUESPHERE].doomednum)
+			ringthing = (nightsreplace) ? MT_NIGHTSCHIP : MT_BLUESPHERE;
+		else if (mthing->type == mobjinfo[MT_BOMBSPHERE].doomednum)
+			ringthing = MT_BOMBSPHERE;
+		else
+		{
+			if (ultimatemode)
+				return; // No rings in Ultimate!
+
+			if (nightsreplace)
+				ringthing = MT_NIGHTSSTAR;
+			else if (mthing->type == mobjinfo[MT_COIN].doomednum)
+				ringthing = MT_COIN;
+			else if (mthing->type == mobjinfo[MT_REDTEAMRING].doomednum) // No team rings in non-CTF
+				ringthing = (gametype == GT_CTF) ? MT_REDTEAMRING : MT_RING;
+			else if (mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum) // Ditto
+				ringthing = (gametype == GT_CTF) ? MT_BLUETEAMRING : MT_RING;
+		}
+
+		// Set proper height
+		if (mthing->options & MTF_OBJECTFLIP)
+		{
+			z = (
+#ifdef ESLOPE
+			sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
+#endif
+			sec->ceilingheight) - mobjinfo[ringthing].height;
+			if (mthing->options >> ZSHIFT)
+				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
+		}
+		else
+		{
+			z =
+#ifdef ESLOPE
+			sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
+#endif
+			sec->floorheight;
+			if (mthing->options >> ZSHIFT)
+				z += ((mthing->options >> ZSHIFT) << FRACBITS);
+		}
+
+		if (mthing->options & MTF_AMBUSH) // Special flag for rings
+		{
+			if (mthing->options & MTF_OBJECTFLIP)
+				z -= 24*FRACUNIT;
+			else
+				z += 24*FRACUNIT;
+		}
+
+		mthing->z = (INT16)(z>>FRACBITS);
+
+		mobj = P_SpawnMobj(x, y, z, ringthing);
+		mobj->spawnpoint = mthing;
+
+		if (mthing->options & MTF_OBJECTFLIP)
+		{
+			mobj->eflags |= MFE_VERTICALFLIP;
+			mobj->flags2 |= MF2_OBJECTFLIP;
+		}
+
+		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+		mthing->mobj = mobj;
+		if (mthing->options & MTF_AMBUSH)
+			mobj->flags2 |= MF2_AMBUSH;
+
+		if (bonustime && (ringthing == MT_BLUESPHERE || ringthing == MT_NIGHTSCHIP))
+			P_SetMobjState(mobj, mobj->info->raisestate);
+		else if ((maptol & TOL_XMAS) && (ringthing == MT_NIGHTSSTAR))
+			P_SetMobjState(mobj, mobj->info->seestate);
 	}
 }
 
diff --git a/src/p_mobj.h b/src/p_mobj.h
index c6e1bfbf2317b3f6a4f2f83f42fe04f9e63102fb..a9d5244b044ad58840238d753e2e70184e2fc585 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -175,8 +175,8 @@ typedef enum
 	MF2_SCATTER        = 1<<8,  // Thrown ring has scatter properties
 	MF2_BEYONDTHEGRAVE = 1<<9,  // Source of this missile has died and has since respawned.
 	MF2_SLIDEPUSH      = 1<<10, // MF_PUSHABLE that pushes continuously.
-	MF2_CLASSICPUSH    = 1<<11, // Drops straight down when object has negative Z.
-	MF2_STANDONME      = 1<<12, // While not pushable, stand on me anyway.
+	MF2_CLASSICPUSH    = 1<<11, // Drops straight down when object has negative momz.
+	MF2_INVERTAIMABLE  = 1<<12, // Flips whether it's targetable by A_LookForEnemies (enemies no, decoys yes)
 	MF2_INFLOAT        = 1<<13, // Floating to a height for a move, don't auto float to target's height.
 	MF2_DEBRIS         = 1<<14, // Splash ring from explosion ring
 	MF2_NIGHTSPULL     = 1<<15, // Attracted from a paraloop
@@ -194,7 +194,6 @@ typedef enum
 	MF2_AMBUSH         = 1<<27, // Alternate behaviour typically set by MTF_AMBUSH
 	MF2_LINKDRAW       = 1<<28, // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	MF2_SHIELD         = 1<<29, // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
-	MF2_MACEROTATE     = 1<<30, // Thinker calls P_MaceRotate around tracer
 	// free: to and including 1<<31
 } mobjflag2_t;
 
@@ -240,6 +239,9 @@ typedef enum
 	MFE_SPRUNG            = 1<<8,
 	// Platform movement
 	MFE_APPLYPMOMZ        = 1<<9,
+	// Compute and trigger on mobj angle relative to tracer
+	// See Linedef Exec 457 (Track mobj angle to point)
+	MFE_TRACERANGLE       = 1<<10,
 	// free: to and including 1<<15
 } mobjeflag_t;
 
@@ -255,6 +257,10 @@ typedef enum {
 	PCF_FOF = 4,
 	// Above MOVING FOF (this means we need to keep floorz up to date...)
 	PCF_MOVINGFOF = 8,
+	// Is rain.
+	PCF_RAIN = 16,
+	// Ran the thinker this tic.
+	PCF_THUNK = 32,
 } precipflag_t;
 // Map Object definition.
 typedef struct mobj_s
@@ -283,6 +289,8 @@ typedef struct mobj_s
 	// The closest interval over all contacted sectors (or things).
 	fixed_t floorz; // Nearest floor below.
 	fixed_t ceilingz; // Nearest ceiling above.
+	struct ffloor_s *floorrover; // FOF referred by floorz
+	struct ffloor_s *ceilingrover; // FOF referred by ceilingz
 
 	// For movement checking.
 	fixed_t radius;
@@ -315,7 +323,7 @@ typedef struct mobj_s
 	mobjtype_t type;
 	const mobjinfo_t *info; // &mobjinfo[mobj->type]
 
-	INT32 health; // for player this is rings + 1
+	INT32 health; // for player this is rings + 1 -- no it isn't, not any more!!
 
 	// Movement direction, movement generation (zig-zagging).
 	angle_t movedir; // dirtype_t 0-7; also used by Deton for up/down angle
@@ -363,6 +371,8 @@ typedef struct mobj_s
 	struct pslope_s *standingslope; // The slope that the object is standing on (shouldn't need synced in savegames, right?)
 #endif
 
+	boolean colorized; // Whether the mobj uses the rainbow colormap
+
 	// WARNING: New fields must be added separately to savegame and Lua.
 } mobj_t;
 
@@ -389,6 +399,7 @@ typedef struct precipmobj_s
 	angle_t angle;  // orientation
 	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
 	UINT16 anim_duration; // for FF_ANIMATE states
 
 	struct mprecipsecnode_s *touching_sectorlist; // a linked list of sectors where this object appears
@@ -398,6 +409,8 @@ typedef struct precipmobj_s
 	// The closest interval over all contacted sectors (or things).
 	fixed_t floorz; // Nearest floor below.
 	fixed_t ceilingz; // Nearest ceiling above.
+	struct ffloor_s *floorrover; // FOF referred by floorz
+	struct ffloor_s *ceilingrover; // FOF referred by ceilingz
 
 	// For movement checking.
 	fixed_t radius; // Fixed at 2*FRACUNIT
@@ -436,7 +449,7 @@ void P_MovePlayerToStarpost(INT32 playernum);
 void P_AfterPlayerSpawn(INT32 playernum);
 
 void P_SpawnMapThing(mapthing_t *mthing);
-void P_SpawnHoopsAndRings(mapthing_t *mthing);
+void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime);
 void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle);
 void P_SpawnPrecipitation(void);
 void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter);
@@ -445,16 +458,19 @@ boolean P_SupermanLook4Players(mobj_t *actor);
 void P_DestroyRobots(void);
 void P_SnowThinker(precipmobj_t *mobj);
 void P_RainThinker(precipmobj_t *mobj);
-FUNCMATH void P_NullPrecipThinker(precipmobj_t *mobj);
+void P_NullPrecipThinker(precipmobj_t *mobj);
 void P_RemovePrecipMobj(precipmobj_t *mobj);
 void P_SetScale(mobj_t *mobj, fixed_t newscale);
 void P_XYMovement(mobj_t *mo);
 void P_EmeraldManager(void);
 
+extern INT32 modulothing;
+
 #define MAXHUNTEMERALDS 64
 extern mapthing_t *huntemeralds[MAXHUNTEMERALDS];
 extern INT32 numhuntemeralds;
 extern boolean runemeraldmanager;
 extern UINT16 emeraldspawndelay;
 extern INT32 numstarposts;
+extern UINT16 bossdisabled;
 #endif
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index fd3237c9da8f71f53686270f6ff7ee5daa9fb465..040bdca2a92143b6353476701d5cb8c527427cba 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by James Haley
-// Copyright (C) 2006-2016 by Sonic Team Junior.
+// Copyright (C) 2006-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,6 +28,10 @@
 #include "r_state.h"
 #include "r_defs.h"
 
+
+#define POLYOBJECTS
+
+
 #ifdef POLYOBJECTS
 
 /*
@@ -142,16 +146,6 @@ FUNCINLINE static ATTRINLINE void Polyobj_vecSub2(vertex_t *dst, vertex_t *v1, v
 	dst->y = v1->y - v2->y;
 }
 
-// Add the polyobject's thinker to the thinker list
-// Unlike P_AddThinker, this adds it to the front of the list instead of the back, so that carrying physics can work right. -Red
-FUNCINLINE static ATTRINLINE void PolyObj_AddThinker(thinker_t *th)
-{
-	thinkercap.next->prev = th;
-	th->next = thinkercap.next;
-	th->prev = &thinkercap;
-	thinkercap.next = th;
-}
-
 //
 // P_PointInsidePolyobj
 //
@@ -985,6 +979,8 @@ static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 		P_CheckPosition(mo, mo->x + momx, mo->y + momy);
 		mo->floorz = tmfloorz;
 		mo->ceilingz = tmceilingz;
+		mo->floorrover = tmfloorrover;
+		mo->ceilingrover = tmceilingrover;
 	}
 }
 
@@ -1148,7 +1144,7 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 //
 // Moves a polyobject on the x-y plane.
 //
-static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y)
+static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs)
 {
 	size_t i;
 	vertex_t vec;
@@ -1169,9 +1165,12 @@ static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y)
 	for (i = 0; i < po->numLines; ++i)
 		Polyobj_bboxAdd(po->lines[i]->bbox, &vec);
 
-	// check for blocking things (yes, it needs to be done separately)
-	for (i = 0; i < po->numLines; ++i)
-		hitflags |= Polyobj_clipThings(po, po->lines[i]);
+	if (checkmobjs)
+	{
+		// check for blocking things (yes, it needs to be done separately)
+		for (i = 0; i < po->numLines; ++i)
+			hitflags |= Polyobj_clipThings(po, po->lines[i]);
+	}
 
 	if (hitflags & 2)
 	{
@@ -1189,7 +1188,8 @@ static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y)
 		po->spawnSpot.x += vec.x;
 		po->spawnSpot.y += vec.y;
 
-		Polyobj_carryThings(po, x, y);
+		if (checkmobjs)
+			Polyobj_carryThings(po, x, y);
 		Polyobj_removeFromBlockmap(po); // unlink it from the blockmap
 		Polyobj_removeFromSubsec(po);   // unlink it from its subsector
 		Polyobj_linkToBlockmap(po);     // relink to blockmap
@@ -1356,7 +1356,7 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta,
 //
 // Rotates a polyobject around its start point.
 //
-static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings)
+static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
 {
 	size_t i;
 	angle_t angle;
@@ -1388,11 +1388,14 @@ static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings)
 	for (i = 0; i < po->numLines; ++i)
 		Polyobj_rotateLine(po->lines[i]);
 
-	// check for blocking things
-	for (i = 0; i < po->numLines; ++i)
-		hitflags |= Polyobj_clipThings(po, po->lines[i]);
+	if (checkmobjs)
+	{
+		// check for blocking things
+		for (i = 0; i < po->numLines; ++i)
+			hitflags |= Polyobj_clipThings(po, po->lines[i]);
 
-	Polyobj_rotateThings(po, origin, delta, turnthings);
+		Polyobj_rotateThings(po, origin, delta, turnthings);
+	}
 
 	if (hitflags & 2)
 	{
@@ -1492,6 +1495,7 @@ void Polyobj_InitLevel(void)
 	mqueue_t    anchorqueue;
 	mobjqitem_t *qitem;
 	INT32 i, numAnchors = 0;
+	mobj_t *mo;
 
 	M_QueueInit(&spawnqueue);
 	M_QueueInit(&anchorqueue);
@@ -1505,31 +1509,31 @@ void Polyobj_InitLevel(void)
 
 	// run down the thinker list, count the number of spawn points, and save
 	// the mobj_t pointers on a queue for use below.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj_t *mo = (mobj_t *)th;
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
-			if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM ||
-				mo->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
-			{
-				++numPolyObjects;
+		mo = (mobj_t *)th;
 
-				qitem = malloc(sizeof(mobjqitem_t));
-				memset(qitem, 0, sizeof(mobjqitem_t));
-				qitem->mo = mo;
-				M_QueueInsert(&(qitem->mqitem), &spawnqueue);
-			}
-			else if (mo->info->doomednum == POLYOBJ_ANCHOR_DOOMEDNUM)
-			{
-				++numAnchors;
+		if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM ||
+			mo->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
+		{
+			++numPolyObjects;
 
-				qitem = malloc(sizeof(mobjqitem_t));
-				memset(qitem, 0, sizeof(mobjqitem_t));
-				qitem->mo = mo;
-				M_QueueInsert(&(qitem->mqitem), &anchorqueue);
-			}
+			qitem = malloc(sizeof(mobjqitem_t));
+			memset(qitem, 0, sizeof(mobjqitem_t));
+			qitem->mo = mo;
+			M_QueueInsert(&(qitem->mqitem), &spawnqueue);
+		}
+		else if (mo->info->doomednum == POLYOBJ_ANCHOR_DOOMEDNUM)
+		{
+			++numAnchors;
+
+			qitem = malloc(sizeof(mobjqitem_t));
+			memset(qitem, 0, sizeof(mobjqitem_t));
+			qitem->mo = mo;
+			M_QueueInsert(&(qitem->mqitem), &anchorqueue);
 		}
 	}
 
@@ -1608,19 +1612,23 @@ void Polyobj_InitLevel(void)
 // Called when a savegame is being loaded. Rotates and translates an
 // existing polyobject to its position when the game was saved.
 //
+// Monster Iestyn 05/04/19: Please do not interact with mobjs! You
+// can cause I_Error crashes that way, and all the important mobjs are
+// going to be deleted afterwards anyway.
+//
 void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y)
 {
 	fixed_t dx, dy;
 
 	// first, rotate to the saved angle
-	Polyobj_rotate(po, angle, false);
+	Polyobj_rotate(po, angle, false, false);
 
 	// determine component distances to translate
 	dx = x - po->spawnSpot.x;
 	dy = y - po->spawnSpot.y;
 
 	// translate
-	Polyobj_moveXY(po, dx, dy);
+	Polyobj_moveXY(po, dx, dy, false);
 }
 
 // Thinker Functions
@@ -1640,7 +1648,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotate: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1660,7 +1668,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed, th->turnobjs) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, th->turnobjs, true) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -1725,7 +1733,7 @@ void T_PolyObjMove(polymove_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjMove: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1744,7 +1752,7 @@ void T_PolyObjMove(polymove_t *th)
 	}
 
 	// move the polyobject one step along its movement angle
-	if (Polyobj_moveXY(po, th->momx, th->momy))
+	if (Polyobj_moveXY(po, th->momx, th->momy, true))
 	{
 		INT32 avel = abs(th->speed);
 
@@ -1798,7 +1806,7 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjWaypoint: thinker with invalid id %d removed.", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1809,9 +1817,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 
 	// Find out target first.
 	// We redo this each tic to make savegame compatibility easier.
-	for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -1858,12 +1866,16 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 		fixed_t diffz;
 		amtx = (target->x - th->diffx) - po->centerPt.x;
 		amty = (target->y - th->diffy) - po->centerPt.y;
-		Polyobj_moveXY(po, amtx, amty);
+		Polyobj_moveXY(po, amtx, amty, true);
 		// TODO: use T_MovePlane
 		amtz = (po->lines[0]->backsector->ceilingheight - po->lines[0]->backsector->floorheight)/2;
 		diffz = po->lines[0]->backsector->floorheight - (target->z - amtz);
 		po->lines[0]->backsector->floorheight = target->z - amtz;
 		po->lines[0]->backsector->ceilingheight = target->z + amtz;
+		// Sal: Remember to check your sectors!
+		// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+		//  updating objects in the front one too just added teleporting to ground bugs
+		P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 		// Apply action to mirroring polyobjects as well
 		start = 0;
 		while ((po = Polyobj_GetChild(oldpo, &start)))
@@ -1871,10 +1883,14 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 			if (po->isBad)
 				continue;
 
-			Polyobj_moveXY(po, amtx, amty);
+			Polyobj_moveXY(po, amtx, amty, true);
 			// TODO: use T_MovePlane
 			po->lines[0]->backsector->floorheight += diffz; // move up/down by same amount as the parent did
 			po->lines[0]->backsector->ceilingheight += diffz;
+			// Sal: Remember to check your sectors!
+			// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+			//  updating objects in the front one too just added teleporting to ground bugs
+			P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 		}
 
 		po = oldpo;
@@ -1884,9 +1900,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 			CONS_Debug(DBG_POLYOBJ, "Looking for next waypoint...\n");
 
 			// Find next waypoint
-			for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+			for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 			{
-				if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)wp;
@@ -1894,23 +1910,23 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 				if (mo2->type != MT_TUBEWAYPOINT)
 					continue;
 
-				if (mo2->threshold == th->sequence)
+				if (mo2->threshold != th->sequence)
+					continue;
+
+				if (th->direction == -1)
 				{
-					if (th->direction == -1)
+					if (mo2->health == target->health - 1)
 					{
-						if (mo2->health == target->health - 1)
-						{
-							waypoint = mo2;
-							break;
-						}
+						waypoint = mo2;
+						break;
 					}
-					else
+				}
+				else
+				{
+					if (mo2->health == target->health + 1)
 					{
-						if (mo2->health == target->health + 1)
-						{
-							waypoint = mo2;
-							break;
-						}
+						waypoint = mo2;
+						break;
 					}
 				}
 			}
@@ -1923,9 +1939,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					th->stophere = true;
 				}
 
-				for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+				for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 				{
-					if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+					if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)wp;
@@ -1933,22 +1949,22 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					if (mo2->type != MT_TUBEWAYPOINT)
 						continue;
 
-					if (mo2->threshold == th->sequence)
+					if (mo2->threshold != th->sequence)
+						continue;
+
+					if (th->direction == -1)
 					{
-						if (th->direction == -1)
-						{
-							if (waypoint == NULL)
-								waypoint = mo2;
-							else if (mo2->health > waypoint->health)
-								waypoint = mo2;
-						}
-						else
+						if (waypoint == NULL)
+							waypoint = mo2;
+						else if (mo2->health > waypoint->health)
+							waypoint = mo2;
+					}
+					else
+					{
+						if (mo2->health == 0)
 						{
-							if (mo2->health == 0)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
 					}
 				}
@@ -1960,9 +1976,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 				if (!th->continuous)
 					th->comeback = false;
 
-				for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+				for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 				{
-					if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+					if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)wp;
@@ -1970,23 +1986,23 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					if (mo2->type != MT_TUBEWAYPOINT)
 						continue;
 
-					if (mo2->threshold == th->sequence)
+					if (mo2->threshold != th->sequence)
+						continue;
+
+					if (th->direction == -1)
 					{
-						if (th->direction == -1)
+						if (mo2->health == target->health - 1)
 						{
-							if (mo2->health == target->health - 1)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
-						else
+					}
+					else
+					{
+						if (mo2->health == target->health + 1)
 						{
-							if (mo2->health == target->health + 1)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
 					}
 				}
@@ -2031,10 +2047,14 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 	}
 
 	// Move the polyobject
-	Polyobj_moveXY(po, momx, momy);
+	Polyobj_moveXY(po, momx, momy, true);
 	// TODO: use T_MovePlane
 	po->lines[0]->backsector->floorheight += momz;
 	po->lines[0]->backsector->ceilingheight += momz;
+	// Sal: Remember to check your sectors!
+	// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+	//  updating objects in the front one too just added teleporting to ground bugs
+	P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 
 	// Apply action to mirroring polyobjects as well
 	start = 0;
@@ -2043,10 +2063,14 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 		if (po->isBad)
 			continue;
 
-		Polyobj_moveXY(po, momx, momy);
+		Polyobj_moveXY(po, momx, momy, true);
 		// TODO: use T_MovePlane
 		po->lines[0]->backsector->floorheight += momz;
 		po->lines[0]->backsector->ceilingheight += momz;
+		// Sal: Remember to check your sectors!
+		// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+		//  updating objects in the front one too just added teleporting to ground bugs
+		P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 	}
 }
 
@@ -2060,7 +2084,7 @@ void T_PolyDoorSlide(polyslidedoor_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSlide: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2089,7 +2113,7 @@ void T_PolyDoorSlide(polyslidedoor_t *th)
 	}
 
 	// move the polyobject one step along its movement angle
-	if (Polyobj_moveXY(po, th->momx, th->momy))
+	if (Polyobj_moveXY(po, th->momx, th->momy, true))
 	{
 		INT32 avel = abs(th->speed);
 
@@ -2165,7 +2189,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSwing: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2195,7 +2219,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed, false) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, false, true) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -2251,7 +2275,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 	}
 }
 
-// T_PolyObjDisplace: shift a polyobject based on a control sector's heights. -Red
+// T_PolyObjDisplace: shift a polyobject based on a control sector's heights.
 void T_PolyObjDisplace(polydisplace_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -2260,11 +2284,11 @@ void T_PolyObjDisplace(polydisplace_t *th)
 
 	if (!po)
 #ifdef RANGECHECK
-		I_Error("T_PolyDoorSwing: thinker has invalid id %d\n", th->polyObjNum);
+		I_Error("T_PolyObjDisplace: thinker has invalid id %d\n", th->polyObjNum);
 #else
 	{
-		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSwing: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		CONS_Debug(DBG_POLYOBJ, "T_PolyObjDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2287,7 +2311,46 @@ void T_PolyObjDisplace(polydisplace_t *th)
 	dx = FixedMul(th->dx, delta);
 	dy = FixedMul(th->dy, delta);
 
-	if (Polyobj_moveXY(po, dx, dy))
+	if (Polyobj_moveXY(po, dx, dy, true))
+		th->oldHeights = newheights;
+}
+
+// T_PolyObjRotDisplace: rotate a polyobject based on a control sector's heights.
+void T_PolyObjRotDisplace(polyrotdisplace_t *th)
+{
+	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
+	fixed_t newheights, delta;
+	fixed_t rotangle;
+
+	if (!po)
+#ifdef RANGECHECK
+		I_Error("T_PolyObjRotDisplace: thinker has invalid id %d\n", th->polyObjNum);
+#else
+	{
+		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
+		P_RemoveThinker(&th->thinker);
+		return;
+	}
+#endif
+
+	// check for displacement due to override and reattach when possible
+	if (po->thinker == NULL)
+	{
+		po->thinker = &th->thinker;
+
+		// reset polyobject's thrust
+		po->thrust = FRACUNIT;
+	}
+
+	newheights = th->controlSector->floorheight+th->controlSector->ceilingheight;
+	delta = newheights-th->oldHeights;
+
+	if (!delta)
+		return;
+
+	rotangle = FixedMul(th->rotscale, delta);
+
+	if (Polyobj_rotate(po, FixedAngle(rotangle), th->turnobjs, true))
 		th->oldHeights = newheights;
 }
 
@@ -2322,7 +2385,7 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotate;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2387,7 +2450,7 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjMove;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2448,7 +2511,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjWaypoint;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2466,9 +2529,9 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	th->stophere = false;
 
 	// Find the first waypoint we need to use
-	for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -2476,31 +2539,31 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		if (mo2->type != MT_TUBEWAYPOINT)
 			continue;
 
-		if (mo2->threshold == th->sequence)
+		if (mo2->threshold != th->sequence)
+			continue;
+
+		if (th->direction == -1) // highest waypoint #
 		{
-			if (th->direction == -1) // highest waypoint #
+			if (mo2->health == 0)
+				last = mo2;
+			else
 			{
-				if (mo2->health == 0)
-					last = mo2;
-				else
-				{
-					if (first == NULL)
-						first = mo2;
-					else if (mo2->health > first->health)
-						first = mo2;
-				}
+				if (first == NULL)
+					first = mo2;
+				else if (mo2->health > first->health)
+					first = mo2;
 			}
-			else // waypoint 0
+		}
+		else // waypoint 0
+		{
+			if (mo2->health == 0)
+				first = mo2;
+			else
 			{
-				if (mo2->health == 0)
-					first = mo2;
-				else
-				{
-					if (last == NULL)
-						last = mo2;
-					else if (mo2->health > last->health)
-						last = mo2;
-				}
+				if (last == NULL)
+					last = mo2;
+				else if (mo2->health > last->health)
+					last = mo2;
 			}
 		}
 	}
@@ -2537,9 +2600,9 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 
 	// Find the actual target movement waypoint
 	target = first;
-	/*for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	/*for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -2547,23 +2610,23 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		if (mo2->type != MT_TUBEWAYPOINT)
 			continue;
 
-		if (mo2->threshold == th->sequence)
+		if (mo2->threshold != th->sequence)
+			continue;
+
+		if (th->direction == -1) // highest waypoint #
 		{
-			if (th->direction == -1) // highest waypoint #
+			if (mo2->health == first->health - 1)
 			{
-				if (mo2->health == first->health - 1)
-				{
-					target = mo2;
-					break;
-				}
+				target = mo2;
+				break;
 			}
-			else // waypoint 0
+		}
+		else // waypoint 0
+		{
+			if (mo2->health == first->health + 1)
 			{
-				if (mo2->health == first->health + 1)
-				{
-					target = mo2;
-					break;
-				}
+				target = mo2;
+				break;
 			}
 		}
 	}*/
@@ -2594,7 +2657,7 @@ static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new slide door thinker
 	th = Z_Malloc(sizeof(polyslidedoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSlide;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2642,7 +2705,7 @@ static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new swing door thinker
 	th = Z_Malloc(sizeof(polyswingdoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSwing;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2724,7 +2787,7 @@ INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjDisplace;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2750,6 +2813,52 @@ INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 	return 1;
 }
 
+INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
+{
+	polyobj_t *po;
+	polyobj_t *oldpo;
+	polyrotdisplace_t *th;
+	INT32 start;
+
+	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
+	{
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
+		return 0;
+	}
+
+	// don't allow line actions to affect bad polyobjects
+	if (po->isBad)
+		return 0;
+
+	// create a new thinker
+	th = Z_Malloc(sizeof(polyrotdisplace_t), PU_LEVSPEC, NULL);
+	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotDisplace;
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
+	po->thinker = &th->thinker;
+
+	// set fields
+	th->polyObjNum = prdata->polyObjNum;
+
+	th->controlSector = prdata->controlSector;
+	th->oldHeights = th->controlSector->floorheight+th->controlSector->ceilingheight;
+
+	th->rotscale = prdata->rotscale;
+	th->turnobjs = prdata->turnobjs;
+
+	oldpo = po;
+
+	// apply action to mirroring polyobjects as well
+	start = 0;
+	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		prdata->polyObjNum = po->id; // change id to match child polyobject's
+		EV_DoPolyObjRotDisplace(prdata);
+	}
+
+	// action was successful
+	return 1;
+}
+
 void T_PolyObjFlag(polymove_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -2761,7 +2870,7 @@ void T_PolyObjFlag(polymove_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFlag: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2825,7 +2934,7 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFlag;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2853,6 +2962,165 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	return 1;
 }
 
+void T_PolyObjFade(polyfade_t *th)
+{
+	boolean stillfading = false;
+	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
+
+	if (!po)
+#ifdef RANGECHECK
+		I_Error("T_PolyObjFade: thinker has invalid id %d\n", th->polyObjNum);
+#else
+	{
+		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFade: thinker with invalid id %d removed.\n", th->polyObjNum);
+		P_RemoveThinker(&th->thinker);
+		return;
+	}
+#endif
+
+	// check for displacement due to override and reattach when possible
+	if (po->thinker == NULL)
+		po->thinker = &th->thinker;
+
+	stillfading = th->ticbased ? !(--(th->timer) <= 0)
+		: !((th->timer -= th->duration) <= 0);
+
+	if (th->timer <= 0)
+	{
+		po->translucency = max(min(th->destvalue, NUMTRANSMAPS), 0);
+
+		// remove thinker
+		if (po->thinker == &th->thinker)
+			po->thinker = NULL;
+		P_RemoveThinker(&th->thinker);
+	}
+	else
+	{
+		INT16 delta = abs(th->destvalue - th->sourcevalue);
+		INT32 duration = th->ticbased ? th->duration
+			: abs(FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->destvalue)
+				- FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->sourcevalue)); // speed-based internal counter duration: delta in 256 scale
+		fixed_t factor = min(FixedDiv(duration - th->timer, duration), 1*FRACUNIT);
+		if (th->destvalue < th->sourcevalue)
+			po->translucency = max(min(po->translucency, th->sourcevalue - (INT16)FixedMul(delta, factor)), th->destvalue);
+		else if (th->destvalue > th->sourcevalue)
+			po->translucency = min(max(po->translucency, th->sourcevalue + (INT16)FixedMul(delta, factor)), th->destvalue);
+	}
+
+	if (!stillfading)
+	{
+		// set render flags
+		if (po->translucency >= NUMTRANSMAPS) // invisible
+			po->flags &= ~POF_RENDERALL;
+		else
+			po->flags |= (po->spawnflags & POF_RENDERALL);
+
+		// set collision
+		if (th->docollision)
+		{
+			if (th->destvalue > th->sourcevalue) // faded out
+			{
+				po->flags &= ~POF_SOLID;
+				po->flags |= POF_NOSPECIALS;
+			}
+			else
+			{
+				po->flags |= (po->spawnflags & POF_SOLID);
+				if (!(po->spawnflags & POF_NOSPECIALS))
+					po->flags &= ~POF_NOSPECIALS;
+			}
+		}
+	}
+	else
+	{
+		if (po->translucency >= NUMTRANSMAPS)
+			// HACK: OpenGL renders fully opaque when >= NUMTRANSMAPS
+			po->translucency = NUMTRANSMAPS-1;
+
+		po->flags |= (po->spawnflags & POF_RENDERALL);
+
+		// set collision
+		if (th->docollision)
+		{
+			if (th->doghostfade)
+			{
+				po->flags &= ~POF_SOLID;
+				po->flags |= POF_NOSPECIALS;
+			}
+			else
+			{
+				po->flags |= (po->spawnflags & POF_SOLID);
+				if (!(po->spawnflags & POF_NOSPECIALS))
+					po->flags &= ~POF_NOSPECIALS;
+			}
+		}
+	}
+}
+
+INT32 EV_DoPolyObjFade(polyfadedata_t *pfdata)
+{
+	polyobj_t *po;
+	polyobj_t *oldpo;
+	polyfade_t *th;
+	INT32 start;
+
+	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
+	{
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjFade: bad polyobj %d\n", pfdata->polyObjNum);
+		return 0;
+	}
+
+	// don't allow line actions to affect bad polyobjects
+	if (po->isBad)
+		return 0;
+
+	// already equal, nothing to do
+	if (po->translucency == pfdata->destvalue)
+		return 1;
+
+	if (po->thinker && po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
+		P_RemoveThinker(po->thinker);
+
+	// create a new thinker
+	th = Z_Malloc(sizeof(polyfade_t), PU_LEVSPEC, NULL);
+	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFade;
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
+	po->thinker = &th->thinker;
+
+	// set fields
+	th->polyObjNum = pfdata->polyObjNum;
+	th->sourcevalue = po->translucency;
+	th->destvalue = pfdata->destvalue;
+	th->docollision = pfdata->docollision;
+	th->doghostfade = pfdata->doghostfade;
+
+	if (pfdata->ticbased)
+	{
+		th->ticbased = true;
+		th->timer = th->duration = abs(pfdata->speed); // pfdata->speed is duration
+	}
+	else
+	{
+		th->ticbased = false;
+		th->timer = abs(FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->destvalue)
+			- FixedMul(FixedDiv(256, NUMTRANSMAPS), NUMTRANSMAPS - th->sourcevalue)); // delta converted to 256 scale, use as internal counter
+		th->duration = abs(pfdata->speed); // use th->duration as speed decrement
+	}
+
+	oldpo = po;
+
+	// apply action to mirroring polyobjects as well
+	start = 0;
+	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		pfdata->polyObjNum = po->id;
+		EV_DoPolyObjFade(pfdata);
+	}
+
+	// action was successful
+	return 1;
+}
+
 #endif // ifdef POLYOBJECTS
 
 // EOF
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index c9838a9224bb42aea8d64b473e0fa9781eaf3368..73a11d237fcaf611005c95154b3be6e671d728c8 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by James Haley
-// Copyright (C) 2006-2016 by Sonic Team Junior.
+// Copyright (C) 2006-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -207,6 +207,31 @@ typedef struct polydisplace_s
 	fixed_t oldHeights;
 } polydisplace_t;
 
+typedef struct polyrotdisplace_s
+{
+	thinker_t thinker; // must be first
+
+	INT32 polyObjNum;
+	struct sector_s *controlSector;
+	fixed_t rotscale;
+	UINT8 turnobjs;
+	fixed_t oldHeights;
+} polyrotdisplace_t;
+
+typedef struct polyfade_s
+{
+	thinker_t thinker; // must be first
+
+	INT32 polyObjNum;
+	INT32 sourcevalue;
+	INT32 destvalue;
+	boolean docollision;
+	boolean doghostfade;
+	boolean ticbased;
+	INT32 duration;
+	INT32 timer;
+} polyfade_t;
+
 //
 // Line Activation Data Structures
 //
@@ -266,6 +291,24 @@ typedef struct polydisplacedata_s
 	fixed_t dy;
 } polydisplacedata_t;
 
+typedef struct polyrotdisplacedata_s
+{
+	INT32 polyObjNum;
+	struct sector_s *controlSector;
+	fixed_t rotscale;
+	UINT8 turnobjs;
+} polyrotdisplacedata_t;
+
+typedef struct polyfadedata_s
+{
+	INT32 polyObjNum;
+	INT32 destvalue;
+	boolean docollision;
+	boolean doghostfade;
+	boolean ticbased;
+	INT32 speed;
+} polyfadedata_t;
+
 //
 // Functions
 //
@@ -286,14 +329,18 @@ void T_PolyObjWaypoint (polywaypoint_t *);
 void T_PolyDoorSlide(polyslidedoor_t *);
 void T_PolyDoorSwing(polyswingdoor_t *);
 void T_PolyObjDisplace  (polydisplace_t *);
+void T_PolyObjRotDisplace  (polyrotdisplace_t *);
 void T_PolyObjFlag  (polymove_t *);
+void T_PolyObjFade  (polyfade_t *);
 
 INT32 EV_DoPolyDoor(polydoordata_t *);
 INT32 EV_DoPolyObjMove(polymovedata_t *);
 INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *);
 INT32 EV_DoPolyObjRotate(polyrotdata_t *);
 INT32 EV_DoPolyObjDisplace(polydisplacedata_t *);
+INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *);
 INT32 EV_DoPolyObjFlag(struct line_s *);
+INT32 EV_DoPolyObjFade(polyfadedata_t *);
 
 
 //
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 0734b78eca2c8500a013a71240aa29823fc50168..c9c66b96133f3d86bf5bce442b3a6ee6657c2d5a 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_saveg.c b/src/p_saveg.c
index d53869d2eebcb0b6d98d34cb7352c556e5303332..ea998b445e40c90135d0454ce412367373fc42d8 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -21,6 +21,7 @@
 #include "p_local.h"
 #include "p_setup.h"
 #include "p_saveg.h"
+#include "r_data.h"
 #include "r_things.h"
 #include "r_state.h"
 #include "w_wad.h"
@@ -57,6 +58,7 @@ typedef enum
 	FIRSTAXIS  = 0x10,
 	SECONDAXIS = 0x20,
 	FOLLOW     = 0x40,
+	DRONE      = 0x80,
 } player_saveflags;
 
 //
@@ -116,7 +118,8 @@ static void P_NetArchivePlayers(void)
 		WRITEANGLE(save_p, players[i].drawangle);
 		WRITEANGLE(save_p, players[i].awayviewaiming);
 		WRITEINT32(save_p, players[i].awayviewtics);
-		WRITEINT32(save_p, players[i].rings);
+		WRITEINT16(save_p, players[i].rings);
+		WRITEINT16(save_p, players[i].spheres);
 
 		WRITESINT8(save_p, players[i].pity);
 		WRITEINT32(save_p, players[i].currentweapon);
@@ -196,15 +199,25 @@ static void P_NetArchivePlayers(void)
 		WRITEINT32(save_p, players[i].drillmeter);
 		WRITEUINT8(save_p, players[i].drilldelay);
 		WRITEUINT8(save_p, players[i].bonustime);
+		WRITEFIXED(save_p, players[i].oldscale);
 		WRITEUINT8(save_p, players[i].mare);
-
+		WRITEUINT8(save_p, players[i].marelap);
+		WRITEUINT8(save_p, players[i].marebonuslap);
 		WRITEUINT32(save_p, players[i].marebegunat);
 		WRITEUINT32(save_p, players[i].startedtime);
 		WRITEUINT32(save_p, players[i].finishedtime);
+		WRITEUINT32(save_p, players[i].lapbegunat);
+		WRITEUINT32(save_p, players[i].lapstartedtime);
+		WRITEINT16(save_p, players[i].finishedspheres);
 		WRITEINT16(save_p, players[i].finishedrings);
 		WRITEUINT32(save_p, players[i].marescore);
 		WRITEUINT32(save_p, players[i].lastmarescore);
+		WRITEUINT32(save_p, players[i].totalmarescore);
 		WRITEUINT8(save_p, players[i].lastmare);
+		WRITEUINT8(save_p, players[i].lastmarelap);
+		WRITEUINT8(save_p, players[i].lastmarebonuslap);
+		WRITEUINT8(save_p, players[i].totalmarelap);
+		WRITEUINT8(save_p, players[i].totalmarebonuslap);
 		WRITEINT32(save_p, players[i].maxlink);
 		WRITEUINT8(save_p, players[i].texttimer);
 		WRITEUINT8(save_p, players[i].textvar);
@@ -224,6 +237,9 @@ static void P_NetArchivePlayers(void)
 		if (players[i].followmobj)
 			flags |= FOLLOW;
 
+		if (players[i].drone)
+			flags |= DRONE;
+
 		WRITEINT16(save_p, players[i].lastsidehit);
 		WRITEINT16(save_p, players[i].lastlinehit);
 
@@ -252,6 +268,9 @@ static void P_NetArchivePlayers(void)
 		if (flags & FOLLOW)
 			WRITEUINT32(save_p, players[i].followmobj->mobjnum);
 
+		if (flags & DRONE)
+			WRITEUINT32(save_p, players[i].drone->mobjnum);
+
 		WRITEFIXED(save_p, players[i].camerascale);
 		WRITEFIXED(save_p, players[i].shieldscale);
 
@@ -303,7 +322,8 @@ static void P_NetUnArchivePlayers(void)
 		players[i].drawangle = READANGLE(save_p);
 		players[i].awayviewaiming = READANGLE(save_p);
 		players[i].awayviewtics = READINT32(save_p);
-		players[i].rings = READINT32(save_p);
+		players[i].rings = READINT16(save_p);
+		players[i].spheres = READINT16(save_p);
 
 		players[i].pity = READSINT8(save_p);
 		players[i].currentweapon = READINT32(save_p);
@@ -383,15 +403,25 @@ static void P_NetUnArchivePlayers(void)
 		players[i].drillmeter = READINT32(save_p);
 		players[i].drilldelay = READUINT8(save_p);
 		players[i].bonustime = (boolean)READUINT8(save_p);
+		players[i].oldscale = READFIXED(save_p);
 		players[i].mare = READUINT8(save_p);
-
+		players[i].marelap = READUINT8(save_p);
+		players[i].marebonuslap = READUINT8(save_p);
 		players[i].marebegunat = READUINT32(save_p);
 		players[i].startedtime = READUINT32(save_p);
 		players[i].finishedtime = READUINT32(save_p);
+		players[i].lapbegunat = READUINT32(save_p);
+		players[i].lapstartedtime = READUINT32(save_p);
+		players[i].finishedspheres = READINT16(save_p);
 		players[i].finishedrings = READINT16(save_p);
 		players[i].marescore = READUINT32(save_p);
 		players[i].lastmarescore = READUINT32(save_p);
+		players[i].totalmarescore = READUINT32(save_p);
 		players[i].lastmare = READUINT8(save_p);
+		players[i].lastmarelap = READUINT8(save_p);
+		players[i].lastmarebonuslap = READUINT8(save_p);
+		players[i].totalmarelap = READUINT8(save_p);
+		players[i].totalmarebonuslap = READUINT8(save_p);
 		players[i].maxlink = READINT32(save_p);
 		players[i].texttimer = READUINT8(save_p);
 		players[i].textvar = READUINT8(save_p);
@@ -424,6 +454,9 @@ static void P_NetUnArchivePlayers(void)
 		if (flags & FOLLOW)
 			players[i].followmobj = (mobj_t *)(size_t)READUINT32(save_p);
 
+		if (flags & DRONE)
+			players[i].drone = (mobj_t *)(size_t)READUINT32(save_p);
+
 		players[i].camerascale = READFIXED(save_p);
 		players[i].shieldscale = READFIXED(save_p);
 
@@ -451,6 +484,243 @@ static void P_NetUnArchivePlayers(void)
 	}
 }
 
+///
+/// Colormaps
+///
+
+static extracolormap_t *net_colormaps = NULL;
+static UINT32 num_net_colormaps = 0;
+static UINT32 num_ffloors = 0; // for loading
+
+// Copypasta from r_data.c AddColormapToList
+// But also check for equality and return the matching index
+static UINT32 CheckAddNetColormapToList(extracolormap_t *extra_colormap)
+{
+	extracolormap_t *exc, *exc_prev = NULL;
+	UINT32 i = 0;
+
+	if (!net_colormaps)
+	{
+		net_colormaps = R_CopyColormap(extra_colormap, false);
+		net_colormaps->next = 0;
+		net_colormaps->prev = 0;
+		num_net_colormaps = i+1;
+		return i;
+	}
+
+	for (exc = net_colormaps; exc; exc_prev = exc, exc = exc->next)
+	{
+		if (R_CheckEqualColormaps(exc, extra_colormap, true, true, true))
+			return i;
+		i++;
+	}
+
+	exc_prev->next = R_CopyColormap(extra_colormap, false);
+	extra_colormap->prev = exc_prev;
+	extra_colormap->next = 0;
+
+	num_net_colormaps = i+1;
+	return i;
+}
+
+static extracolormap_t *GetNetColormapFromList(UINT32 index)
+{
+	// For loading, we have to be tricky:
+	// We load the sectors BEFORE knowing the colormap values
+	// So if an index doesn't exist, fill our list with dummy colormaps
+	// until we get the index we want
+	// Then when we load the color data, we set up the dummy colormaps
+
+	extracolormap_t *exc, *last_exc = NULL;
+	UINT32 i = 0;
+
+	if (!net_colormaps) // initialize our list
+		net_colormaps = R_CreateDefaultColormap(false);
+
+	for (exc = net_colormaps; exc; last_exc = exc, exc = exc->next)
+	{
+		if (i++ == index)
+			return exc;
+	}
+
+
+	// LET'S HOPE that index is a sane value, because we create up to [index]
+	// entries in net_colormaps. At this point, we don't know
+	// what the total colormap count is
+	if (index >= numsectors*3 + num_ffloors)
+		// if every sector had a unique colormap change AND a fade color thinker which has two colormap entries
+		// AND every ffloor had a fade FOF thinker with one colormap entry
+		I_Error("Colormap %d from server is too high for sectors %d", index, (UINT32)numsectors);
+
+	// our index doesn't exist, so just make the entry
+	for (; i <= index; i++)
+	{
+		exc = R_CreateDefaultColormap(false);
+		if (last_exc)
+			last_exc->next = exc;
+		exc->prev = last_exc;
+		exc->next = NULL;
+		last_exc = exc;
+	}
+	return exc;
+}
+
+static void ClearNetColormaps(void)
+{
+	// We're actually Z_Freeing each entry here,
+	// so don't call this in P_NetUnArchiveColormaps (where entries will be used in-game)
+	extracolormap_t *exc, *exc_next;
+
+	for (exc = net_colormaps; exc; exc = exc_next)
+	{
+		exc_next = exc->next;
+		Z_Free(exc);
+	}
+	num_net_colormaps = 0;
+	num_ffloors = 0;
+	net_colormaps = NULL;
+}
+
+static void P_NetArchiveColormaps(void)
+{
+	// We save and then we clean up our colormap mess
+	extracolormap_t *exc, *exc_next;
+	UINT32 i = 0;
+	WRITEUINT32(save_p, num_net_colormaps); // save for safety
+
+	for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next)
+	{
+		// We must save num_net_colormaps worth of data
+		// So fill non-existent entries with default.
+		if (!exc)
+			exc = R_CreateDefaultColormap(false);
+
+		WRITEUINT8(save_p, exc->fadestart);
+		WRITEUINT8(save_p, exc->fadeend);
+		WRITEUINT8(save_p, exc->fog);
+
+		WRITEINT32(save_p, exc->rgba);
+		WRITEINT32(save_p, exc->fadergba);
+
+#ifdef EXTRACOLORMAPLUMPS
+		WRITESTRINGN(save_p, exc->lumpname, 9);
+#endif
+
+		exc_next = exc->next;
+		Z_Free(exc); // don't need anymore
+	}
+
+	num_net_colormaps = 0;
+	num_ffloors = 0;
+	net_colormaps = NULL;
+}
+
+static void P_NetUnArchiveColormaps(void)
+{
+	// When we reach this point, we already populated our list with
+	// dummy colormaps. Now that we are loading the color data,
+	// set up the dummies.
+	extracolormap_t *exc, *existing_exc, *exc_next = NULL;
+	UINT32 i = 0;
+
+	num_net_colormaps = READUINT32(save_p);
+
+	for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next)
+	{
+		UINT8 fadestart, fadeend, fog;
+		INT32 rgba, fadergba;
+#ifdef EXTRACOLORMAPLUMPS
+		char lumpname[9];
+#endif
+
+		fadestart = READUINT8(save_p);
+		fadeend = READUINT8(save_p);
+		fog = READUINT8(save_p);
+
+		rgba = READINT32(save_p);
+		fadergba = READINT32(save_p);
+
+#ifdef EXTRACOLORMAPLUMPS
+		READSTRINGN(save_p, lumpname, 9);
+
+		if (lumpname[0])
+		{
+			if (!exc)
+				// no point making a new entry since nothing points to it,
+				// but we needed to read the data so now continue
+				continue;
+
+			exc_next = exc->next; // this gets overwritten during our operations here, so get it now
+			existing_exc = R_ColormapForName(lumpname);
+			*exc = *existing_exc;
+			R_AddColormapToList(exc); // see HACK note below on why we're adding duplicates
+			continue;
+		}
+#endif
+
+		if (!exc)
+			// no point making a new entry since nothing points to it,
+			// but we needed to read the data so now continue
+			continue;
+
+		exc_next = exc->next; // this gets overwritten during our operations here, so get it now
+
+		exc->fadestart = fadestart;
+		exc->fadeend = fadeend;
+		exc->fog = fog;
+
+		exc->rgba = rgba;
+		exc->fadergba = fadergba;
+
+#ifdef EXTRACOLORMAPLUMPS
+		exc->lump = LUMPERROR;
+		exc->lumpname[0] = 0;
+#endif
+
+		existing_exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog);
+
+		if (existing_exc)
+			exc->colormap = existing_exc->colormap;
+		else
+			// CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n",
+			// 	R_GetRgbaR(rgba), R_GetRgbaG(rgba), R_GetRgbaB(rgba), R_GetRgbaA(rgba),
+			//	R_GetRgbaR(fadergba), R_GetRgbaG(fadergba), R_GetRgbaB(fadergba), R_GetRgbaA(fadergba));
+			exc->colormap = R_CreateLightTable(exc);
+
+		// HACK: If this dummy is a duplicate, we're going to add it
+		// to the extra_colormaps list anyway. I think this is faster
+		// than going through every loaded sector and correcting their
+		// colormap address to the pre-existing one, PER net_colormap entry
+		R_AddColormapToList(exc);
+
+		if (i < num_net_colormaps-1 && !exc_next)
+			exc_next = R_CreateDefaultColormap(false);
+	}
+
+	// if we still have a valid net_colormap after iterating up to num_net_colormaps,
+	// some sector had a colormap index higher than num_net_colormaps. We done goofed or $$$ was corrupted.
+	// In any case, add them to the colormap list too so that at least the sectors' colormap
+	// addresses are valid and accounted properly
+	if (exc_next)
+	{
+		existing_exc = R_GetDefaultColormap();
+		for (exc = exc_next; exc; exc = exc->next)
+		{
+			exc->colormap = existing_exc->colormap; // all our dummies are default values
+			R_AddColormapToList(exc);
+		}
+	}
+
+	// Don't need these anymore
+	num_net_colormaps = 0;
+	num_ffloors = 0;
+	net_colormaps = NULL;
+}
+
+///
+/// World Archiving
+///
+
 #define SD_FLOORHT  0x01
 #define SD_CEILHT   0x02
 #define SD_FLOORPIC 0x04
@@ -465,10 +735,14 @@ static void P_NetUnArchivePlayers(void)
 #define SD_FYOFFS    0x02
 #define SD_CXOFFS    0x04
 #define SD_CYOFFS    0x08
-#define SD_TAG       0x10
-#define SD_FLOORANG  0x20
-#define SD_CEILANG   0x40
-#define SD_TAGLIST   0x80
+#define SD_FLOORANG  0x10
+#define SD_CEILANG   0x20
+#define SD_TAG       0x40
+#define SD_DIFF3     0x80
+
+// diff3 flags
+#define SD_TAGLIST   0x01
+#define SD_COLORMAP  0x02
 
 #define LD_FLAG     0x01
 #define LD_SPECIAL  0x02
@@ -497,20 +771,41 @@ static void P_NetArchiveWorld(void)
 	UINT8 *put;
 
 	// reload the map just to see difference
-	const mapsector_t *ms;
-	const mapsidedef_t *msd;
-	const maplinedef_t *mld;
+	mapsector_t *ms;
+	mapsidedef_t *msd;
+	maplinedef_t *mld;
 	const sector_t *ss = sectors;
-	UINT8 diff, diff2;
+	UINT8 diff, diff2, diff3;
+
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
 	put = save_p;
 
-	ms = W_CacheLumpNum(lastloadedmaplumpnum+ML_SECTORS, PU_CACHE);
+	if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
+	{ // HACK: Open wad file rather quickly so we can get the data from the relevant lumps
+		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
+		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
+#define retrieve_mapdata(d, f)\
+		d = Z_Malloc((f)->size, PU_CACHE, NULL); \
+		M_Memcpy(d, wadData + (f)->filepos, (f)->size)
+		retrieve_mapdata(ms, fileinfo + ML_SECTORS);
+		retrieve_mapdata(mld, fileinfo + ML_LINEDEFS);
+		retrieve_mapdata(msd, fileinfo + ML_SIDEDEFS);
+#undef retrieve_mapdata
+		Z_Free(wadData); // we're done with this now
+	}
+	else // phew it's just a WAD
+	{
+			ms = W_CacheLumpNum(lastloadedmaplumpnum+ML_SECTORS, PU_CACHE);
+			mld = W_CacheLumpNum(lastloadedmaplumpnum+ML_LINEDEFS, PU_CACHE);
+			msd = W_CacheLumpNum(lastloadedmaplumpnum+ML_SIDEDEFS, PU_CACHE);
+	}
 
 	for (i = 0; i < numsectors; i++, ss++, ms++)
 	{
-		diff = diff2 = 0;
+		diff = diff2 = diff3 = 0;
 		if (ss->floorheight != SHORT(ms->floorheight)<<FRACBITS)
 			diff |= SD_FLOORHT;
 		if (ss->ceilingheight != SHORT(ms->ceilingheight)<<FRACBITS)
@@ -544,7 +839,10 @@ static void P_NetArchiveWorld(void)
 		if (ss->tag != SHORT(ms->tag))
 			diff2 |= SD_TAG;
 		if (ss->nexttag != ss->spawn_nexttag || ss->firsttag != ss->spawn_firsttag)
-			diff2 |= SD_TAGLIST;
+			diff3 |= SD_TAGLIST;
+
+		if (ss->extra_colormap != ss->spawn_extra_colormap)
+			diff3 |= SD_COLORMAP;
 
 		// Check if any of the sector's FOFs differ from how they spawned
 		if (ss->ffloors)
@@ -561,6 +859,9 @@ static void P_NetArchiveWorld(void)
 			}
 		}
 
+		if (diff3)
+			diff2 |= SD_DIFF3;
+
 		if (diff2)
 			diff |= SD_DIFF2;
 
@@ -572,6 +873,8 @@ static void P_NetArchiveWorld(void)
 			WRITEUINT8(put, diff);
 			if (diff & SD_DIFF2)
 				WRITEUINT8(put, diff2);
+			if (diff2 & SD_DIFF3)
+				WRITEUINT8(put, diff3);
 			if (diff & SD_FLOORHT)
 				WRITEFIXED(put, ss->floorheight);
 			if (diff & SD_CEILHT)
@@ -592,18 +895,22 @@ static void P_NetArchiveWorld(void)
 				WRITEFIXED(put, ss->ceiling_xoffs);
 			if (diff2 & SD_CYOFFS)
 				WRITEFIXED(put, ss->ceiling_yoffs);
-			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(put, ss->tag);
 			if (diff2 & SD_FLOORANG)
 				WRITEANGLE(put, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
 				WRITEANGLE(put, ss->ceilingpic_angle);
-			if (diff2 & SD_TAGLIST) // save both firsttag and nexttag
+			if (diff2 & SD_TAG) // save only the tag
+				WRITEINT16(put, ss->tag);
+			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
 			{ // either of these could be changed even if tag isn't
 				WRITEINT32(put, ss->firsttag);
 				WRITEINT32(put, ss->nexttag);
 			}
 
+			if (diff3 & SD_COLORMAP)
+				WRITEUINT32(put, CheckAddNetColormapToList(ss->extra_colormap));
+					// returns existing index if already added, or appends to net_colormaps and returns new index
+
 			// Special case: save the stats of all modified ffloors along with their ffloor "number"s
 			// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
 			if (diff & SD_FFLOORS)
@@ -637,12 +944,10 @@ static void P_NetArchiveWorld(void)
 
 	WRITEUINT16(put, 0xffff);
 
-	mld = W_CacheLumpNum(lastloadedmaplumpnum+ML_LINEDEFS, PU_CACHE);
-	msd = W_CacheLumpNum(lastloadedmaplumpnum+ML_SIDEDEFS, PU_CACHE);
 	// do lines
 	for (i = 0; i < numlines; i++, mld++, li++)
 	{
-		diff = diff2 = 0;
+		diff = diff2 = diff3 = 0;
 
 		if (li->special != SHORT(mld->special))
 			diff |= LD_SPECIAL;
@@ -734,11 +1039,22 @@ static void P_NetUnArchiveWorld(void)
 	line_t *li;
 	side_t *si;
 	UINT8 *get;
-	UINT8 diff, diff2;
+	UINT8 diff, diff2, diff3;
 
 	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
 		I_Error("Bad $$$.sav at archive block World");
 
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+
+	// count the level's ffloors so that colormap loading can have an upper limit
+	for (i = 0; i < numsectors; i++)
+	{
+		ffloor_t *rover;
+		for (rover = sectors[i].ffloors; rover; rover = rover->next)
+			num_ffloors++;
+	}
+
 	get = save_p;
 
 	for (;;)
@@ -756,6 +1072,10 @@ static void P_NetUnArchiveWorld(void)
 			diff2 = READUINT8(get);
 		else
 			diff2 = 0;
+		if (diff2 & SD_DIFF3)
+			diff3 = READUINT8(get);
+		else
+			diff3 = 0;
 
 		if (diff & SD_FLOORHT)
 			sectors[i].floorheight = READFIXED(get);
@@ -784,17 +1104,20 @@ static void P_NetUnArchiveWorld(void)
 			sectors[i].ceiling_xoffs = READFIXED(get);
 		if (diff2 & SD_CYOFFS)
 			sectors[i].ceiling_yoffs = READFIXED(get);
+		if (diff2 & SD_FLOORANG)
+			sectors[i].floorpic_angle  = READANGLE(get);
+		if (diff2 & SD_CEILANG)
+			sectors[i].ceilingpic_angle = READANGLE(get);
 		if (diff2 & SD_TAG)
 			sectors[i].tag = READINT16(get); // DON'T use P_ChangeSectorTag
-		if (diff2 & SD_TAGLIST)
+		if (diff3 & SD_TAGLIST)
 		{
 			sectors[i].firsttag = READINT32(get);
 			sectors[i].nexttag = READINT32(get);
 		}
-		if (diff2 & SD_FLOORANG)
-			sectors[i].floorpic_angle  = READANGLE(get);
-		if (diff2 & SD_CEILANG)
-			sectors[i].ceilingpic_angle = READANGLE(get);
+
+		if (diff3 & SD_COLORMAP)
+			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(get));
 
 		if (diff & SD_FFLOORS)
 		{
@@ -853,6 +1176,9 @@ static void P_NetUnArchiveWorld(void)
 			diff2 = READUINT8(get);
 		else
 			diff2 = 0;
+
+		diff3 = 0;
+
 		if (diff & LD_FLAG)
 			li->flags = READINT16(get);
 		if (diff & LD_SPECIAL)
@@ -934,12 +1260,13 @@ typedef enum
 	MD2_EXTVAL1     = 1<<5,
 	MD2_EXTVAL2     = 1<<6,
 	MD2_HNEXT       = 1<<7,
-#ifdef ESLOPE
 	MD2_HPREV       = 1<<8,
-	MD2_SLOPE       = 1<<9
-#else
-	MD2_HPREV       = 1<<8
+	MD2_FLOORROVER  = 1<<9,
+	MD2_CEILINGROVER = 1<<10,
+#ifdef ESLOPE
+	MD2_SLOPE        = 1<<11,
 #endif
+	MD2_COLORIZED    = 1<<12,
 } mobj_diff2_t;
 
 typedef enum
@@ -958,6 +1285,7 @@ typedef enum
 	tc_bouncecheese,
 	tc_startcrumble,
 	tc_marioblock,
+	tc_marioblockchecker,
 	tc_spikesector,
 	tc_floatsector,
 	tc_bridgethinker,
@@ -972,7 +1300,13 @@ typedef enum
 	tc_noenemies,
 	tc_eachtime,
 	tc_disappear,
+	tc_fade,
+	tc_fadecolormap,
 	tc_planedisplace,
+#ifdef ESLOPE
+	tc_dynslopeline,
+	tc_dynslopevert,
+#endif // ESLOPE
 #ifdef POLYOBJECTS
 	tc_polyrotate, // haleyjd 03/26/06: polyobjects
 	tc_polymove,
@@ -981,6 +1315,8 @@ typedef enum
 	tc_polyswingdoor,
 	tc_polyflag,
 	tc_polydisplace,
+	tc_polyrotdisplace,
+	tc_polyfade,
 #endif
 	tc_end
 } specials_e;
@@ -1009,6 +1345,14 @@ static inline UINT32 SavePlayer(const player_t *player)
 	return 0xFFFFFFFF;
 }
 
+#ifdef ESLOPE
+static UINT32 SaveSlope(const pslope_t *slope)
+{
+	if (slope) return (UINT32)(slope->id);
+	return 0xFFFFFFFF;
+}
+#endif // ESLOPE
+
 //
 // SaveMobjThinker
 //
@@ -1132,10 +1476,16 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_HNEXT;
 	if (mobj->hprev)
 		diff2 |= MD2_HPREV;
+	if (mobj->floorrover)
+		diff2 |= MD2_FLOORROVER;
+	if (mobj->ceilingrover)
+		diff2 |= MD2_CEILINGROVER;
 #ifdef ESLOPE
 	if (mobj->standingslope)
 		diff2 |= MD2_SLOPE;
 #endif
+	if (mobj->colorized)
+		diff2 |= MD2_COLORIZED;
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1155,6 +1505,46 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, mobj->floorz);
 	WRITEFIXED(save_p, mobj->ceilingz);
 
+	if (diff2 & MD2_FLOORROVER)
+	{
+		ffloor_t *rover;
+		size_t i = 0;
+		UINT32 roverindex = 0;
+
+		for (rover = mobj->floorrover->target->ffloors; rover; rover = rover->next)
+		{
+			if (rover == mobj->floorrover)
+			{
+				roverindex = i;
+				break;
+			}
+			i++;
+		}
+
+		WRITEUINT32(save_p, (UINT32)(mobj->floorrover->target - sectors));
+		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+	}
+
+	if (diff2 & MD2_CEILINGROVER)
+	{
+		ffloor_t *rover;
+		size_t i = 0;
+		UINT32 roverindex = 0;
+
+		for (rover = mobj->ceilingrover->target->ffloors; rover; rover = rover->next)
+		{
+			if (rover == mobj->ceilingrover)
+			{
+				roverindex = i;
+				break;
+			}
+			i++;
+		}
+
+		WRITEUINT32(save_p, (UINT32)(mobj->ceilingrover->target - sectors));
+		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+	}
+
 	if (diff & MD_SPAWNPOINT)
 	{
 		size_t z;
@@ -1258,6 +1648,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	if (diff2 & MD2_SLOPE)
 		WRITEUINT16(save_p, mobj->standingslope->id);
 #endif
+	if (diff2 & MD2_COLORIZED)
+		WRITEUINT8(save_p, mobj->colorized);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -1273,7 +1665,10 @@ static void SaveSpecialLevelThinker(const thinker_t *th, const UINT8 type)
 	size_t i;
 	WRITEUINT8(save_p, type);
 	for (i = 0; i < 16; i++)
+	{
 		WRITEFIXED(save_p, ht->vars[i]); //var[16]
+		WRITEFIXED(save_p, ht->var2s[i]); //var[16]
+	}
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 }
@@ -1499,8 +1894,11 @@ static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 	const lightlevel_t *ht = (const void *)th;
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, SaveSector(ht->sector));
-	WRITEINT32(save_p, ht->destlevel);
-	WRITEINT32(save_p, ht->speed);
+	WRITEINT16(save_p, ht->sourcelevel);
+	WRITEINT16(save_p, ht->destlevel);
+	WRITEFIXED(save_p, ht->fixedcurlevel);
+	WRITEFIXED(save_p, ht->fixedpertic);
+	WRITEINT32(save_p, ht->timer);
 }
 
 //
@@ -1536,6 +1934,51 @@ static void SaveDisappearThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->exists);
 }
 
+//
+// SaveFadeThinker
+//
+// Saves a fade_t thinker
+//
+static void SaveFadeThinker(const thinker_t *th, const UINT8 type)
+{
+	const fade_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc));
+	WRITEUINT32(save_p, ht->sectornum);
+	WRITEUINT32(save_p, ht->ffloornum);
+	WRITEINT32(save_p, ht->alpha);
+	WRITEINT16(save_p, ht->sourcevalue);
+	WRITEINT16(save_p, ht->destvalue);
+	WRITEINT16(save_p, ht->destlightlevel);
+	WRITEINT16(save_p, ht->speed);
+	WRITEUINT8(save_p, (UINT8)ht->ticbased);
+	WRITEINT32(save_p, ht->timer);
+	WRITEUINT8(save_p, ht->doexists);
+	WRITEUINT8(save_p, ht->dotranslucent);
+	WRITEUINT8(save_p, ht->dolighting);
+	WRITEUINT8(save_p, ht->docolormap);
+	WRITEUINT8(save_p, ht->docollision);
+	WRITEUINT8(save_p, ht->doghostfade);
+	WRITEUINT8(save_p, ht->exactalpha);
+}
+
+//
+// SaveFadeColormapThinker
+//
+// Saves a fadecolormap_t thinker
+//
+static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type)
+{
+	const fadecolormap_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEUINT32(save_p, CheckAddNetColormapToList(ht->source_exc));
+	WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc));
+	WRITEUINT8(save_p, (UINT8)ht->ticbased);
+	WRITEINT32(save_p, ht->duration);
+	WRITEINT32(save_p, ht->timer);
+}
+
 //
 // SavePlaneDisplaceThinker
 //
@@ -1551,6 +1994,23 @@ static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->speed);
 	WRITEUINT8(save_p, ht->type);
 }
+#ifdef ESLOPE
+/// Save a dynamic slope thinker.
+static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type)
+{
+	const dynplanethink_t* ht = (const void*)th;
+
+	WRITEUINT8(save_p, type);
+	WRITEUINT8(save_p, ht->type);
+	WRITEUINT32(save_p, SaveSlope(ht->slope));
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEFIXED(save_p, ht->extent);
+
+	WRITEMEM(save_p, ht->tags, sizeof(ht->tags));
+    WRITEMEM(save_p, ht->vex, sizeof(ht->vex));
+}
+#endif // ESLOPE
+
 #ifdef POLYOBJECTS
 
 //
@@ -1661,6 +2121,31 @@ static void SavePolydisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->oldHeights);
 }
 
+static void SavePolyrotdisplaceThinker(const thinker_t *th, const UINT8 type)
+{
+	const polyrotdisplace_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEINT32(save_p, ht->polyObjNum);
+	WRITEUINT32(save_p, SaveSector(ht->controlSector));
+	WRITEFIXED(save_p, ht->rotscale);
+	WRITEUINT8(save_p, ht->turnobjs);
+	WRITEFIXED(save_p, ht->oldHeights);
+}
+
+static void SavePolyfadeThinker(const thinker_t *th, const UINT8 type)
+{
+	const polyfade_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEINT32(save_p, ht->polyObjNum);
+	WRITEINT32(save_p, ht->sourcevalue);
+	WRITEINT32(save_p, ht->destvalue);
+	WRITEUINT8(save_p, (UINT8)ht->docollision);
+	WRITEUINT8(save_p, (UINT8)ht->doghostfade);
+	WRITEUINT8(save_p, (UINT8)ht->ticbased);
+	WRITEINT32(save_p, ht->duration);
+	WRITEINT32(save_p, ht->timer);
+}
+
 #endif
 /*
 //
@@ -1682,214 +2167,252 @@ static inline void SaveWhatThinker(const thinker_t *th, const UINT8 type)
 static void P_NetArchiveThinkers(void)
 {
 	const thinker_t *th;
-	UINT32 numsaved = 0;
+	UINT32 i;
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_THINKERS);
 
-	// save off the current thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
-		 || th->function.acp1 == (actionf_p1)P_RainThinker
-		 || th->function.acp1 == (actionf_p1)P_SnowThinker))
-			numsaved++;
-
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			SaveMobjThinker(th, tc_mobj);
-			continue;
-		}
-#ifdef PARANOIA
-		else if (th->function.acp1 == (actionf_p1)P_RainThinker
-			|| th->function.acp1 == (actionf_p1)P_SnowThinker);
-#endif
-		else if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
-		{
-			SaveCeilingThinker(th, tc_ceiling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_CrushCeiling)
-		{
-			SaveCeilingThinker(th, tc_crushceiling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MoveFloor)
-		{
-			SaveFloormoveThinker(th, tc_floor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LightningFlash)
-		{
-			SaveLightflashThinker(th, tc_flash);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
-		{
-			SaveStrobeThinker(th, tc_strobe);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Glow)
-		{
-			SaveGlowThinker(th, tc_glow);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_FireFlicker)
-		{
-			SaveFireflickerThinker(th, tc_fireflicker);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MoveElevator)
-		{
-			SaveElevatorThinker(th, tc_elevator);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling)
-		{
-			SaveSpecialLevelThinker(th, tc_continuousfalling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ThwompSector)
-		{
-			SaveSpecialLevelThinker(th, tc_thwomp);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector)
-		{
-			SaveSpecialLevelThinker(th, tc_noenemies);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker)
+		UINT32 numsaved = 0;
+		// save off the current thinkers
+		for (th = thlist[i].next; th != &thlist[i]; th = th->next)
 		{
-			SaveSpecialLevelThinker(th, tc_eachtime);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_RaiseSector)
-		{
-			SaveSpecialLevelThinker(th, tc_raisesector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_CameraScanner)
-		{
-			SaveElevatorThinker(th, tc_camerascanner);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Scroll)
-		{
-			SaveScrollThinker(th, tc_scroll);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Friction)
-		{
-			SaveFrictionThinker(th, tc_friction);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Pusher)
-		{
-			SavePusherThinker(th, tc_pusher);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_BounceCheese)
-		{
-			SaveSpecialLevelThinker(th, tc_bouncecheese);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
-		{
-			SaveElevatorThinker(th, tc_startcrumble);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
-		{
-			SaveSpecialLevelThinker(th, tc_marioblock);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_SpikeSector)
-		{
-			SaveSpecialLevelThinker(th, tc_spikesector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_FloatSector)
-		{
-			SaveSpecialLevelThinker(th, tc_floatsector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_BridgeThinker)
-		{
-			SaveSpecialLevelThinker(th, tc_bridgethinker);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LaserFlash)
-		{
-			SaveLaserThinker(th, tc_laserflash);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LightFade)
-		{
-			SaveLightlevelThinker(th, tc_lightfade);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay)
-		{
-			SaveExecutorThinker(th, tc_executor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Disappear)
-		{
-			SaveDisappearThinker(th, tc_disappear);
-			continue;
-		}
+			if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
+			 || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
+				numsaved++;
 
-		else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace)
-		{
-			SavePlaneDisplaceThinker(th, tc_planedisplace);
-			continue;
-		}
+			if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+			{
+				SaveMobjThinker(th, tc_mobj);
+				continue;
+			}
+	#ifdef PARANOIA
+			else if (th->function.acp1 == (actionf_p1)P_NullPrecipThinker);
+	#endif
+			else if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
+			{
+				SaveCeilingThinker(th, tc_ceiling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_CrushCeiling)
+			{
+				SaveCeilingThinker(th, tc_crushceiling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MoveFloor)
+			{
+				SaveFloormoveThinker(th, tc_floor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LightningFlash)
+			{
+				SaveLightflashThinker(th, tc_flash);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
+			{
+				SaveStrobeThinker(th, tc_strobe);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Glow)
+			{
+				SaveGlowThinker(th, tc_glow);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FireFlicker)
+			{
+				SaveFireflickerThinker(th, tc_fireflicker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MoveElevator)
+			{
+				SaveElevatorThinker(th, tc_elevator);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling)
+			{
+				SaveSpecialLevelThinker(th, tc_continuousfalling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ThwompSector)
+			{
+				SaveSpecialLevelThinker(th, tc_thwomp);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector)
+			{
+				SaveSpecialLevelThinker(th, tc_noenemies);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker)
+			{
+				SaveSpecialLevelThinker(th, tc_eachtime);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_RaiseSector)
+			{
+				SaveSpecialLevelThinker(th, tc_raisesector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_CameraScanner)
+			{
+				SaveElevatorThinker(th, tc_camerascanner);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Scroll)
+			{
+				SaveScrollThinker(th, tc_scroll);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Friction)
+			{
+				SaveFrictionThinker(th, tc_friction);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Pusher)
+			{
+				SavePusherThinker(th, tc_pusher);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_BounceCheese)
+			{
+				SaveSpecialLevelThinker(th, tc_bouncecheese);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
+			{
+				SaveElevatorThinker(th, tc_startcrumble);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
+			{
+				SaveSpecialLevelThinker(th, tc_marioblock);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MarioBlockChecker)
+			{
+				SaveSpecialLevelThinker(th, tc_marioblockchecker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_SpikeSector)
+			{
+				SaveSpecialLevelThinker(th, tc_spikesector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FloatSector)
+			{
+				SaveSpecialLevelThinker(th, tc_floatsector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_BridgeThinker)
+			{
+				SaveSpecialLevelThinker(th, tc_bridgethinker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LaserFlash)
+			{
+				SaveLaserThinker(th, tc_laserflash);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LightFade)
+			{
+				SaveLightlevelThinker(th, tc_lightfade);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay)
+			{
+				SaveExecutorThinker(th, tc_executor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Disappear)
+			{
+				SaveDisappearThinker(th, tc_disappear);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Fade)
+			{
+				SaveFadeThinker(th, tc_fade);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FadeColormap)
+			{
+				SaveFadeColormapThinker(th, tc_fadecolormap);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace)
+			{
+				SavePlaneDisplaceThinker(th, tc_planedisplace);
+				continue;
+			}
 #ifdef POLYOBJECTS
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate)
-		{
-			SavePolyrotatetThinker(th, tc_polyrotate);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjMove)
-		{
-			SavePolymoveThinker(th, tc_polymove);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint)
-		{
-			SavePolywaypointThinker(th, tc_polywaypoint);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide)
-		{
-			SavePolyslidedoorThinker(th, tc_polyslidedoor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing)
-		{
-			SavePolyswingdoorThinker(th, tc_polyswingdoor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag)
-		{
-			SavePolymoveThinker(th, tc_polyflag);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace)
-		{
-			SavePolydisplaceThinker(th, tc_polydisplace);
-			continue;
-		}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate)
+			{
+				SavePolyrotatetThinker(th, tc_polyrotate);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjMove)
+			{
+				SavePolymoveThinker(th, tc_polymove);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint)
+			{
+				SavePolywaypointThinker(th, tc_polywaypoint);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide)
+			{
+				SavePolyslidedoorThinker(th, tc_polyslidedoor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing)
+			{
+				SavePolyswingdoorThinker(th, tc_polyswingdoor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag)
+			{
+				SavePolymoveThinker(th, tc_polyflag);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace)
+			{
+				SavePolydisplaceThinker(th, tc_polydisplace);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjRotDisplace)
+			{
+				SavePolyrotdisplaceThinker(th, tc_polyrotdisplace);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjFade)
+			{
+				SavePolyfadeThinker(th, tc_polyfade);
+				continue;
+			}
 #endif
+#ifdef ESLOPE
+			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine)
+			{
+				SaveDynamicSlopeThinker(th, tc_dynslopeline);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeVert)
+			{
+				SaveDynamicSlopeThinker(th, tc_dynslopevert);
+				continue;
+			}
+#endif // ESLOPE
 #ifdef PARANOIA
-		else if (th->function.acv != P_RemoveThinkerDelayed) // wait garbage collection
-			I_Error("unknown thinker type %p", th->function.acp1);
+			else if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed) // wait garbage collection
+				I_Error("unknown thinker type %p", th->function.acp1);
 #endif
-	}
+		}
 
-	CONS_Debug(DBG_NETPLAY, "%u thinkers saved\n", numsaved);
+		CONS_Debug(DBG_NETPLAY, "%u thinkers saved in list %d\n", numsaved, i);
 
-	WRITEUINT8(save_p, tc_end);
+		WRITEUINT8(save_p, tc_end);
+	}
 }
 
 // Now save the pointers, tracer and target, but at load time we must
@@ -1901,14 +2424,16 @@ mobj_t *P_FindNewPosition(UINT32 oldposition)
 	thinker_t *th;
 	mobj_t *mobj;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mobj = (mobj_t *)th;
-		if (mobj->mobjnum == oldposition)
-			return mobj;
+		if (mobj->mobjnum != oldposition)
+			continue;
+
+		return mobj;
 	}
 	CONS_Debug(DBG_GAMELOGIC, "mobj not found\n");
 	return NULL;
@@ -1938,12 +2463,26 @@ static inline player_t *LoadPlayer(UINT32 player)
 	return &players[player];
 }
 
+#ifdef ESLOPE
+static inline pslope_t *LoadSlope(UINT32 slopeid)
+{
+	pslope_t *p = slopelist;
+	if (slopeid > slopecount) return NULL;
+	do
+	{
+		if (p->id == slopeid)
+			return p;
+	} while ((p = p->next));
+	return NULL;
+}
+#endif // ESLOPE
+
 //
 // LoadMobjThinker
 //
 // Loads a mobj_t from a save game
 //
-static void LoadMobjThinker(actionf_p1 thinker)
+static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 {
 	thinker_t *next;
 	mobj_t *mobj;
@@ -1951,6 +2490,7 @@ static void LoadMobjThinker(actionf_p1 thinker)
 	UINT16 diff2;
 	INT32 i;
 	fixed_t z, floorz, ceilingz;
+	ffloor_t *floorrover = NULL, *ceilingrover = NULL;
 
 	diff = READUINT32(save_p);
 	if (diff & MD_MORE)
@@ -1964,14 +2504,46 @@ static void LoadMobjThinker(actionf_p1 thinker)
 	floorz = READFIXED(save_p);
 	ceilingz = READFIXED(save_p);
 
+	if (diff2 & MD2_FLOORROVER)
+	{
+		size_t floor_sectornum = (size_t)READUINT32(save_p);
+		size_t floor_rovernum = (size_t)READUINT32(save_p);
+		ffloor_t *rover = NULL;
+		size_t rovernum = 0;
+
+		for (rover = sectors[floor_sectornum].ffloors; rover; rover = rover->next)
+		{
+			if (rovernum == floor_rovernum)
+				break;
+			rovernum++;
+		}
+		floorrover = rover;
+	}
+
+	if (diff2 & MD2_CEILINGROVER)
+	{
+		size_t ceiling_sectornum = (size_t)READUINT32(save_p);
+		size_t ceiling_rovernum = (size_t)READUINT32(save_p);
+		ffloor_t *rover = NULL;
+		size_t rovernum = 0;
+
+		for (rover = sectors[ceiling_sectornum].ffloors; rover; rover = rover->next)
+		{
+			if (rovernum == ceiling_rovernum)
+				break;
+			rovernum++;
+		}
+		ceilingrover = rover;
+	}
+
 	if (diff & MD_SPAWNPOINT)
 	{
 		UINT16 spawnpointnum = READUINT16(save_p);
 
 		if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
 		{
-			P_SpawnHoopsAndRings(&mapthings[spawnpointnum]);
-			return;
+			P_SpawnHoopsAndRings(&mapthings[spawnpointnum], false);
+			return NULL;
 		}
 
 		mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
@@ -1988,6 +2560,8 @@ static void LoadMobjThinker(actionf_p1 thinker)
 	mobj->z = z;
 	mobj->floorz = floorz;
 	mobj->ceilingz = ceilingz;
+	mobj->floorrover = floorrover;
+	mobj->ceilingrover = ceilingrover;
 
 	if (diff & MD_TYPE)
 		mobj->type = READUINT32(save_p);
@@ -2150,7 +2724,8 @@ static void LoadMobjThinker(actionf_p1 thinker)
 	if (diff2 & MD2_SLOPE)
 		mobj->standingslope = P_SlopeById(READUINT16(save_p));
 #endif
-
+	if (diff2 & MD2_COLORIZED)
+		mobj->colorized = READUINT8(save_p);
 
 	if (diff & MD_REDFLAG)
 	{
@@ -2176,9 +2751,9 @@ static void LoadMobjThinker(actionf_p1 thinker)
 			mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight;
 	}
 
-	P_AddThinker(&mobj->thinker);
-
 	mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function
+
+	return &mobj->thinker;
 }
 
 //
@@ -2192,13 +2767,16 @@ static void LoadMobjThinker(actionf_p1 thinker)
 //		2 - Ceiling Only
 //		3 - Both
 //
-static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+static thinker_t* LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 {
 	levelspecthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	size_t i;
 	ht->thinker.function.acp1 = thinker;
 	for (i = 0; i < 16; i++)
+	{
 		ht->vars[i] = READFIXED(save_p); //var[16]
+		ht->var2s[i] = READFIXED(save_p); //var[16]
+	}
 	ht->sourceline = LoadLine(READUINT32(save_p));
 	ht->sector = LoadSector(READUINT32(save_p));
 
@@ -2210,7 +2788,7 @@ static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 			ht->sector->floordata = ht;
 	}
 
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2218,7 +2796,7 @@ static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 //
 // Loads a ceiling_t from a save game
 //
-static void LoadCeilingThinker(actionf_p1 thinker)
+static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 {
 	ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2239,7 +2817,7 @@ static void LoadCeilingThinker(actionf_p1 thinker)
 	ht->sourceline = READFIXED(save_p);
 	if (ht->sector)
 		ht->sector->ceilingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2247,7 +2825,7 @@ static void LoadCeilingThinker(actionf_p1 thinker)
 //
 // Loads a floormove_t from a save game
 //
-static void LoadFloormoveThinker(actionf_p1 thinker)
+static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 {
 	floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2263,7 +2841,7 @@ static void LoadFloormoveThinker(actionf_p1 thinker)
 	ht->delaytimer = READFIXED(save_p);
 	if (ht->sector)
 		ht->sector->floordata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2271,7 +2849,7 @@ static void LoadFloormoveThinker(actionf_p1 thinker)
 //
 // Loads a lightflash_t from a save game
 //
-static void LoadLightflashThinker(actionf_p1 thinker)
+static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 {
 	lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2280,7 +2858,7 @@ static void LoadLightflashThinker(actionf_p1 thinker)
 	ht->minlight = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2288,7 +2866,7 @@ static void LoadLightflashThinker(actionf_p1 thinker)
 //
 // Loads a strobe_t from a save game
 //
-static void LoadStrobeThinker(actionf_p1 thinker)
+static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 {
 	strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2300,7 +2878,7 @@ static void LoadStrobeThinker(actionf_p1 thinker)
 	ht->brighttime = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2308,7 +2886,7 @@ static void LoadStrobeThinker(actionf_p1 thinker)
 //
 // Loads a glow_t from a save game
 //
-static void LoadGlowThinker(actionf_p1 thinker)
+static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 {
 	glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2319,14 +2897,14 @@ static void LoadGlowThinker(actionf_p1 thinker)
 	ht->speed = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 //
 // LoadFireflickerThinker
 //
 // Loads a fireflicker_t from a save game
 //
-static void LoadFireflickerThinker(actionf_p1 thinker)
+static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 {
 	fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2337,14 +2915,14 @@ static void LoadFireflickerThinker(actionf_p1 thinker)
 	ht->minlight = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 //
 // LoadElevatorThinker
 //
 // Loads a elevator_t from a save game
 //
-static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 {
 	elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2374,7 +2952,7 @@ static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 			ht->sector->floordata = ht;
 	}
 
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2382,7 +2960,7 @@ static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 //
 // Loads a scroll_t from a save game
 //
-static void LoadScrollThinker(actionf_p1 thinker)
+static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 {
 	scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2396,7 +2974,7 @@ static void LoadScrollThinker(actionf_p1 thinker)
 	ht->accel = READINT32(save_p);
 	ht->exclusive = READINT32(save_p);
 	ht->type = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2404,7 +2982,7 @@ static void LoadScrollThinker(actionf_p1 thinker)
 //
 // Loads a friction_t from a save game
 //
-static inline void LoadFrictionThinker(actionf_p1 thinker)
+static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 {
 	friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2413,7 +2991,7 @@ static inline void LoadFrictionThinker(actionf_p1 thinker)
 	ht->affectee = READINT32(save_p);
 	ht->referrer = READINT32(save_p);
 	ht->roverfriction = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2421,7 +2999,7 @@ static inline void LoadFrictionThinker(actionf_p1 thinker)
 //
 // Loads a pusher_t from a save game
 //
-static void LoadPusherThinker(actionf_p1 thinker)
+static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 {
 	pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2439,7 +3017,7 @@ static void LoadPusherThinker(actionf_p1 thinker)
 	ht->exclusive = READINT32(save_p);
 	ht->slider = READINT32(save_p);
 	ht->source = P_GetPushThing(ht->affectee);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2447,7 +3025,7 @@ static void LoadPusherThinker(actionf_p1 thinker)
 //
 // Loads a laserthink_t from a save game
 //
-static inline void LoadLaserThinker(actionf_p1 thinker)
+static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 {
 	laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ffloor_t *rover = NULL;
@@ -2459,7 +3037,7 @@ static inline void LoadLaserThinker(actionf_p1 thinker)
 		if (rover->secnum == (size_t)(ht->sec - sectors)
 		&& rover->master == ht->sourceline)
 			ht->ffloor = rover;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2467,16 +3045,19 @@ static inline void LoadLaserThinker(actionf_p1 thinker)
 //
 // Loads a lightlevel_t from a save game
 //
-static inline void LoadLightlevelThinker(actionf_p1 thinker)
+static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 {
 	lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 	ht->sector = LoadSector(READUINT32(save_p));
-	ht->destlevel = READINT32(save_p);
-	ht->speed = READINT32(save_p);
+	ht->sourcelevel = READINT16(save_p);
+	ht->destlevel = READINT16(save_p);
+	ht->fixedcurlevel = READFIXED(save_p);
+	ht->fixedpertic = READFIXED(save_p);
+	ht->timer = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2484,7 +3065,7 @@ static inline void LoadLightlevelThinker(actionf_p1 thinker)
 //
 // Loads a executor_t from a save game
 //
-static inline void LoadExecutorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 {
 	executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2492,7 +3073,7 @@ static inline void LoadExecutorThinker(actionf_p1 thinker)
 	ht->caller = LoadMobj(READUINT32(save_p));
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->timer = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2500,7 +3081,7 @@ static inline void LoadExecutorThinker(actionf_p1 thinker)
 //
 // Loads a disappear_t thinker
 //
-static inline void LoadDisappearThinker(actionf_p1 thinker)
+static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 {
 	disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2511,7 +3092,73 @@ static inline void LoadDisappearThinker(actionf_p1 thinker)
 	ht->affectee = READINT32(save_p);
 	ht->sourceline = READINT32(save_p);
 	ht->exists = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
+}
+
+//
+// LoadFadeThinker
+//
+// Loads a fade_t thinker
+//
+static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
+{
+	sector_t *ss;
+	fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->dest_exc = GetNetColormapFromList(READUINT32(save_p));
+	ht->sectornum = READUINT32(save_p);
+	ht->ffloornum = READUINT32(save_p);
+	ht->alpha = READINT32(save_p);
+	ht->sourcevalue = READINT16(save_p);
+	ht->destvalue = READINT16(save_p);
+	ht->destlightlevel = READINT16(save_p);
+	ht->speed = READINT16(save_p);
+	ht->ticbased = (boolean)READUINT8(save_p);
+	ht->timer = READINT32(save_p);
+	ht->doexists = READUINT8(save_p);
+	ht->dotranslucent = READUINT8(save_p);
+	ht->dolighting = READUINT8(save_p);
+	ht->docolormap = READUINT8(save_p);
+	ht->docollision = READUINT8(save_p);
+	ht->doghostfade = READUINT8(save_p);
+	ht->exactalpha = READUINT8(save_p);
+
+	ss = LoadSector(ht->sectornum);
+	if (ss)
+	{
+		size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
+		ffloor_t *rover;
+		for (rover = ss->ffloors; rover; rover = rover->next)
+		{
+			if (j == ht->ffloornum)
+			{
+				ht->rover = rover;
+				rover->fadingdata = ht;
+				break;
+			}
+			j++;
+		}
+	}
+	return &ht->thinker;
+}
+
+// LoadFadeColormapThinker
+//
+// Loads a fadecolormap_t from a save game
+//
+static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
+{
+	fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->source_exc = GetNetColormapFromList(READUINT32(save_p));
+	ht->dest_exc = GetNetColormapFromList(READUINT32(save_p));
+	ht->ticbased = (boolean)READUINT8(save_p);
+	ht->duration = READINT32(save_p);
+	ht->timer = READINT32(save_p);
+	if (ht->sector)
+		ht->sector->fadecolormapdata = ht;
+	return &ht->thinker;
 }
 
 //
@@ -2519,18 +3166,36 @@ static inline void LoadDisappearThinker(actionf_p1 thinker)
 //
 // Loads a planedisplace_t thinker
 //
-static inline void LoadPlaneDisplaceThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 {
 	planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
+
 	ht->affectee = READINT32(save_p);
 	ht->control = READINT32(save_p);
 	ht->last_height = READFIXED(save_p);
 	ht->speed = READFIXED(save_p);
 	ht->type = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
+#ifdef ESLOPE
+/// Save a dynamic slope thinker.
+static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
+{
+	dynplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+
+	ht->type = READUINT8(save_p);
+	ht->slope = LoadSlope(READUINT32(save_p));
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->extent = READFIXED(save_p);
+	READMEM(save_p, ht->tags, sizeof(ht->tags));
+	READMEM(save_p, ht->vex, sizeof(ht->vex));
+	return &ht->thinker;
+}
+#endif // ESLOPE
+
 #ifdef POLYOBJECTS
 
 //
@@ -2538,14 +3203,14 @@ static inline void LoadPlaneDisplaceThinker(actionf_p1 thinker)
 //
 // Loads a polyrotate_t thinker
 //
-static inline void LoadPolyrotatetThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 {
 	polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 	ht->polyObjNum = READINT32(save_p);
 	ht->speed = READINT32(save_p);
 	ht->distance = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2553,7 +3218,7 @@ static inline void LoadPolyrotatetThinker(actionf_p1 thinker)
 //
 // Loads a polymovet_t thinker
 //
-static void LoadPolymoveThinker(actionf_p1 thinker)
+static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 {
 	polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2563,7 +3228,7 @@ static void LoadPolymoveThinker(actionf_p1 thinker)
 	ht->momy = READFIXED(save_p);
 	ht->distance = READINT32(save_p);
 	ht->angle = READANGLE(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2571,7 +3236,7 @@ static void LoadPolymoveThinker(actionf_p1 thinker)
 //
 // Loads a polywaypoint_t thinker
 //
-static inline void LoadPolywaypointThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 {
 	polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2587,7 +3252,7 @@ static inline void LoadPolywaypointThinker(actionf_p1 thinker)
 	ht->diffx = READFIXED(save_p);
 	ht->diffy = READFIXED(save_p);
 	ht->diffz = READFIXED(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2595,7 +3260,7 @@ static inline void LoadPolywaypointThinker(actionf_p1 thinker)
 //
 // loads a polyslidedoor_t thinker
 //
-static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 {
 	polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2612,7 +3277,7 @@ static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
 	ht->momx = READFIXED(save_p);
 	ht->momy = READFIXED(save_p);
 	ht->closing = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2620,7 +3285,7 @@ static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
 //
 // Loads a polyswingdoor_t thinker
 //
-static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 {
 	polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2632,7 +3297,7 @@ static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
 	ht->initDistance = READINT32(save_p);
 	ht->distance = READINT32(save_p);
 	ht->closing = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2640,7 +3305,7 @@ static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
 //
 // Loads a polydisplace_t thinker
 //
-static inline void LoadPolydisplaceThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolydisplaceThinker(actionf_p1 thinker)
 {
 	polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2649,7 +3314,39 @@ static inline void LoadPolydisplaceThinker(actionf_p1 thinker)
 	ht->dx = READFIXED(save_p);
 	ht->dy = READFIXED(save_p);
 	ht->oldHeights = READFIXED(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
+}
+
+static inline thinker_t* LoadPolyrotdisplaceThinker(actionf_p1 thinker)
+{
+	polyrotdisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->polyObjNum = READINT32(save_p);
+	ht->controlSector = LoadSector(READUINT32(save_p));
+	ht->rotscale = READFIXED(save_p);
+	ht->turnobjs = READUINT8(save_p);
+	ht->oldHeights = READFIXED(save_p);
+	return &ht->thinker;
+}
+
+//
+// LoadPolyfadeThinker
+//
+// Loads a polyfadet_t thinker
+//
+static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
+{
+	polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->polyObjNum = READINT32(save_p);
+	ht->sourcevalue = READINT32(save_p);
+	ht->destvalue = READINT32(save_p);
+	ht->docollision = (boolean)READUINT8(save_p);
+	ht->doghostfade = (boolean)READUINT8(save_p);
+	ht->ticbased = (boolean)READUINT8(save_p);
+	ht->duration = READINT32(save_p);
+	ht->timer = READINT32(save_p);
+	return &ht->thinker;
 }
 #endif
 
@@ -2682,15 +3379,18 @@ static void P_NetUnArchiveThinkers(void)
 		I_Error("Bad $$$.sav at archive block Thinkers");
 
 	// remove all the current thinkers
-	currentthinker = thinkercap.next;
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap; currentthinker = next)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		next = currentthinker->next;
+		currentthinker = thlist[i].next;
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next)
+		{
+			next = currentthinker->next;
 
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-			P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
-		else
-			Z_Free(currentthinker);
+			if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
+				P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
+			else
+				Z_Free(currentthinker);
+		}
 	}
 
 	// we don't want the removed mobjs to come back
@@ -2700,187 +3400,223 @@ static void P_NetUnArchiveThinkers(void)
 	// clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity
 	for (i = 0; i < numsectors; i++)
 	{
-		sectors[i].floordata = sectors[i].ceilingdata = sectors[i].lightingdata = NULL;
+		sectors[i].floordata = sectors[i].ceilingdata = sectors[i].lightingdata = sectors[i].fadecolormapdata = NULL;
 	}
 
 	// read in saved thinkers
-	for (;;)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		tclass = READUINT8(save_p);
+		for (;;)
+		{
+			thinker_t* th = NULL;
+			tclass = READUINT8(save_p);
 
-		if (tclass == tc_end)
-			break; // leave the saved thinker reading loop
-		numloaded++;
+			if (tclass == tc_end)
+				break; // leave the saved thinker reading loop
+			numloaded++;
 
-		switch (tclass)
-		{
-			case tc_mobj:
-				LoadMobjThinker((actionf_p1)P_MobjThinker);
-				break;
+			switch (tclass)
+			{
+				case tc_mobj:
+					th = LoadMobjThinker((actionf_p1)P_MobjThinker);
+					break;
 
-			case tc_ceiling:
-				LoadCeilingThinker((actionf_p1)T_MoveCeiling);
-				break;
+				case tc_ceiling:
+					th = LoadCeilingThinker((actionf_p1)T_MoveCeiling);
+					break;
 
-			case tc_crushceiling:
-				LoadCeilingThinker((actionf_p1)T_CrushCeiling);
-				break;
+				case tc_crushceiling:
+					th = LoadCeilingThinker((actionf_p1)T_CrushCeiling);
+					break;
 
-			case tc_floor:
-				LoadFloormoveThinker((actionf_p1)T_MoveFloor);
-				break;
+				case tc_floor:
+					th = LoadFloormoveThinker((actionf_p1)T_MoveFloor);
+					break;
 
-			case tc_flash:
-				LoadLightflashThinker((actionf_p1)T_LightningFlash);
-				break;
+				case tc_flash:
+					th = LoadLightflashThinker((actionf_p1)T_LightningFlash);
+					break;
 
-			case tc_strobe:
-				LoadStrobeThinker((actionf_p1)T_StrobeFlash);
-				break;
+				case tc_strobe:
+					th = LoadStrobeThinker((actionf_p1)T_StrobeFlash);
+					break;
 
-			case tc_glow:
-				LoadGlowThinker((actionf_p1)T_Glow);
-				break;
+				case tc_glow:
+					th = LoadGlowThinker((actionf_p1)T_Glow);
+					break;
 
-			case tc_fireflicker:
-				LoadFireflickerThinker((actionf_p1)T_FireFlicker);
-				break;
+				case tc_fireflicker:
+					th = LoadFireflickerThinker((actionf_p1)T_FireFlicker);
+					break;
 
-			case tc_elevator:
-				LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
-				break;
+				case tc_elevator:
+					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
+					break;
 
-			case tc_continuousfalling:
-				LoadSpecialLevelThinker((actionf_p1)T_ContinuousFalling, 3);
-				break;
+				case tc_continuousfalling:
+					th = LoadSpecialLevelThinker((actionf_p1)T_ContinuousFalling, 3);
+					break;
 
-			case tc_thwomp:
-				LoadSpecialLevelThinker((actionf_p1)T_ThwompSector, 3);
-				break;
+				case tc_thwomp:
+					th = LoadSpecialLevelThinker((actionf_p1)T_ThwompSector, 3);
+					break;
 
-			case tc_noenemies:
-				LoadSpecialLevelThinker((actionf_p1)T_NoEnemiesSector, 0);
-				break;
+				case tc_noenemies:
+					th = LoadSpecialLevelThinker((actionf_p1)T_NoEnemiesSector, 0);
+					break;
 
-			case tc_eachtime:
-				LoadSpecialLevelThinker((actionf_p1)T_EachTimeThinker, 0);
-				break;
+				case tc_eachtime:
+					th = LoadSpecialLevelThinker((actionf_p1)T_EachTimeThinker, 0);
+					break;
 
-			case tc_raisesector:
-				LoadSpecialLevelThinker((actionf_p1)T_RaiseSector, 0);
-				break;
+				case tc_raisesector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_RaiseSector, 0);
+					break;
 
-			/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
-			/// \note working on it!
-			case tc_camerascanner:
-				LoadElevatorThinker((actionf_p1)T_CameraScanner, 0);
-				break;
+				/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
+				/// \note working on it!
+				case tc_camerascanner:
+					th = LoadElevatorThinker((actionf_p1)T_CameraScanner, 0);
+					break;
 
-			case tc_bouncecheese:
-				LoadSpecialLevelThinker((actionf_p1)T_BounceCheese, 2);
-				break;
+				case tc_bouncecheese:
+					th = LoadSpecialLevelThinker((actionf_p1)T_BounceCheese, 2);
+					break;
 
-			case tc_startcrumble:
-				LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
-				break;
+				case tc_startcrumble:
+					th = LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
+					break;
 
-			case tc_marioblock:
-				LoadSpecialLevelThinker((actionf_p1)T_MarioBlock, 3);
-				break;
+				case tc_marioblock:
+					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlock, 3);
+					break;
 
-			case tc_spikesector:
-				LoadSpecialLevelThinker((actionf_p1)T_SpikeSector, 0);
-				break;
+				case tc_marioblockchecker:
+					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlockChecker, 0);
+					break;
 
-			case tc_floatsector:
-				LoadSpecialLevelThinker((actionf_p1)T_FloatSector, 0);
-				break;
+				case tc_spikesector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_SpikeSector, 0);
+					break;
 
-			case tc_bridgethinker:
-				LoadSpecialLevelThinker((actionf_p1)T_BridgeThinker, 3);
-				break;
+				case tc_floatsector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_FloatSector, 0);
+					break;
 
-			case tc_laserflash:
-				LoadLaserThinker((actionf_p1)T_LaserFlash);
-				break;
+				case tc_bridgethinker:
+					th = LoadSpecialLevelThinker((actionf_p1)T_BridgeThinker, 3);
+					break;
 
-			case tc_lightfade:
-				LoadLightlevelThinker((actionf_p1)T_LightFade);
-				break;
+				case tc_laserflash:
+					th = LoadLaserThinker((actionf_p1)T_LaserFlash);
+					break;
 
-			case tc_executor:
-				LoadExecutorThinker((actionf_p1)T_ExecutorDelay);
-				restoreNum = true;
-				break;
+				case tc_lightfade:
+					th = LoadLightlevelThinker((actionf_p1)T_LightFade);
+					break;
 
-			case tc_disappear:
-				LoadDisappearThinker((actionf_p1)T_Disappear);
-				break;
+				case tc_executor:
+					th = LoadExecutorThinker((actionf_p1)T_ExecutorDelay);
+					restoreNum = true;
+					break;
 
-			case tc_planedisplace:
-				LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace);
-				break;
+				case tc_disappear:
+					th = LoadDisappearThinker((actionf_p1)T_Disappear);
+					break;
+
+				case tc_fade:
+					th = LoadFadeThinker((actionf_p1)T_Fade);
+					break;
+
+				case tc_fadecolormap:
+					th = LoadFadeColormapThinker((actionf_p1)T_FadeColormap);
+					break;
+
+				case tc_planedisplace:
+					th = LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace);
+					break;
 #ifdef POLYOBJECTS
-			case tc_polyrotate:
-				LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate);
-				break;
+				case tc_polyrotate:
+					th = LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate);
+					break;
 
-			case tc_polymove:
-				LoadPolymoveThinker((actionf_p1)T_PolyObjMove);
-				break;
+				case tc_polymove:
+					th = LoadPolymoveThinker((actionf_p1)T_PolyObjMove);
+					break;
 
-			case tc_polywaypoint:
-				LoadPolywaypointThinker((actionf_p1)T_PolyObjWaypoint);
-				break;
+				case tc_polywaypoint:
+					th = LoadPolywaypointThinker((actionf_p1)T_PolyObjWaypoint);
+					break;
 
-			case tc_polyslidedoor:
-				LoadPolyslidedoorThinker((actionf_p1)T_PolyDoorSlide);
-				break;
+				case tc_polyslidedoor:
+					th = LoadPolyslidedoorThinker((actionf_p1)T_PolyDoorSlide);
+					break;
 
-			case tc_polyswingdoor:
-				LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing);
-				break;
+				case tc_polyswingdoor:
+					th = LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing);
+					break;
 
-			case tc_polyflag:
-				LoadPolymoveThinker((actionf_p1)T_PolyObjFlag);
-				break;
+				case tc_polyflag:
+					th = LoadPolymoveThinker((actionf_p1)T_PolyObjFlag);
+					break;
 
-			case tc_polydisplace:
-				LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace);
-				break;
+				case tc_polydisplace:
+					th = LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace);
+					break;
+
+				case tc_polyrotdisplace:
+					th = LoadPolyrotdisplaceThinker((actionf_p1)T_PolyObjRotDisplace);
+					break;
+
+				case tc_polyfade:
+					th = LoadPolyfadeThinker((actionf_p1)T_PolyObjFade);
+					break;
 #endif
-			case tc_scroll:
-				LoadScrollThinker((actionf_p1)T_Scroll);
-				break;
+#ifdef ESLOPE
+				case tc_dynslopeline:
+					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeLine);
+					break;
 
-			case tc_friction:
-				LoadFrictionThinker((actionf_p1)T_Friction);
-				break;
+				case tc_dynslopevert:
+					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeVert);
+					break;
+#endif // ESLOPE
 
-			case tc_pusher:
-				LoadPusherThinker((actionf_p1)T_Pusher);
-				break;
+				case tc_scroll:
+					th = LoadScrollThinker((actionf_p1)T_Scroll);
+					break;
 
-			default:
-				I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass);
+				case tc_friction:
+					th = LoadFrictionThinker((actionf_p1)T_Friction);
+					break;
+
+				case tc_pusher:
+					th = LoadPusherThinker((actionf_p1)T_Pusher);
+					break;
+
+				default:
+					I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass);
+			}
+			if (th)
+				P_AddThinker(i, th);
 		}
-	}
 
-	CONS_Debug(DBG_NETPLAY, "%u thinkers loaded\n", numloaded);
+		CONS_Debug(DBG_NETPLAY, "%u thinkers loaded in list %d\n", numloaded, i);
+	}
 
 	if (restoreNum)
 	{
 		executor_t *delay = NULL;
 		UINT32 mobjnum;
-		for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
-			currentthinker = currentthinker->next)
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i];
+		currentthinker = currentthinker->next)
 		{
-			if (currentthinker->function.acp1 == (actionf_p1)T_ExecutorDelay)
-			{
-				delay = (void *)currentthinker;
-				if ((mobjnum = (UINT32)(size_t)delay->caller))
-					delay->caller = P_FindNewPosition(mobjnum);
-			}
+			if (currentthinker->function.acp1 != (actionf_p1)T_ExecutorDelay)
+				continue;
+			delay = (void *)currentthinker;
+			if (!(mobjnum = (UINT32)(size_t)delay->caller))
+				continue;
+			delay->caller = P_FindNewPosition(mobjnum);
 		}
 	}
 }
@@ -2988,14 +3724,14 @@ static inline void P_FinishMobjs(void)
 	mobj_t *mobj;
 
 	// put info field there real value
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
+	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)currentthinker;
-			mobj->info = &mobjinfo[mobj->type];
-		}
+		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj = (mobj_t *)currentthinker;
+		mobj->info = &mobjinfo[mobj->type];
 	}
 }
 
@@ -3006,79 +3742,86 @@ static void P_RelinkPointers(void)
 	UINT32 temp;
 
 	// use info field (value = oldposition) to relink mobjs
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
+	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)currentthinker;
+		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
-			if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
-				continue;
+		mobj = (mobj_t *)currentthinker;
 
-			if (mobj->tracer)
-			{
-				temp = (UINT32)(size_t)mobj->tracer;
-				mobj->tracer = NULL;
-				if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type);
-			}
-			if (mobj->target)
-			{
-				temp = (UINT32)(size_t)mobj->target;
-				mobj->target = NULL;
-				if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type);
-			}
-			if (mobj->hnext)
-			{
-				temp = (UINT32)(size_t)mobj->hnext;
-				mobj->hnext = NULL;
-				if (!(mobj->hnext = P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type);
-			}
-			if (mobj->hprev)
-			{
-				temp = (UINT32)(size_t)mobj->hprev;
-				mobj->hprev = NULL;
-				if (!(mobj->hprev = P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->capsule)
-			{
-				temp = (UINT32)(size_t)mobj->player->capsule;
-				mobj->player->capsule = NULL;
-				if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->axis1)
-			{
-				temp = (UINT32)(size_t)mobj->player->axis1;
-				mobj->player->axis1 = NULL;
-				if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->axis2)
-			{
-				temp = (UINT32)(size_t)mobj->player->axis2;
-				mobj->player->axis2 = NULL;
-				if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->awayviewmobj)
-			{
-				temp = (UINT32)(size_t)mobj->player->awayviewmobj;
-				mobj->player->awayviewmobj = NULL;
-				if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->followmobj)
-			{
-				temp = (UINT32)(size_t)mobj->player->followmobj;
-				mobj->player->followmobj = NULL;
-				if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type);
-			}
+		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+			continue;
+
+		if (mobj->tracer)
+		{
+			temp = (UINT32)(size_t)mobj->tracer;
+			mobj->tracer = NULL;
+			if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type);
+		}
+		if (mobj->target)
+		{
+			temp = (UINT32)(size_t)mobj->target;
+			mobj->target = NULL;
+			if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type);
+		}
+		if (mobj->hnext)
+		{
+			temp = (UINT32)(size_t)mobj->hnext;
+			mobj->hnext = NULL;
+			if (!(mobj->hnext = P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type);
+		}
+		if (mobj->hprev)
+		{
+			temp = (UINT32)(size_t)mobj->hprev;
+			mobj->hprev = NULL;
+			if (!(mobj->hprev = P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->capsule)
+		{
+			temp = (UINT32)(size_t)mobj->player->capsule;
+			mobj->player->capsule = NULL;
+			if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->axis1)
+		{
+			temp = (UINT32)(size_t)mobj->player->axis1;
+			mobj->player->axis1 = NULL;
+			if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->axis2)
+		{
+			temp = (UINT32)(size_t)mobj->player->axis2;
+			mobj->player->axis2 = NULL;
+			if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->awayviewmobj)
+		{
+			temp = (UINT32)(size_t)mobj->player->awayviewmobj;
+			mobj->player->awayviewmobj = NULL;
+			if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->followmobj)
+		{
+			temp = (UINT32)(size_t)mobj->player->followmobj;
+			mobj->player->followmobj = NULL;
+			if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->drone)
+		{
+			temp = (UINT32)(size_t)mobj->player->drone;
+			mobj->player->drone = NULL;
+			if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type);
 		}
 	}
 }
@@ -3245,8 +3988,9 @@ static void P_NetArchiveMisc(void)
 	WRITEUINT32(save_p, tokenlist);
 
 	WRITEUINT32(save_p, leveltime);
-	WRITEUINT32(save_p, totalrings);
+	WRITEUINT32(save_p, ssspheres);
 	WRITEINT16(save_p, lastmap);
+	WRITEUINT16(save_p, bossdisabled);
 
 	WRITEUINT16(save_p, emeralds);
 	WRITEUINT8(save_p, stagefailed);
@@ -3255,6 +3999,7 @@ static void P_NetArchiveMisc(void)
 	WRITEINT32(save_p, sstimer);
 	WRITEUINT32(save_p, bluescore);
 	WRITEUINT32(save_p, redscore);
+	WRITEINT32(save_p, modulothing);
 
 	WRITEINT16(save_p, autobalance);
 	WRITEINT16(save_p, teamscramble);
@@ -3322,8 +4067,9 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	// get the time
 	leveltime = READUINT32(save_p);
-	totalrings = READUINT32(save_p);
+	ssspheres = READUINT32(save_p);
 	lastmap = READINT16(save_p);
+	bossdisabled = READUINT16(save_p);
 
 	emeralds = READUINT16(save_p);
 	stagefailed = READUINT8(save_p);
@@ -3332,6 +4078,7 @@ static inline boolean P_NetUnArchiveMisc(void)
 	sstimer = READINT32(save_p);
 	bluescore = READUINT32(save_p);
 	redscore = READUINT32(save_p);
+	modulothing = READINT32(save_p);
 
 	autobalance = READINT16(save_p);
 	teamscramble = READINT16(save_p);
@@ -3380,15 +4127,15 @@ void P_SaveNetGame(void)
 	P_NetArchiveMisc();
 
 	// Assign the mobjnumber for pointer tracking
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)th;
-			if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
-				continue;
-			mobj->mobjnum = i++;
-		}
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj = (mobj_t *)th;
+		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+			continue;
+		mobj->mobjnum = i++;
 	}
 
 	P_NetArchivePlayers();
@@ -3400,6 +4147,7 @@ void P_SaveNetGame(void)
 #endif
 		P_NetArchiveThinkers();
 		P_NetArchiveSpecials();
+		P_NetArchiveColormaps();
 	}
 #ifdef HAVE_BLUA
 	LUA_Archive();
@@ -3442,6 +4190,7 @@ boolean P_LoadNetGame(void)
 #endif
 		P_NetUnArchiveThinkers();
 		P_NetUnArchiveSpecials();
+		P_NetUnArchiveColormaps();
 		P_RelinkPointers();
 		P_FinishMobjs();
 	}
diff --git a/src/p_saveg.h b/src/p_saveg.h
index 5960660abf2778169a434d08356f724ddd338ada..2f5e88046a85d22aa84083d92444f2d7996e85ef 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_setup.c b/src/p_setup.c
index fc0b7273b2d6f7f8c57e2ee893987df33c43e5bd..d0cd14b22d3011a64e26a23e44be00739420adeb 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -56,8 +56,11 @@
 
 #include "filesrch.h" // refreshdirmenu
 
-// wipes
-#include "f_finale.h"
+#ifdef HAVE_BLUA
+#include "lua_hud.h" // level title
+#endif
+
+#include "f_finale.h" // wipes
 
 #include "md5.h" // map MD5
 
@@ -99,7 +102,9 @@ line_t *lines;
 side_t *sides;
 mapthing_t *mapthings;
 INT32 numstarposts;
+UINT16 bossdisabled;
 boolean levelloading;
+UINT8 levelfadecol;
 
 // BLOCKMAP
 // Created from axis aligned bounding box
@@ -205,9 +210,17 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->actnum = 0;
 	mapheaderinfo[num]->typeoflevel = 0;
 	mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
+	mapheaderinfo[num]->startrings = 0;
 	snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i));
 	mapheaderinfo[num]->musname[6] = 0;
 	mapheaderinfo[num]->mustrack = 0;
+	mapheaderinfo[num]->muspos = 0;
+	mapheaderinfo[num]->musinterfadeout = 0;
+	mapheaderinfo[num]->musintername[0] = '\0';
+	mapheaderinfo[num]->muspostbossname[6] = 0;
+	mapheaderinfo[num]->muspostbosstrack = 0;
+	mapheaderinfo[num]->muspostbosspos = 0;
+	mapheaderinfo[num]->muspostbossfadein = 0;
 	mapheaderinfo[num]->forcecharacter[0] = '\0';
 	mapheaderinfo[num]->weather = 0;
 	mapheaderinfo[num]->skynum = 1;
@@ -225,6 +238,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->unlockrequired = -1;
 	mapheaderinfo[num]->levelselect = 0;
 	mapheaderinfo[num]->bonustype = 0;
+	mapheaderinfo[num]->maxbonuslives = -1;
 	mapheaderinfo[num]->levelflags = 0;
 	mapheaderinfo[num]->menuflags = 0;
 #if 1 // equivalent to "FlickyList = DEMO"
@@ -386,30 +400,26 @@ static inline void P_LoadVertexes(lumpnum_t lumpnum)
 	Z_Free(data);
 }
 
-
-//
-// Computes the line length in fracunits, the OpenGL render needs this
-//
-
 /** Computes the length of a seg in fracunits.
-  * This is needed for splats.
   *
   * \param seg Seg to compute length for.
   * \return Length in fracunits.
   */
 fixed_t P_SegLength(seg_t *seg)
 {
-	fixed_t dx, dy;
-
-	// make a vector (start at origin)
-	dx = seg->v2->x - seg->v1->x;
-	dy = seg->v2->y - seg->v1->y;
-
-	return FixedHypot(dx, dy);
+	INT64 dx = (seg->v2->x - seg->v1->x)>>1;
+	INT64 dy = (seg->v2->y - seg->v1->y)>>1;
+	return FixedHypot(dx, dy)<<1;
 }
 
 #ifdef HWRENDER
-static inline float P_SegLengthf(seg_t *seg)
+/** Computes the length of a seg as a float.
+  * This is needed for OpenGL.
+  *
+  * \param seg Seg to compute length for.
+  * \return Length as a float.
+  */
+static inline float P_SegLengthFloat(seg_t *seg)
 {
 	float dx, dy;
 
@@ -445,11 +455,11 @@ static void P_LoadRawSegs(UINT8 *data, size_t i)
 		li->v1 = &vertexes[SHORT(ml->v1)];
 		li->v2 = &vertexes[SHORT(ml->v2)];
 
-#ifdef HWRENDER // not win32 only 19990829 by Kin
-		// used for the hardware render
-		if (rendermode != render_soft && rendermode != render_none)
+		li->length = P_SegLength(li);
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
 		{
-			li->flength = P_SegLengthf(li);
+			li->flength = P_SegLengthFloat(li);
 			//Hurdler: 04/12/2000: for now, only used in hardware mode
 			li->lightmaps = NULL; // list of static lightmap for this seg
 		}
@@ -677,6 +687,7 @@ static void P_LoadRawSectors(UINT8 *data, size_t i)
 		ss->ceilingpic = P_AddLevelFlat(ms->ceilingpic, foundflats);
 
 		ss->lightlevel = SHORT(ms->lightlevel);
+		ss->spawn_lightlevel = SHORT(ms->lightlevel);
 		ss->special = SHORT(ms->special);
 		ss->tag = SHORT(ms->tag);
 		ss->nexttag = ss->firsttag = -1;
@@ -712,12 +723,12 @@ static void P_LoadRawSectors(UINT8 *data, size_t i)
 		ss->moved = true;
 
 		ss->extra_colormap = NULL;
+		ss->spawn_extra_colormap = NULL;
 
 		ss->floor_xoffs = ss->ceiling_xoffs = ss->floor_yoffs = ss->ceiling_yoffs = 0;
 		ss->spawn_flr_xoffs = ss->spawn_ceil_xoffs = ss->spawn_flr_yoffs = ss->spawn_ceil_yoffs = 0;
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
 		ss->spawn_flrpic_angle = ss->spawn_ceilpic_angle = 0;
-		ss->bottommap = ss->midmap = ss->topmap = -1;
 		ss->gravity = NULL;
 		ss->cullheight = NULL;
 		ss->verticalflip = false;
@@ -808,10 +819,10 @@ void P_ReloadRings(void)
 	mapthing_t *hoopsToRespawn[4096];
 	mapthing_t *mt = mapthings;
 
-	// scan the thinkers to find rings/wings/hoops to unset
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	// scan the thinkers to find rings/spheres/hoops to unset
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -826,7 +837,9 @@ void P_ReloadRings(void)
 			}
 			continue;
 		}
-		if (!(mo->type == MT_RING || mo->type == MT_NIGHTSWING || mo->type == MT_COIN || mo->type == MT_BLUEBALL))
+		if (!(mo->type == MT_RING || mo->type == MT_COIN
+			|| mo->type == MT_BLUESPHERE || mo->type == MT_BOMBSPHERE
+			|| mo->type == MT_NIGHTSCHIP || mo->type == MT_NIGHTSSTAR))
 			continue;
 
 		// Don't auto-disintegrate things being pulled to us
@@ -840,9 +853,10 @@ void P_ReloadRings(void)
 	for (i = 0; i < nummapthings; i++, mt++)
 	{
 		// Notice an omission? We handle hoops differently.
-		if (mt->type == 300 || mt->type == 308 || mt->type == 309
-		 || mt->type == 1706 || (mt->type >= 600 && mt->type <= 609)
-		 || mt->type == 1800)
+		if (mt->type == mobjinfo[MT_RING].doomednum || mt->type == mobjinfo[MT_COIN].doomednum
+		 || mt->type == mobjinfo[MT_REDTEAMRING].doomednum || mt->type == mobjinfo[MT_BLUETEAMRING].doomednum
+		 || mt->type == mobjinfo[MT_BLUESPHERE].doomednum || mt->type == mobjinfo[MT_BOMBSPHERE].doomednum
+		 || (mt->type >= 600 && mt->type <= 609)) // circles and diagonals
 		{
 			mt->mobj = NULL;
 
@@ -850,12 +864,36 @@ void P_ReloadRings(void)
 			mt->z = (INT16)(R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)
 				->sector->floorheight>>FRACBITS);
 
-			P_SpawnHoopsAndRings (mt);
+			P_SpawnHoopsAndRings(mt, true);
 		}
 	}
 	for (i = 0; i < numHoops; i++)
 	{
-		P_SpawnHoopsAndRings(hoopsToRespawn[i]);
+		P_SpawnHoopsAndRings(hoopsToRespawn[i], false);
+	}
+}
+
+void P_SwitchSpheresBonusMode(boolean bonustime)
+{
+	mobj_t *mo;
+	thinker_t *th;
+
+	// scan the thinkers to find spheres to switch
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mo = (mobj_t *)th;
+
+		if (mo->type != MT_BLUESPHERE && mo->type != MT_NIGHTSCHIP
+			&& mo->type != MT_FLINGBLUESPHERE && mo->type != MT_FLINGNIGHTSCHIP)
+			continue;
+
+		if (!mo->health)
+			continue;
+
+		P_SetMobjState(mo, ((bonustime) ? mo->info->raisestate : mo->info->spawnstate));
 	}
 }
 
@@ -922,10 +960,6 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum)
 }
 #endif
 
-//
-// P_LoadThings
-//
-
 static void P_PrepareRawThings(UINT8 *data, size_t i)
 {
 	mapthing_t *mt;
@@ -968,7 +1002,7 @@ static void P_PrepareThings(lumpnum_t lumpnum)
 	Z_Free(data);
 }
 
-static void P_LoadThings(void)
+static void P_LoadThings(boolean loademblems)
 {
 	size_t i;
 	mapthing_t *mt;
@@ -993,6 +1027,9 @@ static void P_LoadThings(void)
 			|| mt->type == 1702) // MT_AXISTRANSFERLINE
 			continue; // These were already spawned
 
+		if (!loademblems && mt->type == mobjinfo[MT_EMBLEM].doomednum)
+			continue;
+
 		mt->mobj = NULL;
 		P_SpawnMapThing(mt);
 	}
@@ -1025,20 +1062,22 @@ static void P_LoadThings(void)
 		}
 
 		//decrement spawn values to the actual number because zero is valid.
-		if (emer1)
-			P_SpawnMobj(huntemeralds[emer1 - 1]->x<<FRACBITS,
-				huntemeralds[emer1 - 1]->y<<FRACBITS,
-				huntemeralds[emer1 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer1--)
+			P_SpawnMobj(huntemeralds[emer1]->x<<FRACBITS,
+				huntemeralds[emer1]->y<<FRACBITS,
+				huntemeralds[emer1]->z<<FRACBITS, MT_EMERHUNT);
 
-		if (emer2)
-			P_SpawnMobj(huntemeralds[emer2 - 1]->x<<FRACBITS,
-				huntemeralds[emer2 - 1]->y<<FRACBITS,
-				huntemeralds[emer2 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer2--)
+			P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer2]->x<<FRACBITS,
+				huntemeralds[emer2]->y<<FRACBITS,
+				huntemeralds[emer2]->z<<FRACBITS, MT_EMERHUNT),
+			mobjinfo[MT_EMERHUNT].spawnstate+1);
 
-		if (emer3)
-			P_SpawnMobj(huntemeralds[emer3 - 1]->x<<FRACBITS,
-				huntemeralds[emer3 - 1]->y<<FRACBITS,
-				huntemeralds[emer3 - 1]->z<<FRACBITS, MT_EMERHUNT);
+		if (emer3--)
+			P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer3]->x<<FRACBITS,
+				huntemeralds[emer3]->y<<FRACBITS,
+				huntemeralds[emer3]->z<<FRACBITS, MT_EMERHUNT),
+			mobjinfo[MT_EMERHUNT].spawnstate+2);
 	}
 
 	if (metalrecording) // Metal Sonic gets no rings to distract him.
@@ -1048,9 +1087,11 @@ static void P_LoadThings(void)
 	mt = mapthings;
 	for (i = 0; i < nummapthings; i++, mt++)
 	{
-		if (mt->type == 300 || mt->type == 308 || mt->type == 309
-		 || mt->type == 1706 || (mt->type >= 600 && mt->type <= 609)
-		 || mt->type == 1705 || mt->type == 1713 || mt->type == 1800)
+		if (mt->type == mobjinfo[MT_RING].doomednum || mt->type == mobjinfo[MT_COIN].doomednum
+		 || mt->type == mobjinfo[MT_REDTEAMRING].doomednum || mt->type == mobjinfo[MT_BLUETEAMRING].doomednum
+		 || mt->type == mobjinfo[MT_BLUESPHERE].doomednum || mt->type == mobjinfo[MT_BOMBSPHERE].doomednum
+		 || (mt->type >= 600 && mt->type <= 609) // circles and diagonals
+		 || mt->type == 1705 || mt->type == 1713) // hoops
 		{
 			mt->mobj = NULL;
 
@@ -1058,70 +1099,11 @@ static void P_LoadThings(void)
 			mt->z = (INT16)(R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)
 				->sector->floorheight>>FRACBITS);
 
-			P_SpawnHoopsAndRings (mt);
+			P_SpawnHoopsAndRings(mt, false);
 		}
 	}
 }
 
-static inline void P_SpawnEmblems(void)
-{
-	INT32 i, color;
-	mobj_t *emblemmobj;
-
-	for (i = 0; i < numemblems; i++)
-	{
-		if (emblemlocations[i].level != gamemap || emblemlocations[i].type > ET_SKIN)
-			continue;
-
-		emblemmobj = P_SpawnMobj(emblemlocations[i].x<<FRACBITS, emblemlocations[i].y<<FRACBITS,
-			emblemlocations[i].z<<FRACBITS, MT_EMBLEM);
-
-		I_Assert(emblemlocations[i].sprite >= 'A' && emblemlocations[i].sprite <= 'Z');
-		P_SetMobjStateNF(emblemmobj, emblemmobj->info->spawnstate + (emblemlocations[i].sprite - 'A'));
-
-		emblemmobj->health = i+1;
-		color = M_GetEmblemColor(&emblemlocations[i]);
-
-		emblemmobj->color = (UINT8)color;
-
-		if (emblemlocations[i].collected
-			|| (emblemlocations[i].type == ET_SKIN && emblemlocations[i].var != players[0].skin))
-		{
-			P_UnsetThingPosition(emblemmobj);
-			emblemmobj->flags |= MF_NOCLIP;
-			emblemmobj->flags &= ~MF_SPECIAL;
-			emblemmobj->flags |= MF_NOBLOCKMAP;
-			emblemmobj->frame |= (tr_trans50<<FF_TRANSSHIFT);
-			P_SetThingPosition(emblemmobj);
-		}
-		else
-		{
-			emblemmobj->frame &= ~FF_TRANSMASK;
-
-			if (emblemlocations[i].type == ET_GLOBAL)
-			{
-				emblemmobj->reactiontime = emblemlocations[i].var;
-				if (emblemlocations[i].var & GE_NIGHTSITEM)
-				{
-					emblemmobj->flags |= MF_NIGHTSITEM;
-					emblemmobj->flags &= ~MF_SPECIAL;
-					emblemmobj->flags2 |= MF2_DONTDRAW;
-				}
-			}
-		}
-	}
-}
-
-static void P_SpawnSecretItems(boolean loademblems)
-{
-	// Now let's spawn those funky emblem things! Tails 12-08-2002
-	if (netgame || multiplayer || (modifiedgame && !savemoddata)) // No cheating!!
-		return;
-
-	if (loademblems)
-		P_SpawnEmblems();
-}
-
 // Experimental groovy write function!
 void P_WriteThings(lumpnum_t lumpnum)
 {
@@ -1289,7 +1271,8 @@ static void P_LoadLineDefs2(void)
 		ld->backsector  = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
 
 		// Repeat count for midtexture
-		if ((ld->flags & ML_EFFECT5) && (ld->sidenum[1] != 0xffff))
+		if ((ld->flags & ML_EFFECT5) && (ld->sidenum[1] != 0xffff)
+			&& !(ld->special >= 300 && ld->special < 500)) // exempt linedef exec specials
 		{
 			sides[ld->sidenum[0]].repeatcnt = (INT16)(((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) >> 12);
 			sides[ld->sidenum[0]].textureoffset = (((unsigned)sides[ld->sidenum[0]].textureoffset >> FRACBITS) & 2047) << FRACBITS;
@@ -1300,6 +1283,9 @@ static void P_LoadLineDefs2(void)
 		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		switch(ld->special)
 		{
+		case 331: // Trigger linedef executor: Skin - Continuous
+		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)
 			{
@@ -1396,7 +1382,6 @@ static inline void P_LoadSideDefs(lumpnum_t lumpnum)
 static void P_LoadRawSideDefs2(void *data)
 {
 	UINT16 i;
-	INT32 num;
 
 	for (i = 0; i < numsides; i++)
 	{
@@ -1418,117 +1403,23 @@ static void P_LoadRawSideDefs2(void *data)
 			sd->sector = sec = &sectors[sector_num];
 		}
 
-		// refined to allow colormaps to work as wall textures if invalid as colormaps
-		// but valid as textures.
-
 		sd->sector = sec = &sectors[SHORT(msd->sector)];
 
+		sd->colormap_data = NULL;
+
 		// Colormaps!
 		switch (sd->special)
 		{
 			case 63: // variable colormap via 242 linedef
 			case 606: //SoM: 4/4/2000: Just colormap transfer
+			case 447: // Change colormap of tagged sectors! -- Monster Iestyn 14/06/18
+			case 455: // Fade colormaps! mazmazz 9/12/2018 (:flag_us:)
 				// SoM: R_CreateColormap will only create a colormap in software mode...
 				// Perhaps we should just call it instead of doing the calculations here.
-				if (rendermode == render_soft || rendermode == render_none)
-				{
-					if (msd->toptexture[0] == '#' || msd->bottomtexture[0] == '#')
-					{
-						sec->midmap = R_CreateColormap(msd->toptexture, msd->midtexture,
-							msd->bottomtexture);
-						sd->toptexture = sd->bottomtexture = 0;
-					}
-					else
-					{
-						if ((num = R_CheckTextureNumForName(msd->toptexture)) == -1)
-							sd->toptexture = 0;
-						else
-							sd->toptexture = num;
-						if ((num = R_CheckTextureNumForName(msd->midtexture)) == -1)
-							sd->midtexture = 0;
-						else
-							sd->midtexture = num;
-						if ((num = R_CheckTextureNumForName(msd->bottomtexture)) == -1)
-							sd->bottomtexture = 0;
-						else
-							sd->bottomtexture = num;
-					}
-					break;
-				}
-#ifdef HWRENDER
-				else
-				{
-					// for now, full support of toptexture only
-					if ((msd->toptexture[0] == '#' && msd->toptexture[1] && msd->toptexture[2] && msd->toptexture[3] && msd->toptexture[4] && msd->toptexture[5] && msd->toptexture[6])
-						|| (msd->bottomtexture[0] == '#' && msd->bottomtexture[1] && msd->bottomtexture[2] && msd->bottomtexture[3] && msd->bottomtexture[4] && msd->bottomtexture[5] && msd->bottomtexture[6]))
-					{
-						char *col;
-
-						sec->midmap = R_CreateColormap(msd->toptexture, msd->midtexture,
-							msd->bottomtexture);
-						sd->toptexture = sd->bottomtexture = 0;
-#define HEX2INT(x) (x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
-#define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0)
-						sec->extra_colormap = &extra_colormaps[sec->midmap];
-
-						if (msd->toptexture[0] == '#' && msd->toptexture[1] && msd->toptexture[2] && msd->toptexture[3] && msd->toptexture[4] && msd->toptexture[5] && msd->toptexture[6])
-						{
-							col = msd->toptexture;
-
-							sec->extra_colormap->rgba =
-								(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-								(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-								(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16);
-
-							// alpha
-							if (msd->toptexture[7])
-								sec->extra_colormap->rgba += (ALPHA2INT(col[7]) << 24);
-							else
-								sec->extra_colormap->rgba += (25 << 24);
-						}
-						else
-							sec->extra_colormap->rgba = 0;
-
-						if (msd->bottomtexture[0] == '#' && msd->bottomtexture[1] && msd->bottomtexture[2] && msd->bottomtexture[3] && msd->bottomtexture[4] && msd->bottomtexture[5] && msd->bottomtexture[6])
-						{
-							col = msd->bottomtexture;
-
-							sec->extra_colormap->fadergba =
-								(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-								(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-								(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16);
-
-							// alpha
-							if (msd->bottomtexture[7])
-								sec->extra_colormap->fadergba += (ALPHA2INT(col[7]) << 24);
-							else
-								sec->extra_colormap->fadergba += (25 << 24);
-						}
-						else
-							sec->extra_colormap->fadergba = 0x19000000; // default alpha, (25 << 24)
-#undef ALPHA2INT
-#undef HEX2INT
-					}
-					else
-					{
-						if ((num = R_CheckTextureNumForName(msd->toptexture)) == -1)
-							sd->toptexture = 0;
-						else
-							sd->toptexture = num;
-
-						if ((num = R_CheckTextureNumForName(msd->midtexture)) == -1)
-							sd->midtexture = 0;
-						else
-							sd->midtexture = num;
-
-						if ((num = R_CheckTextureNumForName(msd->bottomtexture)) == -1)
-							sd->bottomtexture = 0;
-						else
-							sd->bottomtexture = num;
-					}
-					break;
-				}
-#endif
+				sd->colormap_data = R_CreateColormap(msd->toptexture, msd->midtexture,
+					msd->bottomtexture);
+				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+				break;
 
 			case 413: // Change music
 			{
@@ -1539,19 +1430,33 @@ static void P_LoadRawSideDefs2(void *data)
 				{
 					M_Memcpy(process,msd->bottomtexture,8);
 					process[8] = '\0';
-					sd->bottomtexture = get_number(process)-1;
+					sd->bottomtexture = get_number(process);
 				}
-				M_Memcpy(process,msd->toptexture,8);
-				process[8] = '\0';
-				sd->text = Z_Malloc(7, PU_LEVEL, NULL);
-
-				// 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);
-				else // Assume it's a proper music name.
-					M_Memcpy(sd->text, process, 6);
-				sd->text[6] = 0;
+
+				if (!(msd->midtexture[0] == '-' && msd->midtexture[1] == '\0') || msd->midtexture[1] != '\0')
+				{
+					M_Memcpy(process,msd->midtexture,8);
+					process[8] = '\0';
+					sd->midtexture = get_number(process);
+				}
+
+				// always process if back sidedef, because we need that - symbol
+ 				sd->text = Z_Malloc(7, PU_LEVEL, NULL);
+				if (i == 1 || msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
+				{
+					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);
+					else // Assume it's a proper music name.
+						M_Memcpy(sd->text, process, 6);
+					sd->text[6] = 0;
+				}
+				else
+					sd->text[0] = 0;
 				break;
 			}
 
@@ -1591,7 +1496,11 @@ static void P_LoadRawSideDefs2(void *data)
 				break;
 			}
 
+			case 331: // Trigger linedef executor: Skin - Continuous
+			case 332: // Trigger linedef executor: Skin - Each time
+			case 333: // Trigger linedef executor: Skin - Once
 			case 443: // Calls a named Lua function
+			case 459: // Control text prompt (named tag)
 			{
 				char process[8*3+1];
 				memset(process,0,8*3+1);
@@ -1893,6 +1802,30 @@ static void P_CreateBlockMap(void)
 	}
 }
 
+// Split from P_LoadBlockMap for convenience
+// -- Monster Iestyn 08/01/18
+static void P_ReadBlockMapLump(INT16 *wadblockmaplump, size_t count)
+{
+	size_t i;
+	blockmaplump = Z_Calloc(sizeof (*blockmaplump) * count, PU_LEVEL, NULL);
+
+	// killough 3/1/98: Expand wad blockmap into larger internal one,
+	// by treating all offsets except -1 as unsigned and zero-extending
+	// them. This potentially doubles the size of blockmaps allowed,
+	// because Doom originally considered the offsets as always signed.
+
+	blockmaplump[0] = SHORT(wadblockmaplump[0]);
+	blockmaplump[1] = SHORT(wadblockmaplump[1]);
+	blockmaplump[2] = (INT32)(SHORT(wadblockmaplump[2])) & 0xffff;
+	blockmaplump[3] = (INT32)(SHORT(wadblockmaplump[3])) & 0xffff;
+
+	for (i = 4; i < count; i++)
+	{
+		INT16 t = SHORT(wadblockmaplump[i]);          // killough 3/1/98
+		blockmaplump[i] = t == -1 ? (INT32)-1 : (INT32) t & 0xffff;
+	}
+}
+
 //
 // P_LoadBlockMap
 //
@@ -1919,38 +1852,20 @@ static boolean P_LoadBlockMap(lumpnum_t lumpnum)
 		return false;
 
 	{
-		size_t i;
 		INT16 *wadblockmaplump = malloc(count); //INT16 *wadblockmaplump = W_CacheLumpNum (lump, PU_LEVEL);
-
-		if (wadblockmaplump) W_ReadLump(lumpnum, wadblockmaplump);
-		else return false;
+		if (!wadblockmaplump)
+			return false;
+		W_ReadLump(lumpnum, wadblockmaplump);
 		count /= 2;
-		blockmaplump = Z_Calloc(sizeof (*blockmaplump) * count, PU_LEVEL, 0);
-
-		// killough 3/1/98: Expand wad blockmap into larger internal one,
-		// by treating all offsets except -1 as unsigned and zero-extending
-		// them. This potentially doubles the size of blockmaps allowed,
-		// because Doom originally considered the offsets as always signed.
-
-		blockmaplump[0] = SHORT(wadblockmaplump[0]);
-		blockmaplump[1] = SHORT(wadblockmaplump[1]);
-		blockmaplump[2] = (INT32)(SHORT(wadblockmaplump[2])) & 0xffff;
-		blockmaplump[3] = (INT32)(SHORT(wadblockmaplump[3])) & 0xffff;
-
-		for (i = 4; i < count; i++)
-		{
-			INT16 t = SHORT(wadblockmaplump[i]);          // killough 3/1/98
-			blockmaplump[i] = t == -1 ? (INT32)-1 : (INT32) t & 0xffff;
-		}
-
+		P_ReadBlockMapLump(wadblockmaplump, count);
 		free(wadblockmaplump);
-
-		bmaporgx = blockmaplump[0]<<FRACBITS;
-		bmaporgy = blockmaplump[1]<<FRACBITS;
-		bmapwidth = blockmaplump[2];
-		bmapheight = blockmaplump[3];
 	}
 
+	bmaporgx = blockmaplump[0]<<FRACBITS;
+	bmaporgy = blockmaplump[1]<<FRACBITS;
+	bmapwidth = blockmaplump[2];
+	bmapheight = blockmaplump[3];
+
 	// clear out mobj chains
 	count = sizeof (*blocklinks)* bmapwidth*bmapheight;
 	blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
@@ -1984,6 +1899,53 @@ static boolean P_LoadBlockMap(lumpnum_t lumpnum)
 #endif
 }
 
+// This needs to be a separate function
+// because making both the WAD and PK3 loading code use
+// the same functions is trickier than it looks for blockmap
+// -- Monster Iestyn 09/01/18
+static boolean P_LoadRawBlockMap(UINT8 *data, size_t count, const char *lumpname)
+{
+#if 0
+	(void)data;
+	(void)count;
+	(void)lumpname;
+	return false;
+#else
+	// Check if the lump is named "BLOCKMAP"
+	if (!lumpname || memcmp(lumpname, "BLOCKMAP", 8) != 0)
+	{
+		CONS_Printf("No blockmap lump found for pk3!\n");
+		return false;
+	}
+
+	if (!count || count >= 0x20000)
+		return false;
+
+	//CONS_Printf("Reading blockmap lump for pk3...\n");
+
+	// no need to malloc anything, assume the data is uncompressed for now
+	count /= 2;
+	P_ReadBlockMapLump((INT16 *)data, count);
+
+	bmaporgx = blockmaplump[0]<<FRACBITS;
+	bmaporgy = blockmaplump[1]<<FRACBITS;
+	bmapwidth = blockmaplump[2];
+	bmapheight = blockmaplump[3];
+
+	// clear out mobj chains
+	count = sizeof (*blocklinks)* bmapwidth*bmapheight;
+	blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
+	blockmap = blockmaplump+4;
+
+#ifdef POLYOBJECTS
+	// haleyjd 2/22/06: setup polyobject blockmap
+	count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
+	polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
+#endif
+	return true;
+#endif
+}
+
 //
 // P_GroupLines
 // Builds sector line lists and subsector sector numbers.
@@ -2109,6 +2071,30 @@ static void P_LoadReject(lumpnum_t lumpnum)
 		rejectmatrix = W_CacheLumpNum(lumpnum, PU_LEVEL);
 }
 
+// PK3 version
+// -- Monster Iestyn 09/01/18
+static void P_LoadRawReject(UINT8 *data, size_t count, const char *lumpname)
+{
+	// Check if the lump is named "REJECT"
+	if (!lumpname || memcmp(lumpname, "REJECT\0\0", 8) != 0)
+	{
+		rejectmatrix = NULL;
+		CONS_Debug(DBG_SETUP, "P_LoadRawReject: No valid REJECT lump found\n");
+		return;
+	}
+
+	if (!count) // zero length, someone probably used ZDBSP
+	{
+		rejectmatrix = NULL;
+		CONS_Debug(DBG_SETUP, "P_LoadRawReject: REJECT lump has size 0, will not be loaded\n");
+	}
+	else
+	{
+		rejectmatrix = Z_Malloc(count, PU_LEVEL, NULL); // allocate memory for the reject matrix
+		M_Memcpy(rejectmatrix, data, count); // copy the data into it
+	}
+}
+
 #if 0
 static char *levellumps[] =
 {
@@ -2186,12 +2172,13 @@ static void P_LevelInitStuff(void)
 
 	localaiming = 0;
 	localaiming2 = 0;
+	modulothing = 0;
 
 	// special stage tokens, emeralds, and ring total
 	tokenbits = 0;
 	runemeraldmanager = false;
 	emeraldspawndelay = 60*TICRATE;
-	nummaprings = 0;
+	nummaprings = mapheaderinfo[gamemap-1]->startrings;
 
 	// emerald hunt
 	hunt1 = hunt2 = hunt3 = NULL;
@@ -2221,10 +2208,10 @@ static void P_LevelInitStuff(void)
 	// circuit, race and competition stuff
 	circuitmap = false;
 	numstarposts = 0;
-	totalrings = timeinmap = 0;
+	ssspheres = timeinmap = 0;
 
 	// special stage
-	stagefailed = false;
+	stagefailed = true; // assume failed unless proven otherwise - P_GiveEmerald or emerald touchspecial
 	// Reset temporary record data
 	memset(&ntemprecords, 0, sizeof(nightsdata_t));
 
@@ -2243,6 +2230,8 @@ static void P_LevelInitStuff(void)
 		}
 	}
 
+	countdown = countdown2 = exitfadestarted = 0;
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (canresetlives && (netgame || multiplayer) && playeringame[i] && (gametype == GT_COMPETITION || players[i].lives <= 0))
@@ -2251,41 +2240,41 @@ static void P_LevelInitStuff(void)
 			players[i].lives = cv_startinglives.value;
 		}
 
-		players[i].realtime = countdown = countdown2 = 0;
-
+		// obliteration station...
+		players[i].rings = players[i].spheres =\
+		 players[i].xtralife = players[i].deadtimer =\
+		 players[i].numboxes = players[i].totalring =\
+		 players[i].laps = players[i].aiming =\
+		 players[i].losstime = players[i].timeshit =\
+		 players[i].marescore = players[i].lastmarescore =\
+		 players[i].maxlink = players[i].startedtime =\
+		 players[i].finishedtime = players[i].finishedspheres =\
+		 players[i].finishedrings = players[i].lastmare =\
+		 players[i].lastmarelap = players[i].lastmarebonuslap =\
+		 players[i].totalmarelap = players[i].totalmarebonuslap =\
+		 players[i].marebegunat = players[i].textvar =\
+		 players[i].texttimer = players[i].linkcount =\
+		 players[i].linktimer = players[i].flyangle =\
+		 players[i].anotherflyangle = players[i].nightstime =\
+		 players[i].oldscale = players[i].mare = players[i].marelap =\
+		 players[i].marebonuslap = players[i].lapbegunat =\
+		 players[i].lapstartedtime = players[i].totalmarescore =\
+		 players[i].realtime = players[i].exiting = 0;
+
+		// i guess this could be part of the above but i feel mildly uncomfortable implicitly casting
 		players[i].gotcontinue = false;
 
-		players[i].xtralife = players[i].deadtimer = players[i].numboxes = players[i].totalring = players[i].laps = 0;
-		players[i].rings = 0;
-		players[i].aiming = 0;
-		players[i].pflags &= ~PF_GAMETYPEOVER;
-
-		players[i].losstime = 0;
-		players[i].timeshit = 0;
-
-		players[i].marescore = players[i].lastmarescore = players[i].maxlink = 0;
-		players[i].startedtime = players[i].finishedtime = players[i].finishedrings = 0;
-		players[i].lastmare = players[i].marebegunat = 0;
-
-		// Don't show anything
-		players[i].textvar = players[i].texttimer = 0;
-
-		players[i].linkcount = players[i].linktimer = 0;
-		players[i].flyangle = players[i].anotherflyangle = 0;
-		players[i].nightstime = players[i].mare = 0;
-		P_SetTarget(&players[i].capsule, NULL);
+		// aha, the first evidence this shouldn't be a memset!
 		players[i].drillmeter = 40*20;
 
-		players[i].exiting = 0;
 		P_ResetPlayer(&players[i]);
+		// hit these too
+		players[i].pflags &= ~(PF_GAMETYPEOVER|PF_TRANSFERTOCLOSEST);
 
-		players[i].mo = NULL;
-
-		// we must unset axis details too
-		players[i].axis1 = players[i].axis2 = NULL;
-
-		// and this stupid flag as a result
-		players[i].pflags &= ~PF_TRANSFERTOCLOSEST;
+		// unset ALL the pointers. P_SetTarget isn't needed here because if this
+		// function is being called we're just going to clobber the data anyways
+		players[i].mo = players[i].followmobj = players[i].awayviewmobj =\
+		players[i].capsule = players[i].axis1 = players[i].axis2 = players[i].drone = NULL;
 	}
 }
 
@@ -2297,7 +2286,6 @@ static void P_LevelInitStuff(void)
 void P_LoadThingsOnly(void)
 {
 	// Search through all the thinkers.
-	mobj_t *mo;
 	thinker_t *think;
 	INT32 i, viewid = -1, centerid = -1; // for skyboxes
 
@@ -2312,27 +2300,30 @@ void P_LoadThingsOnly(void)
 		}
 
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
-
-		mo = (mobj_t *)think;
-
-		if (mo)
-			P_RemoveMobj(mo);
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+		P_RemoveMobj((mobj_t *)think);
 	}
 
 	P_LevelInitStuff();
 
-	P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
-	P_LoadThings();
-
+	if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
+	{ // HACK: Open wad file rather quickly so we can use the things lump
+		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
+		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
+		fileinfo += ML_THINGS; // we only need the THINGS lump
+		P_PrepareRawThings(wadData + fileinfo->filepos, fileinfo->size);
+		Z_Free(wadData); // we're done with this now
+	}
+	else // phew it's just a WAD
+		P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
+	P_LoadThings(true);
 
 	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
 	skyboxmo[0] = skyboxviewpnts[(viewid >= 0) ? viewid : 0];
 	skyboxmo[1] = skyboxcenterpnts[(centerid >= 0) ? centerid : 0];
-	P_SpawnSecretItems(true);
 }
 
 /** Compute MD5 message digest for bytes read from memory source
@@ -2556,6 +2547,43 @@ static void P_LoadNightsGhosts(void)
 	free(gpath);
 }
 
+static void P_SetupCamera(void)
+{
+	if (players[displayplayer].mo && (server || addedtogame))
+	{
+		camera.x = players[displayplayer].mo->x;
+		camera.y = players[displayplayer].mo->y;
+		camera.z = players[displayplayer].mo->z;
+		camera.angle = players[displayplayer].mo->angle;
+		camera.subsector = R_PointInSubsector(camera.x, camera.y); // make sure camera has a subsector set -- Monster Iestyn (12/11/18)
+	}
+	else
+	{
+		mapthing_t *thing;
+
+		switch (gametype)
+		{
+		case GT_MATCH:
+		case GT_TAG:
+			thing = deathmatchstarts[0];
+			break;
+
+		default:
+			thing = playerstarts[0];
+			break;
+		}
+
+		if (thing)
+		{
+			camera.x = thing->x;
+			camera.y = thing->y;
+			camera.z = thing->z;
+			camera.angle = FixedAngle((fixed_t)thing->angle << FRACBITS);
+			camera.subsector = R_PointInSubsector(camera.x, camera.y); // make sure camera has a subsector set -- Monster Iestyn (12/11/18)
+		}
+	}
+}
+
 static boolean CanSaveLevel(INT32 mapnum)
 {
 	if (ultimatemode) // never save in ultimate (probably redundant with cursaveslot also being checked)
@@ -2581,7 +2609,6 @@ boolean P_SetupLevel(boolean skipprecip)
 	// use gamemap to get map number.
 	// 99% of the things already did, so.
 	// Map header should always be in place at this point
-	char *lumpfullName;
 	INT32 i, loadprecip = 1, ranspecialwipe = 0;
 	INT32 loademblems = 1;
 	INT32 fromnetsave = 0;
@@ -2597,13 +2624,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	CON_Drawer(); // let the user know what we are going to do
 	I_FinishUpdate(); // page flip or blit buffer
 
-
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 
@@ -2655,24 +2676,45 @@ boolean P_SetupLevel(boolean skipprecip)
 	// will be set by player think.
 	players[consoleplayer].viewz = 1;
 
+	// Cancel all d_main.c fadeouts (keep fade in though).
+	wipegamestate = FORCEWIPEOFF;
+
 	// Special stage fade to white
 	// This is handled BEFORE sounds are stopped.
-	if (rendermode != render_none && G_IsSpecialStage(gamemap))
+	if (modeattacking && !demoplayback && (pausedelay == INT32_MIN))
+		ranspecialwipe = 2;
+	else if (rendermode != render_none && G_IsSpecialStage(gamemap))
 	{
 		tic_t starttime = I_GetTime();
 		tic_t endtime = starttime + (3*TICRATE)/2;
+		tic_t nowtime;
 
 		S_StartSound(NULL, sfx_s3kaf);
 
+		// Fade music! Time it to S3KAF: 0.25 seconds is snappy.
+		if (cv_resetmusic.value ||
+			strnicmp(S_MusicName(),
+				(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7))
+			S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
+
 		F_WipeStartScreen();
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
 
 		F_WipeEndScreen();
 		F_RunWipe(wipedefs[wipe_speclevel_towhite], false);
 
+		nowtime = lastwipetic;
+
 		// Hold on white for extra effect.
-		while (I_GetTime() < endtime)
-			I_Sleep();
+		while (nowtime < endtime)
+		{
+			// wait loop
+			while (!((nowtime = I_GetTime()) - lastwipetic))
+				I_Sleep();
+			lastwipetic = nowtime;
+			if (moviemode) // make sure we save frames for the white hold too
+				M_SaveFrame();
+		}
 
 		ranspecialwipe = 1;
 	}
@@ -2681,11 +2723,13 @@ boolean P_SetupLevel(boolean skipprecip)
 	S_StopSounds();
 	S_ClearSfx();
 
-	// As oddly named as this is, this handles music only.
-	// We should be fine starting it here.
-	/// ... as long as this isn't a titlemap transition, that is
-	if (!titlemapinaction)
-		S_Start();
+	// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
+	// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
+	if (!titlemapinaction && (cv_resetmusic.value ||
+		strnicmp(S_MusicName(),
+			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+		S_FadeMusic(0, FixedMul(
+			FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
 
 	// Let's fade to black here
 	// But only if we didn't do the special stage wipe
@@ -2695,23 +2739,48 @@ boolean P_SetupLevel(boolean skipprecip)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 		F_WipeEndScreen();
-		F_RunWipe(wipedefs[wipe_level_toblack], false);
+		// for titlemap: run a specific wipe if specified
+		// needed for exiting time attack
+		if (wipetypepre != INT16_MAX)
+			F_RunWipe(
+				(wipetypepre >= 0 && F_WipeExists(wipetypepre)) ? wipetypepre : wipedefs[wipe_level_toblack],
+				false);
+		wipetypepre = -1;
 	}
 
-	// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
-	if (!titlemapinaction && rendermode != render_none)
+	if (!titlemapinaction)
 	{
-		// Don't include these in the fade!
-		char tx[64];
-		V_DrawSmallString(1, 191, V_ALLOWLOWERCASE, M_GetText("Speeding off to..."));
-		snprintf(tx, 63, "%s%s%s",
-			mapheaderinfo[gamemap-1]->lvlttl,
-			(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " ZONE",
-			(mapheaderinfo[gamemap-1]->actnum > 0) ? va(", Act %d",mapheaderinfo[gamemap-1]->actnum) : "");
-		V_DrawSmallString(1, 195, V_ALLOWLOWERCASE, tx);
-		I_UpdateNoVsync();
+		if (ranspecialwipe == 2)
+		{
+			pausedelay = -3; // preticker plus one
+			S_StartSound(NULL, sfx_s3k73);
+		}
+
+		// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
+		if (rendermode != render_none)
+		{
+			// Don't include these in the fade!
+			char tx[64];
+			V_DrawSmallString(1, 191, V_ALLOWLOWERCASE, M_GetText("Speeding off to..."));
+			snprintf(tx, 63, "%s%s%s",
+				mapheaderinfo[gamemap-1]->lvlttl,
+				(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " ZONE",
+				(mapheaderinfo[gamemap-1]->actnum > 0) ? va(", Act %d",mapheaderinfo[gamemap-1]->actnum) : "");
+			V_DrawSmallString(1, 195, V_ALLOWLOWERCASE, tx);
+			I_UpdateNoVsync();
+		}
+
+		// As oddly named as this is, this handles music only.
+		// We should be fine starting it here.
+		// Don't do this during titlemap, because the menu code handles music by itself.
+		S_Start();
 	}
 
+	levelfadecol = (ranspecialwipe) ? 0 : 31;
+
+	// Close text prompt before freeing the old level
+	F_EndTextPrompt(false, true);
+
 #ifdef HAVE_BLUA
 	LUA_InvalidateLevel();
 #endif
@@ -2759,19 +2828,30 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	P_MakeMapMD5(lastloadedmaplumpnum, &mapmd5);
 
-
 	// HACK ALERT: Cache the WAD, get the map data into the tables, free memory.
 	// As it is implemented right now, we're assuming an uncompressed WAD.
 	// (As in, a normal PWAD, not ZWAD or anything. The lump itself can be compressed.)
 	// We're not accounting for extra lumps and scrambled lump positions. Any additional data will cause an error.
-	lumpfullName = (wadfiles[WADFILENUM(lastloadedmaplumpnum)]->lumpinfo + LUMPNUM(lastloadedmaplumpnum))->name2;
-	if (!strnicmp(lumpfullName + strlen(lumpfullName) - 4, ".wad", 4))
+	if (W_IsLumpWad(lastloadedmaplumpnum))
 	{
 		// Remember that we're assuming that the WAD will have a specific set of lumps in a specific order.
 		UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
 		//filelump_t *fileinfo = wadData + ((wadinfo_t *)wadData)->infotableofs;
 		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
+		UINT32 numlumps = ((wadinfo_t *)wadData)->numlumps;
 
+		if (numlumps < ML_REJECT) // at least 9 lumps should be in the wad for a map to be loaded
+		{
+			I_Error("Bad WAD file for map %s!\n", maplumpname);
+		}
+
+		if (numlumps > ML_BLOCKMAP) // enough room for a BLOCKMAP lump at least
+		{
+			loadedbm = P_LoadRawBlockMap(
+							wadData + (fileinfo + ML_BLOCKMAP)->filepos,
+							(fileinfo + ML_BLOCKMAP)->size,
+							(fileinfo + ML_BLOCKMAP)->name);
+		}
 		P_LoadRawVertexes(wadData + (fileinfo + ML_VERTEXES)->filepos, (fileinfo + ML_VERTEXES)->size);
 		P_LoadRawSectors(wadData + (fileinfo + ML_SECTORS)->filepos, (fileinfo + ML_SECTORS)->size);
 		P_LoadRawSideDefs((fileinfo + ML_SIDEDEFS)->size);
@@ -2780,18 +2860,27 @@ boolean P_SetupLevel(boolean skipprecip)
 		P_LoadRawSubsectors(wadData + (fileinfo + ML_SSECTORS)->filepos, (fileinfo + ML_SSECTORS)->size);
 		P_LoadRawNodes(wadData + (fileinfo + ML_NODES)->filepos, (fileinfo + ML_NODES)->size);
 		P_LoadRawSegs(wadData + (fileinfo + ML_SEGS)->filepos, (fileinfo + ML_SEGS)->size);
+		if (numlumps > ML_REJECT) // enough room for a REJECT lump at least
+		{
+			P_LoadRawReject(
+					wadData + (fileinfo + ML_REJECT)->filepos,
+					(fileinfo + ML_REJECT)->size,
+					(fileinfo + ML_REJECT)->name);
+		}
 
 		// Important: take care of the ordering of the next functions.
 		if (!loadedbm)
 			P_CreateBlockMap(); // Graue 02-29-2004
-		R_MakeColormaps();
 		P_LoadLineDefs2();
 		P_GroupLines();
 		numdmstarts = numredctfstarts = numbluectfstarts = 0;
 
 		// reset the player starts
 		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = NULL;
+			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
+
+		for (i = 0; i < MAX_DM_STARTS; i++)
+			deathmatchstarts[i] = NULL;
 
 		for (i = 0; i < 2; i++)
 			skyboxmo[i] = NULL;
@@ -2821,14 +2910,16 @@ boolean P_SetupLevel(boolean skipprecip)
 		// Important: take care of the ordering of the next functions.
 		if (!loadedbm)
 			P_CreateBlockMap(); // Graue 02-29-2004
-		R_MakeColormaps();
 		P_LoadLineDefs2();
 		P_GroupLines();
 		numdmstarts = numredctfstarts = numbluectfstarts = 0;
 
 		// reset the player starts
 		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = NULL;
+			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
+
+		for (i = 0; i < MAX_DM_STARTS; i++)
+			deathmatchstarts[i] = NULL;
 
 		for (i = 0; i < 2; i++)
 			skyboxmo[i] = NULL;
@@ -2846,15 +2937,13 @@ boolean P_SetupLevel(boolean skipprecip)
 	P_InitSpecials();
 
 #ifdef ESLOPE
-	P_ResetDynamicSlopes();
+	P_ResetDynamicSlopes(fromnetsave);
 #endif
 
-	P_LoadThings();
+	P_LoadThings(loademblems);
 	skyboxmo[0] = skyboxviewpnts[0];
 	skyboxmo[1] = skyboxcenterpnts[0];
 
-	P_SpawnSecretItems(loademblems);
-
 	for (numcoopstarts = 0; numcoopstarts < MAXPLAYERS; numcoopstarts++)
 		if (!playerstarts[numcoopstarts])
 			break;
@@ -2976,37 +3065,7 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	if (!dedicated)
 	{
-		if (players[displayplayer].mo && (server || addedtogame))
-		{
-			camera.x = players[displayplayer].mo->x;
-			camera.y = players[displayplayer].mo->y;
-			camera.z = players[displayplayer].mo->z;
-			camera.angle = players[displayplayer].mo->angle;
-		}
-		else
-		{
-			mapthing_t *thing;
-
-			switch (gametype)
-			{
-			case GT_MATCH:
-			case GT_TAG:
-				thing = deathmatchstarts[0];
-				break;
-
-			default:
-				thing = playerstarts[0];
-				break;
-			}
-
-			if (thing)
-			{
-				camera.x = thing->x;
-				camera.y = thing->y;
-				camera.z = thing->z;
-				camera.angle = FixedAngle((fixed_t)thing->angle << FRACBITS);
-			}
-		}
+		P_SetupCamera();
 
 		// Salt: CV_ClearChangedFlags() messes with your settings :(
 		/*if (!cv_cam_height.changed)
@@ -3069,14 +3128,14 @@ boolean P_SetupLevel(boolean skipprecip)
 	P_MapEnd();
 
 	// Remove the loading shit from the screen
-	if (rendermode != render_none)
-		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (ranspecialwipe) ? 0 : 31);
+	if (rendermode != render_none && !titlemapinaction)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
 
 	if (precache || dedicated)
 		R_PrecacheLevel();
 
 	nextmapoverride = 0;
-	skipstats = false;
+	skipstats = 0;
 
 	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
 		mapvisited[gamemap-1] |= MV_VISITED;
@@ -3105,7 +3164,6 @@ boolean P_SetupLevel(boolean skipprecip)
 		savedata.lives = 0;
 	}
 
-	skyVisible = skyVisible1 = skyVisible2 = true; // assume the skybox is visible on level load.
 	if (loadprecip) // uglier hack
 	{ // to make a newly loaded level start on the second frame.
 		INT32 buf = gametic % BACKUPTICS;
@@ -3120,6 +3178,45 @@ boolean P_SetupLevel(boolean skipprecip)
 #endif
 	}
 
+	// Stage title!
+	if (rendermode != render_none
+		&& (!titlemapinaction)
+		&& ranspecialwipe != 2
+		&& *mapheaderinfo[gamemap-1]->lvlttl != '\0'
+#ifdef HAVE_BLUA
+		&& LUA_HudEnabled(hud_stagetitle)
+#endif
+	)
+	{
+		tic_t starttime = I_GetTime();
+		tic_t endtime = starttime + (10*NEWTICRATERATIO);
+		tic_t nowtime = starttime;
+		tic_t lasttime = starttime;
+		while (nowtime < endtime)
+		{
+			// draw loop
+			while (!((nowtime = I_GetTime()) - lasttime))
+				I_Sleep();
+			lasttime = nowtime;
+
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
+			stplyr = &players[consoleplayer];
+			ST_drawLevelTitle(nowtime - starttime);
+			if (splitscreen)
+			{
+				stplyr = &players[secondarydisplayplayer];
+				ST_drawLevelTitle(nowtime - starttime);
+			}
+
+			I_OsPolling();
+			I_UpdateNoBlit();
+			I_FinishUpdate(); // page flip or blit buffer
+
+			if (moviemode) // make sure we save frames for the white hold too
+				M_SaveFrame();
+		}
+	}
+
 	return true;
 }
 
@@ -3133,7 +3230,7 @@ boolean P_RunSOC(const char *socfilename)
 	lumpnum_t lump;
 
 	if (strstr(socfilename, ".soc") != NULL)
-		return P_AddWadFile(socfilename, NULL);
+		return P_AddWadFile(socfilename);
 
 	lump = W_CheckNumForName(socfilename);
 	if (lump == LUMPERROR)
@@ -3163,6 +3260,7 @@ void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num)
 				CONS_Debug(DBG_SETUP, "Sound %.8s replaced\n", lumpinfo->name);
 
 				I_FreeSfx(&S_sfx[j]);
+				break; // there shouldn't be two sounds with the same name, so stop looking
 			}
 		}
 	}
@@ -3214,13 +3312,15 @@ 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
 //
-boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
+boolean P_AddWadFile(const char *wadfilename)
 {
 	size_t i, j, sreplaces = 0, mreplaces = 0, digmreplaces = 0;
 	UINT16 numlumps, wadnum;
-	INT16 firstmapreplaced = 0, num;
 	char *name;
 	lumpinfo_t *lumpinfo;
+
+	//boolean texturechange = false; ///\todo Useless; broken when back-frontporting PK3 changes?
+	boolean mapsadded = false;
 	boolean replacedcurrentmap = false;
 
 	// Vars to help us with the position start and amount of each resource type.
@@ -3237,10 +3337,10 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 //	UINT16 mapPos, mapNum = 0;
 
 	// Init file.
-	if ((numlumps = W_InitFile(wadfilename)) == INT16_MAX)
+	if ((numlumps = W_InitFile(wadfilename, false)) == INT16_MAX)
 	{
 		refreshdirmenu |= REFRESHDIR_NOTLOADED;
-		CONS_Printf(M_GetText("Errors occured while loading %s; not added.\n"), wadfilename);
+		CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), wadfilename);
 		return false;
 	}
 	else
@@ -3254,7 +3354,7 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 		for (i = 0; i < numlumps; i++, lumpinfo++)
 		{
 //			lumpinfo = FindFolder("Lua/",      &luaPos, &luaNum, lumpinfo, &numlumps, &i);
-//			lumpinfo = FindFolder("SOCs/",     &socPos, &socNum, lumpinfo, &numlumps, &i);
+//			lumpinfo = FindFolder("SOC/",      &socPos, &socNum, lumpinfo, &numlumps, &i);
 			lumpinfo = FindFolder("Sounds/",   &sfxPos, &sfxNum, lumpinfo, &numlumps, &i);
 			lumpinfo = FindFolder("Music/",    &musPos, &musNum, lumpinfo, &numlumps, &i);
 //			lumpinfo = FindFolder("Sprites/",  &sprPos, &sprNum, lumpinfo, &numlumps, &i);
@@ -3290,17 +3390,21 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 			name = lumpinfo->name;
 			if (name[0] == 'D')
 			{
-				if (name[1] == 'S') for (j = 1; j < NUMSFX; j++)
+				if (name[1] == 'S')
 				{
-					if (S_sfx[j].name && !strnicmp(S_sfx[j].name, name + 2, 6))
+					for (j = 1; j < NUMSFX; j++)
 					{
-						// the sound will be reloaded when needed,
-						// since sfx->data will be NULL
-						CONS_Debug(DBG_SETUP, "Sound %.8s replaced\n", name);
+						if (S_sfx[j].name && !strnicmp(S_sfx[j].name, name + 2, 6))
+						{
+							// the sound will be reloaded when needed,
+							// since sfx->data will be NULL
+							CONS_Debug(DBG_SETUP, "Sound %.8s replaced\n", name);
 
-						I_FreeSfx(&S_sfx[j]);
+							I_FreeSfx(&S_sfx[j]);
 
-						sreplaces++;
+							sreplaces++;
+							break; // there shouldn't be two sounds with the same name, so stop looking
+						}
 					}
 				}
 				else if (name[1] == '_')
@@ -3324,7 +3428,10 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 	if (!devparm && digmreplaces)
 		CONS_Printf(M_GetText("%s digital musics replaced\n"), sizeu1(digmreplaces));
 
-	// Search for sprite replacements.
+
+	//
+	// search for sprite replacements
+	//
 	R_AddSpriteDefs(wadnum);
 
 	// Reload it all anyway, just in case they
@@ -3339,13 +3446,13 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 	ST_UnloadGraphics();
 	HU_LoadGraphics();
 	ST_LoadGraphics();
-	ST_ReloadSkinFaceGraphics();
 
 	//
 	// look for skins
 	//
 	R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
 	R_PatchSkins(wadnum); // toast: PATCH PATCH
+	ST_ReloadSkinFaceGraphics();
 
 	//
 	// search for maps
@@ -3354,10 +3461,9 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 	for (i = 0; i < numlumps; i++, lumpinfo++)
 	{
 		name = lumpinfo->name;
-		num = firstmapreplaced;
-
 		if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P') // Ignore the headers
 		{
+			INT16 num;
 			if (name[5]!='\0')
 				continue;
 			num = (INT16)M_MapNumber(name[3], name[4]);
@@ -3367,16 +3473,10 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 				replacedcurrentmap = true;
 
 			CONS_Printf("%s\n", name);
-		}
-
-		if (num && (num < firstmapreplaced || !firstmapreplaced))
-		{
-			firstmapreplaced = num;
-			if (firstmapname)
-				*firstmapname = name;
+			mapsadded = true;
 		}
 	}
-	if (!firstmapreplaced)
+	if (!mapsadded)
 		CONS_Printf(M_GetText("No maps added\n"));
 
 	// reload status bar (warning should have valid player!)
diff --git a/src/p_setup.h b/src/p_setup.h
index de763bed0c274e0eeacc98630a8c4f330c81571d..7e8a5d7e6bd31185d48c936eb95e7b33cf847413 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,6 +27,7 @@ extern mapthing_t *deathmatchstarts[MAX_DM_STARTS];
 extern INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts;
 
 extern boolean levelloading;
+extern UINT8 levelfadecol;
 
 extern lumpnum_t lastloadedmaplumpnum; // for comparative savegame
 //
@@ -59,7 +60,7 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 #endif
 void P_LoadThingsOnly(void);
 boolean P_SetupLevel(boolean skipprecip);
-boolean P_AddWadFile(const char *wadfilename, char **firstmapname);
+boolean P_AddWadFile(const char *wadfilename);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
 void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num);
@@ -72,6 +73,7 @@ void P_DeleteFlickies(INT16 i);
 
 // Needed for NiGHTS
 void P_ReloadRings(void);
+void P_SwitchSpheresBonusMode(boolean bonustime);
 void P_DeleteGrades(INT16 i);
 void P_AddGradesForMare(INT16 i, UINT8 mare, char *gtext);
 UINT8 P_GetGrade(UINT32 pscore, INT16 map, UINT8 mare);
diff --git a/src/p_sight.c b/src/p_sight.c
index 132f993cfe1a1a95e4f083438fcd79eec2a4533b..aa59314f19728b6376d6ceddd2a083e44254142f 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -14,6 +14,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "p_local.h"
+#include "p_slopes.h"
 #include "r_main.h"
 #include "r_state.h"
 
@@ -216,6 +217,10 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 		const sector_t *front, *back;
 		const vertex_t *v1,*v2;
 		fixed_t frac;
+		fixed_t frontf, backf, frontc, backc;
+#ifdef ESLOPE
+		fixed_t fracx, fracy;
+#endif
 
 		// already checked other side?
 		if (line->validcount == validcount)
@@ -250,36 +255,51 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 		if (!(line->flags & ML_TWOSIDED))
 			return false;
 
+		// calculate fractional intercept (how far along we are divided by how far we are from t2)
+		frac = P_InterceptVector2(&los->strace, &divl);
+
+		front = seg->frontsector;
+		back  = seg->backsector;
+#ifdef ESLOPE
+		// calculate position at intercept
+		fracx = los->strace.x + FixedMul(los->strace.dx, frac);
+		fracy = los->strace.y + FixedMul(los->strace.dy, frac);
+		// calculate sector heights
+		frontf = (front->f_slope) ? P_GetZAt(front->f_slope, fracx, fracy) : front->floorheight;
+		frontc = (front->c_slope) ? P_GetZAt(front->c_slope, fracx, fracy) : front->ceilingheight;
+		backf  = (back->f_slope)  ? P_GetZAt(back->f_slope, fracx, fracy)  : back->floorheight;
+		backc  = (back->c_slope)  ? P_GetZAt(back->c_slope, fracx, fracy)  : back->ceilingheight;
+#else
+		frontf = front->floorheight;
+		frontc = front->ceilingheight;
+		backf  = back->floorheight;
+		backc  = back->ceilingheight;
+#endif
 		// crosses a two sided line
 		// no wall to block sight with?
-		if ((front = seg->frontsector)->floorheight ==
-			(back = seg->backsector)->floorheight   &&
-			front->ceilingheight == back->ceilingheight)
+		if (frontf == backf && frontc == backc
+		&& !front->ffloors & !back->ffloors) // (and no FOFs)
 			continue;
 
 		// possible occluder
 		// because of ceiling height differences
-		popentop = front->ceilingheight < back->ceilingheight ?
-			front->ceilingheight : back->ceilingheight ;
+		popentop = min(frontc, backc);
 
 		// because of floor height differences
-		popenbottom = front->floorheight > back->floorheight ?
-			front->floorheight : back->floorheight ;
+		popenbottom = max(frontf, backf);
 
 		// quick test for totally closed doors
 		if (popenbottom >= popentop)
 			return false;
 
-		frac = P_InterceptVector2(&los->strace, &divl);
-
-		if (front->floorheight != back->floorheight)
+		if (frontf != backf)
 		{
 			fixed_t slope = FixedDiv(popenbottom - los->sightzstart , frac);
 			if (slope > los->bottomslope)
 				los->bottomslope = slope;
 		}
 
-		if (front->ceilingheight != back->ceilingheight)
+		if (frontc != backc)
 		{
 			fixed_t slope = FixedDiv(popentop - los->sightzstart , frac);
 			if (slope < los->topslope)
@@ -288,6 +308,58 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 
 		if (los->topslope <= los->bottomslope)
 			return false;
+
+		// Monster Iestyn: check FOFs!
+		if (front->ffloors || back->ffloors)
+		{
+			ffloor_t *rover;
+			fixed_t topslope, bottomslope;
+			fixed_t topz, bottomz;
+			// check front sector's FOFs first
+			for (rover = front->ffloors; rover; rover = rover->next)
+			{
+				if (!(rover->flags & FF_EXISTS)
+					|| !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT)
+				{
+					continue;
+				}
+
+#ifdef ESLOPE
+				topz    = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight;
+				bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight;
+#else
+				topz    = *rover->topheight;
+				bottomz = *rover->bottomheight;
+#endif
+				topslope    = FixedDiv(topz - los->sightzstart , frac);
+				bottomslope = FixedDiv(bottomz - los->sightzstart , frac);
+				if (topslope >= los->topslope && bottomslope <= los->bottomslope)
+					return false; // view completely blocked
+			}
+			// check back sector's FOFs as well
+			for (rover = back->ffloors; rover; rover = rover->next)
+			{
+				if (!(rover->flags & FF_EXISTS)
+					|| !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT)
+				{
+					continue;
+				}
+
+#ifdef ESLOPE
+				topz    = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight;
+				bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight;
+#else
+				topz    = *rover->topheight;
+				bottomz = *rover->bottomheight;
+#endif
+				topslope    = FixedDiv(topz - los->sightzstart , frac);
+				bottomslope = FixedDiv(bottomz - los->sightzstart , frac);
+				if (topslope >= los->topslope && bottomslope <= los->bottomslope)
+					return false; // view completely blocked
+			}
+			// TODO: figure out if it's worth considering partially blocked cases or not?
+			// maybe to adjust los's top/bottom slopes if needed
+		}
 	}
 
 	// passed the subsector ok
@@ -398,6 +470,8 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 	if (s1 == s2) // Both sectors are the same.
 	{
 		ffloor_t *rover;
+		fixed_t topz1, bottomz1; // top, bottom heights at t1's position
+		fixed_t topz2, bottomz2; // likewise but for t2
 
 		for (rover = s1->ffloors; rover; rover = rover->next)
 		{
@@ -410,9 +484,30 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 				continue;
 			}
 
+#ifdef ESLOPE
+			if (*rover->t_slope)
+			{
+				topz1 = P_GetZAt(*rover->t_slope, t1->x, t1->y);
+				topz2 = P_GetZAt(*rover->t_slope, t2->x, t2->y);
+			}
+			else
+				topz1 = topz2 = *rover->topheight;
+
+			if (*rover->b_slope)
+			{
+				bottomz1 = P_GetZAt(*rover->b_slope, t1->x, t1->y);
+				bottomz2 = P_GetZAt(*rover->b_slope, t2->x, t2->y);
+			}
+			else
+				bottomz1 = bottomz2 = *rover->bottomheight;
+#else
+			topz1 = topz2 = *rover->topheight;
+			bottomz1 = bottomz2 = *rover->bottomheight;
+#endif
+
 			// Check for blocking floors here.
-			if ((los.sightzstart < *rover->bottomheight && t2->z >= *rover->topheight)
-				|| (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->bottomheight))
+			if ((los.sightzstart < bottomz1 && t2->z >= topz2)
+				|| (los.sightzstart >= topz1 && t2->z + t2->height < bottomz2))
 			{
 				// no way to see through that
 				return false;
@@ -423,19 +518,19 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 
 			if (!(rover->flags & FF_INVERTPLANES))
 			{
-				if (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->topheight)
+				if (los.sightzstart >= topz1 && t2->z + t2->height < topz2)
 					return false; // blocked by upper outside plane
 
-				if (los.sightzstart < *rover->bottomheight && t2->z >= *rover->bottomheight)
+				if (los.sightzstart < bottomz1 && t2->z >= bottomz2)
 					return false; // blocked by lower outside plane
 			}
 
 			if (rover->flags & FF_INVERTPLANES || rover->flags & FF_BOTHPLANES)
 			{
-				if (los.sightzstart < *rover->topheight && t2->z >= *rover->topheight)
+				if (los.sightzstart < topz1 && t2->z >= topz2)
 					return false; // blocked by upper inside plane
 
-				if (los.sightzstart >= *rover->bottomheight && t2->z + t2->height < *rover->bottomheight)
+				if (los.sightzstart >= bottomz1 && t2->z + t2->height < bottomz2)
 					return false; // blocked by lower inside plane
 			}
 		}
diff --git a/src/p_slopes.c b/src/p_slopes.c
index f480b6e4f9d234e95e451ae5b9a503794c624f03..f89dd3c96dfff65d32ed71fbef996270315b5202 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2004      by Stephen McGranahan
-// Copyright (C) 2015-2016 by Sonic Team Junior.
+// Copyright (C) 2015-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -25,68 +25,66 @@
 
 #ifdef ESLOPE
 
-static pslope_t *slopelist = NULL;
-static UINT16 slopecount = 0;
+pslope_t *slopelist = NULL;
+UINT16 slopecount = 0;
 
 // Calculate line normal
-static void P_CalculateSlopeNormal(pslope_t *slope) {
+void P_CalculateSlopeNormal(pslope_t *slope) {
 	slope->normal.z = FINECOSINE(slope->zangle>>ANGLETOFINESHIFT);
-	slope->normal.x = -FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.x);
-	slope->normal.y = -FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.y);
+	slope->normal.x = FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.x);
+	slope->normal.y = FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.y);
 }
 
-// With a vertex slope that has its vertices set, configure relevant slope info
-static void P_ReconfigureVertexSlope(pslope_t *slope)
+/// Setup slope via 3 vertexes.
+static void ReconfigureViaVertexes (pslope_t *slope, const vector3_t v1, const vector3_t v2, const vector3_t v3)
 {
 	vector3_t vec1, vec2;
 
-	// Set slope normal
-	vec1.x = (slope->vertices[1]->x - slope->vertices[0]->x) << FRACBITS;
-	vec1.y = (slope->vertices[1]->y - slope->vertices[0]->y) << FRACBITS;
-	vec1.z = (slope->vertices[1]->z - slope->vertices[0]->z) << FRACBITS;
-
-	vec2.x = (slope->vertices[2]->x - slope->vertices[0]->x) << FRACBITS;
-	vec2.y = (slope->vertices[2]->y - slope->vertices[0]->y) << FRACBITS;
-	vec2.z = (slope->vertices[2]->z - slope->vertices[0]->z) << FRACBITS;
-
-	// ugggggggh fixed-point maaaaaaath
-	slope->extent = max(
-		max(max(abs(vec1.x), abs(vec1.y)), abs(vec1.z)),
-		max(max(abs(vec2.x), abs(vec2.y)), abs(vec2.z))
-	) >> (FRACBITS+5);
-	vec1.x /= slope->extent;
-	vec1.y /= slope->extent;
-	vec1.z /= slope->extent;
-	vec2.x /= slope->extent;
-	vec2.y /= slope->extent;
-	vec2.z /= slope->extent;
-
-	FV3_Cross(&vec1, &vec2, &slope->normal);
-
-	slope->extent = R_PointToDist2(0, 0, R_PointToDist2(0, 0, slope->normal.x, slope->normal.y), slope->normal.z);
-	if (slope->normal.z < 0)
-		slope->extent = -slope->extent;
-
-	slope->normal.x = FixedDiv(slope->normal.x, slope->extent);
-	slope->normal.y = FixedDiv(slope->normal.y, slope->extent);
-	slope->normal.z = FixedDiv(slope->normal.z, slope->extent);
-
-	// Set origin
-	slope->o.x = slope->vertices[0]->x << FRACBITS;
-	slope->o.y = slope->vertices[0]->y << FRACBITS;
-	slope->o.z = slope->vertices[0]->z << FRACBITS;
-
-	if (slope->normal.x == 0 && slope->normal.y == 0) { // Set some defaults for a non-sloped "slope"
+	// Set origin.
+	FV3_Copy(&slope->o, &v1);
+
+	// Get slope's normal.
+	FV3_SubEx(&v2, &v1, &vec1);
+	FV3_SubEx(&v3, &v1, &vec2);
+
+	// Set some defaults for a non-sloped "slope"
+	if (vec1.z == 0 && vec2.z == 0)
+	{
+		/// \todo Fix fully flat cases.
+
 		slope->zangle = slope->xydirection = 0;
 		slope->zdelta = slope->d.x = slope->d.y = 0;
-	} else {
+	}
+	else
+	{
+		/// \note Using fixed point for vectorial products easily leads to overflows so we work around by downscaling them.
+		fixed_t m = max(
+			max(max(abs(vec1.x), abs(vec1.y)), abs(vec1.z)),
+			max(max(abs(vec2.x), abs(vec2.y)), abs(vec2.z))
+		) >> 5; // shifting right by 5 is good enough.
+
+		FV3_Cross(
+				FV3_Divide(&vec1, m),
+				FV3_Divide(&vec2, m),
+				&slope->normal
+				);
+
+		// NOTE: FV3_Magnitude() doesn't work properly in some cases, and chaining FixedHypot() seems to give worse results.
+		m = R_PointToDist2(0, 0, R_PointToDist2(0, 0, slope->normal.x, slope->normal.y), slope->normal.z);
+
+		// Invert normal if it's facing down.
+		if (slope->normal.z < 0)
+			m = -m;
+
+		FV3_Divide(&slope->normal, m);
+
 		// Get direction vector
-		slope->extent = R_PointToDist2(0, 0, slope->normal.x, slope->normal.y);
-		slope->d.x = -FixedDiv(slope->normal.x, slope->extent);
-		slope->d.y = -FixedDiv(slope->normal.y, slope->extent);
+		m = FixedHypot(slope->normal.x, slope->normal.y);
+		slope->d.x = -FixedDiv(slope->normal.x, m);
+		slope->d.y = -FixedDiv(slope->normal.y, m);
 
 		// Z delta
-		slope->zdelta = FixedDiv(slope->extent, slope->normal.z);
+		slope->zdelta = FixedDiv(m, slope->normal.z);
 
 		// Get angles
 		slope->xydirection = R_PointToAngle2(0, 0, slope->d.x, slope->d.y)+ANGLE_180;
@@ -94,88 +92,95 @@ static void P_ReconfigureVertexSlope(pslope_t *slope)
 	}
 }
 
-// Recalculate dynamic slopes
-void P_RunDynamicSlopes(void) {
-	pslope_t *slope;
+/// Recalculate dynamic slopes.
+void T_DynamicSlopeLine (dynplanethink_t* th)
+{
+	pslope_t* slope = th->slope;
+	line_t* srcline = th->sourceline;
 
-	for (slope = slopelist; slope; slope = slope->next) {
-		fixed_t zdelta;
+	fixed_t zdelta;
 
-		if (slope->flags & SL_NODYNAMIC)
-			continue;
+	switch(th->type) {
+	case DP_FRONTFLOOR:
+		zdelta = srcline->backsector->floorheight - srcline->frontsector->floorheight;
+		slope->o.z = srcline->frontsector->floorheight;
+		break;
 
-		switch(slope->refpos) {
-		case 1: // front floor
-			zdelta = slope->sourceline->backsector->floorheight - slope->sourceline->frontsector->floorheight;
-			slope->o.z = slope->sourceline->frontsector->floorheight;
-			break;
-		case 2: // front ceiling
-			zdelta = slope->sourceline->backsector->ceilingheight - slope->sourceline->frontsector->ceilingheight;
-			slope->o.z = slope->sourceline->frontsector->ceilingheight;
-			break;
-		case 3: // back floor
-			zdelta = slope->sourceline->frontsector->floorheight - slope->sourceline->backsector->floorheight;
-			slope->o.z = slope->sourceline->backsector->floorheight;
-			break;
-		case 4: // back ceiling
-			zdelta = slope->sourceline->frontsector->ceilingheight - slope->sourceline->backsector->ceilingheight;
-			slope->o.z = slope->sourceline->backsector->ceilingheight;
-			break;
-		case 5: // vertices
-			{
-				mapthing_t *mt;
-				size_t i;
-				INT32 l;
-				line_t *line;
-
-				for (i = 0; i < 3; i++) {
-					mt = slope->vertices[i];
-					l = P_FindSpecialLineFromTag(799, mt->angle, -1);
-					if (l != -1) {
-						line = &lines[l];
-						mt->z = line->frontsector->floorheight >> FRACBITS;
-					}
-				}
-
-				P_ReconfigureVertexSlope(slope);
-			}
-			continue; // TODO
-
-		default:
-			I_Error("P_RunDynamicSlopes: slope has invalid type!");
-		}
+	case DP_FRONTCEIL:
+		zdelta = srcline->backsector->ceilingheight - srcline->frontsector->ceilingheight;
+		slope->o.z = srcline->frontsector->ceilingheight;
+		break;
 
-		if (slope->zdelta != FixedDiv(zdelta, slope->extent)) {
-			slope->zdelta = FixedDiv(zdelta, slope->extent);
-			slope->zangle = R_PointToAngle2(0, 0, slope->extent, -zdelta);
-			P_CalculateSlopeNormal(slope);
+	case DP_BACKFLOOR:
+		zdelta = srcline->frontsector->floorheight - srcline->backsector->floorheight;
+		slope->o.z = srcline->backsector->floorheight;
+		break;
+
+	case DP_BACKCEIL:
+		zdelta = srcline->frontsector->ceilingheight - srcline->backsector->ceilingheight;
+		slope->o.z = srcline->backsector->ceilingheight;
+		break;
+
+	default:
+		return;
+	}
+
+	if (slope->zdelta != FixedDiv(zdelta, th->extent)) {
+		slope->zdelta = FixedDiv(zdelta, th->extent);
+		slope->zangle = R_PointToAngle2(0, 0, th->extent, -zdelta);
+		P_CalculateSlopeNormal(slope);
+	}
+}
+
+/// Mapthing-defined
+void T_DynamicSlopeVert (dynplanethink_t* th)
+{
+	pslope_t* slope = th->slope;
+
+	size_t i;
+	INT32 l;
+
+	for (i = 0; i < 3; i++) {
+		l = P_FindSpecialLineFromTag(799, th->tags[i], -1);
+		if (l != -1) {
+			th->vex[i].z = lines[l].frontsector->floorheight;
 		}
+		else
+			th->vex[i].z = 0;
 	}
+
+	ReconfigureViaVertexes(slope, th->vex[0], th->vex[1], th->vex[2]);
 }
 
-//
-// P_MakeSlope
-//
-// Alocates and fill the contents of a slope structure.
-//
-static pslope_t *P_MakeSlope(const vector3_t *o, const vector2_t *d,
-                             const fixed_t zdelta, UINT8 flags)
+static inline void P_AddDynSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent, const INT16 tags[3], const vector3_t vx[3])
 {
-	pslope_t *ret = Z_Malloc(sizeof(pslope_t), PU_LEVEL, NULL);
-	memset(ret, 0, sizeof(*ret));
+	dynplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL);
+	switch (type)
+	{
+	case DP_VERTEX:
+		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeVert;
+		memcpy(th->tags, tags, sizeof(th->tags));
+		memcpy(th->vex, vx, sizeof(th->vex));
+		break;
+	default:
+		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeLine;
+		th->sourceline = sourceline;
+		th->extent = extent;
+	}
 
-	ret->o.x = o->x;
-	ret->o.y = o->y;
-	ret->o.z = o->z;
+	th->slope = slope;
+	th->type = type;
 
-	ret->d.x = d->x;
-	ret->d.y = d->y;
+	P_AddThinker(THINK_DYNSLOPE, &th->thinker);
+}
 
-	ret->zdelta = zdelta;
 
+/// Create a new slope and add it to the slope list.
+static inline pslope_t* Slope_Add (const UINT8 flags)
+{
+	pslope_t *ret = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
 	ret->flags = flags;
 
-	// Add to the slope list
 	ret->next = slopelist;
 	slopelist = ret;
 
@@ -185,13 +190,24 @@ static pslope_t *P_MakeSlope(const vector3_t *o, const vector2_t *d,
 	return ret;
 }
 
-//
-// P_GetExtent
-//
-// Returns the distance to the first line within the sector that
-// is intersected by a line parallel to the plane normal with the point (ox, oy)
-//
-static fixed_t P_GetExtent(sector_t *sector, line_t *line)
+/// Alocates and fill the contents of a slope structure.
+static pslope_t *MakeViaVectors(const vector3_t *o, const vector2_t *d,
+                             const fixed_t zdelta, UINT8 flags)
+{
+	pslope_t *ret = Slope_Add(flags);
+
+	FV3_Copy(&ret->o, o);
+	FV2_Copy(&ret->d, d);
+
+	ret->zdelta = zdelta;
+
+	ret->flags = flags;
+
+	return ret;
+}
+
+/// Get furthest perpendicular distance from all vertexes in a sector for a given line.
+static fixed_t GetExtent(sector_t *sector, line_t *line)
 {
 	// ZDoom code reference: v3float_t = vertex_t
 	fixed_t fardist = -FRACUNIT;
@@ -224,14 +240,8 @@ static fixed_t P_GetExtent(sector_t *sector, line_t *line)
 	return fardist;
 }
 
-
-//
-// P_SpawnSlope_Line
-//
-// Creates one or more slopes based on the given line type and front/back
-// sectors.
-//
-void P_SpawnSlope_Line(int linenum)
+/// Creates one or more slopes based on the given line type and front/back sectors.
+static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 {
 	// With dynamic slopes, it's fine to just leave this function as normal,
 	// because checking to see if a slope had changed will waste more memory than
@@ -249,12 +259,10 @@ void P_SpawnSlope_Line(int linenum)
 	boolean backceil   = (special == 711 || special == 712 || special == 703);
 
 	UINT8 flags = 0; // Slope flags
-	if (line->flags & ML_NOSONIC)
+	if (line->flags & ML_NETONLY)
 		flags |= SL_NOPHYSICS;
-	if (line->flags & ML_NOTAILS)
-		flags |= SL_NODYNAMIC;
-	if (line->flags & ML_NOKNUX)
-		flags |= SL_ANCHORVERTEX;
+	if (line->flags & ML_NONET)
+		flags |= SL_DYNAMIC;
 
 	if(!frontfloor && !backfloor && !frontceil && !backceil)
 	{
@@ -264,7 +272,7 @@ void P_SpawnSlope_Line(int linenum)
 
 	if(!line->frontsector || !line->backsector)
 	{
-		CONS_Printf("P_SpawnSlope_Line used on a line without two sides.\n");
+		CONS_Debug(DBG_SETUP, "P_SpawnSlope_Line used on a line without two sides. (line number %i)\n", linenum);
 		return;
 	}
 
@@ -274,6 +282,7 @@ void P_SpawnSlope_Line(int linenum)
 		ny = -FixedDiv(line->dx, len);
 	}
 
+	// Set origin to line's center.
 	origin.x = line->v1->x + (line->v2->x - line->v1->x)/2;
 	origin.y = line->v1->y + (line->v2->y - line->v1->y)/2;
 
@@ -286,7 +295,7 @@ void P_SpawnSlope_Line(int linenum)
 		direction.x = nx;
 		direction.y = ny;
 
-		extent = P_GetExtent(line->frontsector, line);
+		extent = GetExtent(line->frontsector, line);
 
 		if(extent < 0)
 		{
@@ -304,104 +313,43 @@ void P_SpawnSlope_Line(int linenum)
 
 		if(frontfloor)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			point.z = line->frontsector->floorheight; // Startz
 			dz = FixedDiv(origin.z - point.z, extent); // Destinationz
 
 			// In P_SpawnSlopeLine the origin is the centerpoint of the sourcelinedef
 
 			fslope = line->frontsector->f_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            fslope->extent = extent;
-            fslope->refpos = 1;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			// Now remember that f_slope IS a vector
 			// fslope->o = origin      3D point 1 of the vector
 			// fslope->d = destination 3D point 2 of the vector
 			// fslope->normal is a 3D line perpendicular to the 3D vector
 
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			fslope->sourceline = line;
-
-			// To find the real highz/lowz of a slope, you need to check all the vertexes
-			// in the slope's sector with P_GetZAt to get the REAL lowz & highz
-			// Although these slopes are set by floorheights the ANGLE is what a slope is,
-			// so technically any slope can extend on forever (they are just bound by sectors)
-			// *You can use sourceline as a reference to see if two slopes really are the same
-
-			// Default points for high and low
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			// Now check to see what the REAL high and low points of the slope inside the sector
-			// TODO: Is this really needed outside of FOFs? -Red
-
-			for (l = 0; l < line->frontsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->frontsector->f_slope, line->frontsector->lines[l]->v1->x, line->frontsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// Sets extra clipping data for the frontsector's slope
-			fslope->highz = highest;
-			fslope->lowz = lowest;
-
 			fslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			fslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(fslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(fslope, DP_FRONTFLOOR, line, extent, NULL, NULL);
 		}
 		if(frontceil)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			origin.z = line->backsector->ceilingheight;
 			point.z = line->frontsector->ceilingheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			cslope = line->frontsector->c_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            cslope->extent = extent;
-            cslope->refpos = 2;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			cslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->frontsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->frontsector->c_slope, line->frontsector->lines[l]->v1->x, line->frontsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the frontsector's slope
-			cslope->highz = highest;
-			cslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			cslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			cslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(cslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(cslope, DP_FRONTCEIL, line, extent, NULL, NULL);
 		}
 	}
 	if(backfloor || backceil)
@@ -413,7 +361,7 @@ void P_SpawnSlope_Line(int linenum)
 		direction.x = -nx;
 		direction.y = -ny;
 
-		extent = P_GetExtent(line->backsector, line);
+		extent = GetExtent(line->backsector, line);
 
 		if(extent < 0)
 		{
@@ -429,88 +377,36 @@ void P_SpawnSlope_Line(int linenum)
 
 		if(backfloor)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			point.z = line->backsector->floorheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			fslope = line->backsector->f_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            fslope->extent = extent;
-            fslope->refpos = 3;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			fslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->backsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->backsector->f_slope, line->backsector->lines[l]->v1->x, line->backsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the frontsector's slope
-			fslope->highz = highest;
-			fslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			fslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			fslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(fslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(fslope, DP_BACKFLOOR, line, extent, NULL, NULL);
 		}
 		if(backceil)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			origin.z = line->frontsector->ceilingheight;
 			point.z = line->backsector->ceilingheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			cslope = line->backsector->c_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            cslope->extent = extent;
-            cslope->refpos = 4;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			cslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->backsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->backsector->c_slope, line->backsector->lines[l]->v1->x, line->backsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the backsector's slope
-			cslope->highz = highest;
-			cslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			cslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			cslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(cslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
 		}
 	}
 
@@ -518,63 +414,99 @@ void P_SpawnSlope_Line(int linenum)
 		return;
 }
 
-//
-// P_NewVertexSlope
-//
-// Creates a new slope from three vertices with the specified IDs
-//
-static pslope_t *P_NewVertexSlope(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flags)
+/// Creates a new slope from three mapthings with the specified IDs
+static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flags, const boolean spawnthinker)
 {
 	size_t i;
-	mapthing_t *mt = mapthings;
-
-	pslope_t *ret = Z_Malloc(sizeof(pslope_t), PU_LEVEL, NULL);
-	memset(ret, 0, sizeof(*ret));
-
-	// Start by setting flags
-	ret->flags = flags;
+	mapthing_t* mt = mapthings;
+	mapthing_t* vertices[3] = {0};
+	INT16 tags[3] = {tag1, tag2, tag3};
 
-	// Now set up the vertex list
-	ret->vertices = Z_Malloc(3*sizeof(mapthing_t), PU_LEVEL, NULL);
-	memset(ret->vertices, 0, 3*sizeof(mapthing_t));
+	vector3_t vx[3];
+	pslope_t* ret = Slope_Add(flags);
 
 	// And... look for the vertices in question.
 	for (i = 0; i < nummapthings; i++, mt++) {
 		if (mt->type != 750) // Haha, I'm hijacking the old Chaos Spawn thingtype for something!
 			continue;
 
-		if (!ret->vertices[0] && mt->angle == tag1)
-			ret->vertices[0] = mt;
-		else if (!ret->vertices[1] && mt->angle == tag2)
-			ret->vertices[1] = mt;
-		else if (!ret->vertices[2] && mt->angle == tag3)
-			ret->vertices[2] = mt;
+		if (!vertices[0] && mt->angle == tag1)
+			vertices[0] = mt;
+		else if (!vertices[1] && mt->angle == tag2)
+			vertices[1] = mt;
+		else if (!vertices[2] && mt->angle == tag3)
+			vertices[2] = mt;
 	}
 
 	// Now set heights for each vertex, because they haven't been set yet
 	for (i = 0; i < 3; i++) {
-		mt = ret->vertices[i];
+		mt = vertices[i];
 		if (!mt) // If a vertex wasn't found, it's game over. There's nothing you can do to recover (except maybe try and kill the slope instead - TODO?)
-			I_Error("P_NewVertexSlope: Slope vertex %s (for linedef tag %d) not found!", sizeu1(i), tag1);
+			I_Error("MakeViaMapthings: Slope vertex %s (for linedef tag %d) not found!", sizeu1(i), tag1);
+		vx[i].x = mt->x << FRACBITS;
+		vx[i].y = mt->y << FRACBITS;
 		if (mt->extrainfo)
-			mt->z = mt->options;
+			vx[i].z = mt->options << FRACBITS;
 		else
-			mt->z = (R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)->sector->floorheight >> FRACBITS) + (mt->options >> ZSHIFT);
+			vx[i].z = (R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)->sector->floorheight) + ((mt->options >> ZSHIFT) << FRACBITS);
 	}
 
-	P_ReconfigureVertexSlope(ret);
-	ret->refpos = 5;
+	ReconfigureViaVertexes(ret, vx[0], vx[1], vx[2]);
 
-	// Add to the slope list
-	ret->next = slopelist;
-	slopelist = ret;
-
-	slopecount++;
-	ret->id = slopecount;
+	if (spawnthinker && (flags & SL_DYNAMIC))
+		P_AddDynSlopeThinker(ret, DP_VERTEX, NULL, 0, tags, vx);
 
 	return ret;
 }
 
+/// Create vertex based slopes.
+static void line_SpawnViaVertexes(const int linenum, const boolean spawnthinker)
+{
+	line_t *line = lines + linenum;
+	side_t *side;
+	pslope_t **slopetoset;
+	UINT16 tag1, tag2, tag3;
+
+	UINT8 flags = 0;
+	if (line->flags & ML_NETONLY)
+		flags |= SL_NOPHYSICS;
+	if (line->flags & ML_NONET)
+		flags |= SL_DYNAMIC;
+
+	switch(line->special)
+	{
+	case 704:
+		slopetoset = &line->frontsector->f_slope;
+		side = &sides[line->sidenum[0]];
+		break;
+	case 705:
+		slopetoset = &line->frontsector->c_slope;
+		side = &sides[line->sidenum[0]];
+		break;
+	case 714:
+		slopetoset = &line->backsector->f_slope;
+		side = &sides[line->sidenum[1]];
+		break;
+	case 715:
+		slopetoset = &line->backsector->c_slope;
+		side = &sides[line->sidenum[1]];
+	default:
+		return;
+	}
+
+	if (line->flags & ML_EFFECT6)
+	{
+		tag1 = line->tag;
+		tag2 = side->textureoffset >> FRACBITS;
+		tag3 = side->rowoffset >> FRACBITS;
+	}
+	else
+		tag1 = tag2 = tag3 = line->tag;
+
+	*slopetoset = MakeViaMapthings(tag1, tag2, tag3, flags, spawnthinker);
+
+	side->sector->hasslope = true;
+}
 
 
 //
@@ -584,23 +516,28 @@ static pslope_t *P_NewVertexSlope(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag
 //
 void P_CopySectorSlope(line_t *line)
 {
-   sector_t *fsec = line->frontsector;
-   int i, special = line->special;
+	sector_t *fsec = line->frontsector;
+	int i, special = line->special;
+
+	// Check for copy linedefs
+	for (i = -1; (i = P_FindSectorFromLineTag(line, i)) >= 0;)
+	{
+		sector_t *srcsec = sectors + i;
 
-   // Check for copy linedefs
-   for(i = -1; (i = P_FindSectorFromLineTag(line, i)) >= 0;)
-   {
-      sector_t *srcsec = sectors + i;
+		if ((special - 719) & 1 && !fsec->f_slope && srcsec->f_slope)
+			fsec->f_slope = srcsec->f_slope; //P_CopySlope(srcsec->f_slope);
+		if ((special - 719) & 2 && !fsec->c_slope && srcsec->c_slope)
+			fsec->c_slope = srcsec->c_slope; //P_CopySlope(srcsec->c_slope);
+	}
 
-      if((special - 719) & 1 && !fsec->f_slope && srcsec->f_slope)
-         fsec->f_slope = srcsec->f_slope; //P_CopySlope(srcsec->f_slope);
-      if((special - 719) & 2 && !fsec->c_slope && srcsec->c_slope)
-         fsec->c_slope = srcsec->c_slope; //P_CopySlope(srcsec->c_slope);
-   }
+	fsec->hasslope = true;
 
-   fsec->hasslope = true;
+	// if this is an FOF control sector, make sure any target sectors also are marked as having slopes
+	if (fsec->numattached)
+		for (i = 0; i < (int)fsec->numattached; i++)
+			sectors[fsec->attached[i]].hasslope = true;
 
-   line->special = 0; // Linedef was use to set slopes, it finished its job, so now make it a normal linedef
+	line->special = 0; // Linedef was use to set slopes, it finished its job, so now make it a normal linedef
 }
 
 //
@@ -615,56 +552,20 @@ pslope_t *P_SlopeById(UINT16 id)
 	return ret;
 }
 
-// Reset the dynamic slopes pointer, and read all of the fancy schmancy slopes
-void P_ResetDynamicSlopes(void) {
+/// Reset slopes and read them from special lines.
+void P_ResetDynamicSlopes(const UINT32 fromsave) {
 	size_t i;
-#ifdef ESLOPE_TYPESHIM // Rewrite old specials to new ones, and give a console warning
-	boolean warned = false;
-#endif
+
+	boolean spawnthinkers = !(boolean)fromsave;
 
 	slopelist = NULL;
 	slopecount = 0;
 
-	// We'll handle copy slopes later, after all the tag lists have been made.
-	// Yes, this means copied slopes won't affect things' spawning heights. Too bad for you.
+	/// Generates line special-defined slopes.
 	for (i = 0; i < numlines; i++)
 	{
 		switch (lines[i].special)
 		{
-#ifdef ESLOPE_TYPESHIM // Rewrite old specials to new ones, and give a console warning
-#define WARNME if (!warned) {warned = true; CONS_Alert(CONS_WARNING, "This level uses old slope specials.\nA conversion will be needed before 2.2's release.\n");}
-			case 386:
-			case 387:
-			case 388:
-				lines[i].special += 700-386;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 389:
-			case 390:
-			case 391:
-			case 392:
-				lines[i].special += 710-389;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 393:
-				lines[i].special = 703;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 394:
-			case 395:
-			case 396:
-				lines[i].special += 720-394;
-				WARNME
-				break;
-
-#endif
-
 			case 700:
 			case 701:
 			case 702:
@@ -673,62 +574,34 @@ void P_ResetDynamicSlopes(void) {
 			case 711:
 			case 712:
 			case 713:
-				P_SpawnSlope_Line(i);
+				line_SpawnViaLine(i, spawnthinkers);
 				break;
 
 			case 704:
 			case 705:
 			case 714:
 			case 715:
-				{
-					pslope_t **slopetoset;
-					size_t which = lines[i].special;
-
-					UINT8 flags = SL_VERTEXSLOPE;
-					if (lines[i].flags & ML_NOSONIC)
-						flags |= SL_NOPHYSICS;
-					if (!(lines[i].flags & ML_NOTAILS))
-						flags |= SL_NODYNAMIC;
-
-					if (which == 704)
-					{
-						slopetoset = &lines[i].frontsector->f_slope;
-						which = 0;
-					}
-					else if (which == 705)
-					{
-						slopetoset = &lines[i].frontsector->c_slope;
-						which = 0;
-					}
-					else if (which == 714)
-					{
-						slopetoset = &lines[i].backsector->f_slope;
-						which = 1;
-					}
-					else // 715
-					{
-						slopetoset = &lines[i].backsector->c_slope;
-						which = 1;
-					}
-
-					if (lines[i].flags & ML_NOKNUX)
-						*slopetoset = P_NewVertexSlope(lines[i].tag, sides[lines[i].sidenum[which]].textureoffset >> FRACBITS,
-																			sides[lines[i].sidenum[which]].rowoffset >> FRACBITS, flags);
-					else
-						*slopetoset = P_NewVertexSlope(lines[i].tag, lines[i].tag, lines[i].tag, flags);
-
-					sides[lines[i].sidenum[which]].sector->hasslope = true;
-				}
+				line_SpawnViaVertexes(i, spawnthinkers);
 				break;
 
 			default:
 				break;
 		}
 	}
-}
-
-
 
+	/// Copies slopes from tagged sectors via line specials.
+	/// \note Doesn't actually copy, but instead they share the same pointers.
+	for (i = 0; i < numlines; i++)
+		switch (lines[i].special)
+		{
+			case 720:
+			case 721:
+			case 722:
+				P_CopySectorSlope(&lines[i]);
+			default:
+				break;
+		}
+}
 
 // ============================================================================
 //
@@ -844,7 +717,7 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope)
 
 	if (slope->flags & SL_NOPHYSICS) { // No physics, no need to make anything complicated.
 		if (P_MobjFlip(thing)*(thing->momz) < 0) { // falling, land on slope
-			thing->momz = -P_MobjFlip(thing);
+			//thing->momz = -P_MobjFlip(thing);
 			thing->standingslope = slope;
 		}
 		return;
@@ -859,7 +732,7 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope)
 	if (P_MobjFlip(thing)*mom.z < 0) { // falling, land on slope
 		thing->momx = mom.x;
 		thing->momy = mom.y;
-		thing->momz = -P_MobjFlip(thing);
+		//thing->momz = -P_MobjFlip(thing);
 
 		thing->standingslope = slope;
 	}
diff --git a/src/p_slopes.h b/src/p_slopes.h
index f59c5b767b4dac1762a0a458c899efce590f8531..8f9e7d61e5bf135e82b42c56593465a0f5fc73e0 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2004      by Stephen McGranahan
-// Copyright (C) 2015-2016 by Sonic Team Junior.
+// Copyright (C) 2015-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -13,13 +13,17 @@
 #ifndef P_SLOPES_H__
 #define P_SLOPES_H__
 
+#include "m_fixed.h" // Vectors
+
 #ifdef ESLOPE
-void P_ResetDynamicSlopes(void);
-void P_RunDynamicSlopes(void);
-// P_SpawnSlope_Line
-// Creates one or more slopes based on the given line type and front/back
-// sectors.
-void P_SpawnSlope_Line(int linenum);
+
+extern pslope_t *slopelist;
+extern UINT16 slopecount;
+
+void P_LinkSlopeThinkers (void);
+
+void P_CalculateSlopeNormal(pslope_t *slope);
+void P_ResetDynamicSlopes(const UINT32 fromsave);
 
 //
 // P_CopySectorSlope
@@ -41,7 +45,34 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope);
 void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope);
 void P_ButteredSlope(mobj_t *mo);
 
-#endif
 
-// EOF
+/// Dynamic plane type enum for the thinker. Will have a different functionality depending on this.
+typedef enum {
+	DP_FRONTFLOOR,
+	DP_FRONTCEIL,
+	DP_BACKFLOOR,
+	DP_BACKCEIL,
+	DP_VERTEX
+} dynplanetype_t;
+
+/// Permit slopes to be dynamically altered through a thinker.
+typedef struct
+{
+	thinker_t thinker;
+
+	pslope_t* slope;
+	dynplanetype_t type;
+
+	// Used by line slopes.
+	line_t* sourceline;
+	fixed_t extent;
+
+	// Used by mapthing vertex slopes.
+	INT16 tags[3];
+	vector3_t vex[3];
+} dynplanethink_t;
+
+void T_DynamicSlopeLine (dynplanethink_t* th);
+void T_DynamicSlopeVert (dynplanethink_t* th);
 #endif // #ifdef ESLOPE
+#endif // #ifndef P_SLOPES_H__
diff --git a/src/p_spec.c b/src/p_spec.c
index 797aba049cef29e9622e4f55d60dfe79ad6ca1cc..5e06a18a76683b0967cd412c916541f527a133de 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -31,9 +31,12 @@
 #include "p_polyobj.h"
 #include "p_slopes.h"
 #include "hu_stuff.h"
+#include "v_video.h" // V_AUTOFADEOUT|V_ALLOWLOWERCASE
 #include "m_misc.h"
 #include "m_cond.h" //unlock triggers
 #include "lua_hook.h" // LUAh_LinedefExecute
+#include "f_finale.h" // control text prompt
+#include "r_things.h" // skins
 
 #ifdef HW3SOUND
 #include "hardware/hw3sound.h"
@@ -96,12 +99,23 @@ typedef struct
 	thinker_t **thinkers;
 } thinkerlist_t;
 
-static void P_SearchForDisableLinedefs(void);
 static void P_SpawnScrollers(void);
 static void P_SpawnFriction(void);
 static void P_SpawnPushers(void);
 static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider); //SoM: 3/9/2000
 static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline);
+static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize);
+#define P_RemoveFakeFloorFader(l) P_ResetFakeFloorFader(l, NULL, false);
+static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
+	boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
+	boolean docollision, boolean doghostfade, boolean exactalpha);
+static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
+	INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
+	boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
+	boolean docollision, boolean doghostfade, boolean exactalpha);
+static void P_ResetColormapFader(sector_t *sector);
+static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
+	boolean ticbased, INT32 duration);
 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
 static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline);
 //static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
@@ -1217,7 +1231,7 @@ static void PolyVisible(line_t *line)
 		po->flags |= POF_SOLID;
 
 	po->flags &= ~POF_NOSPECIALS;
-	po->flags |= POF_RENDERALL;
+	po->flags |= (po->spawnflags & POF_RENDERALL);
 }
 
 //
@@ -1241,7 +1255,94 @@ static void PolyTranslucency(line_t *line)
 	if (po->isBad)
 		return;
 
-	po->translucency = (line->frontsector->floorheight >> FRACBITS) / 100;
+	// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
+	// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
+	if (line->flags & ML_EFFECT3) // relative calc
+		po->translucency = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
+			(sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
+				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
+			: (sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
+				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
+			NUMTRANSMAPS), 0);
+	else
+		po->translucency = (line->flags & ML_DONTPEGBOTTOM) ?
+			(sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
+				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
+			: (sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
+				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
+}
+
+//
+// PolyFade
+//
+// Makes a polyobject translucency fade and applies tangibility
+//
+static boolean PolyFade(line_t *line)
+{
+	INT32 polyObjNum = line->tag;
+	polyobj_t *po;
+	polyfadedata_t pfd;
+
+	if (!(po = Polyobj_GetForNum(polyObjNum)))
+	{
+		CONS_Debug(DBG_POLYOBJ, "PolyFade: bad polyobj %d\n", polyObjNum);
+		return 0;
+	}
+
+	// don't allow line actions to affect bad polyobjects
+	if (po->isBad)
+		return 0;
+
+	// Prevent continuous execs from interfering on an existing fade
+	if (!(line->flags & ML_EFFECT5)
+		&& po->thinker
+		&& po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
+	{
+		CONS_Debug(DBG_POLYOBJ, "Line type 492 Executor: Fade PolyObject thinker already exists\n");
+		return 0;
+	}
+
+	pfd.polyObjNum = polyObjNum;
+
+	// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
+	// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
+	if (line->flags & ML_EFFECT3) // relative calc
+		pfd.destvalue = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
+			(sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
+				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
+			: (sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
+				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
+			NUMTRANSMAPS), 0);
+	else
+		pfd.destvalue = (line->flags & ML_DONTPEGBOTTOM) ?
+			(sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
+				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
+			: (sides[line->sidenum[0]].textureoffset ?
+				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
+				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
+
+	// already equal, nothing to do
+	if (po->translucency == pfd.destvalue)
+		return 1;
+
+	pfd.docollision = !(line->flags & ML_BOUNCY);         // do not handle collision flags
+	pfd.doghostfade = (line->flags & ML_EFFECT1);         // do ghost fade (no collision flags during fade)
+	pfd.ticbased = (line->flags & ML_EFFECT4);            // Speed = Tic Duration
+
+	// allow Back Y Offset to be consistent with other fade specials
+	pfd.speed = (line->sidenum[1] != 0xFFFF && !sides[line->sidenum[0]].rowoffset) ?
+		abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)
+		: abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+
+
+	return EV_DoPolyObjFade(&pfd);
 }
 
 //
@@ -1311,6 +1412,34 @@ static boolean PolyDisplace(line_t *line)
 	return EV_DoPolyObjDisplace(&pdd);
 }
 
+
+/** Similar to PolyDisplace().
+ */
+static boolean PolyRotDisplace(line_t *line)
+{
+	polyrotdisplacedata_t pdd;
+	fixed_t anginter, distinter;
+
+	pdd.polyObjNum = line->tag;
+	pdd.controlSector = line->frontsector;
+
+	// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
+	// Use default values if not provided as fallback.
+	anginter	= sides[line->sidenum[0]].rowoffset ? sides[line->sidenum[0]].rowoffset : 90*FRACUNIT;
+	distinter	= sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : 128*FRACUNIT;
+	pdd.rotscale = FixedDiv(anginter, distinter);
+
+	// Same behavior as other rotators when carrying things.
+	if (line->flags & ML_NOCLIMB)
+		pdd.turnobjs = 0;
+	else if (line->flags & ML_EFFECT4)
+		pdd.turnobjs = 2;
+	else
+		pdd.turnobjs = 1;
+
+	return EV_DoPolyObjRotDisplace(&pdd);
+}
+
 #endif // ifdef POLYOBJECTS
 
 /** Changes a sector's tag.
@@ -1374,6 +1503,67 @@ void P_ChangeSectorTag(UINT32 sector, INT16 newtag)
 	}
 }
 
+//
+// P_RunNightserizeExecutors
+//
+void P_RunNightserizeExecutors(mobj_t *actor)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		if (lines[i].special == 323 || lines[i].special == 324)
+			P_RunTriggerLinedef(&lines[i], actor, NULL);
+	}
+}
+
+//
+// P_RunDeNightserizeExecutors
+//
+void P_RunDeNightserizeExecutors(mobj_t *actor)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		if (lines[i].special == 325 || lines[i].special == 326)
+			P_RunTriggerLinedef(&lines[i], actor, NULL);
+	}
+}
+
+//
+// P_RunNightsLapExecutors
+//
+void P_RunNightsLapExecutors(mobj_t *actor)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		if (lines[i].special == 327 || lines[i].special == 328)
+			P_RunTriggerLinedef(&lines[i], actor, NULL);
+	}
+}
+
+//
+// P_RunNightsCapsuleTouchExecutors
+//
+void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		if ((lines[i].special == 329 || lines[i].special == 330)
+			&& ((entering && (lines[i].flags & ML_TFERLINE))
+				|| (!entering && !(lines[i].flags & ML_TFERLINE)))
+			&& ((lines[i].flags & ML_DONTPEGTOP)
+				|| (enoughspheres && !(lines[i].flags & ML_BOUNCY))
+				|| (!enoughspheres && (lines[i].flags & ML_BOUNCY))))
+			P_RunTriggerLinedef(&lines[i], actor, NULL);
+	}
+}
+
 /** Hashes the sector tags across the sectors and linedefs.
   *
   * \sa P_FindSectorFromTag, P_ChangeSectorTag
@@ -1454,7 +1644,147 @@ static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
 	e->sector = sector;
 	e->timer = (line->backsector->ceilingheight>>FRACBITS)+(line->backsector->floorheight>>FRACBITS);
 	P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
-	P_AddThinker(&e->thinker);
+	P_AddThinker(THINK_MAIN, &e->thinker);
+}
+
+/** Used by P_RunTriggerLinedef to check a NiGHTS trigger linedef's conditions
+  *
+  * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
+  * \param actor Object initiating the action; should not be NULL.
+  * \sa P_RunTriggerLinedef
+  */
+static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
+{
+	INT16 specialtype = triggerline->special;
+	size_t i;
+
+	UINT8 inputmare = max(0, min(255, sides[triggerline->sidenum[0]].textureoffset>>FRACBITS));
+	UINT8 inputlap = max(0, min(255, sides[triggerline->sidenum[0]].rowoffset>>FRACBITS));
+
+	boolean ltemare = triggerline->flags & ML_NOCLIMB;
+	boolean gtemare = triggerline->flags & ML_BLOCKMONSTERS;
+	boolean ltelap = triggerline->flags & ML_EFFECT1;
+	boolean gtelap = triggerline->flags & ML_EFFECT2;
+
+	boolean lapfrombonustime = triggerline->flags & ML_EFFECT3;
+	boolean perglobalinverse = triggerline->flags & ML_DONTPEGBOTTOM;
+	boolean perglobal = !(triggerline->flags & ML_EFFECT4) && !perglobalinverse;
+
+	boolean donomares = triggerline->flags & ML_BOUNCY; // nightserize: run at end of level (no mares)
+	boolean fromnonights = triggerline->flags & ML_TFERLINE; // nightserize: from non-nights // denightserize: all players no nights
+	boolean fromnights = triggerline->flags & ML_DONTPEGTOP; // nightserize: from nights // denightserize: >0 players are nights
+
+	UINT8 currentmare = UINT8_MAX;
+	UINT8 currentlap = UINT8_MAX;
+
+	// Do early returns for Nightserize
+	if (specialtype >= 323 && specialtype <= 324)
+	{
+		// run only when no mares are found
+		if (donomares && P_FindLowestMare() != UINT8_MAX)
+			return false;
+
+		// run only if there is a mare present
+		if (!donomares && P_FindLowestMare() == UINT8_MAX)
+			return false;
+
+		// run only if player is nightserizing from non-nights
+		if (fromnonights)
+		{
+			if (!actor->player)
+				return false;
+			else if (actor->player->powers[pw_carry] == CR_NIGHTSMODE)
+				return false;
+		}
+		// run only if player is nightserizing from nights
+		else if (fromnights)
+		{
+			if (!actor->player)
+				return false;
+			else if (actor->player->powers[pw_carry] != CR_NIGHTSMODE)
+				return false;
+		}
+	}
+
+	// Get current mare and lap (and check early return for DeNightserize)
+	if (perglobal || perglobalinverse
+		|| (specialtype >= 325 && specialtype <= 326 && (fromnonights || fromnights)))
+	{
+		UINT8 playersarenights = 0;
+
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			UINT8 lap;
+			if (!playeringame[i] || players[i].spectator)
+				continue;
+
+			// denightserize: run only if all players are not nights
+			if (specialtype >= 325 && specialtype <= 326 && fromnonights
+				&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
+				return false;
+
+			// count number of nights players for denightserize return
+			if (specialtype >= 325 && specialtype <= 326 && fromnights
+				&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
+				playersarenights++;
+
+			lap = lapfrombonustime ? players[i].marebonuslap : players[i].marelap;
+
+			// get highest mare/lap of players
+			if (perglobal)
+			{
+				if (players[i].mare > currentmare || currentmare == UINT8_MAX)
+				{
+					currentmare = players[i].mare;
+					currentlap = UINT8_MAX;
+				}
+				if (players[i].mare == currentmare
+					&& (lap > currentlap || currentlap == UINT8_MAX))
+					currentlap = lap;
+			}
+			// get lowest mare/lap of players
+			else if (perglobalinverse)
+			{
+				if (players[i].mare < currentmare || currentmare == UINT8_MAX)
+				{
+					currentmare = players[i].mare;
+					currentlap = UINT8_MAX;
+				}
+				if (players[i].mare == currentmare
+					&& (lap < currentlap || currentlap == UINT8_MAX))
+					currentlap = lap;
+			}
+		}
+
+		// denightserize: run only if >0 players are nights
+		if (specialtype >= 325 && specialtype <= 326 && fromnights
+			&& playersarenights < 1)
+			return false;
+	}
+	// get current mare/lap from triggering player
+	else if (!perglobal && !perglobalinverse)
+	{
+		if (!actor->player)
+			return false;
+		currentmare = actor->player->mare;
+		currentlap = lapfrombonustime ? actor->player->marebonuslap : actor->player->marelap;
+	}
+
+	if (lapfrombonustime && !currentlap)
+		return false; // special case: player->marebonuslap is 0 until passing through on bonus time. Don't trigger lines looking for inputlap 0.
+
+	// Compare current mare/lap to input mare/lap based on rules
+	if (!(specialtype >= 323 && specialtype <= 324 && donomares) // don't return false if donomares and we got this far
+		&& ((ltemare && currentmare > inputmare)
+		|| (gtemare && currentmare < inputmare)
+		|| (!ltemare && !gtemare && currentmare != inputmare)
+		|| (ltelap && currentlap > inputlap)
+		|| (gtelap && currentlap < inputlap)
+		|| (!ltelap && !gtelap && currentlap != inputlap))
+		)
+		return false;
+
+	return true;
 }
 
 /** Used by P_LinedefExecute to check a trigger linedef's conditions
@@ -1492,10 +1822,10 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 				if (!playeringame[i] || players[i].spectator)
 					continue;
 
-				if (!players[i].mo || players[i].rings <= 0)
+				if (!players[i].mo || ((maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings) <= 0)
 					continue;
 
-				rings += players[i].rings;
+				rings += (maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings;
 			}
 		}
 		else
@@ -1503,7 +1833,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (!(actor && actor->player))
 				return false; // no player to count rings from here, sorry
 
-			rings = actor->player->rings;
+			rings = (maptol & TOL_NIGHTS) ? actor->player->spheres : actor->player->rings;
 		}
 
 		if (triggerline->flags & ML_NOCLIMB)
@@ -1667,6 +1997,23 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 					return false;
 			}
 			break;
+		case 323: // nightserize - each time
+		case 324: // nightserize - once
+		case 325: // denightserize - each time
+		case 326: // denightserize - once
+		case 327: // nights lap - each time
+		case 328: // nights lap - once
+		case 329: // nights egg capsule touch - each time
+		case 330: // nights egg capsule touch - once
+			if (!P_CheckNightsTriggerLine(triggerline, actor))
+				return false;
+			break;
+		case 331: // continuous
+		case 332: // each time
+		case 333: // once
+			if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB))))
+				return false;
+			break;
 		default:
 			break;
 	}
@@ -1795,6 +2142,11 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	 || specialtype == 318  // Unlockable trigger - Once
 	 || specialtype == 320  // Unlockable - Once
 	 || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
+	 || specialtype == 324 // Nightserize - Once
+	 || specialtype == 326 // DeNightserize - Once
+	 || specialtype == 328 // Nights lap - Once
+	 || specialtype == 330 // Nights Bonus Time - Once
+	 || specialtype == 333 // Skin - Once
 	 || specialtype == 399) // Level Load
 		triggerline->special = 0; // Clear it out
 
@@ -1835,7 +2187,8 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		 || lines[masterline].special == 306 // Character ability - Each time
 		 || lines[masterline].special == 310 // CTF Red team - Each time
 		 || lines[masterline].special == 312 // CTF Blue team - Each time
-		 || lines[masterline].special == 322) // Trigger on X calls - Each Time
+		 || lines[masterline].special == 322 // Trigger on X calls - Each Time
+		 || lines[masterline].special == 332)// Skin - Each time
 			continue;
 
 		if (lines[masterline].special < 300
@@ -1907,10 +2260,9 @@ void P_SwitchWeather(INT32 weathernum)
 		thinker_t *think;
 		precipmobj_t *precipmobj;
 
-		for (think = thinkercap.next; think != &thinkercap; think = think->next)
+		for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
 		{
-			if ((think->function.acp1 != (actionf_p1)P_SnowThinker)
-				&& (think->function.acp1 != (actionf_p1)P_RainThinker))
+			if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
 				continue; // not a precipmobj thinker
 
 			precipmobj = (precipmobj_t *)think;
@@ -1924,16 +2276,14 @@ void P_SwitchWeather(INT32 weathernum)
 		precipmobj_t *precipmobj;
 		state_t *st;
 
-		for (think = thinkercap.next; think != &thinkercap; think = think->next)
+		for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
 		{
+			if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
+				continue; // not a precipmobj thinker
+			precipmobj = (precipmobj_t *)think;
+
 			if (swap == PRECIP_RAIN) // Snow To Rain
 			{
-				if (!(think->function.acp1 == (actionf_p1)P_SnowThinker
-					|| think->function.acp1 == (actionf_p1)P_NullPrecipThinker))
-					continue; // not a precipmobj thinker
-
-				precipmobj = (precipmobj_t *)think;
-
 				precipmobj->flags = mobjinfo[MT_RAIN].flags;
 				st = &states[mobjinfo[MT_RAIN].spawnstate];
 				precipmobj->state = st;
@@ -1944,18 +2294,13 @@ void P_SwitchWeather(INT32 weathernum)
 
 				precipmobj->precipflags &= ~PCF_INVISIBLE;
 
-				think->function.acp1 = (actionf_p1)P_RainThinker;
+				precipmobj->precipflags |= PCF_RAIN;
+				//think->function.acp1 = (actionf_p1)P_RainThinker;
 			}
 			else if (swap == PRECIP_SNOW) // Rain To Snow
 			{
 				INT32 z;
 
-				if (!(think->function.acp1 == (actionf_p1)P_RainThinker
-					|| think->function.acp1 == (actionf_p1)P_NullPrecipThinker))
-					continue; // not a precipmobj thinker
-
-				precipmobj = (precipmobj_t *)think;
-
 				precipmobj->flags = mobjinfo[MT_SNOWFLAKE].flags;
 				z = M_RandomByte();
 
@@ -1973,19 +2318,13 @@ void P_SwitchWeather(INT32 weathernum)
 				precipmobj->frame = st->frame;
 				precipmobj->momz = mobjinfo[MT_SNOWFLAKE].speed;
 
-				precipmobj->precipflags &= ~PCF_INVISIBLE;
+				precipmobj->precipflags &= ~(PCF_INVISIBLE|PCF_RAIN);
 
-				think->function.acp1 = (actionf_p1)P_SnowThinker;
+				//think->function.acp1 = (actionf_p1)P_SnowThinker;
 			}
 			else if (swap == PRECIP_BLANK || swap == PRECIP_STORM_NORAIN) // Remove precip, but keep it around for reuse.
 			{
-				if (!(think->function.acp1 == (actionf_p1)P_RainThinker
-					|| think->function.acp1 == (actionf_p1)P_SnowThinker))
-					continue;
-
-				precipmobj = (precipmobj_t *)think;
-
-				think->function.acp1 = (actionf_p1)P_NullPrecipThinker;
+				//think->function.acp1 = (actionf_p1)P_NullPrecipThinker;
 
 				precipmobj->precipflags |= PCF_INVISIBLE;
 			}
@@ -2287,18 +2626,70 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 413: // Change music
 			// console player only unless NOCLIMB is set
-			if ((line->flags & ML_NOCLIMB) || (mo && mo->player && P_IsLocalPlayer(mo->player)))
+			if ((line->flags & ML_NOCLIMB) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
 			{
-				UINT16 tracknum = (UINT16)sides[line->sidenum[0]].bottomtexture;
+				boolean musicsame = (!sides[line->sidenum[0]].text[0] || !strnicmp(sides[line->sidenum[0]].text, S_MusicName(), 7));
+				UINT16 tracknum = (UINT16)max(sides[line->sidenum[0]].bottomtexture, 0);
+				INT32 position = (INT32)max(sides[line->sidenum[0]].midtexture, 0);
+				UINT32 prefadems = (UINT32)max(sides[line->sidenum[0]].textureoffset >> FRACBITS, 0);
+				UINT32 postfadems = (UINT32)max(sides[line->sidenum[0]].rowoffset >> FRACBITS, 0);
+				UINT8 fadetarget = (UINT8)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].textureoffset >> FRACBITS : 0, 0);
+				INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : -1, -1);
+
+				// Seek offset from current song position
+				if (line->flags & ML_EFFECT1)
+				{
+					// adjust for loop point if subtracting
+					if (position < 0 && S_GetMusicLength() &&
+						S_GetMusicPosition() > S_GetMusicLoopPoint() &&
+						S_GetMusicPosition() + position < S_GetMusicLoopPoint())
+						position = max(S_GetMusicLength() - (S_GetMusicLoopPoint() - (S_GetMusicPosition() + position)), 0);
+					else
+						position = max(S_GetMusicPosition() + position, 0);
+				}
+
+				// Fade current music to target volume (if music won't be changed)
+				if ((line->flags & ML_EFFECT2) && fadetarget && musicsame)
+				{
+					// 0 fadesource means fade from current volume.
+					// meaning that we can't specify volume 0 as the source volume -- this starts at 1.
+					if (!fadesource)
+						fadesource = -1;
+
+					if (!postfadems)
+						S_SetInternalMusicVolume(fadetarget);
+					else
+						S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
+
+					if (position)
+						S_SetMusicPosition(position);
+				}
+				// Change the music and apply position/fade operations
+				else
+				{
+					strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
+					mapmusname[6] = 0;
 
-				strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
-				mapmusname[6] = 0;
+					mapmusflags = tracknum & MUSIC_TRACKMASK;
+					if (!(line->flags & ML_BLOCKMONSTERS))
+						mapmusflags |= MUSIC_RELOADRESET;
+					if (line->flags & ML_BOUNCY)
+						mapmusflags |= MUSIC_FORCERESET;
 
-				mapmusflags = tracknum & MUSIC_TRACKMASK;
-				if (!(line->flags & ML_BLOCKMONSTERS))
-					mapmusflags |= MUSIC_RELOADRESET;
+					mapmusposition = position;
 
-				S_ChangeMusic(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4));
+					S_ChangeMusicEx(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4), position,
+						!(line->flags & ML_EFFECT2) ? prefadems : 0,
+						!(line->flags & ML_EFFECT2) ? postfadems : 0);
+
+					if ((line->flags & ML_EFFECT2) && fadetarget)
+					{
+						if (!postfadems)
+							S_SetInternalMusicVolume(fadetarget);
+						else
+							S_FadeMusicFromVolume(fadetarget, fadesource, postfadems);
+					}
+				}
 
 				// Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
 				// if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
@@ -2560,7 +2951,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(line->tag, line->frontsector->lightlevel, P_AproxDistance(line->dx, line->dy)>>FRACBITS);
+			P_FadeLight(line->tag,
+				(line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
+				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
+				// to be consistent with other light and fade specials
+				(line->flags & ML_DONTPEGBOTTOM) ?
+					((line->sidenum[1] != 0xFFFF && !(sides[line->sidenum[0]].rowoffset>>FRACBITS)) ?
+						max(min(sides[line->sidenum[1]].rowoffset>>FRACBITS, 255), 0)
+						: max(min(sides[line->sidenum[0]].rowoffset>>FRACBITS, 255), 0))
+					: abs(P_AproxDistance(line->dx, line->dy))>>FRACBITS,
+				(line->flags & ML_EFFECT4),
+				(line->flags & ML_EFFECT5));
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
@@ -2576,7 +2977,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				mobj_t *altview;
 
-				if (!mo || !mo->player) // only players have views
+				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
 				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
@@ -2586,6 +2987,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (!altview)
 					return;
 
+				// If titlemap, set the camera ref for title's thinker
+				// This is not revoked until overwritten; awayviewtics is ignored
+				if (titlemapinaction)
+				{
+					titlemapcameraref = altview;
+					return;
+				}
+
 				P_SetTarget(&mo->player->awayviewmobj, altview);
 				mo->player->awayviewtics = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
 
@@ -2742,7 +3151,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				scroll_t *scroller;
 				thinker_t *th;
 
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 				{
 					if (th->function.acp1 != (actionf_p1)T_Scroll)
 						continue;
@@ -2764,6 +3173,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
 				sector_t *sec; // Sector that the FOF is visible in
 				ffloor_t *rover; // FOF that we are going to crumble
+				boolean foundrover = false; // for debug, "Can't find a FOF" message
 
 				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
 				{
@@ -2778,16 +3188,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
 						if (rover->master->frontsector->tag == foftag)
-							break;
+						{
+							foundrover = true;
+
+							EV_CrumbleChain(sec, rover);
+						}
 					}
 
-					if (!rover)
+					if (!foundrover)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 436 Executor: Can't find a FOF control sector with tag %d\n", foftag);
 						return;
 					}
-
-					EV_CrumbleChain(sec, rover);
 				}
 			}
 			break;
@@ -2867,7 +3279,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					// Unlocked something?
 					if (M_UpdateUnlockablesAndExtraEmblems())
 					{
-						S_StartSound(NULL, sfx_ncitem);
+						S_StartSound(NULL, sfx_s3k68);
 						G_SaveGameData(); // only save if unlocked something
 					}
 				}
@@ -2948,6 +3360,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
 				sector_t *sec; // Sector that the FOF is visible (or not visible) in
 				ffloor_t *rover; // FOF to vanish/un-vanish
+				boolean foundrover = false; // for debug, "Can't find a FOF" message
 				ffloortype_e oldflags; // store FOF's old flags
 
 				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
@@ -2963,26 +3376,31 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
 						if (rover->master->frontsector->tag == foftag)
-							break;
+						{
+							foundrover = true;
+
+							oldflags = rover->flags;
+
+							// Abracadabra!
+							if (line->flags & ML_NOCLIMB)
+								rover->flags |= FF_EXISTS;
+							else
+								rover->flags &= ~FF_EXISTS;
+
+							// if flags changed, reset sector's light list
+							if (rover->flags != oldflags)
+							{
+								sec->moved = true;
+								P_RecalcPrecipInSector(sec);
+							}
+						}
 					}
 
-					if (!rover)
+					if (!foundrover)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 445 Executor: Can't find a FOF control sector with tag %d\n", foftag);
 						return;
 					}
-
-					oldflags = rover->flags;
-
-					// Abracadabra!
-					if (line->flags & ML_NOCLIMB)
-						rover->flags |= FF_EXISTS;
-					else
-						rover->flags &= ~FF_EXISTS;
-
-					// if flags changed, reset sector's light list
-					if (rover->flags != oldflags)
-						sec->moved = true;
 				}
 			}
 			break;
@@ -2993,6 +3411,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
 				sector_t *sec; // Sector that the FOF is visible in
 				ffloor_t *rover; // FOF that we are going to make fall down
+				boolean foundrover = false; // for debug, "Can't find a FOF" message
 				player_t *player = NULL; // player that caused FOF to fall
 				boolean respawn = true; // should the fallen FOF respawn?
 
@@ -3015,20 +3434,82 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
 						if (rover->master->frontsector->tag == foftag)
-							break;
+						{
+							foundrover = true;
+
+							if (line->flags & ML_BLOCKMONSTERS) // FOF flags determine respawn ability instead?
+								respawn = !(rover->flags & FF_NORETURN) ^ !!(line->flags & ML_NOCLIMB); // no climb inverts
+
+							EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), player, rover->alpha, respawn);
+						}
 					}
 
-					if (!rover)
+					if (!foundrover)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 446 Executor: Can't find a FOF control sector with tag %d\n", foftag);
 						return;
 					}
+				}
+			}
+			break;
 
-					if (line->flags & ML_BLOCKMONSTERS) // FOF flags determine respawn ability instead?
-						respawn = !(rover->flags & FF_NORETURN) ^ !!(line->flags & ML_NOCLIMB); // no climb inverts
+		case 447: // Change colormap of tagged sectors!
+			// Basically this special applies a colormap to the tagged sectors, just like 606 (the colormap linedef)
+			// Except it is activated by linedef executor, not level load
+			// This could even override existing colormaps I believe
+			// -- Monster Iestyn 14/06/18
+			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+			{
+				P_ResetColormapFader(&sectors[secnum]);
 
-					EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), player, rover->alpha, respawn);
+				if (line->flags & ML_EFFECT3) // relative calc
+				{
+					extracolormap_t *exc = R_AddColormaps(
+						(line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF ?
+							sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap, // use back colormap instead of target sector
+						sides[line->sidenum[0]].colormap_data,
+						line->flags & ML_EFFECT1,  // subtract R
+						line->flags & ML_NOCLIMB,  // subtract G
+						line->flags & ML_EFFECT2,  // subtract B
+						false,                     // subtract A (no flag for this, just pass negative alpha)
+						line->flags & ML_EFFECT1,  // subtract FadeR
+						line->flags & ML_NOCLIMB,  // subtract FadeG
+						line->flags & ML_EFFECT2,  // subtract FadeB
+						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
+						false,                     // subtract FadeStart (we ran out of flags)
+						false,                     // subtract FadeEnd (we ran out of flags)
+						false,                     // ignore Fog (we ran out of flags)
+						line->flags & ML_DONTPEGBOTTOM,
+						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
+						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
+						false);
+
+					if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
+					{
+						exc->colormap = R_CreateLightTable(exc);
+						R_AddColormapToList(exc);
+						sectors[secnum].extra_colormap = exc;
+					}
+					else
+						Z_Free(exc);
 				}
+				else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
+				{
+					extracolormap_t *exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
+					exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
+					exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
+
+					if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
+					{
+						exc->colormap = R_CreateLightTable(exc);
+						R_AddColormapToList(exc);
+						sectors[secnum].extra_colormap = exc;
+					}
+					else
+						Z_Free(exc);
+				}
+				else
+					sectors[secnum].extra_colormap = sides[line->sidenum[0]].colormap_data;
 			}
 			break;
 
@@ -3073,6 +3554,29 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 449: // Enable bosses with parameter
+		{
+			INT32 bossid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
+			if (bossid & ~15) // if any bits other than first 16 are set
+			{
+				CONS_Alert(CONS_WARNING,
+					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
+					line->tag);
+				break;
+			}
+			if (line->flags & ML_NOCLIMB)
+			{
+				bossdisabled |= (1<<bossid);
+				CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid disabled = %d", bossid);
+			}
+			else
+			{
+				bossdisabled &= ~(1<<bossid);
+				CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid enabled = %d", bossid);
+			}
+			break;
+		}
+
 		case 450: // Execute Linedef Executor - for recursion
 			P_LinedefExecute(line->tag, mo, NULL);
 			break;
@@ -3092,6 +3596,363 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 		}
 
+		case 452: // Set FOF alpha
+		{
+			INT16 destvalue = line->sidenum[1] != 0xffff ?
+				(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(P_AproxDistance(line->dx, line->dy)>>FRACBITS);
+			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			sector_t *sec; // Sector that the FOF is visible in
+			ffloor_t *rover; // FOF that we are going to operate
+			boolean foundrover = false; // for debug, "Can't find a FOF" message
+
+			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			{
+				sec = sectors + secnum;
+
+				if (!sec->ffloors)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Target sector #%d has no FOFs.\n", secnum);
+					return;
+				}
+
+				for (rover = sec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master->frontsector->tag == foftag)
+					{
+						foundrover = true;
+
+						// If fading an invisible FOF whose render flags we did not yet set,
+						// initialize its alpha to 1
+						// for relative alpha calc
+						if (!(line->flags & ML_NOCLIMB) &&      // do translucent
+							(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+							!(rover->spawnflags & FF_RENDERSIDES) &&
+							!(rover->spawnflags & FF_RENDERPLANES) &&
+							!(rover->flags & FF_RENDERALL))
+							rover->alpha = 1;
+
+						P_RemoveFakeFloorFader(rover);
+						P_FadeFakeFloor(rover,
+							rover->alpha,
+							max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
+							0,                                  // set alpha immediately
+							false, NULL,                        // tic-based logic
+							false,                              // do not handle FF_EXISTS
+							!(line->flags & ML_NOCLIMB),        // handle FF_TRANSLUCENT
+							false,                              // do not handle lighting
+							false,                              // do not handle colormap
+							false,                              // do not handle collision
+							false,                              // do not do ghost fade (no collision during fade)
+							true);                               // use exact alpha values (for opengl)
+					}
+				}
+
+				if (!foundrover)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 452 Executor: Can't find a FOF control sector with tag %d\n", foftag);
+					return;
+				}
+			}
+			break;
+		}
+
+		case 453: // Fade FOF
+		{
+			INT16 destvalue = line->sidenum[1] != 0xffff ?
+				(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(line->dx>>FRACBITS);
+			INT16 speed = line->sidenum[1] != 0xffff ?
+				(INT16)(abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)) : (INT16)(abs(line->dy)>>FRACBITS);
+			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			sector_t *sec; // Sector that the FOF is visible in
+			ffloor_t *rover; // FOF that we are going to operate
+			boolean foundrover = false; // for debug, "Can't find a FOF" message
+			size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
+
+			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			{
+				sec = sectors + secnum;
+
+				if (!sec->ffloors)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Target sector #%d has no FOFs.\n", secnum);
+					return;
+				}
+
+				for (rover = sec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master->frontsector->tag == foftag)
+					{
+						foundrover = true;
+
+						// Prevent continuous execs from interfering on an existing fade
+						if (!(line->flags & ML_EFFECT5)
+							&& rover->fadingdata)
+							//&& ((fade_t*)rover->fadingdata)->timer > (ticbased ? 2 : speed*2))
+						{
+							CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Fade FOF thinker already exists, timer: %d\n", ((fade_t*)rover->fadingdata)->timer);
+							continue;
+						}
+
+						if (speed > 0)
+							P_AddFakeFloorFader(rover, secnum, j,
+								destvalue,
+								speed,
+								(line->flags & ML_EFFECT4),         // tic-based logic
+								(line->flags & ML_EFFECT3),         // Relative destvalue
+								!(line->flags & ML_BLOCKMONSTERS),  // do not handle FF_EXISTS
+								!(line->flags & ML_NOCLIMB),        // do not handle FF_TRANSLUCENT
+								!(line->flags & ML_EFFECT2),        // do not handle lighting
+								!(line->flags & ML_EFFECT2),        // do not handle colormap (ran out of flags)
+								!(line->flags & ML_BOUNCY),         // do not handle collision
+								(line->flags & ML_EFFECT1),         // do ghost fade (no collision during fade)
+								(line->flags & ML_TFERLINE));       // use exact alpha values (for opengl)
+						else
+						{
+							// If fading an invisible FOF whose render flags we did not yet set,
+							// initialize its alpha to 1
+							// for relative alpha calc
+							if (!(line->flags & ML_NOCLIMB) &&      // do translucent
+								(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+								!(rover->spawnflags & FF_RENDERSIDES) &&
+								!(rover->spawnflags & FF_RENDERPLANES) &&
+								!(rover->flags & FF_RENDERALL))
+								rover->alpha = 1;
+
+							P_RemoveFakeFloorFader(rover);
+							P_FadeFakeFloor(rover,
+								rover->alpha,
+								max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
+								0,                                  // set alpha immediately
+								false, NULL,                        // tic-based logic
+								!(line->flags & ML_BLOCKMONSTERS),  // do not handle FF_EXISTS
+								!(line->flags & ML_NOCLIMB),        // do not handle FF_TRANSLUCENT
+								!(line->flags & ML_EFFECT2),        // do not handle lighting
+								!(line->flags & ML_EFFECT2),        // do not handle colormap (ran out of flags)
+								!(line->flags & ML_BOUNCY),         // do not handle collision
+								(line->flags & ML_EFFECT1),         // do ghost fade (no collision during fade)
+								(line->flags & ML_TFERLINE));       // use exact alpha values (for opengl)
+						}
+					}
+					j++;
+				}
+
+				if (!foundrover)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 453 Executor: Can't find a FOF control sector with tag %d\n", foftag);
+					return;
+				}
+			}
+			break;
+		}
+
+		case 454: // Stop fading FOF
+		{
+			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			sector_t *sec; // Sector that the FOF is visible in
+			ffloor_t *rover; // FOF that we are going to operate
+			boolean foundrover = false; // for debug, "Can't find a FOF" message
+
+			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			{
+				sec = sectors + secnum;
+
+				if (!sec->ffloors)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Target sector #%d has no FOFs.\n", secnum);
+					return;
+				}
+
+				for (rover = sec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master->frontsector->tag == foftag)
+					{
+						foundrover = true;
+
+						P_ResetFakeFloorFader(rover, NULL,
+							!(line->flags & ML_BLOCKMONSTERS)); // do not finalize collision flags
+					}
+				}
+
+				if (!foundrover)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 454 Executor: Can't find a FOF control sector with tag %d\n", foftag);
+					return;
+				}
+			}
+			break;
+		}
+
+		case 455: // Fade colormap
+			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+			{
+				extracolormap_t *source_exc, *dest_exc, *exc;
+				INT32 speed = (INT32)((line->flags & ML_DONTPEGBOTTOM) || !sides[line->sidenum[0]].rowoffset) && line->sidenum[1] != 0xFFFF ?
+					abs(sides[line->sidenum[1]].rowoffset >> FRACBITS)
+					: abs(sides[line->sidenum[0]].rowoffset >> FRACBITS);
+
+				// Prevent continuous execs from interfering on an existing fade
+				if (!(line->flags & ML_EFFECT5)
+					&& sectors[secnum].fadecolormapdata)
+					//&& ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer > (ticbased ? 2 : speed*2))
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Fade color thinker already exists, timer: %d\n", ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer);
+					continue;
+				}
+
+				if (line->flags & ML_TFERLINE) // use back colormap instead of target sector
+					sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ?
+						sides[line->sidenum[1]].colormap_data : NULL;
+
+				exc = sectors[secnum].extra_colormap;
+
+				if (!(line->flags & ML_BOUNCY) // BOUNCY: Do not override fade from default rgba
+					&& !R_CheckDefaultColormap(sides[line->sidenum[0]].colormap_data, true, false, false)
+					&& R_CheckDefaultColormap(exc, true, false, false))
+				{
+					exc = R_CopyColormap(exc, false);
+					exc->rgba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->rgba));
+					//exc->fadergba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->fadergba));
+
+					if (!(source_exc = R_GetColormapFromList(exc)))
+					{
+						exc->colormap = R_CreateLightTable(exc);
+						R_AddColormapToList(exc);
+						source_exc = exc;
+					}
+					else
+						Z_Free(exc);
+
+					sectors[secnum].extra_colormap = source_exc;
+				}
+				else
+					source_exc = exc ? exc : R_GetDefaultColormap();
+
+				if (line->flags & ML_EFFECT3) // relative calc
+				{
+					exc = R_AddColormaps(
+						source_exc,
+						sides[line->sidenum[0]].colormap_data,
+						line->flags & ML_EFFECT1,  // subtract R
+						line->flags & ML_NOCLIMB,  // subtract G
+						line->flags & ML_EFFECT2,  // subtract B
+						false,                     // subtract A (no flag for this, just pass negative alpha)
+						line->flags & ML_EFFECT1,  // subtract FadeR
+						line->flags & ML_NOCLIMB,  // subtract FadeG
+						line->flags & ML_EFFECT2,  // subtract FadeB
+						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
+						false,                     // subtract FadeStart (we ran out of flags)
+						false,                     // subtract FadeEnd (we ran out of flags)
+						false,                     // ignore Fog (we ran out of flags)
+						line->flags & ML_DONTPEGBOTTOM,
+						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
+						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
+						false);
+				}
+				else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
+				{
+					exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
+					exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
+					exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
+				}
+				else
+					exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
+
+				if (!(dest_exc = R_GetColormapFromList(exc)))
+				{
+					exc->colormap = R_CreateLightTable(exc);
+					R_AddColormapToList(exc);
+					dest_exc = exc;
+				}
+				else
+					Z_Free(exc);
+
+				Add_ColormapFader(&sectors[secnum], source_exc, dest_exc, (line->flags & ML_EFFECT4), // tic-based timing
+					speed);
+			}
+			break;
+
+		case 456: // Stop fade colormap
+			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+				P_ResetColormapFader(&sectors[secnum]);
+			break;
+
+		case 457: // Track mobj angle to point
+			if (mo)
+			{
+				INT32 failureangle = FixedAngle((min(max(abs(sides[line->sidenum[0]].textureoffset>>FRACBITS), 0), 360))*FRACUNIT);
+				INT32 failuredelay = abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT32 failureexectag = line->sidenum[1] != 0xffff ?
+					(INT32)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : 0;
+				boolean persist = (line->flags & ML_EFFECT2);
+				mobj_t *anchormo;
+
+				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+					return;
+
+				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
+				if (!anchormo)
+					return;
+
+				mo->eflags |= MFE_TRACERANGLE;
+				P_SetTarget(&mo->tracer, anchormo);
+				mo->lastlook = persist; // don't disable behavior after first failure
+				mo->extravalue1 = failureangle; // angle to exceed for failure state
+				mo->extravalue2 = failureexectag; // exec tag for failure state (angle is not within range)
+				mo->cusval = mo->cvmem = failuredelay; // cusval = tics to allow failure before line trigger; cvmem = decrement timer
+			}
+			break;
+
+		case 458: // Stop tracking mobj angle to point
+			if (mo && (mo->eflags & MFE_TRACERANGLE))
+			{
+				mo->eflags &= ~MFE_TRACERANGLE;
+				P_SetTarget(&mo->tracer, NULL);
+				mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = mo->extravalue2 = 0;
+			}
+			break;
+
+		case 459: // Control Text Prompt
+			// console player only unless NOCLIMB is set
+			if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo))
+			{
+				INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
+				INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
+				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : line->tag);
+
+				boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
+				//boolean allplayers = (line->flags & ML_NOCLIMB);
+				boolean runpostexec = (line->flags & ML_EFFECT1);
+				boolean blockcontrols = !(line->flags & ML_EFFECT2);
+				boolean freezerealtime = !(line->flags & ML_EFFECT3);
+				//boolean freezethinkers = (line->flags & ML_EFFECT4);
+				boolean callbynamedtag = (line->flags & ML_TFERLINE);
+
+				if (closetextprompt)
+					F_EndTextPrompt(false, false);
+				else
+				{
+					if (callbynamedtag && sides[line->sidenum[0]].text && sides[line->sidenum[0]].text[0])
+						F_GetPromptPageByNamedTag(sides[line->sidenum[0]].text, &promptnum, &pagenum);
+					F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime);
+				}
+			}
+			break;
+
+		case 460: // Award rings
+			{
+				INT16 rings = (sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				INT32 delay = (sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				if (mo && mo->player)
+				{
+					if (delay <= 0 || !(leveltime % delay))
+						P_GivePlayerRings(mo->player, rings);
+				}
+			}
+			break;
+
 #ifdef POLYOBJECTS
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
@@ -3119,6 +3980,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 491:
 			PolyTranslucency(line);
 			break;
+		case 492:
+			PolyFade(line);
+			break;
 #endif
 
 		default:
@@ -3161,10 +4025,10 @@ void P_SetupSignExit(player_t *player)
 
 	// didn't find any signposts in the exit sector.
 	// spin all signposts in the level then.
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		thing = (mobj_t *)think;
 		if (thing->type != MT_SIGN)
@@ -3191,23 +4055,18 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 {
 	thinker_t *think;
 	mobj_t *mo;
-	INT32 specialnum = 0;
+	INT32 specialnum = (flag == MT_REDFLAG) ? 3 : 4;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
 		if (mo->type != flag)
 			continue;
 
-		if (mo->type == MT_REDFLAG)
-			specialnum = 3;
-		else if (mo->type == MT_BLUEFLAG)
-			specialnum = 4;
-
 		if (GETSECSPECIAL(mo->subsector->sector->special, 4) == specialnum)
 			return true;
 		else if (mo->subsector->sector->ffloors) // Check the 3D floors
@@ -3222,9 +4081,11 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 				if (GETSECSPECIAL(rover->master->frontsector->special, 4) != specialnum)
 					continue;
 
-				if (mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
-					&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
-					return true;
+				if (!(mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
+					&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector)))
+					continue;
+
+				return true;
 			}
 		}
 	}
@@ -3526,7 +4387,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
 				break;
 
-			if (!(player->powers[pw_shield] || player->rings > 0)) // Don't do anything if no shield or rings anyway
+			if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
 				break;
 
 			P_SpecialStageDamage(player, NULL, NULL);
@@ -3626,14 +4487,14 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 
 			// Find the center of the Eggtrap and release all the pretty animals!
 			// The chimps are my friends.. heeheeheheehehee..... - LouisJM
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
-
 				mo2 = (mobj_t *)th;
-				if (mo2->type == MT_EGGTRAP)
-					P_KillMobj(mo2, NULL, player->mo, 0);
+				if (mo2->type != MT_EGGTRAP)
+					continue;
+				P_KillMobj(mo2, NULL, player->mo, 0);
 			}
 
 			// clear the special so you can't push the button twice.
@@ -3695,7 +4556,7 @@ DoneSection2:
 					break;
 				}
 
-				player->mo->angle = lineangle;
+				player->mo->angle = player->drawangle = lineangle;
 
 				if (!demoplayback || P_AnalogMove(player))
 				{
@@ -3774,8 +4635,11 @@ DoneSection2:
 		case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
 			if (player->bot)
 				break;
-			if (!useNightsSS && G_IsSpecialStage(gamemap) && sstimer > 6)
-				sstimer = 6; // Just let P_Ticker take care of the rest.
+			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
+			{
+				player->nightstime = 6; // Just let P_Ticker take care of the rest.
+				return;
+			}
 
 			// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
 			{
@@ -3798,7 +4662,7 @@ DoneSection2:
 						nextmapoverride = (INT16)(lines[lineindex].frontsector->floorheight>>FRACBITS);
 
 					if (lines[lineindex].flags & ML_NOCLIMB)
-						skipstats = true;
+						skipstats = 1;
 				}
 			}
 			break;
@@ -3815,9 +4679,9 @@ DoneSection2:
 					if (!P_IsFlagAtBase(MT_REDFLAG))
 						break;
 
-					HU_SetCEchoFlags(0);
+					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s\\captured the blue flag.\\\\\\\\"), player_names[player-players]));
+					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sBLUE FLAG%s.\\\\\\\\"), "\x85", player_names[player-players], "\x80", "\x84", "\x80"));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 1)
 						S_StartSound(NULL, sfx_flgcap);
@@ -3848,9 +4712,9 @@ DoneSection2:
 					if (!P_IsFlagAtBase(MT_BLUEFLAG))
 						break;
 
-					HU_SetCEchoFlags(0);
+					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s\\captured the red flag.\\\\\\\\"), player_names[player-players]));
+					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sRED FLAG%s.\\\\\\\\"), "\x84", player_names[player-players], "\x80", "\x85", "\x80"));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 2)
 						S_StartSound(NULL, sfx_flgcap);
@@ -3932,19 +4796,22 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the first waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
 
-					if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence
-						&& mo2->health == 0)
-					{
-						waypoint = mo2;
-						break;
-					}
+					if (mo2->type != MT_TUBEWAYPOINT)
+						continue;
+					if (mo2->threshold != sequence)
+						continue;
+					if (mo2->health != 0)
+						continue;
+
+					waypoint = mo2;
+					break;
 				}
 
 				if (!waypoint)
@@ -4011,20 +4878,22 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the last waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
 
-					if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence)
-					{
-						if (!waypoint)
-							waypoint = mo2;
-						else if (mo2->health > waypoint->health)
-							waypoint = mo2;
-					}
+					if (mo2->type != MT_TUBEWAYPOINT)
+						continue;
+					if (mo2->threshold != sequence)
+						continue;
+
+					if (!waypoint)
+						waypoint = mo2;
+					else if (mo2->health > waypoint->health)
+						waypoint = mo2;
 				}
 
 				if (!waypoint)
@@ -4163,9 +5032,9 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the first waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -4201,9 +5070,9 @@ DoneSection2:
 				}
 
 				// Find waypoint before this one (waypointlow)
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -4228,9 +5097,9 @@ DoneSection2:
 				}
 
 				// Find waypoint after this one (waypointhigh)
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -4642,7 +5511,7 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
 	switch(GETSECSPECIAL(sector->special, 4))
 	{
 		case 2: // Level Exit / GOAL Sector / Flag Return
-			if (!useNightsSS && G_IsSpecialStage(gamemap))
+			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 			{
 				// Special stage GOAL sector
 				// requires touching floor.
@@ -4747,11 +5616,6 @@ void P_UpdateSpecials(void)
 	// POINT LIMIT
 	P_CheckPointLimit();
 
-#ifdef ESLOPE
-	// Dynamic slopeness
-	P_RunDynamicSlopes();
-#endif
-
 	// ANIMATE TEXTURES
 	for (anim = anims; anim < lastanim; anim++)
 	{
@@ -4821,26 +5685,26 @@ ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
 /** Adds a newly formed 3Dfloor structure to a sector's ffloors list.
   *
   * \param sec    Target sector.
-  * \param ffloor Newly formed 3Dfloor structure.
+  * \param fflr   Newly formed 3Dfloor structure.
   * \sa P_AddFakeFloor
   */
-static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *ffloor)
+static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *fflr)
 {
 	ffloor_t *rover;
 
 	if (!sec->ffloors)
 	{
-		sec->ffloors = ffloor;
-		ffloor->next = 0;
-		ffloor->prev = 0;
+		sec->ffloors = fflr;
+		fflr->next = 0;
+		fflr->prev = 0;
 		return;
 	}
 
 	for (rover = sec->ffloors; rover->next; rover = rover->next);
 
-	rover->next = ffloor;
-	ffloor->prev = rover;
-	ffloor->next = 0;
+	rover->next = fflr;
+	fflr->prev = rover;
+	fflr->next = 0;
 }
 
 /** Adds a 3Dfloor.
@@ -4855,7 +5719,7 @@ static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *ffloor)
   */
 static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, ffloortype_e flags, thinkerlist_t *secthinkers)
 {
-	ffloor_t *ffloor;
+	ffloor_t *fflr;
 	thinker_t *th;
 	friction_t *f;
 	pusher_t *p;
@@ -4865,8 +5729,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if (sec == sec2)
 		return NULL; //Don't need a fake floor on a control sector.
-	if ((ffloor = (P_GetFFloorBySec(sec, sec2))))
-		return ffloor; // If this ffloor already exists, return it
+	if ((fflr = (P_GetFFloorBySec(sec, sec2))))
+		return fflr; // If this ffloor already exists, return it
 
 	if (sec2->ceilingheight < sec2->floorheight)
 	{
@@ -4905,27 +5769,31 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	}
 
 	// Add the floor
-	ffloor = Z_Calloc(sizeof (*ffloor), PU_LEVEL, NULL);
-	ffloor->secnum = sec2 - sectors;
-	ffloor->target = sec;
-	ffloor->bottomheight = &sec2->floorheight;
-	ffloor->bottompic = &sec2->floorpic;
-	ffloor->bottomxoffs = &sec2->floor_xoffs;
-	ffloor->bottomyoffs = &sec2->floor_yoffs;
-	ffloor->bottomangle = &sec2->floorpic_angle;
+	fflr = Z_Calloc(sizeof (*fflr), PU_LEVEL, NULL);
+	fflr->secnum = sec2 - sectors;
+	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;
 
 	// Add the ceiling
-	ffloor->topheight = &sec2->ceilingheight;
-	ffloor->toppic = &sec2->ceilingpic;
-	ffloor->toplightlevel = &sec2->lightlevel;
-	ffloor->topxoffs = &sec2->ceiling_xoffs;
-	ffloor->topyoffs = &sec2->ceiling_yoffs;
-	ffloor->topangle = &sec2->ceilingpic_angle;
+	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;
 
 #ifdef ESLOPE
 	// Add slopes
-	ffloor->t_slope = &sec2->c_slope;
-	ffloor->b_slope = &sec2->f_slope;
+	fflr->t_slope = &sec2->c_slope;
+	fflr->b_slope = &sec2->f_slope;
+	// mark the target sector as having slopes, if the FOF has any of its own
+	// (this fixes FOF slopes glitching initially at level load in software mode)
+	if (sec2->hasslope)
+		sec->hasslope = true;
 #endif
 
 	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT1)) // Block player only
@@ -4934,10 +5802,10 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
 		flags &= ~FF_BLOCKPLAYER;
 
-	ffloor->spawnflags = ffloor->flags = flags;
-	ffloor->master = master;
-	ffloor->norender = INFTICS;
-
+	fflr->spawnflags = fflr->flags = flags;
+	fflr->master = master;
+	fflr->norender = INFTICS;
+	fflr->fadingdata = NULL;
 
 	// Scan the thinkers to check for special conditions applying to this FOF.
 	// If we have thinkers sorted by sector, just check the relevant ones;
@@ -4947,7 +5815,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	// Just initialise both of these to placate the compiler.
 	i = 0;
-	th = thinkercap.next;
+	th = thlist[THINK_MAIN].next;
 
 	for(;;)
 	{
@@ -4957,7 +5825,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 				th = secthinkers[sec2num].thinkers[i];
 			else break;
 		}
-		else if (th == &thinkercap)
+		else if (th == &thlist[THINK_MAIN])
 			break;
 
 		// Should this FOF have spikeness?
@@ -4993,14 +5861,14 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if (flags & FF_TRANSLUCENT)
 	{
 		if (sides[master->sidenum[0]].toptexture > 0)
-			ffloor->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
+			fflr->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
 		else
-			ffloor->alpha = 0x80;
+			fflr->alpha = 0x80;
 	}
 	else
-		ffloor->alpha = 0xff;
+		fflr->alpha = 0xff;
 
-	ffloor->spawnalpha = ffloor->alpha; // save for netgames
+	fflr->spawnalpha = fflr->alpha; // save for netgames
 
 	if (flags & FF_QUICKSAND)
 		CheckForQuicksand = true;
@@ -5024,9 +5892,9 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 		CheckForFloatBob = true;
 	}
 
-	P_AddFFloorToList(sec, ffloor);
+	P_AddFFloorToList(sec, fflr);
 
-	return ffloor;
+	return fflr;
 }
 
 //
@@ -5047,7 +5915,7 @@ static void P_AddSpikeThinker(sector_t *sec, INT32 referrer)
 
 	// create and initialize new thinker
 	spikes = Z_Calloc(sizeof (*spikes), PU_LEVSPEC, NULL);
-	P_AddThinker(&spikes->thinker);
+	P_AddThinker(THINK_MAIN, &spikes->thinker);
 
 	spikes->thinker.function.acp1 = (actionf_p1)T_SpikeSector;
 
@@ -5069,7 +5937,7 @@ static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline)
 
 	// create and initialize new thinker
 	floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL);
-	P_AddThinker(&floater->thinker);
+	P_AddThinker(THINK_MAIN, &floater->thinker);
 
 	floater->thinker.function.acp1 = (actionf_p1)T_FloatSector;
 
@@ -5093,7 +5961,7 @@ static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
 
 	// create an initialize new thinker
 	bridge = Z_Calloc(sizeof (*bridge), PU_LEVSPEC, NULL);
-	P_AddThinker(&bridge->thinker);
+	P_AddThinker(THINK_MAIN, &bridge->thinker);
 
 	bridge->thinker.function.acp1 = (actionf_p1)T_BridgeThinker;
 
@@ -5129,7 +5997,7 @@ static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control,
 
 	// create and initialize new displacement thinker
 	displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL);
-	P_AddThinker(&displace->thinker);
+	P_AddThinker(THINK_MAIN, &displace->thinker);
 
 	displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace;
 	displace->affectee = affectee;
@@ -5156,7 +6024,7 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
 
 	// create and initialize new elevator thinker
 	block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
-	P_AddThinker(&block->thinker);
+	P_AddThinker(THINK_MAIN, &block->thinker);
 
 	block->thinker.function.acp1 = (actionf_p1)T_MarioBlockChecker;
 	block->sourceline = sourceline;
@@ -5185,7 +6053,7 @@ static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline)
 	levelspecthink_t *raise;
 
 	raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL);
-	P_AddThinker(&raise->thinker);
+	P_AddThinker(THINK_MAIN, &raise->thinker);
 
 	raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
@@ -5224,7 +6092,7 @@ static void P_AddOldAirbob(sector_t *sec, line_t *sourceline, boolean noadjust)
 	levelspecthink_t *airbob;
 
 	airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL);
-	P_AddThinker(&airbob->thinker);
+	P_AddThinker(THINK_MAIN, &airbob->thinker);
 
 	airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
@@ -5285,7 +6153,7 @@ static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, lin
 
 	// create and initialize new elevator thinker
 	thwomp = Z_Calloc(sizeof (*thwomp), PU_LEVSPEC, NULL);
-	P_AddThinker(&thwomp->thinker);
+	P_AddThinker(THINK_MAIN, &thwomp->thinker);
 
 	thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector;
 
@@ -5321,7 +6189,7 @@ static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
 
 	// create and initialize new thinker
 	nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL);
-	P_AddThinker(&nobaddies->thinker);
+	P_AddThinker(THINK_MAIN, &nobaddies->thinker);
 
 	nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector;
 
@@ -5337,13 +6205,13 @@ static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
   * \sa P_SpawnSpecials, T_EachTimeThinker
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
+static void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
 {
 	levelspecthink_t *eachtime;
 
 	// create and initialize new thinker
 	eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL);
-	P_AddThinker(&eachtime->thinker);
+	P_AddThinker(THINK_MAIN, &eachtime->thinker);
 
 	eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
 
@@ -5365,7 +6233,7 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto
 
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(&elevator->thinker);
+	P_AddThinker(THINK_MAIN, &elevator->thinker);
 
 	elevator->thinker.function.acp1 = (actionf_p1)T_CameraScanner;
 	elevator->type = elevateBounce;
@@ -5389,30 +6257,30 @@ void T_LaserFlash(laserthink_t *flash)
 	msecnode_t *node;
 	mobj_t *thing;
 	sector_t *sourcesec;
-	ffloor_t *ffloor = flash->ffloor;
+	ffloor_t *fflr = flash->ffloor;
 	sector_t *sector = flash->sector;
 	fixed_t top, bottom;
 
-	if (!ffloor || !(ffloor->flags & FF_EXISTS))
+	if (!fflr || !(fflr->flags & FF_EXISTS))
 		return;
 
 	if (leveltime & 2)
-		//ffloor->flags |= FF_RENDERALL;
-		ffloor->alpha = 0xB0;
+		//fflr->flags |= FF_RENDERALL;
+		fflr->alpha = 0xB0;
 	else
-		//ffloor->flags &= ~FF_RENDERALL;
-		ffloor->alpha = 0x90;
+		//fflr->flags &= ~FF_RENDERALL;
+		fflr->alpha = 0x90;
 
-	sourcesec = ffloor->master->frontsector; // Less to type!
+	sourcesec = fflr->master->frontsector; // Less to type!
 
 #ifdef ESLOPE
-	top = (*ffloor->t_slope) ? P_GetZAt(*ffloor->t_slope, sector->soundorg.x, sector->soundorg.y)
-			: *ffloor->topheight;
-	bottom = (*ffloor->b_slope) ? P_GetZAt(*ffloor->b_slope, sector->soundorg.x, sector->soundorg.y)
-			: *ffloor->bottomheight;
+	top = (*fflr->t_slope) ? P_GetZAt(*fflr->t_slope, sector->soundorg.x, sector->soundorg.y)
+			: *fflr->topheight;
+	bottom = (*fflr->b_slope) ? P_GetZAt(*fflr->b_slope, sector->soundorg.x, sector->soundorg.y)
+			: *fflr->bottomheight;
 	sector->soundorg.z = (top + bottom)/2;
 #else
-	sector->soundorg.z = (*ffloor->topheight + *ffloor->bottomheight)/2;
+	sector->soundorg.z = (*fflr->topheight + *fflr->bottomheight)/2;
 #endif
 	S_StartSound(&sector->soundorg, sfx_laser);
 
@@ -5421,7 +6289,7 @@ void T_LaserFlash(laserthink_t *flash)
 	{
 		thing = node->m_thing;
 
-		if ((ffloor->master->flags & ML_EFFECT1)
+		if ((fflr->master->flags & ML_EFFECT1)
 			&& thing->flags & MF_BOSS)
 			continue; // Don't hurt bosses
 
@@ -5445,7 +6313,7 @@ void T_LaserFlash(laserthink_t *flash)
 
 /** Adds a laser thinker to a 3Dfloor.
   *
-  * \param ffloor      3Dfloor to turn into a laser block.
+  * \param fflr      3Dfloor to turn into a laser block.
   * \param sector      Target sector.
   * \param secthkiners Lists of thinkers sorted by sector. May be NULL.
   * \sa T_LaserFlash
@@ -5454,17 +6322,17 @@ void T_LaserFlash(laserthink_t *flash)
 static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers)
 {
 	laserthink_t *flash;
-	ffloor_t *ffloor = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
+	ffloor_t *fflr = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
 
-	if (!ffloor)
+	if (!fflr)
 		return;
 
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->thinker.function.acp1 = (actionf_p1)T_LaserFlash;
-	flash->ffloor = ffloor;
+	flash->ffloor = fflr;
 	flash->sector = sec; // For finding mobjs
 	flash->sec = sec2;
 	flash->sourceline = line;
@@ -5501,7 +6369,7 @@ void P_InitSpecials(void)
 
 	// Defaults in case levels don't have them set.
 	sstimer = 90*TICRATE + 6;
-	totalrings = 1;
+	ssspheres = 1;
 
 	CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
 
@@ -5523,7 +6391,31 @@ void P_InitSpecials(void)
 	// Set globalweather
 	globalweather = mapheaderinfo[gamemap-1]->weather;
 
-	P_InitTagLists();   // Create xref tables for tags
+	P_InitTagLists();   // Create xref tables for tags
+}
+
+static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
+{
+	if (!(master->flags & ML_NETONLY)) // Modify floor flat alignment unless ML_NETONLY flag is set
+	{
+		sector->spawn_flrpic_angle = sector->floorpic_angle = flatangle;
+		sector->floor_xoffs += xoffs;
+		sector->floor_yoffs += yoffs;
+		// saved for netgames
+		sector->spawn_flr_xoffs = sector->floor_xoffs;
+		sector->spawn_flr_yoffs = sector->floor_yoffs;
+	}
+
+	if (!(master->flags & ML_NONET)) // Modify ceiling flat alignment unless ML_NONET flag is set
+	{
+		sector->spawn_ceilpic_angle = sector->ceilingpic_angle = flatangle;
+		sector->ceiling_xoffs += xoffs;
+		sector->ceiling_yoffs += yoffs;
+		// saved for netgames
+		sector->spawn_ceil_xoffs = sector->ceiling_xoffs;
+		sector->spawn_ceil_yoffs = sector->ceiling_yoffs;
+	}
+
 }
 
 /** After the map has loaded, scans for specials that spawn 3Dfloors and
@@ -5547,6 +6439,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	// but currently isn't.
 	(void)fromnetsave;
 
+	// yep, we do this here - "bossdisabled" is considered an apparatus of specials.
+	bossdisabled = 0;
+
 	// Init special SECTORs.
 	sector = sectors;
 	for (i = 0; i < numsectors; i++, sector++)
@@ -5571,7 +6466,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		{
 			case 10: // Time for special stage
 				sstimer = (sector->floorheight>>FRACBITS) * TICRATE + 6; // Time to finish
-				totalrings = sector->ceilingheight>>FRACBITS; // Ring count for special stage
+				ssspheres = sector->ceilingheight>>FRACBITS; // Ring count for special stage
 				break;
 
 			case 11: // Custom global gravity!
@@ -5595,8 +6490,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 	}
 
-	P_SearchForDisableLinedefs(); // Disable linedefs are now allowed to disable *any* line
-
 	P_SpawnScrollers(); // Add generalized scrollers
 	P_SpawnFriction();  // Friction model using linedefs
 	P_SpawnPushers();   // Pusher model using linedefs
@@ -5606,7 +6499,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	secthinkers = Z_Calloc(numsectors * sizeof(thinkerlist_t), PU_STATIC, NULL);
 
 	// Firstly, find out how many there are in each sector
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 	{
 		if (th->function.acp1 == (actionf_p1)T_SpikeSector)
 			secthinkers[((levelspecthink_t *)th)->sector - sectors].count++;
@@ -5626,7 +6519,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 
 	// Finally, populate the lists.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 	{
 		size_t secnum = (size_t)-1;
 
@@ -5645,28 +6538,22 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
-		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment with arbitrary skin setups...
+		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
 		{
 			// set line specials to 0 here too, same reason as above
 			if (netgame || multiplayer)
 			{
-				// future: nonet flag?
-			}
-			else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
-			{
-				lines[i].special = 0;
-				continue;
-			}
-			else
-			{
-				if ((players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
-				|| (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
-				|| (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX)))
+				if (lines[i].flags & ML_NONET)
 				{
 					lines[i].special = 0;
 					continue;
 				}
 			}
+			else if (lines[i].flags & ML_NETONLY)
+			{
+				lines[i].special = 0;
+				continue;
+			}
 		}
 
 		switch (lines[i].special)
@@ -5705,20 +6592,14 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
-#ifdef PARANOIA
-			case 6: // Disable tags if level not cleared
-				I_Error("Failed to catch a disable linedef");
-				break;
-#endif
-
 			case 7: // Flat alignment - redone by toast
-				if ((lines[i].flags & (ML_NOSONIC|ML_NOTAILS)) != (ML_NOSONIC|ML_NOTAILS)) // If you can do something...
+				if ((lines[i].flags & (ML_NETONLY|ML_NONET)) != (ML_NETONLY|ML_NONET)) // If you can do something...
 				{
 					angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
 					fixed_t xoffs;
 					fixed_t yoffs;
 
-					if (lines[i].flags & ML_NOKNUX) // Set offset through x and y texture offsets if NOKNUX flag is set
+					if (lines[i].flags & ML_EFFECT6) // Set offset through x and y texture offsets if ML_EFFECT6 flag is set
 					{
 						xoffs = sides[lines[i].sidenum[0]].textureoffset;
 						yoffs = sides[lines[i].sidenum[0]].rowoffset;
@@ -5729,27 +6610,13 @@ void P_SpawnSpecials(INT32 fromnetsave)
 						yoffs = lines[i].v1->y;
 					}
 
-					for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+					//If no tag is given, apply to front sector
+					if (lines[i].tag == 0)
+						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
+					else
 					{
-						if (!(lines[i].flags & ML_NOSONIC)) // Modify floor flat alignment unless NOSONIC flag is set
-						{
-							sectors[s].spawn_flrpic_angle = sectors[s].floorpic_angle = flatangle;
-							sectors[s].floor_xoffs += xoffs;
-							sectors[s].floor_yoffs += yoffs;
-							// saved for netgames
-							sectors[s].spawn_flr_xoffs = sectors[s].floor_xoffs;
-							sectors[s].spawn_flr_yoffs = sectors[s].floor_yoffs;
-						}
-
-						if (!(lines[i].flags & ML_NOTAILS)) // Modify ceiling flat alignment unless NOTAILS flag is set
-						{
-							sectors[s].spawn_ceilpic_angle = sectors[s].ceilingpic_angle = flatangle;
-							sectors[s].ceiling_xoffs += xoffs;
-							sectors[s].ceiling_yoffs += yoffs;
-							// saved for netgames
-							sectors[s].spawn_ceil_xoffs = sectors[s].ceiling_xoffs;
-							sectors[s].spawn_ceil_yoffs = sectors[s].ceiling_yoffs;
-						}
+						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0;)
+							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
 				else // Otherwise, print a helpful warning. Can I do no less?
@@ -6305,9 +7172,21 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 259: // Make-Your-Own FOF!
 				if (lines[i].sidenum[1] != 0xffff)
 				{
-					UINT8 *data = W_CacheLumpNum(lastloadedmaplumpnum + ML_SIDEDEFS,PU_STATIC);
+					UINT8 *data;
 					UINT16 b;
 
+					if (W_IsLumpWad(lastloadedmaplumpnum)) // welp it's a map wad in a pk3
+					{ // HACK: Open wad file rather quickly so we can get the data from the sidedefs lump
+						UINT8 *wadData = W_CacheLumpNum(lastloadedmaplumpnum, PU_STATIC);
+						filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
+						fileinfo += ML_SIDEDEFS; // we only need the SIDEDEFS lump
+						data = Z_Malloc(fileinfo->size, PU_STATIC, NULL);
+						M_Memcpy(data, wadData + fileinfo->filepos, fileinfo->size); // copy data
+						Z_Free(wadData); // we're done with this now
+					}
+					else // phew it's just a WAD
+						data = W_CacheLumpNum(lastloadedmaplumpnum + ML_SIDEDEFS,PU_STATIC);
+
 					for (b = 0; b < (INT16)numsides; b++)
 					{
 						register mapsidedef_t *msd = (mapsidedef_t *)data + b;
@@ -6359,6 +7238,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 301:
 			case 310:
 			case 312:
+			case 332:
 				sec = sides[*lines[i].sidenum].sector - sectors;
 				P_AddEachTimeThinker(&sectors[sec], &lines[i]);
 				break;
@@ -6396,6 +7276,22 @@ void P_SpawnSpecials(INT32 fromnetsave)
 				}
 				break;
 
+			// NiGHTS trigger executors
+			case 323:
+			case 324:
+			case 325:
+			case 326:
+			case 327:
+			case 328:
+			case 329:
+			case 330:
+				break;
+
+			// Skin trigger executors
+			case 331:
+			case 333:
+				break;
+
 			case 399: // Linedef execute on map load
 				// This is handled in P_RunLevelLoadExecutors.
 				break;
@@ -6434,6 +7330,24 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 431:
 				break;
 
+			case 449: // Enable bosses with parameter
+			{
+				INT32 bossid = sides[*lines[i].sidenum].textureoffset>>FRACBITS;
+				if (bossid & ~15) // if any bits other than first 16 are set
+				{
+					CONS_Alert(CONS_WARNING,
+						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
+						lines[i].tag);
+					break;
+				}
+				if (!(lines[i].flags & ML_NOCLIMB))
+				{
+					bossdisabled |= (1<<bossid); // gotta disable in the first place to enable
+					CONS_Debug(DBG_GAMELOGIC, "Line type 449 spawn effect: bossid disabled = %d", bossid);
+				}
+				break;
+			}
+
 			// 500 is used for a scroller
 			// 501 is used for a scroller
 			// 502 is used for a scroller
@@ -6509,16 +7423,8 @@ void P_SpawnSpecials(INT32 fromnetsave)
 
 			case 606: // HACK! Copy colormaps. Just plain colormaps.
 				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					sectors[s].midmap = lines[i].frontsector->midmap;
-				break;
-
-#ifdef ESLOPE // Slope copy specials. Handled here for sanity.
-			case 720:
-			case 721:
-			case 722:
-				P_CopySectorSlope(&lines[i]);
+					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = sides[lines[i].sidenum[0]].colormap_data;
 				break;
-#endif
 
 			default:
 				break;
@@ -6547,6 +7453,10 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 31: // Polyobj_Displace
 				PolyDisplace(&lines[i]);
 				break;
+
+			case 32: // Polyobj_RotDisplace
+				PolyRotDisplace(&lines[i]);
+				break;
 		}
 	}
 #endif
@@ -6676,6 +7586,7 @@ void T_Scroll(scroll_t *s)
 		line_t *line;
 		size_t i;
 		INT32 sect;
+		ffloor_t *rover;
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -6717,6 +7628,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
@@ -6780,6 +7704,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
@@ -6848,7 +7785,7 @@ static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT3
 	if ((s->control = control) != -1)
 		s->last_height = sectors[control].floorheight + sectors[control].ceilingheight;
 	s->affectee = affectee;
-	P_AddThinker(&s->thinker);
+	P_AddThinker(THINK_MAIN, &s->thinker);
 }
 
 /** Initializes the scrollers.
@@ -6982,7 +7919,7 @@ static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t o
 	d->exists = true;
 	d->timer = 1;
 
-	P_AddThinker(&d->thinker);
+	P_AddThinker(THINK_MAIN, &d->thinker);
 }
 
 /** Makes a FOF appear/disappear
@@ -7029,6 +7966,7 @@ void T_Disappear(disappear_t *d)
 				}
 			}
 			sectors[s].moved = true;
+			P_RecalcPrecipInSector(&sectors[s]);
 		}
 
 		if (d->exists)
@@ -7044,6 +7982,591 @@ void T_Disappear(disappear_t *d)
 	}
 }
 
+/** Removes fadingdata from FOF control sector
+ *
+ * \param line	line to search for target faders
+ * \param data	pointer to set new fadingdata to. Can be NULL to erase.
+ */
+static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize)
+{
+	fade_t *fadingdata = (fade_t *)rover->fadingdata;
+	// find any existing thinkers and remove them, then replace with new data
+	if (fadingdata != data)
+	{
+		if (fadingdata)
+		{
+			if (finalize)
+				P_FadeFakeFloor(rover,
+					fadingdata->sourcevalue,
+					fadingdata->alpha >= fadingdata->destvalue ?
+						fadingdata->alpha - 1 : // trigger fade-out finish
+						fadingdata->alpha + 1, // trigger fade-in finish
+					0,
+					fadingdata->ticbased,
+					&fadingdata->timer,
+					fadingdata->doexists,
+					fadingdata->dotranslucent,
+					fadingdata->dolighting,
+					fadingdata->docolormap,
+					fadingdata->docollision,
+					fadingdata->doghostfade,
+					fadingdata->exactalpha);
+			rover->alpha = fadingdata->alpha;
+
+			if (fadingdata->dolighting)
+				P_RemoveLighting(&sectors[rover->secnum]);
+
+			if (fadingdata->docolormap)
+				P_ResetColormapFader(&sectors[rover->secnum]);
+
+			P_RemoveThinker(&fadingdata->thinker);
+		}
+
+		rover->fadingdata = data;
+	}
+}
+
+static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destvalue, INT16 speed, boolean ticbased, INT32 *timer,
+	boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
+	boolean docollision, boolean doghostfade, boolean exactalpha)
+{
+	boolean stillfading = false;
+	INT32 alpha;
+	fade_t *fadingdata = (fade_t *)rover->fadingdata;
+	(void)docolormap; // *shrug* maybe we can use this in the future. For now, let's be consistent with our other function params
+
+	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 (dotranslucent &&
+		(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+		!(rover->flags & FF_FOG) && // do not include fog
+		!(rover->spawnflags & FF_RENDERSIDES) &&
+		!(rover->spawnflags & FF_RENDERPLANES) &&
+		!(rover->flags & FF_RENDERALL))
+		rover->alpha = 1;
+
+	if (fadingdata)
+		alpha = fadingdata->alpha;
+	else
+		alpha = rover->alpha;
+
+	// routines specific to fade in and fade out
+	if (!ticbased && alpha == destvalue)
+		return stillfading;
+	else if (alpha > destvalue) // fade out
+	{
+		// finish fading out
+		if (speed < 1 || (!ticbased && alpha - speed <= destvalue + speed) ||
+			(ticbased && (--(*timer) <= 0 || alpha <= destvalue)))
+		{
+			alpha = destvalue;
+
+			if (docollision)
+			{
+				if (rover->spawnflags & FF_SOLID)
+					rover->flags &= ~FF_SOLID;
+				if (rover->spawnflags & FF_SWIMMABLE)
+					rover->flags &= ~FF_SWIMMABLE;
+				if (rover->spawnflags & FF_QUICKSAND)
+					rover->flags &= ~FF_QUICKSAND;
+				if (rover->spawnflags & FF_BUSTUP)
+					rover->flags &= ~FF_BUSTUP;
+				if (rover->spawnflags & FF_MARIO)
+					rover->flags &= ~FF_MARIO;
+			}
+		}
+		else // continue fading out
+		{
+			if (!ticbased)
+				alpha -= speed;
+			else
+			{
+				INT16 delta = abs(destvalue - sourcevalue);
+				fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
+				alpha = max(min(alpha, sourcevalue - (INT16)FixedMul(delta, factor)), destvalue);
+			}
+			stillfading = true;
+		}
+	}
+	else // fade in
+	{
+		// finish fading in
+		if (speed < 1 || (!ticbased && alpha + speed >= destvalue - speed) ||
+			(ticbased && (--(*timer) <= 0 || alpha >= destvalue)))
+		{
+			alpha = destvalue;
+
+			if (docollision)
+			{
+				if (rover->spawnflags & FF_SOLID)
+					rover->flags |= FF_SOLID;
+				if (rover->spawnflags & FF_SWIMMABLE)
+					rover->flags |= FF_SWIMMABLE;
+				if (rover->spawnflags & FF_QUICKSAND)
+					rover->flags |= FF_QUICKSAND;
+				if (rover->spawnflags & FF_BUSTUP)
+					rover->flags |= FF_BUSTUP;
+				if (rover->spawnflags & FF_MARIO)
+					rover->flags |= FF_MARIO;
+			}
+		}
+		else // continue fading in
+		{
+			if (!ticbased)
+				alpha += speed;
+			else
+			{
+				INT16 delta = abs(destvalue - sourcevalue);
+				fixed_t factor = min(FixedDiv(speed - (*timer), speed), 1*FRACUNIT);
+				alpha = min(max(alpha, sourcevalue + (INT16)FixedMul(delta, factor)), destvalue);
+			}
+			stillfading = true;
+		}
+	}
+
+	// routines common to both fade in and fade out
+	if (!stillfading)
+	{
+		if (doexists && !(rover->spawnflags & FF_BUSTUP))
+		{
+			if (alpha <= 1)
+				rover->flags &= ~FF_EXISTS;
+			else
+				rover->flags |= FF_EXISTS;
+
+			// Re-render lighting at end of fade
+			if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
+				rover->target->moved = true;
+		}
+
+		if (dotranslucent && !(rover->flags & FF_FOG))
+		{
+			if (alpha >= 256)
+			{
+				if (!(rover->flags & FF_CUTSOLIDS) &&
+					(rover->spawnflags & FF_CUTSOLIDS))
+				{
+					rover->flags |= FF_CUTSOLIDS;
+					rover->target->moved = true;
+				}
+
+				rover->flags &= ~FF_TRANSLUCENT;
+			}
+			else
+			{
+				rover->flags |= FF_TRANSLUCENT;
+
+				if ((rover->flags & FF_CUTSOLIDS) &&
+					(rover->spawnflags & FF_CUTSOLIDS))
+				{
+					rover->flags &= ~FF_CUTSOLIDS;
+					rover->target->moved = true;
+				}
+			}
+
+			if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+				!(rover->spawnflags & FF_RENDERSIDES) &&
+				!(rover->spawnflags & FF_RENDERPLANES))
+			{
+				if (rover->alpha > 1)
+					rover->flags |= FF_RENDERALL;
+				else
+					rover->flags &= ~FF_RENDERALL;
+			}
+		}
+	}
+	else
+	{
+		if (doexists && !(rover->spawnflags & FF_BUSTUP))
+		{
+			// Re-render lighting if we haven't yet set FF_EXISTS (beginning of fade)
+			if (dolighting && !(rover->spawnflags & FF_NOSHADE) && !(rover->flags & FF_EXISTS))
+				rover->target->moved = true;
+
+			rover->flags |= FF_EXISTS;
+		}
+
+		if (dotranslucent && !(rover->flags & FF_FOG))
+		{
+			rover->flags |= FF_TRANSLUCENT;
+
+			if ((rover->flags & FF_CUTSOLIDS) &&
+				(rover->spawnflags & FF_CUTSOLIDS))
+			{
+				rover->flags &= ~FF_CUTSOLIDS;
+				rover->target->moved = true;
+			}
+
+			if ((rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+				!(rover->spawnflags & FF_RENDERSIDES) &&
+				!(rover->spawnflags & FF_RENDERPLANES))
+				rover->flags |= FF_RENDERALL;
+		}
+
+		if (docollision)
+		{
+			if (doghostfade) // remove collision flags during fade
+			{
+				if (rover->spawnflags & FF_SOLID)
+					rover->flags &= ~FF_SOLID;
+				if (rover->spawnflags & FF_SWIMMABLE)
+					rover->flags &= ~FF_SWIMMABLE;
+				if (rover->spawnflags & FF_QUICKSAND)
+					rover->flags &= ~FF_QUICKSAND;
+				if (rover->spawnflags & FF_BUSTUP)
+					rover->flags &= ~FF_BUSTUP;
+				if (rover->spawnflags & FF_MARIO)
+					rover->flags &= ~FF_MARIO;
+			}
+			else // keep collision during fade
+			{
+				if (rover->spawnflags & FF_SOLID)
+					rover->flags |= FF_SOLID;
+				if (rover->spawnflags & FF_SWIMMABLE)
+					rover->flags |= FF_SWIMMABLE;
+				if (rover->spawnflags & FF_QUICKSAND)
+					rover->flags |= FF_QUICKSAND;
+				if (rover->spawnflags & FF_BUSTUP)
+					rover->flags |= FF_BUSTUP;
+				if (rover->spawnflags & FF_MARIO)
+					rover->flags |= FF_MARIO;
+			}
+		}
+	}
+
+	if (!(rover->flags & FF_FOG)) // don't set FOG alpha
+	{
+		if (!stillfading || exactalpha)
+			rover->alpha = alpha;
+		else // clamp fadingdata->alpha to software's alpha levels
+		{
+			if (alpha < 12)
+				rover->alpha = destvalue < 12 ? destvalue : 1; // 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;
+			else if (alpha < 89)
+				rover->alpha = destvalue >= 64 && destvalue < 89 ? destvalue : 76;
+			else if (alpha < 115)
+				rover->alpha = destvalue >= 89 && destvalue < 115 ? destvalue : 102;
+			else if (alpha < 140)
+				rover->alpha = destvalue >= 115 && destvalue < 140 ? destvalue : 128;
+			else if (alpha < 166)
+				rover->alpha = destvalue >= 140 && destvalue < 166 ? destvalue : 154;
+			else if (alpha < 192)
+				rover->alpha = destvalue >= 166 && destvalue < 192 ? destvalue : 179;
+			else if (alpha < 217)
+				rover->alpha = destvalue >= 192 && destvalue < 217 ? destvalue : 204;
+			else if (alpha < 243)
+				rover->alpha = destvalue >= 217 && destvalue < 243 ? destvalue : 230;
+			else // Opaque
+				rover->alpha = destvalue >= 243 ? destvalue : 256;
+		}
+	}
+
+	if (fadingdata)
+		fadingdata->alpha = alpha;
+
+	return stillfading;
+}
+
+/** Adds fake floor fader thinker.
+  *
+  * \param destvalue    transparency value to fade to
+  * \param speed        speed to fade by
+  * \param ticbased     tic-based logic, speed = duration
+  * \param relative     Destvalue is relative to rover->alpha
+  * \param doexists	    handle FF_EXISTS
+  * \param dotranslucent handle FF_TRANSLUCENT
+  * \param dolighting  fade FOF light
+  * \param docollision handle interactive flags
+  * \param doghostfade  no interactive flags during fading
+  * \param exactalpha   use exact alpha values (opengl)
+  */
+static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloornum,
+	INT16 destvalue, INT16 speed, boolean ticbased, boolean relative,
+	boolean doexists, boolean dotranslucent, boolean dolighting, boolean docolormap,
+	boolean docollision, boolean doghostfade, boolean exactalpha)
+{
+	fade_t *d;
+
+	// If fading an invisible FOF whose render flags we did not yet set,
+	// initialize its alpha to 1
+	if (dotranslucent &&
+		(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
+		!(rover->spawnflags & FF_RENDERSIDES) &&
+		!(rover->spawnflags & FF_RENDERPLANES) &&
+		!(rover->flags & FF_RENDERALL))
+		rover->alpha = 1;
+
+	// already equal, nothing to do
+	if (rover->alpha == max(1, min(256, relative ? rover->alpha + destvalue : destvalue)))
+		return;
+
+	d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
+
+	d->thinker.function.acp1 = (actionf_p1)T_Fade;
+	d->rover = rover;
+	d->sectornum = (UINT32)sectornum;
+	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
+
+	if (ticbased)
+	{
+		d->ticbased = true;
+		d->timer = d->speed = abs(speed); // use d->speed as total duration
+	}
+	else
+	{
+		d->ticbased = false;
+		d->speed = max(1, speed); // minimum speed 1/tic // if speed < 1, alpha is set immediately in thinker
+		d->timer = -1;
+	}
+
+	d->doexists = doexists;
+	d->dotranslucent = dotranslucent;
+	d->dolighting = dolighting;
+	d->docolormap = docolormap;
+	d->docollision = docollision;
+	d->doghostfade = doghostfade;
+	d->exactalpha = exactalpha;
+
+	// find any existing thinkers and remove them, then replace with new data
+	P_ResetFakeFloorFader(rover, d, false);
+
+	// Set a separate thinker for shadow fading
+	if (dolighting && !(rover->flags & FF_NOSHADE))
+	{
+		UINT16 lightdelta = abs(sectors[rover->secnum].spawn_lightlevel - rover->target->lightlevel);
+		fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
+		fixed_t adjustedlightdelta = FixedMul(lightdelta, alphapercent);
+
+		if (rover->target->lightlevel >= sectors[rover->secnum].spawn_lightlevel) // fading out, get lighter
+			d->destlightlevel = rover->target->lightlevel - adjustedlightdelta;
+		else // fading in, get darker
+			d->destlightlevel = rover->target->lightlevel + adjustedlightdelta;
+
+		P_FadeLightBySector(&sectors[rover->secnum],
+			d->destlightlevel,
+			ticbased ? d->timer :
+				FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT,
+			true);
+	}
+	else
+		d->destlightlevel = -1;
+
+	// Set a separate thinker for colormap fading
+	if (docolormap && !(rover->flags & FF_NOSHADE) && sectors[rover->secnum].spawn_extra_colormap)
+	{
+		extracolormap_t *dest_exc,
+			*source_exc = sectors[rover->secnum].extra_colormap ? sectors[rover->secnum].extra_colormap : R_GetDefaultColormap();
+
+		INT32 colordelta = R_GetRgbaA(sectors[rover->secnum].spawn_extra_colormap->rgba); // alpha is from 0
+		fixed_t alphapercent = min(FixedDiv(d->destvalue, rover->spawnalpha), 1*FRACUNIT); // don't make darker than spawn_lightlevel
+		fixed_t adjustedcolordelta = FixedMul(colordelta, alphapercent);
+		INT32 coloralpha;
+
+		coloralpha = adjustedcolordelta;
+
+		dest_exc = R_CopyColormap(sectors[rover->secnum].spawn_extra_colormap, false);
+		dest_exc->rgba = R_GetRgbaRGB(dest_exc->rgba) + R_PutRgbaA(coloralpha);
+
+		if (!(d->dest_exc = R_GetColormapFromList(dest_exc)))
+		{
+			dest_exc->colormap = R_CreateLightTable(dest_exc);
+			R_AddColormapToList(dest_exc);
+			d->dest_exc = dest_exc;
+		}
+		else
+			Z_Free(dest_exc);
+
+		// If fading from 0, set source_exc rgb same to dest_exc
+		if (!R_CheckDefaultColormap(d->dest_exc, true, false, false)
+			&& R_CheckDefaultColormap(source_exc, true, false, false))
+		{
+			extracolormap_t *exc = R_CopyColormap(source_exc, false);
+			exc->rgba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->rgba));
+			exc->fadergba = R_GetRgbaRGB(d->dest_exc->rgba) + R_PutRgbaA(R_GetRgbaA(source_exc->fadergba));
+
+			if (!(source_exc = R_GetColormapFromList(exc)))
+			{
+				exc->colormap = R_CreateLightTable(exc);
+				R_AddColormapToList(exc);
+				source_exc = exc;
+			}
+			else
+				Z_Free(exc);
+		}
+
+		Add_ColormapFader(&sectors[rover->secnum], source_exc, d->dest_exc, true,
+			ticbased ? d->timer :
+				FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT);
+	}
+
+	P_AddThinker(THINK_MAIN, &d->thinker);
+}
+
+/** Makes a FOF fade
+  *
+  * \param d Fade thinker.
+  * \sa P_AddFakeFloorFader
+  */
+void T_Fade(fade_t *d)
+{
+	if (d->rover && !P_FadeFakeFloor(d->rover, d->sourcevalue, d->destvalue, d->speed, d->ticbased, &d->timer,
+		d->doexists, d->dotranslucent, d->dolighting, d->docolormap, d->docollision, d->doghostfade, d->exactalpha))
+	{
+		// Finalize lighting, copypasta from P_AddFakeFloorFader
+		if (d->dolighting && !(d->rover->flags & FF_NOSHADE) && d->destlightlevel > -1)
+			sectors[d->rover->secnum].lightlevel = d->destlightlevel;
+
+		// Finalize colormap
+		if (d->docolormap && !(d->rover->flags & FF_NOSHADE) && sectors[d->rover->secnum].spawn_extra_colormap)
+			sectors[d->rover->secnum].extra_colormap = d->dest_exc;
+
+		P_RemoveFakeFloorFader(d->rover);
+	}
+}
+
+static void P_ResetColormapFader(sector_t *sector)
+{
+	if (sector->fadecolormapdata)
+	{
+		// The thinker is the first member in all the action structs,
+		// so just let the thinker get freed, and that will free the whole
+		// structure.
+		P_RemoveThinker(&((elevator_t *)sector->fadecolormapdata)->thinker);
+		sector->fadecolormapdata = NULL;
+	}
+}
+
+static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
+	boolean ticbased, INT32 duration)
+{
+	fadecolormap_t *d;
+
+	P_ResetColormapFader(sector);
+
+	// nothing to do, set immediately
+	if (!duration || R_CheckEqualColormaps(source_exc, dest_exc, true, true, true))
+	{
+		sector->extra_colormap = dest_exc;
+		return;
+	}
+
+	d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL);
+	d->thinker.function.acp1 = (actionf_p1)T_FadeColormap;
+	d->sector = sector;
+	d->source_exc = source_exc;
+	d->dest_exc = dest_exc;
+
+	if (ticbased)
+	{
+		d->ticbased = true;
+		d->duration = d->timer = duration;
+	}
+	else
+	{
+		d->ticbased = false;
+		d->timer = 256;
+		d->duration = duration; // use as speed
+	}
+
+	sector->fadecolormapdata = d;
+	P_AddThinker(THINK_MAIN, &d->thinker);
+}
+
+void T_FadeColormap(fadecolormap_t *d)
+{
+	if ((d->ticbased && --d->timer <= 0)
+		|| (!d->ticbased && (d->timer -= d->duration) <= 0)) // d->duration used as speed decrement
+	{
+		d->sector->extra_colormap = d->dest_exc;
+		P_ResetColormapFader(d->sector);
+	}
+	else
+	{
+		extracolormap_t *exc;
+		INT32 duration = d->ticbased ? d->duration : 256;
+		fixed_t factor = min(FixedDiv(duration - d->timer, duration), 1*FRACUNIT);
+		INT16 cr, cg, cb, ca, fadestart, fadeend, fog;
+		INT32 rgba, fadergba;
+
+		// NULL failsafes (or intentionally set to signify default)
+		if (!d->sector->extra_colormap)
+			d->sector->extra_colormap = R_GetDefaultColormap();
+
+		if (!d->source_exc)
+			d->source_exc = R_GetDefaultColormap();
+
+		if (!d->dest_exc)
+			d->dest_exc = R_GetDefaultColormap();
+
+		// For each var (rgba + fadergba + params = 11 vars), we apply
+		// percentage fading: currentval = sourceval + (delta * percent of duration elapsed)
+		// delta is negative when fading out (destval is lower)
+		// max/min are used to ensure progressive calcs don't go backwards and to cap values to dest.
+
+#define APPLYFADE(dest, src, cur) (\
+(dest-src < 0) ? \
+	max(\
+		min(cur,\
+			src + (INT16)FixedMul(dest-src, factor)),\
+		dest)\
+: (dest-src > 0) ? \
+	min(\
+		max(cur,\
+			src + (INT16)FixedMul(dest-src, factor)),\
+		dest)\
+: \
+	dest\
+)
+
+		cr = APPLYFADE(R_GetRgbaR(d->dest_exc->rgba), R_GetRgbaR(d->source_exc->rgba), R_GetRgbaR(d->sector->extra_colormap->rgba));
+		cg = APPLYFADE(R_GetRgbaG(d->dest_exc->rgba), R_GetRgbaG(d->source_exc->rgba), R_GetRgbaG(d->sector->extra_colormap->rgba));
+		cb = APPLYFADE(R_GetRgbaB(d->dest_exc->rgba), R_GetRgbaB(d->source_exc->rgba), R_GetRgbaB(d->sector->extra_colormap->rgba));
+		ca = APPLYFADE(R_GetRgbaA(d->dest_exc->rgba), R_GetRgbaA(d->source_exc->rgba), R_GetRgbaA(d->sector->extra_colormap->rgba));
+
+		rgba = R_PutRgbaRGBA(cr, cg, cb, ca);
+
+		cr = APPLYFADE(R_GetRgbaR(d->dest_exc->fadergba), R_GetRgbaR(d->source_exc->fadergba), R_GetRgbaR(d->sector->extra_colormap->fadergba));
+		cg = APPLYFADE(R_GetRgbaG(d->dest_exc->fadergba), R_GetRgbaG(d->source_exc->fadergba), R_GetRgbaG(d->sector->extra_colormap->fadergba));
+		cb = APPLYFADE(R_GetRgbaB(d->dest_exc->fadergba), R_GetRgbaB(d->source_exc->fadergba), R_GetRgbaB(d->sector->extra_colormap->fadergba));
+		ca = APPLYFADE(R_GetRgbaA(d->dest_exc->fadergba), R_GetRgbaA(d->source_exc->fadergba), R_GetRgbaA(d->sector->extra_colormap->fadergba));
+
+		fadergba = R_PutRgbaRGBA(cr, cg, cb, ca);
+
+		fadestart = APPLYFADE(d->dest_exc->fadestart, d->source_exc->fadestart, d->sector->extra_colormap->fadestart);
+		fadeend = APPLYFADE(d->dest_exc->fadeend, d->source_exc->fadeend, d->sector->extra_colormap->fadeend);
+		fog = abs(factor) > FRACUNIT/2 ? d->dest_exc->fog : d->source_exc->fog; // set new fog flag halfway through fade
+
+#undef APPLYFADE
+
+		//////////////////
+		// setup new colormap
+		//////////////////
+
+		if (!(d->sector->extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog)))
+		{
+			exc = R_CreateDefaultColormap(false);
+			exc->fadestart = fadestart;
+			exc->fadeend = fadeend;
+			exc->fog = (boolean)fog;
+			exc->rgba = rgba;
+			exc->fadergba = fadergba;
+			exc->colormap = R_CreateLightTable(exc);
+			R_AddColormapToList(exc);
+			d->sector->extra_colormap = exc;
+		}
+	}
+}
+
 /*
  SoM: 3/8/2000: Friction functions start.
  Add_Friction,
@@ -7075,7 +8598,7 @@ static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32
 	else
 		f->roverfriction = false;
 
-	P_AddThinker(&f->thinker);
+	P_AddThinker(THINK_MAIN, &f->thinker);
 }
 
 /** Applies friction to all things in a sector.
@@ -7241,7 +8764,7 @@ static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *
 		p->z = p->source->z;
 	}
 	p->affectee = affectee;
-	P_AddThinker(&p->thinker);
+	P_AddThinker(THINK_MAIN, &p->thinker);
 }
 
 
@@ -7633,7 +9156,6 @@ void T_Pusher(pusher_t *p)
 					thing->player->pflags |= jumped;
 
 				thing->player->pflags |= PF_SLIDING;
-				P_SetPlayerMobjState (thing, thing->info->painstate); // Whee!
 				thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
 
 				if (!demoplayback || P_AnalogMove(thing->player))
@@ -7744,39 +9266,3 @@ static void P_SpawnPushers(void)
 				break;
 		}
 }
-
-static void P_SearchForDisableLinedefs(void)
-{
-	size_t i;
-	INT32 j;
-
-	// Look for disable linedefs
-	for (i = 0; i < numlines; i++)
-	{
-		if (lines[i].special == 6)
-		{
-			// Remove special
-			// Do *not* remove tag. That would mess with the tag lists
-			// that P_InitTagLists literally just created!
-			lines[i].special = 0;
-
-			// Ability flags can disable disable linedefs now, lol
-			if (netgame || multiplayer)
-			{
-				// future: nonet flag?
-			}
-			else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
-				continue; // Net-only never triggers in single player
-			else if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
-				continue;
-			else if (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
-				continue;
-			else if (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))
-				continue;
-
-			// Disable any linedef specials with our tag.
-			for (j = -1; (j = P_FindLineFromLineTag(&lines[i], j)) >= 0;)
-				lines[j].special = 0;
-		}
-	}
-}
diff --git a/src/p_spec.h b/src/p_spec.h
index c4e05e0723485c4fd226227a463234eeab5c02f1..d7176afee73e7b4bb3a8356744c6633f60070cdc 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -66,6 +66,10 @@ void P_SwitchWeather(INT32 weathernum);
 boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller);
 void P_ChangeSectorTag(UINT32 sector, INT16 newtag);
+void P_RunNightserizeExecutors(mobj_t *actor);
+void P_RunDeNightserizeExecutors(mobj_t *actor);
+void P_RunNightsLapExecutors(mobj_t *actor);
+void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres);
 
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id);
 
@@ -130,10 +134,15 @@ typedef struct
   */
 typedef struct
 {
-	thinker_t thinker; ///< Thinker in use for the effect.
-	sector_t *sector;  ///< Sector where action is taking place.
-	INT32 destlevel;   ///< Light level we're fading to.
-	INT32 speed;       ///< Speed at which to change light level.
+	thinker_t thinker;		///< Thinker in use for the effect.
+	sector_t *sector;		///< Sector where action is taking place.
+	INT16 sourcelevel;		///< Light level we're fading from.
+	INT16 destlevel;		///< Light level we're fading to.
+
+	fixed_t fixedcurlevel;	///< Fixed point for current light level.
+	fixed_t fixedpertic;	///< Fixed point for increment per tic.
+	// The reason for those two above to be fixed point is to deal with decimal values that would otherwise get trimmed away.
+	INT32 timer;			///< Internal timer.
 } lightlevel_t;
 
 #define GLOWSPEED 8
@@ -141,6 +150,8 @@ typedef struct
 #define FASTDARK 15
 #define SLOWDARK 35
 
+void P_RemoveLighting(sector_t *sector);
+
 void T_FireFlicker(fireflicker_t *flick);
 fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxsector, INT32 length);
 void T_LightningFlash(lightflash_t *flash);
@@ -152,7 +163,8 @@ strobe_t * P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector
 void T_Glow(glow_t *g);
 glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector, INT32 length);
 
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed);
+void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean ticbased);
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force);
 void T_LightFade(lightlevel_t *ll);
 
 typedef enum
@@ -449,6 +461,47 @@ typedef struct
 
 void T_Disappear(disappear_t *d);
 
+// Model for fading FOFs
+typedef struct
+{
+	thinker_t thinker;  ///< Thinker structure for effect.
+	ffloor_t *rover;    ///< Target ffloor
+	extracolormap_t *dest_exc; ///< Colormap to fade to
+	UINT32 sectornum;    ///< Number of ffloor target sector
+	UINT32 ffloornum;    ///< Number of ffloor of target sector
+	INT32 alpha;        ///< Internal alpha counter
+	INT16 sourcevalue;  ///< Transparency value to fade from
+	INT16 destvalue;    ///< Transparency value to fade to
+	INT16 destlightlevel; ///< Light level to fade to
+	INT16 speed;        ///< Speed to fade by
+	boolean ticbased;    ///< Tic-based logic toggle
+	INT32 timer;        ///< Timer for tic-based logic
+	boolean doexists;   ///< Handle FF_EXISTS
+	boolean dotranslucent; ///< Handle FF_TRANSLUCENT
+	boolean dolighting; ///< Handle shadows and light blocks
+	boolean docolormap; ///< Handle colormaps
+	boolean docollision; ///< Handle interactive flags
+	boolean doghostfade; ///< No interactive flags during fading
+	boolean exactalpha; ///< Use exact alpha values (opengl)
+} fade_t;
+
+void T_Fade(fade_t *d);
+
+// Model for fading colormaps
+
+typedef struct
+{
+	thinker_t thinker;          ///< Thinker structure for effect.
+	sector_t *sector;           ///< Sector where action is taking place.
+	extracolormap_t *source_exc;
+	extracolormap_t *dest_exc;
+	boolean ticbased;           ///< Tic-based timing
+	INT32 duration;             ///< Total duration for tic-based logic (OR: speed increment)
+	INT32 timer;                ///< Timer for tic-based logic (OR: internal speed counter)
+} fadecolormap_t;
+
+void T_FadeColormap(fadecolormap_t *d);
+
 // Prototype functions for pushers
 void T_Pusher(pusher_t *p);
 mobj_t *P_GetPushThing(UINT32 s);
diff --git a/src/p_telept.c b/src/p_telept.c
index f70c4664c810c2f38568377b8eaca471826b63f7..e80dd04284778301e8210e45349be8d66a2e8a3d 100644
--- a/src/p_telept.c
+++ b/src/p_telept.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -125,12 +125,10 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle
 	if (!P_TeleportMove(thing, x, y, z))
 		return false;
 
-	thing->angle = angle;
-
 	if (!dontstopmove)
 		thing->momx = thing->momy = thing->momz = 0;
 	else // Change speed to match direction
-		P_InstaThrust(thing, thing->angle, P_AproxDistance(thing->momx, thing->momy));
+		P_InstaThrust(thing, angle, FixedHypot(thing->momx, thing->momy));
 
 	if (thing->player)
 	{
@@ -139,21 +137,6 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle
 		else
 			thing->player->viewz = thing->z + thing->player->viewheight;
 
-		if (!dontstopmove)
-			thing->reactiontime = TICRATE/2; // don't move for about half a second
-
-		// absolute angle position
-		if (thing->player == &players[consoleplayer])
-			localangle = angle;
-		if (thing->player == &players[secondarydisplayplayer])
-			localangle2 = angle;
-
-		// move chasecam at new player location
-		if (splitscreen && camera2.chase && thing->player == &players[secondarydisplayplayer])
-			P_ResetCamera(thing->player, &camera2);
-		else if (camera.chase && thing->player == &players[displayplayer])
-			P_ResetCamera(thing->player, &camera);
-
 		// don't run in place after a teleport
 		if (!dontstopmove)
 		{
@@ -171,11 +154,30 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle
 			thing->player->speed = 0;
 			P_ResetPlayer(thing->player);
 			P_SetPlayerMobjState(thing, S_PLAY_STND);
+
+			thing->reactiontime = TICRATE/2; // don't move for about half a second
+			thing->player->drawangle = angle;
 		}
+		else
+			thing->player->drawangle += (angle - thing->angle);
+
+		// absolute angle position
+		if (thing->player == &players[consoleplayer])
+			localangle = angle;
+		if (thing->player == &players[secondarydisplayplayer])
+			localangle2 = angle;
+
+		// move chasecam at new player location
+		if (splitscreen && camera2.chase && thing->player == &players[secondarydisplayplayer])
+			P_ResetCamera(thing->player, &camera2);
+		else if (camera.chase && thing->player == &players[displayplayer])
+			P_ResetCamera(thing->player, &camera);
 
 		if (flash)
 			P_FlashPal(thing->player, PAL_MIXUP, 10);
 	}
 
+	thing->angle = angle;
+
 	return true;
 }
diff --git a/src/p_tick.c b/src/p_tick.c
index 658b1e4ea82255519c1574bf0098a88ddeef8a15..7606510fef2d8a302809f0d541616babb003e95a 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -35,8 +35,8 @@ tic_t leveltime;
 // but the first element must be thinker_t.
 //
 
-// Both the head and tail of the thinker list.
-thinker_t thinkercap;
+// The entries will behave like both the head and tail of the lists.
+thinker_t thlist[NUM_THINKERLISTS];
 
 void Command_Numthinkers_f(void)
 {
@@ -44,6 +44,9 @@ void Command_Numthinkers_f(void)
 	INT32 count = 0;
 	actionf_p1 action;
 	thinker_t *think;
+	thinklistnum_t start = 0;
+	thinklistnum_t end = NUM_THINKERLISTS - 1;
+	thinklistnum_t i;
 
 	if (gamestate != GS_LEVEL)
 	{
@@ -56,12 +59,12 @@ void Command_Numthinkers_f(void)
 		CONS_Printf(M_GetText("numthinkers <#>: Count number of thinkers\n"));
 		CONS_Printf(
 			"\t1: P_MobjThinker\n"
-			"\t2: P_RainThinker\n"
-			"\t3: P_SnowThinker\n"
-			"\t4: P_NullPrecipThinker\n"
-			"\t5: T_Friction\n"
-			"\t6: T_Pusher\n"
-			"\t7: P_RemoveThinkerDelayed\n");
+			/*"\t2: P_RainThinker\n"
+			"\t3: P_SnowThinker\n"*/
+			"\t2: P_NullPrecipThinker\n"
+			"\t3: T_Friction\n"
+			"\t4: T_Pusher\n"
+			"\t5: P_RemoveThinkerDelayed\n");
 		return;
 	}
 
@@ -70,30 +73,34 @@ void Command_Numthinkers_f(void)
 	switch (num)
 	{
 		case 1:
+			start = end = THINK_MOBJ;
 			action = (actionf_p1)P_MobjThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_MobjThinker");
 			break;
-		case 2:
+		/*case 2:
 			action = (actionf_p1)P_RainThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_RainThinker");
 			break;
 		case 3:
 			action = (actionf_p1)P_SnowThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_SnowThinker");
-			break;
-		case 4:
+			break;*/
+		case 2:
+			start = end = THINK_PRECIP;
 			action = (actionf_p1)P_NullPrecipThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_NullPrecipThinker");
 			break;
-		case 5:
+		case 3:
+			start = end = THINK_MAIN;
 			action = (actionf_p1)T_Friction;
 			CONS_Printf(M_GetText("Number of %s: "), "T_Friction");
 			break;
-		case 6:
+		case 4:
+			start = end = THINK_MAIN;
 			action = (actionf_p1)T_Pusher;
 			CONS_Printf(M_GetText("Number of %s: "), "T_Pusher");
 			break;
-		case 7:
+		case 5:
 			action = (actionf_p1)P_RemoveThinkerDelayed;
 			CONS_Printf(M_GetText("Number of %s: "), "P_RemoveThinkerDelayed");
 			break;
@@ -102,12 +109,15 @@ void Command_Numthinkers_f(void)
 			return;
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (i = start; i <= end; i++)
 	{
-		if (think->function.acp1 != action)
-			continue;
+		for (think = thlist[i].next; think != &thlist[i]; think = think->next)
+		{
+			if (think->function.acp1 != action)
+				continue;
 
-		count++;
+			count++;
+		}
 	}
 
 	CONS_Printf("%d\n", count);
@@ -139,9 +149,9 @@ void Command_CountMobjs_f(void)
 
 			count = 0;
 
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				if (((mobj_t *)th)->type == i)
@@ -159,9 +169,9 @@ void Command_CountMobjs_f(void)
 	{
 		count = 0;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			if (((mobj_t *)th)->type == i)
@@ -178,19 +188,22 @@ void Command_CountMobjs_f(void)
 //
 void P_InitThinkers(void)
 {
-	thinkercap.prev = thinkercap.next = &thinkercap;
+	UINT8 i;
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+		thlist[i].prev = thlist[i].next = &thlist[i];
 }
 
-//
-// P_AddThinker
 // Adds a new thinker at the end of the list.
-//
-void P_AddThinker(thinker_t *thinker)
+void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 {
-	thinkercap.prev->next = thinker;
-	thinker->next = &thinkercap;
-	thinker->prev = thinkercap.prev;
-	thinkercap.prev = thinker;
+#ifdef PARANOIA
+	I_Assert(n < NUM_THINKERLISTS);
+#endif
+
+	thlist[n].prev->next = thinker;
+	thinker->next = &thlist[n];
+	thinker->prev = thlist[n].prev;
+	thlist[n].prev = thinker;
 
 	thinker->references = 0;    // killough 11/98: init reference counter to 0
 }
@@ -213,22 +226,33 @@ static thinker_t *currentthinker;
 // remove it, and set currentthinker to one node preceeding it, so
 // that the next step in P_RunThinkers() will get its successor.
 //
-void P_RemoveThinkerDelayed(void *pthinker)
+void P_RemoveThinkerDelayed(thinker_t *thinker)
 {
-	thinker_t *thinker = pthinker;
-	if (!thinker->references)
+	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)
 	{
-		{
-			/* Remove from main thinker list */
-			thinker_t *next = thinker->next;
-			/* Note that currentthinker is guaranteed to point to us,
-			 * and since we're freeing our memory, we had better change that. So
-			 * point it to thinker->prev, so the iterator will correctly move on to
-			 * thinker->prev->next = thinker->next */
-			(next->prev = currentthinker = thinker->prev)->next = next;
-		}
-		Z_Free(thinker);
+		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;
+		return;
 	}
+#undef BEENAROUNDBIT
+#else
+	if (thinker->references)
+		return;
+#endif
+
+	/* Remove from main thinker list */
+	next = thinker->next;
+	/* Note that currentthinker is guaranteed to point to us,
+	* and since we're freeing our memory, we had better change that. So
+	* point it to thinker->prev, so the iterator will correctly move on to
+	* thinker->prev->next = thinker->next */
+	(next->prev = currentthinker = thinker->prev)->next = next;
+
+	Z_Free(thinker);
 }
 
 //
@@ -248,7 +272,7 @@ void P_RemoveThinker(thinker_t *thinker)
 #ifdef HAVE_BLUA
 	LUA_InvalidateUserdata(thinker);
 #endif
-	thinker->function.acp1 = P_RemoveThinkerDelayed;
+	thinker->function.acp1 = (actionf_p1)P_RemoveThinkerDelayed;
 }
 
 /*
@@ -296,11 +320,18 @@ if ((*mop = targ) != NULL) // Set new target and if non-NULL, increase its count
 //
 static inline void P_RunThinkers(void)
 {
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap; currentthinker = currentthinker->next)
+	size_t i;
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		if (currentthinker->function.acp1)
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = currentthinker->next)
+		{
+#ifdef PARANOIA
+			I_Assert(currentthinker->function.acp1 != NULL);
+#endif
 			currentthinker->function.acp1(currentthinker);
+		}
 	}
+
 }
 
 //
@@ -424,7 +455,7 @@ void P_DoTeamscrambling(void)
 
 static inline void P_DoSpecialStageStuff(void)
 {
-	boolean inwater = false;
+	boolean stillalive = false;
 	INT32 i;
 
 	// Can't drown in a special stage
@@ -436,69 +467,58 @@ static inline void P_DoSpecialStageStuff(void)
 		players[i].powers[pw_underwater] = players[i].powers[pw_spacetime] = 0;
 	}
 
-	if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
-		S_SpeedMusic(1.4f);
-
-	if (sstimer < 7 && sstimer > 0) // The special stage time is up!
-	{
-		sstimer = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (playeringame[i])
-			{
-				players[i].exiting = (14*TICRATE)/5 + 1;
-				players[i].pflags &= ~PF_GLIDING;
-			}
-
-			if (i == consoleplayer)
-				S_StartSound(NULL, sfx_lose);
-		}
-
-		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
-			S_SpeedMusic(1.0f);
+	//if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
+		//S_SpeedMusic(1.4f);
 
-		stagefailed = true;
-	}
-
-	if (sstimer > 1) // As long as time isn't up...
+	if (sstimer && !objectplacing)
 	{
-		UINT32 ssrings = 0;
+		UINT16 countspheres = 0;
 		// Count up the rings of all the players and see if
 		// they've collected the required amount.
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i])
 			{
-				ssrings += players[i].rings;
+				tic_t oldnightstime = players[i].nightstime;
+				countspheres += players[i].spheres;
 
 				// If in water, deplete timer 6x as fast.
-				if ((players[i].mo->eflags & MFE_TOUCHWATER)
-					|| (players[i].mo->eflags & MFE_UNDERWATER))
-					inwater = true;
-			}
-
-		if (ssrings >= totalrings && totalrings > 0)
-		{
-			// Halt all the players
-			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i])
+				if (players[i].mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
+					players[i].nightstime -= 5;
+				if (--players[i].nightstime > 6)
+				{
+					if (P_IsLocalPlayer(&players[i]) && oldnightstime > 10*TICRATE && players[i].nightstime <= 10*TICRATE)
+						S_ChangeMusicInternal("_drown", false);
+					stillalive = true;
+				}
+				else if (!players[i].exiting)
 				{
-					players[i].mo->momx = players[i].mo->momy = 0;
 					players[i].exiting = (14*TICRATE)/5 + 1;
+					players[i].pflags &= ~(PF_GLIDING|PF_BOUNCING);
+					players[i].nightstime = 0;
+					if (P_IsLocalPlayer(&players[i]))
+						S_StartSound(NULL, sfx_s3k66);
 				}
+			}
 
-			sstimer = 0;
-
-			P_GiveEmerald(true);
-		}
-
-		// Decrement the timer
-		if (!objectplacing)
+		if (stillalive)
 		{
-			if (inwater)
-				sstimer -= 6;
-			else
-				sstimer--;
+			if (countspheres >= ssspheres)
+			{
+				// Halt all the players
+				for (i = 0; i < MAXPLAYERS; i++)
+					if (playeringame[i])
+					{
+						players[i].mo->momx = players[i].mo->momy = 0;
+						players[i].exiting = (14*TICRATE)/5 + 1;
+					}
+
+				sstimer = 0;
+				P_GiveEmerald(true);
+				P_RestoreMusic(&players[consoleplayer]);
+			}
 		}
+		else
+			sstimer = 0;
 	}
 }
 
@@ -581,13 +601,20 @@ void P_Ticker(boolean run)
 			OP_ObjectplaceMovement(&players[0]);
 			P_MoveChaseCamera(&players[0], &camera, false);
 			P_MapEnd();
+			S_SetStackAdjustmentStart();
 			return;
 		}
 	}
 
 	// Check for pause or menu up in single player
 	if (paused || P_AutoPause())
+	{
+		S_SetStackAdjustmentStart();
 		return;
+	}
+
+	if (!S_MusicPaused())
+		S_AdjustMusicStackTics();
 
 	postimgtype = postimgtype2 = postimg_none;
 
@@ -606,9 +633,10 @@ void P_Ticker(boolean run)
 	}
 
 	// Keep track of how long they've been playing!
-	totalplaytime++;
+	if (!demoplayback) // Don't increment if a demo is playing.
+		totalplaytime++;
 
-	if (!useNightsSS && G_IsSpecialStage(gamemap))
+	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
 
 	if (runemeraldmanager)
diff --git a/src/p_tick.h b/src/p_tick.h
index 75868fdd8b6b274cedd4d93a268069a63693b488..8551ccc7606015b895c928c197328f2ee1247289 100644
--- a/src/p_tick.h
+++ b/src/p_tick.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,7 +27,7 @@ void Command_CountMobjs_f(void);
 void P_Ticker(boolean run);
 void P_PreTicker(INT32 frames);
 void P_DoTeamscrambling(void);
-void P_RemoveThinkerDelayed(void *pthinker); //killed
+void P_RemoveThinkerDelayed(thinker_t *thinker); //killed
 mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target);   // killough 11/98
 
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index f422e9b650994aba6311691d1aa1d5ed9a8acfa9..0861398d671a00fdfd777fc3bc04ddb4c1804dd5 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -56,6 +56,29 @@
 static void P_NukeAllPlayers(player_t *player);
 #endif
 
+//
+// Jingle stuff.
+//
+
+jingle_t jingleinfo[NUMJINGLES] = {
+	// {musname, looping, reset, nest}
+	{""        , false}, // JT_NONE
+	{""        , false}, // JT_OTHER
+	{""        , false}, // JT_MASTER
+	{"_1up"    , false},
+	{"_shoes"  ,  true},
+	{"_inv"    , false},
+	{"_minv"   , false},
+	{"_drown"  , false},
+	{"_super"  ,  true},
+	{"_gover"  , false},
+	{"_ntime"  , false},  // JT_NIGHTSTIMEOUT
+	{"_drown"  , false}   // JT_SSTIMEOUT
+	// {"_clear"  , false},
+	// {"_inter"  ,  true},
+	// {"_conti"  ,  true}
+};
+
 //
 // Movement.
 //
@@ -283,22 +306,11 @@ boolean P_PlayerMoving(INT32 pnum)
 //
 UINT8 P_GetNextEmerald(void)
 {
-	if (!useNightsSS) // In order
-	{
-		if (!(emeralds & EMERALD1)) return 0;
-		if (!(emeralds & EMERALD2)) return 1;
-		if (!(emeralds & EMERALD3)) return 2;
-		if (!(emeralds & EMERALD4)) return 3;
-		if (!(emeralds & EMERALD5)) return 4;
-		if (!(emeralds & EMERALD6)) return 5;
-		return 6;
-	}
-	else // Depends on stage
-	{
-		if (gamemap < sstage_start || gamemap > sstage_end)
-			return 0;
+	if (gamemap >= sstage_start && gamemap <= sstage_end)
 		return (UINT8)(gamemap - sstage_start);
-	}
+	if (gamemap >= smpstage_start || gamemap <= smpstage_end)
+		return (UINT8)(gamemap - smpstage_start);
+	return 0;
 }
 
 //
@@ -309,20 +321,43 @@ UINT8 P_GetNextEmerald(void)
 //
 void P_GiveEmerald(boolean spawnObj)
 {
-	INT32 i;
-	UINT8 em;
+	UINT8 em = P_GetNextEmerald();
 
 	S_StartSound(NULL, sfx_cgot); // Got the emerald!
-	em = P_GetNextEmerald();
 	emeralds |= (1 << em);
+	stagefailed = false;
 
 	if (spawnObj)
 	{
+		// The Chaos Emerald begins to orbit us!
+		// Only visibly give it to ONE person!
+		UINT8 i, pnum = ((playeringame[consoleplayer]) && (!players[consoleplayer].spectator) && (players[consoleplayer].mo)) ? consoleplayer : 255;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i])
-				P_SetMobjState(P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_GOTEMERALD),
-				mobjinfo[MT_GOTEMERALD].spawnstate + em);
+		{
+			mobj_t *emmo;
+			if (!playeringame[i])
+				continue;
+			if (players[i].spectator)
+				continue;
+			if (!players[i].mo)
+				continue;
+
+			emmo = P_SpawnMobjFromMobj(players[i].mo, 0, 0, players[i].mo->height, MT_GOTEMERALD);
+			P_SetTarget(&emmo->target, players[i].mo);
+			P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
+			P_SetTarget(&players[i].mo->tracer, emmo);
+
+			if (pnum == 255)
+			{
+				i = pnum;
+				continue;
+			}
+
+			if (i == pnum)
+				continue;
 
+			emmo->flags2 |= MF2_DONTDRAW;
+		}
 	}
 }
 
@@ -355,14 +390,18 @@ UINT8 P_FindLowestMare(void)
 
 	// scan the thinkers
 	// to find the egg capsule with the lowest mare
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_EGGCAPSULE && mo2->health > 0)
+		if (mo2->type != MT_EGGCAPSULE)
+			continue;
+		if (mo2->health <= 0)
+			continue;
+
 		{
 			const UINT8 threshold = (UINT8)mo2->threshold;
 			if (mare == 255)
@@ -399,37 +438,39 @@ boolean P_TransferToNextMare(player_t *player)
 	CONS_Debug(DBG_NIGHTS, "Mare is %d\n", mare);
 
 	player->mare = mare;
+	player->marelap = 0;
+	player->marebonuslap = 0;
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_AXIS)
+		if (mo2->type != MT_AXIS)
+			continue;
+
+		if (mo2->threshold != mare)
+			continue;
+
+		if (closestaxis == NULL)
 		{
-			if (mo2->threshold == mare)
-			{
-				if (closestaxis == NULL)
-				{
-					closestaxis = mo2;
-					lowestaxisnum = mo2->health;
-					dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
-				}
-				else if (mo2->health < lowestaxisnum)
-				{
-					dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
+			closestaxis = mo2;
+			lowestaxisnum = mo2->health;
+			dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
+		}
+		else if (mo2->health < lowestaxisnum)
+		{
+			dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
 
-					if (dist1 < dist2)
-					{
-						closestaxis = mo2;
-						lowestaxisnum = mo2->health;
-						dist2 = dist1;
-					}
-				}
+			if (dist1 < dist2)
+			{
+				closestaxis = mo2;
+				lowestaxisnum = mo2->health;
+				dist2 = dist1;
 			}
 		}
 	}
@@ -453,9 +494,9 @@ static mobj_t *P_FindAxis(INT32 mare, INT32 axisnum)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -464,11 +505,13 @@ static mobj_t *P_FindAxis(INT32 mare, INT32 axisnum)
 		if (!(mo2->flags2 & MF2_AXIS))
 			return NULL;
 
-		if (mo2->type == MT_AXIS)
-		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-				return mo2;
-		}
+		if (mo2->type != MT_AXIS)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+		return mo2;
 	}
 
 	return NULL;
@@ -486,9 +529,9 @@ static mobj_t *P_FindAxisTransfer(INT32 mare, INT32 axisnum, mobjtype_t type)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -497,11 +540,13 @@ static mobj_t *P_FindAxisTransfer(INT32 mare, INT32 axisnum, mobjtype_t type)
 		if (!(mo2->flags2 & MF2_AXIS))
 			return NULL;
 
-		if (mo2->type == type)
-		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-				return mo2;
-		}
+		if (mo2->type != type)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+		return mo2;
 	}
 
 	return NULL;
@@ -525,32 +570,33 @@ void P_TransferToAxis(player_t *player, INT32 axisnum)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_AXIS)
+		if (mo2->type != MT_AXIS)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+
+		if (closestaxis == NULL)
 		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-			{
-				if (closestaxis == NULL)
-				{
-					closestaxis = mo2;
-					dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
-				}
-				else
-				{
-					dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
+			closestaxis = mo2;
+			dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
+		}
+		else
+		{
+			dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
 
-					if (dist1 < dist2)
-					{
-						closestaxis = mo2;
-						dist2 = dist1;
-					}
-				}
+			if (dist1 < dist2)
+			{
+				closestaxis = mo2;
+				dist2 = dist1;
 			}
 		}
 	}
@@ -586,6 +632,10 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->climbing = 0;
 	player->mo->fuse = 0;
 	player->speed = 0;
+	player->marelap = 0;
+	player->marebonuslap = 0;
+	player->flyangle = 0;
+	player->anotherflyangle = 0;
 	P_SetTarget(&player->mo->target, NULL);
 	P_SetTarget(&player->axis1, P_SetTarget(&player->axis2, NULL));
 
@@ -594,7 +644,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->mo->skin = &skins[player->skin];
 	player->followitem = skins[player->skin].followitem;
 	player->mo->color = player->skincolor;
-	G_GhostAddColor(GHC_NORMAL);
+	G_GhostAddColor(GHC_RETURNSKIN);
 
 	// Restore aiming angle
 	if (player == &players[consoleplayer])
@@ -602,9 +652,6 @@ static void P_DeNightserizePlayer(player_t *player)
 	else if (player == &players[secondarydisplayplayer])
 		localaiming2 = 0;
 
-	// If you screwed up, kiss your score goodbye.
-	player->marescore = 0;
-
 	P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 
 	// If in a special stage, add some preliminary exit time.
@@ -615,13 +662,17 @@ static void P_DeNightserizePlayer(player_t *player)
 			if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				players[i].nightstime = 1; // force everyone else to fall too.
 		player->exiting = 3*TICRATE;
-		stagefailed = true; // NIGHT OVER
+
+		// If you screwed up, kiss your score and ring bonus goodbye.
+		// But only do this in special stage (and instakill!) In regular stages, wait til we hit the ground.
+		player->marescore = player->spheres =\
+		 player->rings = 0;
 	}
 
 	// Check to see if the player should be killed.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -629,13 +680,51 @@ static void P_DeNightserizePlayer(player_t *player)
 			continue;
 
 		if (mo2->flags2 & MF2_AMBUSH)
+		{
+			player->marescore = player->spheres =\
+			 player->rings = 0;
 			P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
 
+			// Reset music to beginning if MIXNIGHTSCOUNTDOWN
+			if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+#ifdef _WIN32
+				&& S_MusicType() != MU_MID
+#endif
+			)
+				S_SetMusicPosition(0);
+		}
+
 		break;
 	}
 
+	if (player->mo->scale != player->oldscale)
+		player->mo->destscale = player->oldscale;
+	player->oldscale = 0;
+
 	// Restore from drowning music
-	P_RestoreMusic(player);
+	if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+#ifdef _WIN32
+		&& S_MusicType() != MU_MID
+#endif
+	)
+	{
+		S_StopSoundByNum(sfx_timeup);
+		S_StopFadingMusic();
+		S_SetInternalMusicVolume(100);
+
+		// Reset the music if you did not destroy all the capsules, because you failed.
+		// Why make the all-capsules exception: because it's your reward for nearly finishing the level!
+		// (unless the player auto-loses upon denightserizing; for death case, see above.)
+		if (P_FindLowestMare() != UINT8_MAX || G_IsSpecialStage(gamemap))
+			S_SetMusicPosition(0);
+	}
+	else
+	{
+		music_stack_fadein = 0; // HACK: Change fade-in for restore music
+		P_RestoreMusic(player);
+	}
+
+	P_RunDeNightserizeExecutors(player->mo);
 }
 
 //
@@ -644,14 +733,26 @@ static void P_DeNightserizePlayer(player_t *player)
 // NiGHTS Time!
 void P_NightserizePlayer(player_t *player, INT32 nighttime)
 {
-	INT32 oldmare;
+	UINT8 oldmare, oldmarelap, oldmarebonuslap;
 
 	// Bots can't be NiGHTSerized, silly!1 :P
 	if (player->bot)
 		return;
 
 	if (player->powers[pw_carry] != CR_NIGHTSMODE)
+	{
 		player->mo->height = P_GetPlayerHeight(player); // Just to make sure jumping into the drone doesn't result in a squashed hitbox.
+		player->oldscale = player->mo->scale;
+
+		if (skins[player->skin].sprites[SPR2_NGT0].numframes == 0) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
+		{
+			player->mo->skin = &skins[DEFAULTNIGHTSSKIN];
+			if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback))
+				player->mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor;
+			player->followitem = skins[DEFAULTNIGHTSSKIN].followitem;
+			G_GhostAddColor(GHC_NIGHTSSKIN);
+		}
+	}
 
 	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
 	player->homing = 0;
@@ -659,25 +760,29 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->speed = 0;
 	player->climbing = 0;
 	player->secondjump = 0;
+	player->flyangle = 0;
+	player->anotherflyangle = 0;
 
 	player->powers[pw_shield] = SH_NONE;
 	player->powers[pw_super] = 0;
 
 	player->mo->flags |= MF_NOGRAVITY;
 
-	if (skins[player->skin].sprites[SPR2_NGT0].numframes == 0) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
-	{
-		player->mo->skin = &skins[DEFAULTNIGHTSSKIN];
-		if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback))
-			player->mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor;
-		player->followitem = skins[DEFAULTNIGHTSSKIN].followitem;
-	}
-
-	player->nightstime = player->startedtime = nighttime*TICRATE;
+	player->nightstime = player->startedtime = player->lapstartedtime = nighttime*TICRATE;
 	player->bonustime = false;
 
-	P_RestoreMusic(player);
-	P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_TRANS1);
+	// Restore from drowning music
+	if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+	{
+		S_StopSoundByNum(sfx_timeup);
+		S_StopFadingMusic();
+		S_SetInternalMusicVolume(100);
+	}
+	else
+	{
+		music_stack_fadein = 0; // HACK: Change fade-in for restore music
+		P_RestoreMusic(player);
+	}
 
 	if (gametype == GT_RACE || gametype == GT_COMPETITION)
 	{
@@ -691,10 +796,13 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	}
 
 	oldmare = player->mare;
+	oldmarelap = player->marelap;
+	oldmarebonuslap = player->marebonuslap;
 
-	if (P_TransferToNextMare(player) == false)
+	if (!P_TransferToNextMare(player))
 	{
 		INT32 i;
+		INT32 total_spheres = 0;
 		INT32 total_rings = 0;
 
 		P_SetTarget(&player->mo->target, NULL);
@@ -703,7 +811,10 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 				if (playeringame[i]/* && players[i].powers[pw_carry] == CR_NIGHTSMODE*/)
+				{
+					total_spheres += players[i].spheres;
 					total_rings += players[i].rings;
+				}
 		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -714,15 +825,19 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			players[i].texttimer = (3 * TICRATE) - 10;
 			players[i].textvar = 4; // Score and grades
 			players[i].lastmare = players[i].mare;
+			players[i].lastmarelap = players[i].marelap;
+			players[i].lastmarebonuslap = players[i].marebonuslap;
 			if (G_IsSpecialStage(gamemap))
 			{
+				players[i].finishedspheres = (INT16)total_spheres;
 				players[i].finishedrings = (INT16)total_rings;
-				P_AddPlayerScore(player, total_rings * 50);
+				P_AddPlayerScore(player, total_spheres * 50);
 			}
 			else
 			{
+				players[i].finishedspheres = (INT16)(players[i].spheres);
 				players[i].finishedrings = (INT16)(players[i].rings);
-				P_AddPlayerScore(&players[i], (players[i].rings) * 50);
+				P_AddPlayerScore(&players[i], (players[i].spheres) * 50);
 			}
 
 			// Add score to leaderboards now
@@ -730,22 +845,26 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 				G_AddTempNightsRecords(players[i].marescore, leveltime - player->marebegunat, players[i].mare + 1);
 
 			// transfer scores anyway
+			players[i].totalmarescore += players[i].marescore;
 			players[i].lastmarescore = players[i].marescore;
 			players[i].marescore = 0;
 
-			players[i].rings = 0;
+			players[i].spheres = players[i].rings = 0;
 			P_DoPlayerExit(&players[i]);
 		}
 	}
 	else if (oldmare != player->mare)
 	{
 		/// \todo Handle multi-mare special stages.
-		// Ring bonus
-		P_AddPlayerScore(player, (player->rings) * 50);
+		// Spheres bonus
+		P_AddPlayerScore(player, (player->spheres) * 50);
 
-		player->lastmare = (UINT8)oldmare;
+		player->lastmare = oldmare;
+		player->lastmarelap = oldmarelap;
+		player->lastmarebonuslap = oldmarebonuslap;
 		player->texttimer = 4*TICRATE;
 		player->textvar = 4; // Score and grades
+		player->finishedspheres = (INT16)(player->spheres);
 		player->finishedrings = (INT16)(player->rings);
 
 		// Add score to temp leaderboards
@@ -753,11 +872,13 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 			G_AddTempNightsRecords(player->marescore, leveltime - player->marebegunat, (UINT8)(oldmare + 1));
 
 		// Starting a new mare, transfer scores
+		player->totalmarescore += player->marescore;
 		player->lastmarescore = player->marescore;
 		player->marescore = 0;
 		player->marebegunat = leveltime;
+		player->lapbegunat = leveltime;
 
-		player->rings = 0;
+		player->spheres = player->rings = 0;
 	}
 	else
 	{
@@ -766,11 +887,29 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 
 		// Don't show before title card
 		// Not consistency safe, but this only affects drawing
-		if (timeinmap + 40 < 110)
-			player->texttimer = (UINT8)(110 - timeinmap);
+		if (timeinmap + 40 < (110 - 70))
+			player->texttimer = (UINT8)((110 - 70) - timeinmap);
 	}
 
+	if (player->drone && player->drone->scale != player->mo->scale)
+		player->mo->destscale = player->drone->scale;
+
+	// force NiGHTS to face forward or backward
+	if (player->mo->target)
+	{
+		player->angle_pos = R_PointToAngle2(player->mo->target->x, player->mo->target->y, player->mo->x, player->mo->y);
+		player->drawangle = player->mo->angle = player->angle_pos
+			+ ((player->mo->target->flags2 & MF2_AMBUSH) ? // if axis is invert, take the opposite right angle
+				-ANGLE_90 : ANGLE_90); // flyangle is always 0 here, below is kept for posterity
+				/*(player->flyangle > 90 && player->flyangle < 270 ? ANGLE_90 : -ANGLE_90)
+				: (player->flyangle > 90 && player->flyangle < 270 ? -ANGLE_90 : ANGLE_90));*/
+	}
+
+	// Do this before setting CR_NIGHTSMODE so we can tell if player was non-NiGHTS
+	P_RunNightserizeExecutors(player->mo);
+
 	player->powers[pw_carry] = CR_NIGHTSMODE;
+	P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_TRANS1);
 }
 
 pflags_t P_GetJumpFlags(player_t *player)
@@ -792,6 +931,9 @@ boolean P_PlayerInPain(player_t *player)
 	if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate] && player->powers[pw_flashing])
 		return true;
 
+	if (player->mo->state == &states[S_PLAY_STUN])
+		return true;
+
 	return false;
 }
 
@@ -857,7 +999,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 		}
 		else
 		{
-			ang = R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0);
+			ang = ((player->mo->momx || player->mo->momy) ? R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0) : player->drawangle);
 			fallbackspeed = FixedMul(4*FRACUNIT, player->mo->scale);
 		}
 
@@ -889,7 +1031,7 @@ void P_ResetPlayer(player_t *player)
 {
 	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
-	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_NIGHTSFALL || player->powers[pw_carry] == CR_BRAKGOOP))
+	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_NIGHTSFALL || player->powers[pw_carry] == CR_BRAKGOOP || player->powers[pw_carry] == CR_MINECART))
 		player->powers[pw_carry] = CR_NONE;
 
 	player->secondjump = 0;
@@ -903,14 +1045,80 @@ void P_ResetPlayer(player_t *player)
 		CV_SetValue(&cv_analog2, true);
 }
 
+// P_PlayerCanDamage
+//
+// Can player do damage?
+//
+boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
+{
+	if (!player->mo || player->spectator || !thing || P_MobjWasRemoved(thing))
+		return false;
+
+#ifdef HAVE_BLUA
+	{
+		UINT8 shouldCollide = LUAh_PlayerCanDamage(player, thing);
+		if (P_MobjWasRemoved(thing))
+			return false; // removed???
+		if (shouldCollide == 1)
+			return true; // force yes
+		else if (shouldCollide == 2)
+			return false; // force no
+	}
+#endif
+
+	// Invinc/super. Not for Monitors.
+	if (!(thing->flags & MF_MONITOR) && (player->powers[pw_invulnerability] || player->powers[pw_super]))
+		return true;
+
+	// NiGHTS drill. Wasn't originally for monitors, but that's more an oversight being corrected than anything else.
+	if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+		return true;
+
+	// Jumping.
+	if ((player->pflags & PF_JUMPED)
+	&& (!(player->pflags & PF_NOJUMPDAMAGE)
+		|| (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+		return true;
+
+	// Spinning.
+	if (player->pflags & PF_SPINNING)
+		return true;
+
+	// From the front.
+	if (((player->pflags & PF_GLIDING) || (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+	&& (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.
+	if (P_MobjFlip(player->mo)*(player->mo->z - (thing->z + thing->height/2)) > 0)
+	{
+		if ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(player->mo)*player->mo->momz < 0))
+			return true;
+	}
+	else if (player->charability == CA_FLY && player->panim == PA_ABILITY)
+		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;
+}
+
+
+
 //
 // P_GivePlayerRings
 //
 // Gives rings to the player, and does any special things required.
 // Call this function when you want to increment the player's health.
 //
+
 void P_GivePlayerRings(player_t *player, INT32 num_rings)
 {
+	if (!player)
+		return;
+
 	if (player->bot)
 		player = &players[consoleplayer];
 
@@ -919,8 +1127,7 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 
 	player->rings += num_rings;
 
-	if (!G_IsSpecialStage(gamemap) || !useNightsSS)
-		player->totalring += num_rings;
+	player->totalring += num_rings;
 
 	// Can only get up to 9999 rings, sorry!
 	if (player->rings > 9999)
@@ -929,7 +1136,7 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 		player->rings = 0;
 
 	// Now extra life bonuses are handled here instead of in P_MovePlayer, since why not?
-	if (!ultimatemode && !modeattacking && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives())
+	if (!ultimatemode && !modeattacking && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives() && player->lives != INFLIVES)
 	{
 		INT32 gainlives = 0;
 
@@ -941,12 +1148,37 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 
 		if (gainlives)
 		{
-			P_GivePlayerLives(player, gainlives);
+			player->lives += gainlives;
+			if (player->lives > 99)
+				player->lives = 99;
+			else if (player->lives < 1)
+				player->lives = 1;
+
 			P_PlayLivesJingle(player);
 		}
 	}
 }
 
+void P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
+{
+	if (!player)
+		return;
+
+	if (player->bot)
+		player = &players[consoleplayer];
+
+	if (!player->mo)
+		return;
+
+	player->spheres += num_spheres;
+
+	// Can only get up to 9999 spheres, sorry!
+	if (player->spheres > 9999)
+		player->spheres = 9999;
+	else if (player->spheres < 0)
+		player->spheres = 0;
+}
+
 //
 // P_GivePlayerLives
 //
@@ -955,7 +1187,30 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 //
 void P_GivePlayerLives(player_t *player, INT32 numlives)
 {
-	if (player->lives == 0x7f) return;
+	if (!player)
+		return;
+
+	if (player->bot)
+		player = &players[consoleplayer];
+
+	if (gamestate == GS_LEVEL)
+	{
+		if (player->lives == INFLIVES || (gametype != GT_COOP && gametype != GT_COMPETITION))
+		{
+			P_GivePlayerRings(player, 100*numlives);
+			return;
+		}
+
+		if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0)
+		{
+			UINT8 prevlives = player->lives;
+			P_GivePlayerRings(player, 100*numlives);
+			if (player->lives - prevlives >= numlives)
+				return;
+
+			numlives = (numlives + prevlives - player->lives);
+		}
+	}
 
 	player->lives += numlives;
 
@@ -996,19 +1251,15 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 {
 	player->powers[pw_super] = 1;
 	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC) && P_IsLocalPlayer(player))
-	{
-		S_StopMusic();
-		S_ChangeMusicInternal("_super", true);
-	}
+		P_PlayJingle(player, JT_SUPER);
 
 	S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi
 
+	player->mo->momx = player->mo->momy = player->mo->momz = player->cmomx = player->cmomy = player->rmomx = player->rmomy = 0;
+
 	// Transformation animation
 	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1);
 
-	player->mo->momx = player->mo->momy = player->mo->momz = 0;
-	player->pflags |= PF_NOJUMPDAMAGE; // just to avoid recurling but still allow thok
-
 	if (giverings)
 		player->rings = 50;
 
@@ -1159,25 +1410,122 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if ((player && player->lives == 0x7f)
-	|| (!player && &players[consoleplayer] && players[consoleplayer].lives == 0x7f)
-	|| (gametype == GT_COOP && (netgame || multiplayer) && cv_cooplives.value == 0))
-		S_StartSound(NULL, sfx_lose);
-	else if (use1upSound)
+	if (use1upSound)
 		S_StartSound(NULL, sfx_oneup);
 	else if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
 	else
 	{
 		if (player)
-			player->powers[pw_extralife] = extralifetics+1;
-		S_StopMusic(); // otherwise it won't restart if this is done twice in a row
+			player->powers[pw_extralife] = extralifetics + 1;
 		strlcpy(S_sfx[sfx_None].caption, "One-up", 7);
 		S_StartCaption(sfx_None, -1, extralifetics+1);
-		S_ChangeMusicInternal("_1up", false);
+		P_PlayJingle(player, JT_1UP);
 	}
 }
 
+void P_PlayJingle(player_t *player, jingletype_t jingletype)
+{
+	const char *musname = jingleinfo[jingletype].musname;
+	UINT16 musflags = 0;
+	boolean looping = jingleinfo[jingletype].looping;
+
+	char newmusic[7];
+	strncpy(newmusic, musname, 7);
+#if defined(HAVE_BLUA) && defined(HAVE_LUA_MUSICPLUS)
+ 	if(LUAh_MusicJingle(jingletype, newmusic, &musflags, &looping))
+ 		return;
+#endif
+	newmusic[6] = 0;
+
+	P_PlayJingleMusic(player, newmusic, musflags, looping, jingletype);
+}
+
+//
+// P_PlayJingleMusic
+//
+void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status)
+{
+	// If gamestate != GS_LEVEL, always play the jingle (1-up intermission)
+	if (gamestate == GS_LEVEL && !P_IsLocalPlayer(player))
+		return;
+
+	S_RetainMusic(musname, musflags, looping, 0, status);
+	S_StopMusic();
+	S_ChangeMusicInternal(musname, looping);
+}
+
+boolean P_EvaluateMusicStatus(UINT16 status)
+{
+	// \todo lua hook
+	int i;
+	boolean result = false;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!P_IsLocalPlayer(&players[i]))
+			continue;
+
+		switch(status)
+		{
+			case JT_1UP: // Extra life
+				result = (players[i].powers[pw_extralife] > 1);
+				break;
+
+			case JT_SHOES:  // Speed shoes
+				if (players[i].powers[pw_sneakers] > 1 && !players[i].powers[pw_super])
+				{
+					//strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
+					//S_StartCaption(sfx_None, -1, players[i].powers[pw_sneakers]);
+					result = true;
+				}
+				else
+					result = false;
+				break;
+
+			case JT_INV: // Invincibility
+			case JT_MINV: // Mario Invincibility
+				if (players[i].powers[pw_invulnerability] > 1)
+				{
+					//strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
+					//S_StartCaption(sfx_None, -1, players[i].powers[pw_invulnerability]);
+					result = true;
+				}
+				else
+					result = false;
+				break;
+
+			case JT_DROWN:  // Drowning
+				result = (players[i].powers[pw_underwater] && players[i].powers[pw_underwater] <= 11*TICRATE + 1);
+				break;
+
+			case JT_SUPER:  // Super Sonic
+				result = (players[i].powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC));
+				break;
+
+			case JT_GOVER: // Game Over
+				result = (players[i].lives <= 0);
+				break;
+
+			case JT_NIGHTSTIMEOUT: // NiGHTS Time Out (10 seconds)
+			case JT_SSTIMEOUT:
+				result = (players[i].nightstime && players[i].nightstime <= 10*TICRATE);
+				break;
+
+			case JT_NONE:   // Null state
+			case JT_OTHER:  // Other state
+			case JT_MASTER: // Main level music
+			default:
+				result = true;
+		}
+
+		if (result)
+			break;
+ 	}
+
+	return result;
+ }
+
 //
 // P_RestoreMusic
 //
@@ -1188,17 +1536,30 @@ void P_RestoreMusic(player_t *player)
 	if (!P_IsLocalPlayer(player)) // Only applies to a local player
 		return;
 
+	S_SpeedMusic(1.0f);
+
+	// Jingles have a priority in this order, so follow it
+	// and as a default case, go down the music stack.
+
+	// Extra life
 	if (player->powers[pw_extralife] > 1)
 		return;
-	S_SpeedMusic(1.0f);
-	if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC))
-		S_ChangeMusicInternal("_super", true);
+
+	// Super
+	else if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)
+		&& !S_RecallMusic(JT_SUPER, false))
+		P_PlayJingle(player, JT_SUPER);
+
+	// Invulnerability
 	else if (player->powers[pw_invulnerability] > 1)
 	{
 		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
 		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
-		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
+		if (!S_RecallMusic(JT_INV, false) && !S_RecallMusic(JT_MINV, false))
+			P_PlayJingle(player, (mariomode) ? JT_MINV : JT_INV);
 	}
+
+	// Shoes
 	else if (player->powers[pw_sneakers] > 1 && !player->powers[pw_super])
 	{
 		strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
@@ -1206,13 +1567,19 @@ void P_RestoreMusic(player_t *player)
 		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
 		{
 			S_SpeedMusic(1.4f);
-			S_ChangeMusic(mapmusname, mapmusflags, true);
+			if (!S_RecallMusic(JT_MASTER, true))
+				S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
 		}
-		else
-			S_ChangeMusicInternal("_shoes", true);
+		else if (!S_RecallMusic(JT_SHOES, false))
+			P_PlayJingle(player, JT_SHOES);
 	}
-	else
-		S_ChangeMusic(mapmusname, mapmusflags, true);
+
+	// Default
+	else if (!S_RecallMusic(JT_NONE, false)) // go down the stack
+	{
+		CONS_Debug(DBG_BASIC, "Cannot find any music in resume stack!\n");
+	 	S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+ 	}
 }
 
 //
@@ -1431,6 +1798,7 @@ void P_SpawnShieldOrb(player_t *player)
 		orbtype = MT_ARMAGEDDON_ORB;
 		break;
 	case SH_PITY:
+	case SH_PINK: // PITY IN PINK
 		orbtype = MT_PITY_ORB;
 		break;
 	case SH_FLAMEAURA:
@@ -1447,9 +1815,9 @@ void P_SpawnShieldOrb(player_t *player)
 	}
 
 	// blaze through the thinkers to see if an orb already exists!
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		shieldobj = (mobj_t *)th;
@@ -1461,7 +1829,13 @@ 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);
-	shieldobj->color = (UINT8)shieldobj->info->painchance;
+	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK)
+	{
+		shieldobj->color = SKINCOLOR_PINK;
+		shieldobj->colorized = true;
+	}
+	else
+		shieldobj->color = (UINT8)shieldobj->info->painchance;
 	shieldobj->threshold = (player->powers[pw_shield] & SH_FORCE) ? SH_FORCE : (player->powers[pw_shield] & SH_NOSTACK);
 
 	if (shieldobj->info->seestate)
@@ -1535,9 +1909,12 @@ void P_SwitchShield(player_t *player, UINT16 shieldtype)
 		if (shieldtype & SH_PROTECTWATER)
 		{
 			if (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1)
+			{
+				player->powers[pw_underwater] = 0;
 				P_RestoreMusic(player);
-
-			player->powers[pw_underwater] = 0;
+			}
+			else
+				player->powers[pw_underwater] = 0;
 
 			if (player->powers[pw_spacetime] > 1)
 			{
@@ -1567,6 +1944,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	}
 
 	ghost->color = mobj->color;
+	ghost->colorized = mobj->colorized; // alternatively, "true" for sonic advance style colourisation
 
 	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
 	ghost->sprite = mobj->sprite;
@@ -1609,6 +1987,9 @@ void P_SpawnThokMobj(player_t *player)
 	if (player->spectator)
 		return;
 
+	if (!type)
+		return;
+
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
 	else
@@ -1669,6 +2050,9 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 	if (player->spectator)
 		return;
 
+	if (!type)
+		return;
+
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
 	else
@@ -1725,7 +2109,7 @@ void P_DoPlayerExit(player_t *player)
 	else if (gametype == GT_RACE || gametype == GT_COMPETITION) // If in Race Mode, allow
 	{
 		if (!countdown) // a 60-second wait ala Sonic 2.
-			countdown = cv_countdowntime.value*TICRATE + 1; // Use cv_countdowntime
+			countdown = (cv_countdowntime.value - 1)*TICRATE + 1; // Use cv_countdowntime
 
 		player->exiting = 3*TICRATE;
 
@@ -1768,6 +2152,9 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
+			if (!(rover->flags & FF_EXISTS))
+				continue;
+
 			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != SPACESPECIAL)
 				continue;
 #ifdef ESLOPE
@@ -1796,67 +2183,120 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 //
 // Handles player hitting floor surface.
 // Returns whether to clip momz.
-boolean P_PlayerHitFloor(player_t *player)
+boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 {
 	boolean clipmomz;
 
 	I_Assert(player->mo != NULL);
 
+	if (player->powers[pw_carry] == CR_NIGHTSMODE)
+		return true;
+
 	if ((clipmomz = !(P_CheckDeathPitCollide(player->mo))) && player->mo->health && !player->spectator)
 	{
-		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+		if (dorollstuff)
 		{
-			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-			S_StartSound(player->mo, sfx_spin);
+			if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+				player->pflags |= PF_SPINNING;
+			else
+				player->pflags &= ~PF_SPINNING;
 		}
-		else
-		{
-			player->pflags &= ~PF_SPINNING;
 
-			if (player->pflags & PF_GLIDING) // ground gliding
+		if (player->pflags & PF_SPINNING)
+		{
+			if (player->mo->state-states != S_PLAY_ROLL && !(player->pflags & PF_STARTDASH))
+			{
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+				S_StartSound(player->mo, sfx_spin);
+			}
+		}
+		else if (player->pflags & PF_GLIDING) // ground gliding
+		{
+			if (dorollstuff)
 			{
 				player->skidtime = TICRATE;
 				player->mo->tics = -1;
 			}
-			else if (player->charability2 == CA2_MELEE && (player->panim == PA_ABILITY2 && player->mo->state-states != S_PLAY_MELEE_LANDING))
+			else
+				player->pflags &= ~PF_GLIDING;
+		}
+		else if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+		{
+			if (player->mo->state-states != S_PLAY_MELEE_LANDING)
 			{
+				mobjtype_t type = player->revitem;
 				P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING);
 				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;
-			}
-			else if (player->pflags & PF_JUMPED || !(player->pflags & PF_SPINNING)
-			|| player->powers[pw_tailsfly] || player->mo->state-states == S_PLAY_FLY_TIRED)
-			{
-				if (player->cmomx || player->cmomy)
-				{
-					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
-						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
-					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
-					else if ((player->rmomx || player->rmomy)
-					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-					else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
-						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-				}
-				else
+
+				// hearticles
+				if (type)
 				{
-					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
-						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
-					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
-					else if ((player->mo->momx || player->mo->momy)
-					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-					else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
-						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+					UINT8 i = 0;
+					angle_t throwang = -(2*ANG30);
+					fixed_t xo = P_ReturnThrustX(player->mo, player->drawangle, 16*player->mo->scale);
+					fixed_t yo = P_ReturnThrustY(player->mo, player->drawangle, 16*player->mo->scale);
+					fixed_t zo = 6*player->mo->scale;
+					fixed_t mu = FixedMul(player->maxdash, player->mo->scale);
+					fixed_t mu2 = FixedHypot(player->mo->momx, player->mo->momy);
+					fixed_t ev;
+					mobj_t *missile = NULL;
+					if (mu2 < mu)
+						mu2 = mu;
+					ev = (50*FRACUNIT - (mu/25))/50;
+					while (i < 5)
+					{
+						missile = P_SpawnMobjFromMobj(player->mo, xo, yo, zo, type);
+						P_SetTarget(&missile->target, player->mo);
+						missile->angle = throwang + player->drawangle;
+						P_Thrust(missile, player->drawangle + ANGLE_90,
+							P_ReturnThrustY(missile, throwang, mu)); // side to side component
+						P_Thrust(missile, player->drawangle, mu2); // forward component
+						P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true);
+						missile->momz += player->mo->pmomz;
+						missile->fuse = TICRATE/2;
+						missile->extravalue2 = ev;
+
+						i++;
+						throwang += ANG30;
+					}
+					if (mobjinfo[type].seesound && missile)
+						S_StartSound(missile, missile->info->seesound);
 				}
 			}
 		}
+		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+			;
+		else if (player->pflags & PF_JUMPED || player->powers[pw_tailsfly] || player->mo->state-states == S_PLAY_FLY_TIRED)
+		{
+			if (player->cmomx || player->cmomy)
+			{
+				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+				else if ((player->rmomx || player->rmomy)
+				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
+					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			}
+			else
+			{
+				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+				else if ((player->mo->momx || player->mo->momy)
+				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
+					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			}
+		}
 
 		if (!(player->pflags & PF_GLIDING))
 			player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
@@ -2074,7 +2514,7 @@ static void P_CheckBustableBlocks(player_t *player)
 					//if (metalrecording)
 					//	G_RecordBustup(rover);
 
-					EV_CrumbleChain(node->m_sector, rover);
+					EV_CrumbleChain(NULL, rover); // node->m_sector
 
 					// Run a linedef executor??
 					if (rover->master->flags & ML_EFFECT5)
@@ -2128,6 +2568,12 @@ static void P_CheckBouncySectors(player_t *player)
 
 			for (rover = node->m_sector->ffloors; rover; rover = rover->next)
 			{
+				if (!(rover->flags & FF_EXISTS))
+					continue; // FOFs should not be bouncy if they don't even "exist"
+
+				if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 15)
+					continue; // this sector type is required for FOFs to be bouncy
+
 				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);
 
@@ -2141,7 +2587,6 @@ static void P_CheckBouncySectors(player_t *player)
 						&& oldz + player->mo->height > P_GetFOFBottomZ(player->mo, node->m_sector, rover, oldx, oldy, NULL))
 					top = false;
 
-				if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 15)
 				{
 					fixed_t linedist;
 
@@ -2286,7 +2731,7 @@ static void P_CheckQuicksand(player_t *player)
 					player->mo->z = ceilingheight - player->mo->height;
 
 				if (player->mo->momz <= 0)
-					P_PlayerHitFloor(player);
+					P_PlayerHitFloor(player, false);
 			}
 			else
 			{
@@ -2298,7 +2743,7 @@ static void P_CheckQuicksand(player_t *player)
 					player->mo->z = floorheight;
 
 				if (player->mo->momz >= 0)
-					P_PlayerHitFloor(player);
+					P_PlayerHitFloor(player, false);
 			}
 
 			friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
@@ -2316,10 +2761,6 @@ static void P_CheckQuicksand(player_t *player)
 //
 static void P_CheckSneakerAndLivesTimer(player_t *player)
 {
-	if ((player->powers[pw_underwater] <= 11*TICRATE + 1)
-	&& (player->powers[pw_underwater] > 1))
-		return; // don't restore music if drowning music is playing
-
 	if (player->powers[pw_extralife] == 1) // Extra Life!
 		P_RestoreMusic(player);
 
@@ -2336,12 +2777,17 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 {
 	tic_t timeleft = (player->powers[pw_spacetime]) ? player->powers[pw_spacetime] : player->powers[pw_underwater];
 
-	if ((timeleft == 11*TICRATE + 1) // 5
-	 || (timeleft ==  9*TICRATE + 1) // 4
-	 || (timeleft ==  7*TICRATE + 1) // 3
-	 || (timeleft ==  5*TICRATE + 1) // 2
-	 || (timeleft ==  3*TICRATE + 1) // 1
-	 || (timeleft ==  1*TICRATE + 1) // 0
+	if (player->exiting)
+		player->powers[pw_underwater] = player->powers[pw_spacetime] = 0;
+
+	timeleft--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
+
+	if ((timeleft == 11*TICRATE) // 5
+	 || (timeleft ==  9*TICRATE) // 4
+	 || (timeleft ==  7*TICRATE) // 3
+	 || (timeleft ==  5*TICRATE) // 2
+	 || (timeleft ==  3*TICRATE) // 1
+	 || (timeleft ==  1*TICRATE) // 0
 	) {
 		fixed_t height = (player->mo->eflags & MFE_VERTICALFLIP)
 		? player->mo->z - FixedMul(8*FRACUNIT + mobjinfo[MT_DROWNNUMBERS].height, FixedMul(player->mo->scale, player->shieldscale))
@@ -2349,7 +2795,7 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 
 		mobj_t *numbermobj = P_SpawnMobj(player->mo->x, player->mo->y, height, MT_DROWNNUMBERS);
 
-		timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be (((timeleft - 1)/TICRATE) - 1)/2, but integer division rounds down for us
+		timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us
 
 		if (player->charflags & SF_MACHINE)
 		{
@@ -2382,16 +2828,16 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 	if (!(player->mo->eflags & MFE_UNDERWATER) && player->powers[pw_underwater])
 	{
 		if (player->powers[pw_underwater] <= 12*TICRATE + 1)
+		{
+			player->powers[pw_underwater] = 0;
 			P_RestoreMusic(player);
-
-		player->powers[pw_underwater] = 0;
+		}
+		else
+			player->powers[pw_underwater] = 0;
 	}
 
 	if (player->powers[pw_spacetime] > 1 && !P_InSpaceSector(player->mo))
-	{
-		P_RestoreMusic(player);
 		player->powers[pw_spacetime] = 0;
-	}
 
 	// Underwater audio cues
 	if (P_IsLocalPlayer(player) && !player->bot)
@@ -2404,18 +2850,9 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 		if (player->powers[pw_underwater] == 11*TICRATE + 1
 		&& player == &players[consoleplayer])
 		{
-			S_StopMusic();
-			S_ChangeMusicInternal("_drown", false);
+			P_PlayJingle(player, JT_DROWN);
 		}
 	}
-
-	if (player->exiting)
-	{
-		if (player->powers[pw_underwater] > 1)
-			player->powers[pw_underwater] = 0;
-
-		player->powers[pw_spacetime] = 0;
-	}
 }
 
 //
@@ -2460,10 +2897,6 @@ static void P_CheckInvincibilityTimer(player_t *player)
 			P_SpawnShieldOrb(player);
 		}
 
-		if ((player->powers[pw_underwater] <= 11*TICRATE + 1)
-		&& (player->powers[pw_underwater] > 1))
-			return; // don't restore music if drowning music is playing
-
 		if (!player->powers[pw_super] || (mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC))
 			P_RestoreMusic(player);
 	}
@@ -2585,13 +3018,11 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 			else
 				sign->z += P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale);
-			if (leveltime & 4)
-			{
-				if (player->gotflag & GF_REDFLAG)
-					P_SetMobjStateNF(sign, S_GOTREDFLAG);
-			}
-			else if (player->gotflag & GF_BLUEFLAG)
-				P_SetMobjStateNF(sign, S_GOTBLUEFLAG);
+
+			if (player->gotflag & GF_REDFLAG)
+				sign->frame = 1|FF_FULLBRIGHT;
+			else //if (player->gotflag & GF_BLUEFLAG)
+				sign->frame = 2|FF_FULLBRIGHT;
 		}
 	}
 }
@@ -2934,7 +3365,7 @@ static void P_DoClimbing(player_t *player)
 			angle_t sideangle;
 			fixed_t dx, dy;
 
-			for (think = thinkercap.next; think != &thinkercap; think = think->next)
+			for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 			{
 				if (think->function.acp1 != (actionf_p1)T_Scroll)
 					continue;
@@ -3409,7 +3840,8 @@ static void P_DoTeeter(player_t *player)
 		if (teeter) // only bother with objects as a last resort if you were already teetering
 		{
 			mobj_t *oldtmthing = tmthing;
-			tmthing = teeterer = player->mo;
+			teeterer = player->mo;
+			P_SetTarget(&tmthing, teeterer);
 			teeterxl = teeterxh = player->mo->x;
 			teeteryl = teeteryh = player->mo->y;
 			couldteeter = false;
@@ -3423,7 +3855,7 @@ static void P_DoTeeter(player_t *player)
 				}
 teeterdone:
 			teeter = solidteeter;
-			tmthing = oldtmthing; // restore old tmthing, goodness knows what the game does with this before mobj thinkers
+			P_SetTarget(&tmthing, oldtmthing); // restore old tmthing, goodness knows what the game does with this before mobj thinkers
 		}
 	}
 	if (teeter)
@@ -3461,194 +3893,198 @@ static void P_SetWeaponDelay(player_t *player, INT32 delay)
 static void P_DoFiring(player_t *player, ticcmd_t *cmd)
 {
 	INT32 i;
+	mobj_t *mo = NULL;
 
 	I_Assert(player != NULL);
 	I_Assert(!P_MobjWasRemoved(player->mo));
 
-	if (cmd->buttons & BT_ATTACK || cmd->buttons & BT_FIRENORMAL)
+	if (!(cmd->buttons & (BT_ATTACK|BT_FIRENORMAL)))
 	{
-		if (!(player->pflags & PF_ATTACKDOWN) && (player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER && !player->climbing)
-		{
-			player->pflags |= PF_ATTACKDOWN;
-			P_SpawnPlayerMissile(player->mo, MT_FIREBALL, 0);
-			S_StartSound(player->mo, sfx_mario7);
-		}
-		else if (G_RingSlingerGametype() && (!G_TagGametype() || player->pflags & PF_TAGIT)
-		  && !player->weapondelay && !player->climbing
-		  && !(player->pflags & PF_ATTACKDOWN))
-		{
-			mobj_t *mo = NULL;
-			player->pflags |= PF_ATTACKDOWN;
+		// Not holding any firing buttons anymore.
+		// Release the grenade / whatever.
+		player->pflags &= ~PF_ATTACKDOWN;
+		return;
+	}
 
-			#define TAKE_AMMO(player, power) \
-			player->powers[power]--; \
-			if (player->rings < 1) \
-			{ \
-				if (player->powers[power] > 0) \
-					player->powers[power]--; \
-			} \
-			else \
-				player->rings--;
+	if (player->pflags & PF_ATTACKDOWN || player->climbing || (G_TagGametype() && !(player->pflags & PF_TAGIT)))
+		return;
 
-			if (cmd->buttons & BT_FIRENORMAL) // No powers, just a regular ring.
-				goto firenormal; //code repetition sucks.
-			// Bounce ring
-			else if (player->currentweapon == WEP_BOUNCE && player->powers[pw_bouncering])
-			{
-				TAKE_AMMO(player, pw_bouncering);
-				P_SetWeaponDelay(player, TICRATE/4);
+	if ((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER)
+	{
+		player->pflags |= PF_ATTACKDOWN;
+		mo = P_SpawnPlayerMissile(player->mo, MT_FIREBALL, 0);
+		P_InstaThrust(mo, player->mo->angle, ((mo->info->speed>>FRACBITS)*player->mo->scale) + player->speed);
+		S_StartSound(player->mo, sfx_mario7);
+		return;
+	}
 
-				mo = P_SpawnPlayerMissile(player->mo, MT_THROWNBOUNCE, MF2_BOUNCERING);
+	if (!G_RingSlingerGametype() || player->weapondelay)
+		return;
 
-				if (mo)
-					mo->fuse = 3*TICRATE; // Bounce Ring time
-			}
-			// Rail ring
-			else if (player->currentweapon == WEP_RAIL && player->powers[pw_railring])
-			{
-				TAKE_AMMO(player, pw_railring);
-				P_SetWeaponDelay(player, (3*TICRATE)/2);
+	player->pflags |= PF_ATTACKDOWN;
+
+#define TAKE_AMMO(player, power) \
+		player->powers[power]--; \
+		if (player->rings < 1) \
+		{ \
+			if (player->powers[power] > 0) \
+				player->powers[power]--; \
+		} \
+		else \
+			player->rings--;
 
-				mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, MF2_RAILRING|MF2_DONTDRAW);
+	if (cmd->buttons & BT_FIRENORMAL) // No powers, just a regular ring.
+		goto firenormal; //code repetition sucks.
+	// Bounce ring
+	else if (player->currentweapon == WEP_BOUNCE && player->powers[pw_bouncering])
+	{
+		TAKE_AMMO(player, pw_bouncering);
+		P_SetWeaponDelay(player, TICRATE/4);
 
-				// Rail has no unique thrown object, therefore its sound plays here.
-				S_StartSound(player->mo, sfx_rail1);
-			}
-			// Automatic
-			else if (player->currentweapon == WEP_AUTO && player->powers[pw_automaticring])
-			{
-				TAKE_AMMO(player, pw_automaticring);
-				player->pflags &= ~PF_ATTACKDOWN;
-				P_SetWeaponDelay(player, 2);
+		mo = P_SpawnPlayerMissile(player->mo, MT_THROWNBOUNCE, MF2_BOUNCERING);
 
-				mo = P_SpawnPlayerMissile(player->mo, MT_THROWNAUTOMATIC, MF2_AUTOMATIC);
-			}
-			// Explosion
-			else if (player->currentweapon == WEP_EXPLODE && player->powers[pw_explosionring])
-			{
-				TAKE_AMMO(player, pw_explosionring);
-				P_SetWeaponDelay(player, (3*TICRATE)/2);
+	if (mo)
+		mo->fuse = 3*TICRATE; // Bounce Ring time
+	}
+	// Rail ring
+	else if (player->currentweapon == WEP_RAIL && player->powers[pw_railring])
+	{
+		TAKE_AMMO(player, pw_railring);
+		P_SetWeaponDelay(player, (3*TICRATE)/2);
 
-				mo = P_SpawnPlayerMissile(player->mo, MT_THROWNEXPLOSION, MF2_EXPLOSION);
-			}
-			// Grenade
-			else if (player->currentweapon == WEP_GRENADE && player->powers[pw_grenadering])
-			{
-				TAKE_AMMO(player, pw_grenadering);
-				P_SetWeaponDelay(player, TICRATE/3);
+		mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, MF2_RAILRING|MF2_DONTDRAW);
 
-				mo = P_SpawnPlayerMissile(player->mo, MT_THROWNGRENADE, MF2_EXPLOSION);
+		// Rail has no unique thrown object, therefore its sound plays here.
+		S_StartSound(player->mo, sfx_rail1);
+	}
+	// Automatic
+	else if (player->currentweapon == WEP_AUTO && player->powers[pw_automaticring])
+	{
+		TAKE_AMMO(player, pw_automaticring);
+		player->pflags &= ~PF_ATTACKDOWN;
+		P_SetWeaponDelay(player, 2);
 
-				if (mo)
-				{
-					//P_InstaThrust(mo, player->mo->angle, FixedMul(mo->info->speed, player->mo->scale));
-					mo->fuse = mo->info->mass;
-				}
-			}
-			// Scatter
-			// Note: Ignores MF2_RAILRING
-			else if (player->currentweapon == WEP_SCATTER && player->powers[pw_scatterring])
-			{
-				fixed_t oldz = player->mo->z;
-				angle_t shotangle = player->mo->angle;
-				angle_t oldaiming = player->aiming;
+		mo = P_SpawnPlayerMissile(player->mo, MT_THROWNAUTOMATIC, MF2_AUTOMATIC);
+	}
+	// Explosion
+	else if (player->currentweapon == WEP_EXPLODE && player->powers[pw_explosionring])
+	{
+		TAKE_AMMO(player, pw_explosionring);
+		P_SetWeaponDelay(player, (3*TICRATE)/2);
+
+		mo = P_SpawnPlayerMissile(player->mo, MT_THROWNEXPLOSION, MF2_EXPLOSION);
+	}
+	// Grenade
+	else if (player->currentweapon == WEP_GRENADE && player->powers[pw_grenadering])
+	{
+		TAKE_AMMO(player, pw_grenadering);
+		P_SetWeaponDelay(player, TICRATE/3);
 
-				TAKE_AMMO(player, pw_scatterring);
-				P_SetWeaponDelay(player, (2*TICRATE)/3);
+		mo = P_SpawnPlayerMissile(player->mo, MT_THROWNGRENADE, MF2_EXPLOSION);
 
-				// Center
-				mo = P_SpawnPlayerMissile(player->mo, MT_THROWNSCATTER, MF2_SCATTER);
-				if (mo)
-					shotangle = R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y);
+		if (mo)
+		{
+			//P_InstaThrust(mo, player->mo->angle, FixedMul(mo->info->speed, player->mo->scale));
+			mo->fuse = mo->info->reactiontime;
+		}
+	}
+	// Scatter
+	// Note: Ignores MF2_RAILRING
+	else if (player->currentweapon == WEP_SCATTER && player->powers[pw_scatterring])
+	{
+		fixed_t oldz = player->mo->z;
+		angle_t shotangle = player->mo->angle;
+		angle_t oldaiming = player->aiming;
 
-				// Left
-				mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle-ANG2, true, MF2_SCATTER);
+		TAKE_AMMO(player, pw_scatterring);
+		P_SetWeaponDelay(player, (2*TICRATE)/3);
 
-				// Right
-				mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle+ANG2, true, MF2_SCATTER);
+		// Center
+		mo = P_SpawnPlayerMissile(player->mo, MT_THROWNSCATTER, MF2_SCATTER);
+		if (mo)
+			shotangle = R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y);
 
-				// Down
-				player->mo->z += FixedMul(12*FRACUNIT, player->mo->scale);
-				player->aiming += ANG1;
-				mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle, true, MF2_SCATTER);
+		// Left
+		mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle-ANG2, true, MF2_SCATTER);
 
-				// Up
-				player->mo->z -= FixedMul(24*FRACUNIT, player->mo->scale);
-				player->aiming -= ANG2;
-				mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle, true, MF2_SCATTER);
+		// Right
+		mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle+ANG2, true, MF2_SCATTER);
 
-				player->mo->z = oldz;
-				player->aiming = oldaiming;
-				return;
-			}
-			// No powers, just a regular ring.
-			else
-			{
+		// Down
+		player->mo->z += FixedMul(12*FRACUNIT, player->mo->scale);
+		player->aiming += ANG1;
+		mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle, true, MF2_SCATTER);
+
+		// Up
+		player->mo->z -= FixedMul(24*FRACUNIT, player->mo->scale);
+		player->aiming -= ANG2;
+		mo = P_SPMAngle(player->mo, MT_THROWNSCATTER, shotangle, true, MF2_SCATTER);
+
+		player->mo->z = oldz;
+		player->aiming = oldaiming;
+		return;
+	}
+	// No powers, just a regular ring.
+	else
+	{
 firenormal:
-				// Infinity ring was selected.
-				// Mystic wants this ONLY to happen specifically if it's selected,
-				// and to not be able to get around it EITHER WAY with firenormal.
+		// Infinity ring was selected.
+		// Mystic wants this ONLY to happen specifically if it's selected,
+		// and to not be able to get around it EITHER WAY with firenormal.
 
-				// Infinity Ring
-				if (player->currentweapon == 0
-				&& player->powers[pw_infinityring])
-				{
-					P_SetWeaponDelay(player, TICRATE/4);
+		// Infinity Ring
+		if (player->currentweapon == 0
+		&& player->powers[pw_infinityring])
+		{
+			P_SetWeaponDelay(player, TICRATE/4);
 
-					mo = P_SpawnPlayerMissile(player->mo, MT_THROWNINFINITY, 0);
+			mo = P_SpawnPlayerMissile(player->mo, MT_THROWNINFINITY, 0);
 
-					player->powers[pw_infinityring]--;
-				}
-				// Red Ring
-				else
-				{
-					if (player->rings <= 0)
-						return;
-					P_SetWeaponDelay(player, TICRATE/4);
+			player->powers[pw_infinityring]--;
+		}
+		// Red Ring
+		else
+		{
+			if (player->rings <= 0)
+				return;
+			P_SetWeaponDelay(player, TICRATE/4);
 
-					mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, 0);
+			mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, 0);
 
-					if (mo)
-						P_ColorTeamMissile(mo, player);
+			if (mo)
+				P_ColorTeamMissile(mo, player);
 
-					player->rings--;
-				}
-			}
+			player->rings--;
+		}
+	}
 
-			#undef TAKE_AMMO
+	#undef TAKE_AMMO
 
-			if (mo)
+	if (mo)
+	{
+		if (mo->flags & MF_MISSILE && mo->flags2 & MF2_RAILRING)
+		{
+			const boolean nblockmap = !(mo->flags & MF_NOBLOCKMAP);
+			for (i = 0; i < 256; i++)
 			{
-				if (mo->flags & MF_MISSILE && mo->flags2 & MF2_RAILRING)
+				if (nblockmap)
 				{
-					const boolean nblockmap = !(mo->flags & MF_NOBLOCKMAP);
-					for (i = 0; i < 256; i++)
-					{
-						if (nblockmap)
-						{
-							P_UnsetThingPosition(mo);
-							mo->flags |= MF_NOBLOCKMAP;
-							P_SetThingPosition(mo);
-						}
-
-						if (i&1)
-							P_SpawnMobj(mo->x, mo->y, mo->z, MT_SPARK);
+					P_UnsetThingPosition(mo);
+					mo->flags |= MF_NOBLOCKMAP;
+					P_SetThingPosition(mo);
+				}
 
-						if (P_RailThinker(mo))
-							break; // mobj was removed (missile hit a wall) or couldn't move
-					}
+				if (i&1)
+					P_SpawnMobj(mo->x, mo->y, mo->z, MT_SPARK);
 
-					// Other rail sound plays at contact point.
-					S_StartSound(mo, sfx_rail2);
-				}
+				if (P_RailThinker(mo))
+					break; // mobj was removed (missile hit a wall) or couldn't move
 			}
+
+			// Other rail sound plays at contact point.
+			S_StartSound(mo, sfx_rail2);
 		}
-		return;
 	}
-
-	// Not holding any firing buttons anymore.
-	// Release the grenade / whatever.
-	player->pflags &= ~PF_ATTACKDOWN;
 }
 
 //
@@ -3674,6 +4110,8 @@ static void P_DoSuperStuff(player_t *player)
 		{
 			player->powers[pw_super] = 0;
 			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			music_stack_noposition = true; // HACK: Do not reposition next music
+			music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
 			P_RestoreMusic(player);
 			P_SpawnShieldOrb(player);
 
@@ -3755,6 +4193,8 @@ static void P_DoSuperStuff(player_t *player)
 			}
 
 			// Resume normal music if you're the console player
+			music_stack_noposition = true; // HACK: Do not reposition next music
+			music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
 			P_RestoreMusic(player);
 
 			// If you had a shield, restore its visual significance.
@@ -3770,12 +4210,15 @@ static void P_DoSuperStuff(player_t *player)
 //
 boolean P_SuperReady(player_t *player)
 {
-	if ((ALL7EMERALDS(emeralds) && player->rings >= 50) && !player->powers[pw_super] && !player->powers[pw_tailsfly]
-	&& !(player->powers[pw_shield] & SH_NOSTACK)
+	if (!player->powers[pw_super]
 	&& !player->powers[pw_invulnerability]
-	&& !(maptol & TOL_NIGHTS || (player->powers[pw_carry] == CR_NIGHTSMODE)) // don't turn 'regular super' in nights levels
-	&& player->pflags & PF_JUMPED
-	&& player->charflags & SF_SUPER)
+	&& !player->powers[pw_tailsfly]
+	&& (player->charflags & SF_SUPER)
+	&& (player->pflags & PF_JUMPED)
+	&& !(player->powers[pw_shield] & SH_NOSTACK)
+	&& !(maptol & TOL_NIGHTS)
+	&& ALL7EMERALDS(emeralds)
+	&& (player->rings >= 50))
 		return true;
 
 	return false;
@@ -3869,7 +4312,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			}
 		}
 		else if (maptol & TOL_NIGHTS)
-			player->mo->momz = 24*FRACUNIT;
+			player->mo->momz = 18*FRACUNIT;
 		else if (player->powers[pw_super])
 		{
 			player->mo->momz = 13*FRACUNIT;
@@ -3910,6 +4353,9 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP)
 		factor -= max(0, player->secondjump * player->jumpfactor / ((player->actionspd >> FRACBITS) + 1)); // Reduce the jump height each time
 
+	//if (maptol & TOL_NIGHTS)
+	//	factor = player->jumpfactor; // all skins jump the same. if you nerf jumping abilities, you may want this.
+
 	P_SetObjectMomZ(player->mo, FixedMul(factor, player->mo->momz), false); // Custom height
 
 	// set just an eensy above the ground
@@ -4088,7 +4534,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 							if (P_IsLocalPlayer(player)) // Only display it on your own view.
 							{
 								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-								visual->target = lockon;
+								P_SetTarget(&visual->target, lockon);
 							}
 						}
 						if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN))
@@ -4153,9 +4599,17 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					{
 						player->mo->z += P_MobjFlip(player->mo);
 						P_SetObjectMomZ(player->mo, player->mindash, false);
+						if (P_MobjFlip(player->mo)*player->mo->pmomz > 0)
+							player->mo->momz += player->mo->pmomz; // Add the platform's momentum to your jump.
+						else
+							player->mo->pmomz = 0;
 						if (player->mo->eflags & MFE_UNDERWATER)
 							player->mo->momz >>= 1;
+#if 0
 						if (FixedMul(player->speed, FINECOSINE(((player->mo->angle - R_PointToAngle2(0, 0, player->rmomx, player->rmomy)) >> ANGLETOFINESHIFT) & FINEMASK)) < FixedMul(player->maxdash, player->mo->scale))
+#else
+						if (player->speed < FixedMul(player->maxdash, player->mo->scale))
+#endif
 						{
 							player->drawangle = player->mo->angle;
 							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
@@ -4229,6 +4683,7 @@ void P_DoJumpShield(player_t *player)
 			P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale));
 			if (i % 2)
 				P_SetObjectMomZ(spark, -4*FRACUNIT, false);
+			spark->fuse = 18;
 		}
 #undef limitangle
 #undef numangles
@@ -4275,6 +4730,7 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 		return;
 	if (changemomz)
 	{
+		fixed_t minmomz;
 		prevmomz = player->mo->momz;
 		if (P_MobjFlip(player->mo)*prevmomz < 0)
 			prevmomz = 0;
@@ -4282,13 +4738,65 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 			prevmomz /= 2;
 		P_DoJump(player, false);
 		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED);
-		player->mo->momz = (FixedMul(player->mo->momz, 3*FRACUNIT/2) + prevmomz)/2;
+		minmomz = FixedMul(player->mo->momz, 3*FRACUNIT/2);
+		player->mo->momz = max(minmomz, (minmomz + prevmomz)/2);
 	}
 	S_StartSound(player->mo, sfx_boingf);
 	P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
 	player->pflags |= PF_BOUNCING|PF_THOKKED;
 }
 
+//
+// P_TwinSpinRejuvenate
+//
+// CA_TWINSPIN landing handling
+//
+void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type)
+{
+	fixed_t actionspd;
+	angle_t movang, ang, fa;
+	fixed_t v, h;
+	UINT8 i;
+
+	if (!player->mo || !type)
+		return;
+
+	actionspd = FixedMul(player->actionspd, player->mo->scale);
+
+	fa = (R_PointToAngle2(0, 0, player->mo->momz, FixedHypot(player->mo->momx, player->mo->momy))>>ANGLETOFINESHIFT) & FINEMASK;
+	movang = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+	ang = 0;
+
+	v = FixedMul(actionspd, FINESINE(fa));
+	h = actionspd - FixedMul(actionspd, FINECOSINE(fa));
+
+	// hearticles
+	for (i = 0; i <= 7; i++)
+	{
+		fixed_t side = actionspd - FixedMul(h, abs(FINESINE((ang>>ANGLETOFINESHIFT) & FINEMASK)));
+		fixed_t xo = P_ReturnThrustX(NULL, ang + movang, side);
+		fixed_t yo = P_ReturnThrustY(NULL, ang + movang, side);
+		fixed_t zo = -FixedMul(FINECOSINE(((ang>>ANGLETOFINESHIFT) & FINEMASK)), v);
+		mobj_t *missile = P_SpawnMobjFromMobj(player->mo,
+			xo,
+			yo,
+			player->mo->height/2 + zo,
+			type);
+		P_SetTarget(&missile->target, player->mo);
+		P_SetScale(missile, (missile->destscale >>= 1));
+		missile->angle = ang + movang;
+		missile->fuse = TICRATE/2;
+		missile->extravalue2 = (99*FRACUNIT)/100;
+		missile->momx = xo;
+		missile->momy = yo;
+		missile->momz = zo;
+
+		ang += ANGLE_45;
+	}
+
+	player->pflags &= ~PF_THOKKED;
+}
+
 //
 // P_Telekinesis
 //
@@ -4305,9 +4813,9 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 	if (player->powers[pw_super]) // increase range when super
 		range *= 2;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -4358,7 +4866,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		if (P_IsLocalPlayer(player)) // Only display it on your own view.
 		{
 			mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-			visual->target = lockon;
+			P_SetTarget(&visual->target, lockon);
 		}
 	}
 
@@ -4461,12 +4969,12 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 		else if (player->pflags & PF_SLIDING || (gametype == GT_CTF && player->gotflag))
 			;
-		else if (P_SuperReady(player))
+		/*else if (P_SuperReady(player))
 		{
 			// If you can turn super and aren't already,
 			// and you don't have a shield, do it!
 			P_DoSuperTransformation(player, false);
-		}
+		}*/
 		else if (player->pflags & PF_JUMPED)
 		{
 #ifdef HAVE_BLUA
@@ -4504,10 +5012,10 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							player->mo->momx /= 2;
 							player->mo->momy /= 2;
 						}
-						else if (player->charability == CA_HOMINGTHOK)
+						if (player->charability == CA_HOMINGTHOK)
 						{
-							player->mo->momx /= 3;
-							player->mo->momy /= 3;
+							player->mo->momx /= 2;
+							player->mo->momy /= 2;
 						}
 
 						if (player->charability == CA_HOMINGTHOK)
@@ -5502,9 +6010,9 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		fixed_t truexspeed = xspeed*(!(player->pflags & PF_TRANSFERTOCLOSEST) && player->mo->target->flags2 & MF2_AMBUSH ? -1 : 1);
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -5512,24 +6020,24 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 			// Axis things are only at beginning of list.
 			if (!(mo2->flags2 & MF2_AXIS))
 				break;
+			if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				continue;
+			if (mo2->threshold != sequence)
+				continue;
 
-			if ((mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE)
-				&& mo2->threshold == sequence)
+			if (player->pflags & PF_TRANSFERTOCLOSEST)
 			{
-				if (player->pflags & PF_TRANSFERTOCLOSEST)
-				{
-					if (mo2->health == player->axis1->health)
-						transfer1 = mo2;
-					else if (mo2->health == player->axis2->health)
-						transfer2 = mo2;
-				}
-				else
-				{
-					if (mo2->health == player->mo->target->health)
-						transfer1 = mo2;
-					else if (mo2->health == player->mo->target->health + 1)
-						transfer2 = mo2;
-				}
+				if (mo2->health == player->axis1->health)
+					transfer1 = mo2;
+				else if (mo2->health == player->axis2->health)
+					transfer2 = mo2;
+			}
+			else
+			{
+				if (mo2->health == player->mo->target->health)
+					transfer1 = mo2;
+				else if (mo2->health == player->mo->target->health + 1)
+					transfer2 = mo2;
 			}
 		}
 
@@ -5538,9 +6046,9 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		// Look for a wrapper point.
 		if (!transfer1)
 		{
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -5548,27 +6056,28 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 				// Axis things are only at beginning of list.
 				if (!(mo2->flags2 & MF2_AXIS))
 					break;
+				if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+					continue;
+				if (mo2->threshold != sequence)
+					continue;
 
-				if (mo2->threshold == sequence && (mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				if (!transfer1)
 				{
-					if (!transfer1)
-					{
-						transfer1 = mo2;
-						transfer1last = true;
-					}
-					else if (mo2->health > transfer1->health)
-					{
-						transfer1 = mo2;
-						transfer1last = true;
-					}
+					transfer1 = mo2;
+					transfer1last = true;
+				}
+				else if (mo2->health > transfer1->health)
+				{
+					transfer1 = mo2;
+					transfer1last = true;
 				}
 			}
 		}
 		if (!transfer2)
 		{
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -5576,19 +6085,20 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 				// Axis things are only at beginning of list.
 				if (!(mo2->flags2 & MF2_AXIS))
 					break;
+				if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+					continue;
+				if (mo2->threshold != sequence)
+					continue;
 
-				if (mo2->threshold == sequence && (mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				if (!transfer2)
 				{
-					if (!transfer2)
-					{
-						transfer2 = mo2;
-						transfer2last = true;
-					}
-					else if (mo2->health > transfer2->health)
-					{
-						transfer2 = mo2;
-						transfer2last = true;
-					}
+					transfer2 = mo2;
+					transfer2last = true;
+				}
+				else if (mo2->health > transfer2->health)
+				{
+					transfer2 = mo2;
+					transfer2last = true;
 				}
 			}
 		}
@@ -5874,9 +6384,10 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 //
 static void P_DoNiGHTSCapsule(player_t *player)
 {
-	INT32 i;
+	INT32 i, spherecount, totalduration, popduration, deductinterval, deductquantity, sphereresult, firstpoptic, startingspheres;
+	INT32 tictimer = ++player->capsule->extravalue2;
 
-	if (abs(player->mo->x-player->capsule->x) <= 2*FRACUNIT)
+	if (abs(player->mo->x-player->capsule->x) <= 3*FRACUNIT)
 	{
 		P_UnsetThingPosition(player->mo);
 		player->mo->x = player->capsule->x;
@@ -5884,7 +6395,7 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		player->mo->momx = 0;
 	}
 
-	if (abs(player->mo->y-player->capsule->y) <= 2*FRACUNIT)
+	if (abs(player->mo->y-player->capsule->y) <= 3*FRACUNIT)
 	{
 		P_UnsetThingPosition(player->mo);
 		player->mo->y = player->capsule->y;
@@ -5892,26 +6403,26 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		player->mo->momy = 0;
 	}
 
-	if (abs(player->mo->z - (player->capsule->z+(player->capsule->height/3))) <= 2*FRACUNIT)
+	if (abs(player->mo->z - (player->capsule->z+(player->capsule->height/3))) <= 3*FRACUNIT)
 	{
 		player->mo->z = player->capsule->z+(player->capsule->height/3);
 		player->mo->momz = 0;
 	}
 
 	if (player->mo->x > player->capsule->x)
-		player->mo->momx = -2*FRACUNIT;
+		player->mo->momx = -3*FRACUNIT;
 	else if (player->mo->x < player->capsule->x)
-		player->mo->momx = 2*FRACUNIT;
+		player->mo->momx = 3*FRACUNIT;
 
 	if (player->mo->y > player->capsule->y)
-		player->mo->momy = -2*FRACUNIT;
+		player->mo->momy = -3*FRACUNIT;
 	else if (player->mo->y < player->capsule->y)
-		player->mo->momy = 2*FRACUNIT;
+		player->mo->momy = 3*FRACUNIT;
 
 	if (player->mo->z > player->capsule->z+(player->capsule->height/3))
-		player->mo->momz = -2*FRACUNIT;
+		player->mo->momz = -3*FRACUNIT;
 	else if (player->mo->z < player->capsule->z+(player->capsule->height/3))
-		player->mo->momz = 2*FRACUNIT;
+		player->mo->momz = 3*FRACUNIT;
 
 	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
@@ -5926,41 +6437,108 @@ static void P_DoNiGHTSCapsule(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_ATTACK);
 		}
 	}
+	else
+	{
+		if (!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING))
+			player->pflags |= PF_JUMPED;
+		if (player->panim != PA_ROLL)
+			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+	}
 
 	if (G_IsSpecialStage(gamemap))
 	{ // In special stages, share rings. Everyone gives up theirs to the capsule player always, because we can't have any individualism here!
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && (&players[i] != player) && players[i].rings > 0)
+			if (playeringame[i] && (&players[i] != player) && players[i].spheres > 0)
 			{
-				player->rings += players[i].rings;
-				players[i].rings = 0;
+				player->spheres += players[i].spheres;
+				players[i].spheres = 0;
 			}
 	}
 
+	if (player->capsule->extravalue2 <= 0 && player->capsule->health > 0)
+		P_RunNightsCapsuleTouchExecutors(player->mo, true, player->spheres >= player->capsule->health); // run capsule entrance executors
+
 	// Time to blow it up!
 	if (player->mo->x == player->capsule->x
 		&& player->mo->y == player->capsule->y
 		&& player->mo->z == player->capsule->z+(player->capsule->height/3))
 	{
-		if (player->rings > 0)
+		if (player->capsule->lastlook < 0)
 		{
-			player->rings--;
-			player->capsule->health--;
-			player->capsule->extravalue1++;
+			// Stretch the sphere deduction across the capsule time!
+			// 1. Force the remaining capsule time to `popduration`
+			// 2. Given `popduration` and `spherecount`, at what tic interval do we deduct spheres? `deductinterval`
+			// 3. And on each deduction, how many spheres do we deduct? `deductquantity`
+			// 4. Store the expected capsule health upon completion: `sphereresult`
+			spherecount = min(player->spheres, player->capsule->health);
+			totalduration = min(40 + spherecount, 60);
+
+			popduration = player->capsule->extravalue1 = max(totalduration - tictimer, 1);
+			deductinterval = player->capsule->cvmem = max(FixedFloor(FixedDiv(popduration, spherecount))/FRACUNIT, 1);
+			deductquantity = player->capsule->cusval = max(FixedRound(FixedDiv(spherecount, popduration))/FRACUNIT, 1);
+			sphereresult = player->capsule->movecount = player->capsule->health - spherecount;
+			firstpoptic = player->capsule->lastlook = tictimer;
+		}
+		else
+		{
+			popduration = player->capsule->extravalue1;
+			deductinterval = player->capsule->cvmem;
+			deductquantity = player->capsule->cusval;
+			sphereresult = player->capsule->movecount;
+			firstpoptic = player->capsule->lastlook;
+		}
+
+		if (tictimer - firstpoptic < popduration)
+		{
+			if (!((tictimer - firstpoptic) % deductinterval))
+			{
+				// Did you somehow get more spheres during destruct?
+				if (player->capsule->health <= sphereresult && player->spheres > 0 && player->capsule->health > 0)
+					sphereresult = max(sphereresult - player->spheres, 0);
+
+				if (player->capsule->health > sphereresult && player->spheres > 0)
+				{
+					player->spheres -= deductquantity;
+					player->capsule->health -= deductquantity;
+				}
+
+				if (player->spheres < 0)
+					player->spheres = 0;
+
+				if (player->capsule->health < sphereresult)
+					player->capsule->health = sphereresult;
+			}
 
-			// Spawn a 'pop' for every 5 rings you deposit
-			if (!(player->capsule->extravalue1 % 5))
+			// Spawn a 'pop' for every 5 tics
+			if (!((tictimer - firstpoptic) % 5))
 				S_StartSound(P_SpawnMobj(player->capsule->x + ((P_SignedRandom()/2)<<FRACBITS),
 				player->capsule->y + ((P_SignedRandom()/2)<<FRACBITS),
 				player->capsule->z + (player->capsule->height/2) + ((P_SignedRandom()/2)<<FRACBITS),
 				MT_BOSSEXPLODE),sfx_cybdth);
+		}
+		else
+		{
+			if (player->spheres != 0 && player->capsule->health > 0)
+			{
+				if (player->spheres < player->capsule->health)
+				{
+					player->capsule->health -= player->spheres;
+					player->spheres = 0;
+				}
+				else
+				{
+					startingspheres = player->spheres - player->capsule->health;
+					player->capsule->health = 0;
+					player->spheres = startingspheres;
+				}
+			}
 
 			if (player->capsule->health <= 0)
 			{
 				player->capsule->flags &= ~MF_NOGRAVITY;
 				player->capsule->momz = 5*FRACUNIT;
 				player->capsule->reactiontime = 0;
-				player->capsule->extravalue1 = -1;
+				tictimer = -1;
 
 				for (i = 0; i < MAXPLAYERS; i++)
 					if (playeringame[i] && !player->exiting && players[i].mare == player->mare)
@@ -5976,9 +6554,6 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 				if (G_IsSpecialStage(gamemap))
 				{
-					// The Chaos Emerald begins to orbit us!
-					mobj_t *emmo;
-					UINT8 em = P_GetNextEmerald();
 					tic_t lowest_time;
 
 					/*for (i = 0; i < MAXPLAYERS; i++)
@@ -5993,8 +6568,11 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 					if (player->powers[pw_carry] == CR_NIGHTSMODE)
 					{
+						// The Chaos Emerald begins to orbit us!
+						UINT8 em = P_GetNextEmerald();
 						// Only give it to ONE person, and THAT player has to get to the goal!
-						emmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->info->height, MT_GOTEMERALD);
+						mobj_t *emmo = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
+						emmo->health = em; // for identification
 						P_SetTarget(&emmo->target, player->mo);
 						P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
 						P_SetTarget(&player->mo->tracer, emmo);
@@ -6012,31 +6590,99 @@ static void P_DoNiGHTSCapsule(player_t *player)
 				}
 				else
 				{
-					for (i = 0; i < 16; i++)
+					/*for (i = 0; i < 16; i++)
 					{
 						mobj_t *flicky = P_InternalFlickySpawn(player->capsule, 0, ((i%4) + 1)*2*FRACUNIT, true);
 						flicky->z += player->capsule->height/2;
 						flicky->angle = (i*(ANGLE_MAX/16));
 						P_InstaThrust(flicky, flicky->angle, 8*FRACUNIT);
+					}*/
+					mobj_t *idya = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
+					idya->extravalue2 = player->mare/5;
+					idya->health = player->mare + 1; // for identification
+					P_SetTarget(&idya->target, player->mo);
+					P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5));
+
+					if (player->mo->tracer)
+					{
+						P_SetTarget(&idya->hnext, player->mo->tracer);
+						idya->extravalue1 = (angle_t)(player->mo->tracer->extravalue1 - 72*ANG1);
+						if (idya->extravalue1 > player->mo->tracer->extravalue1)
+							idya->extravalue1 -= (72*ANG1)/idya->extravalue1;
 					}
+					P_SetTarget(&player->mo->tracer, idya);
 				}
 				for (i = 0; i < MAXPLAYERS; i++)
 					if (playeringame[i] && players[i].mare == player->mare)
 						P_SetTarget(&players[i].capsule, NULL); // Remove capsule from everyone now that it is dead!
 				S_StartScreamSound(player->mo, sfx_ngdone);
+				P_SwitchSpheresBonusMode(true);
+				P_RunNightsCapsuleTouchExecutors(player->mo, false, true); // run capsule exit executors, and we destroyed it
+			}
+			else
+			{
+				S_StartScreamSound(player->mo, sfx_lose);
+				player->texttimer = 4*TICRATE;
+				player->textvar = 3; // Get more rings!
+				player->capsule->reactiontime = 0;
+				player->capsule->extravalue1 = player->capsule->cvmem =\
+				 player->capsule->cusval = player->capsule->movecount =\
+				 player->capsule->lastlook = player->capsule->extravalue2 = -1;
+				P_RunNightsCapsuleTouchExecutors(player->mo, false, false); // run capsule exit executors, and we lacked rings
 			}
 		}
+	}
+	else if (player->capsule->lastlook > -1)
+		// We somehow moved out of the capsule (OBJECTPLACE?)
+		// So recalculate all the timings
+		player->capsule->lastlook = player->capsule->extravalue2 = -1;
+}
+
+//
+// P_MoveNiGHTSToDrone
+//
+// Pull NiGHTS to the drone during Nightserizing
+//
+static void P_MoveNiGHTSToDrone(player_t *player)
+{
+	boolean flip, topaligned, middlealigned, bottomoffsetted;
+	fixed_t droneboxmandiff, zofs;
+
+	if (!player->drone)
+		return;
+
+	flip = player->drone->flags2 & MF2_OBJECTFLIP;
+	topaligned = (player->drone->flags & MF_SLIDEME) && !(player->drone->flags & MF_GRENADEBOUNCE);
+	middlealigned = (player->drone->flags & MF_GRENADEBOUNCE) && !(player->drone->flags & MF_SLIDEME);
+	bottomoffsetted = !(player->drone->flags & MF_SLIDEME) && !(player->drone->flags & MF_GRENADEBOUNCE);
+	droneboxmandiff = max(player->drone->height - player->mo->height, 0);
+
+	if (!flip)
+	{
+		if (topaligned)
+			zofs = droneboxmandiff;
+		else if (middlealigned)
+			zofs = droneboxmandiff / 2;
+		else if (bottomoffsetted)
+			zofs = FixedMul(24*FRACUNIT, player->drone->scale);
 		else
-		{
-			S_StartScreamSound(player->mo, sfx_lose);
-			player->texttimer = 4*TICRATE;
-			player->textvar = 3; // Get more rings!
-			player->capsule->reactiontime = 0;
-			player->capsule->extravalue1 = -1;
-		}
+			zofs = 0;
 	}
 	else
-		player->capsule->extravalue1 = -1;
+	{
+		if (topaligned)
+			zofs = 0;
+		else if (middlealigned)
+			zofs = droneboxmandiff / 2;
+		else if (bottomoffsetted)
+			zofs = droneboxmandiff - FixedMul(24*FRACUNIT, player->drone->scale);
+		else
+			zofs = droneboxmandiff;
+	}
+
+	player->mo->momx = player->mo->momy = player->mo->momz = 0;
+	P_TeleportMove(player->mo, player->drone->x, player->drone->y, player->drone->z + zofs);
+	P_SetTarget(&player->drone, NULL);
 }
 
 //
@@ -6109,15 +6755,18 @@ static void P_NiGHTSMovement(player_t *player)
 	{
 		P_DeNightserizePlayer(player);
 		S_StartScreamSound(player->mo, sfx_s3k66);
-//		S_StopSoundByNum(sfx_timeup); // Kill the "out of time" music, if it's playing. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
-		P_RestoreMusic(player); // I have my doubts that this is the right place for this...
-
 		return;
 	}
 	else if (P_IsLocalPlayer(player) && player->nightstime == 10*TICRATE)
-//		S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. Dummied out, as some on the dev team thought it wasn't Sonic-y enough (Mystic, notably). Uncomment to restore. -SH
-		S_ChangeMusicInternal("_drown",false);
-
+	{
+		if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+		{
+			S_FadeMusic(0, 10*MUSICRATE);
+			S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
+		}
+		else
+			P_PlayJingle(player, ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? JT_NIGHTSTIMEOUT : JT_SSTIMEOUT);
+	}
 
 	if (player->mo->z < player->mo->floorz)
 		player->mo->z = player->mo->floorz;
@@ -6134,36 +6783,34 @@ static void P_NiGHTSMovement(player_t *player)
 
 		// scan the thinkers
 		// to find the closest axis point
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
 
-			if (mo2->type == MT_AXIS)
+			if (mo2->type != MT_AXIS)
+				continue;
+			if (mo2->threshold != player->mare)
+				continue;
+
+			if (closestaxis == NULL)
 			{
-				if (mo2->threshold == player->mare)
-				{
-					if (closestaxis == NULL)
-					{
-						closestaxis = mo2;
-						dist2 = R_PointToDist2(newx, newy, mo2->x, mo2->y)-mo2->radius;
-					}
-					else
-					{
-						dist1 = R_PointToDist2(newx, newy, mo2->x, mo2->y)-mo2->radius;
+				closestaxis = mo2;
+				dist2 = R_PointToDist2(newx, newy, mo2->x, mo2->y) - mo2->radius;
+			}
+			else
+			{
+				dist1 = R_PointToDist2(newx, newy, mo2->x, mo2->y) - mo2->radius;
 
-						if (dist1 < dist2)
-						{
-							closestaxis = mo2;
-							dist2 = dist1;
-						}
-					}
+				if (dist1 < dist2)
+				{
+					closestaxis = mo2;
+					dist2 = dist1;
 				}
 			}
 		}
-
 		P_SetTarget(&player->mo->target, closestaxis);
 	}
 
@@ -6495,7 +7142,10 @@ static void P_NiGHTSMovement(player_t *player)
 	}
 
 	if (player->mo->momx || player->mo->momy)
+	{
 		player->mo->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+		player->drawangle = player->mo->angle;
+	}
 
 	if (still)
 	{
@@ -6586,7 +7236,18 @@ static void P_NiGHTSMovement(player_t *player)
 			S_StartSound(player->mo, sfx_drill1);
 			player->drilltimer = 32;
 		}
-		else if (--player->drilltimer <= 0)
+		else if (player->drilltimer == 32)
+		{
+			// drill mash penalty
+			player->drilltimer = 31;
+			player->drillmeter -= TICRATE/2;
+			if (player->drillmeter <= 0)
+				player->drillmeter = TICRATE/10;
+		}
+		else if (--player->drilltimer == 11)
+			// give that drill mash penalty back (after 0.6 seconds)
+			player->drillmeter += TICRATE/2;
+		else if (player->drilltimer <= 0)
 		{
 			player->drilltimer = 10;
 			S_StartSound(player->mo, sfx_drill2);
@@ -6915,12 +7576,17 @@ static void P_MovePlayer(player_t *player)
 		if ((player->powers[pw_carry] == CR_NIGHTSMODE)
 		&& (player->exiting
 		|| !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
-			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6])))
+			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6]))) // Note the < instead of <=
 		{
 			skin_t *skin = ((skin_t *)(player->mo->skin));
-			if (skin->flags & SF_SUPER && player->mo->color < MAXSKINCOLORS)
+			if (skin->flags & SF_SUPER)
+			{
+				player->mo->color = skin->supercolor
+					+ ((player->nightstime == player->startedtime)
+						? 4
+						: abs((((signed)leveltime >> 1) % 9) - 4)); // This is where super flashing is handled.
 				G_GhostAddColor(GHC_SUPER);
-			player->mo->color = (skin->flags & SF_SUPER) ? skin->supercolor + abs((((signed)(player->startedtime - player->nightstime) >> 1) % 9) - 4) : player->mo->color; // This is where super flashing is handled.
+			}
 		}
 
 		if (!player->capsule && !player->bonustime)
@@ -6928,9 +7594,9 @@ static void P_MovePlayer(player_t *player)
 			thinker_t *th;
 			mobj_t *mo2;
 
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -6946,6 +7612,13 @@ static void P_MovePlayer(player_t *player)
 			return;
 		}
 
+		// Suck player into their drone
+		if (player->drone)
+		{
+			P_MoveNiGHTSToDrone(player);
+			return;
+		}
+
 		// Test revamped NiGHTS movement.
 		if (player->powers[pw_carry] == CR_NIGHTSMODE)
 		{
@@ -6969,8 +7642,14 @@ static void P_MovePlayer(player_t *player)
 					if (playeringame[i])
 						players[i].exiting = (14*TICRATE)/5 + 1;
 			}
-			else if (player->rings > 0)
+			else {
+				// Damage whether or not we have spheres, as player should recoil upon losing points
 				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
+
+				// Now deduct our mare score!
+				player->marescore = player->spheres =\
+				 player->rings = 0;
+			}
 			player->powers[pw_carry] = CR_NONE;
 		}
 	}
@@ -7423,7 +8102,7 @@ static void P_MovePlayer(player_t *player)
 				if (P_IsLocalPlayer(player)) // Only display it on your own view.
 				{
 					mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-					visual->target = lockon;
+					P_SetTarget(&visual->target, lockon);
 					P_SetMobjStateNF(visual, visual->info->spawnstate+1);
 				}
 			}
@@ -7454,9 +8133,9 @@ static void P_MovePlayer(player_t *player)
 #endif
 			{
 				if (!(player->pflags & (PF_USEDOWN|PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player is not holding down BT_USE, or having used an ability previously
-					&& (!(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX))) // thokked is optional if you're bubblewrapped
+					&& (!(player->powers[pw_shield] & SH_NOSTACK) || !(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX))) // thokked is optional if you're bubblewrapped/turning super
 				{
-					// Force shield activation
+					// Force stop
 					if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
 					{
 						player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
@@ -7467,17 +8146,22 @@ static void P_MovePlayer(player_t *player)
 					{
 						switch (player->powers[pw_shield] & SH_NOSTACK)
 						{
-							// Whirlwind/Thundercoin shield activation
+							// Super!
+							case SH_NONE:
+								if (P_SuperReady(player))
+									P_DoSuperTransformation(player, false);
+								break;
+							// Whirlwind jump/Thunder jump
 							case SH_WHIRLWIND:
 							case SH_THUNDERCOIN:
 								P_DoJumpShield(player);
 								break;
-							// Armageddon shield activation
+							// Armageddon pow
 							case SH_ARMAGEDDON:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								P_BlackOw(player);
 								break;
-							// Attract shield activation
+							// Attraction blast
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
@@ -7486,13 +8170,14 @@ static void P_MovePlayer(player_t *player)
 								{
 									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 									player->pflags &= ~PF_NOJUMPDAMAGE;
+									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 									S_StartSound(player->mo, sfx_s3k40);
 									player->homing = 3*TICRATE;
 								}
 								else
 									S_StartSound(player->mo, sfx_s3ka6);
 								break;
-							// Elemental/Bubblewrap shield activation
+							// Elemental stomp/Bubble bounce
 							case SH_ELEMENTAL:
 							case SH_BUBBLEWRAP:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
@@ -7506,7 +8191,7 @@ static void P_MovePlayer(player_t *player)
 									? sfx_s3k43
 									: sfx_s3k44);
 								break;
-							// Flame shield activation
+							// Flame burst
 							case SH_FLAMEAURA:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
@@ -7527,8 +8212,7 @@ static void P_MovePlayer(player_t *player)
 	{
 		if (player->homing && player->mo->tracer)
 		{
-			P_HomingAttack(player->mo, player->mo->tracer);
-			if (player->mo->tracer->health <= 0 || (player->mo->tracer->flags2 & MF2_FRET))
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
 			{
 				P_SetObjectMomZ(player->mo, 6*FRACUNIT, false);
 				if (player->mo->eflags & MFE_UNDERWATER)
@@ -7547,10 +8231,9 @@ static void P_MovePlayer(player_t *player)
 		if (player->homing && player->mo->tracer)
 		{
 			P_SpawnThokMobj(player);
-			P_HomingAttack(player->mo, player->mo->tracer);
 
 			// But if you don't, then stop homing.
-			if (player->mo->tracer->health <= 0 || (player->mo->tracer->flags2 & MF2_FRET))
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
 			{
 				if (player->mo->eflags & MFE_UNDERWATER)
 					P_SetObjectMomZ(player->mo, FixedDiv(457*FRACUNIT,72*FRACUNIT), false);
@@ -7818,9 +8501,9 @@ static void P_DoZoomTube(player_t *player)
 		CONS_Debug(DBG_GAMELOGIC, "Looking for next waypoint...\n");
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -7828,15 +8511,17 @@ static void P_DoZoomTube(player_t *player)
 			if (mo2->type != MT_TUBEWAYPOINT)
 				continue;
 
-			if (mo2->threshold == sequence)
-			{
-				if ((reverse && mo2->health == player->mo->tracer->health - 1)
-					|| (!reverse && mo2->health == player->mo->tracer->health + 1))
-				{
-					waypoint = mo2;
-					break;
-				}
-			}
+			if (mo2->threshold != sequence)
+				continue;
+
+			if (reverse && mo2->health != player->mo->tracer->health - 1)
+				continue;
+
+			if (!reverse && mo2->health != player->mo->tracer->health + 1)
+				continue;
+
+			waypoint = mo2;
+			break;
 		}
 
 		if (waypoint)
@@ -7918,17 +8603,18 @@ static void P_DoRopeHang(player_t *player)
 
 	if (player->cmd.buttons & BT_USE && !(player->pflags & PF_STASIS)) // Drop off of the rope
 	{
-		P_SetTarget(&player->mo->tracer, NULL);
-
 		player->pflags |= P_GetJumpFlags(player);
+		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+
+		P_SetTarget(&player->mo->tracer, NULL);
 		player->powers[pw_carry] = CR_NONE;
 
-		if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
-		&& !(player->panim == PA_JUMP))
-			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		return;
 	}
 
+	if (player->mo->state-states != S_PLAY_RIDE)
+		P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+
 	// If not allowed to move, we're done here.
 	if (!speed)
 		return;
@@ -7951,9 +8637,9 @@ static void P_DoRopeHang(player_t *player)
 		CONS_Debug(DBG_GAMELOGIC, "Looking for next waypoint...\n");
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -7961,14 +8647,14 @@ static void P_DoRopeHang(player_t *player)
 			if (mo2->type != MT_TUBEWAYPOINT)
 				continue;
 
-			if (mo2->threshold == sequence)
-			{
-				if (mo2->health == player->mo->tracer->health + 1)
-				{
-					waypoint = mo2;
-					break;
-				}
-			}
+			if (mo2->threshold != sequence)
+				continue;
+
+			if (mo2->health != player->mo->tracer->health + 1)
+				continue;
+
+			waypoint = mo2;
+			break;
 		}
 
 		if (!(player->mo->tracer->flags & MF_SLIDEME) && !waypoint)
@@ -7976,9 +8662,9 @@ static void P_DoRopeHang(player_t *player)
 			CONS_Debug(DBG_GAMELOGIC, "Next waypoint not found, wrapping to start...\n");
 
 			// Wrap around back to first waypoint
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -7986,14 +8672,14 @@ static void P_DoRopeHang(player_t *player)
 				if (mo2->type != MT_TUBEWAYPOINT)
 					continue;
 
-				if (mo2->threshold == sequence)
-				{
-					if (mo2->health == 0)
-					{
-						waypoint = mo2;
-						break;
-					}
-				}
+				if (mo2->threshold != sequence)
+					continue;
+
+				if (mo2->health != 0)
+					continue;
+
+				waypoint = mo2;
+				break;
 			}
 		}
 
@@ -8019,10 +8705,7 @@ static void P_DoRopeHang(player_t *player)
 			if (player->mo->tracer->flags & MF_SLIDEME)
 			{
 				player->pflags |= P_GetJumpFlags(player);
-
-				if (!(player->pflags & PF_SLIDING) && (player->pflags & PF_JUMPED)
-				&& !(player->panim == PA_JUMP))
-					P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 			}
 
 			P_SetTarget(&player->mo->tracer, NULL);
@@ -8043,25 +8726,22 @@ static void P_DoRopeHang(player_t *player)
 static void P_NukeAllPlayers(player_t *player)
 {
 	mobj_t *mo;
-	thinker_t *think;
+	UINT8 i;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
-
-		mo = (mobj_t *)think;
-
-		if (!mo->player)
+		if (!playeringame[i])
 			continue;
-
-		if (mo->health <= 0) // dead
+		if (players[i].spectator)
 			continue;
-
-		if (mo == player->mo)
+		if (!players[i].mo)
+			continue;
+		if (players[i].mo == player->mo)
+			continue;
+		if (players[i].mo->health <= 0)
 			continue;
 
-		P_DamageMobj(mo, player->mo, player->mo, 1, 0);
+		P_DamageMobj(players[i].mo, player->mo, player->mo, 1, 0);
 	}
 
 	CONS_Printf(M_GetText("%s caused a world of pain.\n"), player_names[player-players]);
@@ -8093,10 +8773,10 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 		}
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
@@ -8140,20 +8820,17 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 	mobj_t *mo;
 	thinker_t *think;
 	mobj_t *closestmo = NULL;
-	const UINT32 targetmask = (MF_ENEMY|MF_BOSS|(nonenemies ? (MF_MONITOR|MF_SPRING) : 0));
 	const fixed_t maxdist = FixedMul((bullet ? RING_DIST*2 : RING_DIST), player->mo->scale);
 	const angle_t span = (bullet ? ANG30 : ANGLE_90);
 	fixed_t dist, closestdist = 0;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
-		if (!(mo->flags & targetmask
-		|| mo->type == MT_FAKEMOBILE // hehehehe
-		|| mo->type == MT_EGGSHIELD))
+		if (!((mo->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR) && (mo->flags & MF_SHOOTABLE)) || (mo->flags & MF_SPRING)) == !(mo->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
 			continue; // not a valid target
 
 		if (mo->health <= 0) // dead
@@ -8165,7 +8842,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		if (mo->flags2 & MF2_FRET)
 			continue;
 
-		if ((mo->flags & (MF_ENEMY|MF_BOSS)) && !(mo->flags & MF_SHOOTABLE)) // don't aim at something you can't shoot at anyway (see Egg Guard or Minus)
+		if (!nonenemies && mo->flags & (MF_MONITOR|MF_SPRING))
 			continue;
 
 		if (!bullet && mo->type == MT_DETON) // Don't be STUPID, Sonic!
@@ -8205,7 +8882,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		if (closestmo && dist > closestdist)
 			continue;
 
-		if ((R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y) - player->mo->angle + span) > span*2)
+		if ((R_PointToAngle2(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle, player->mo->radius), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle, player->mo->radius), mo->x, mo->y) - player->mo->angle + span) > span*2)
 			continue; // behind back
 
 		if (!P_CheckSight(player->mo, mo))
@@ -8218,17 +8895,23 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 	return closestmo;
 }
 
-void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
+boolean P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 {
 	fixed_t zdist;
 	fixed_t dist;
 	fixed_t ns = 0;
 
 	if (!enemy)
-		return;
+		return false;
+
+	if (!enemy->health)
+		return false;
+
+	if (enemy->flags2 & MF2_FRET)
+		return false;
 
-	if (!(enemy->health))
-		return;
+	if (!(enemy->flags & (MF_SHOOTABLE|MF_SPRING)) == !(enemy->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
+		return false;
 
 	// change angle
 	source->angle = R_PointToAngle2(source->x, source->y, enemy->x, enemy->y);
@@ -8271,6 +8954,8 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	source->momx = FixedMul(FixedDiv(enemy->x - source->x, dist), ns);
 	source->momy = FixedMul(FixedDiv(enemy->y - source->y, dist), ns);
 	source->momz = FixedMul(FixedDiv(zdist, dist), ns);
+
+	return true;
 }
 
 // Search for emeralds
@@ -8283,9 +8968,9 @@ void P_FindEmerald(void)
 
 	// scan the remaining thinkers
 	// to find all emeralds
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8313,7 +8998,7 @@ boolean P_GetLives(player_t *player)
 	if (!(netgame || multiplayer)
 	|| (gametype != GT_COOP)
 	|| (cv_cooplives.value == 1)
-	|| (player->lives == 0x7f))
+	|| (player->lives == INFLIVES))
 		return true;
 
 	if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
@@ -8340,7 +9025,7 @@ boolean P_GetLives(player_t *player)
 	{
 		if (cv_cooplives.value == 2 && (P_IsLocalPlayer(player) || P_IsLocalPlayer(&players[maxlivesplayer])))
 			S_StartSound(NULL, sfx_jshard); // placeholder
-		if (players[maxlivesplayer].lives != 0x7f)
+		if (players[maxlivesplayer].lives != INFLIVES)
 			players[maxlivesplayer].lives--;
 		player->lives++;
 		if (player->lives < 1)
@@ -8425,6 +9110,9 @@ static void P_DeathThink(player_t *player)
 	if (player->deadtimer < INT32_MAX)
 		player->deadtimer++;
 
+	if (player->bot) // don't allow bots to do any of the below, B_CheckRespawn does all they need for respawning already
+		goto notrealplayer;
+
 	// continue logic
 	if (!(netgame || multiplayer) && player->lives <= 0)
 	{
@@ -8551,6 +9239,8 @@ static void P_DeathThink(player_t *player)
 			P_RestoreMultiMusic(player);
 	}
 
+notrealplayer:
+
 	if (!player->mo)
 		return;
 
@@ -8648,16 +9338,22 @@ void P_ResetCamera(player_t *player, camera_t *thiscam)
 boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled)
 {
 	angle_t angle = 0, focusangle = 0, focusaiming = 0;
-	fixed_t x, y, z, dist, height, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight;
+	fixed_t x, y, z, dist, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight;
 	INT32 camrotate;
 	boolean camstill, cameranoclip;
 	mobj_t *mo;
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
 
-	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (player->mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
+	// We probably shouldn't move the camera if there is no player or player mobj somehow
+	if (!player || !player->mo)
+		return true;
+
+	mo = player->mo;
+
+	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
 
-	if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD))
+	if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || tutorialmode))
 	{
 		if (player->spectator) // force cam off for spectators
 			return true;
@@ -8676,7 +9372,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		else if (player == &players[secondarydisplayplayer])
 			focusangle = localangle2;
 		else
-			focusangle = player->mo->angle;
+			focusangle = mo->angle;
 		if (thiscam == &camera)
 			camrotate = cv_cam_rotate.value;
 		else if (thiscam == &camera2)
@@ -8688,17 +9384,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		return true;
 	}
 
-	if (!player || !player->mo)
-		return true;
-
-	mo = player->mo;
-
 	thiscam->radius = FixedMul(20*FRACUNIT, mo->scale);
 	thiscam->height = FixedMul(16*FRACUNIT, mo->scale);
 
-	if (!mo)
-		return true;
-
 	// Don't run while respawning from a starpost
 	// Inu 4/8/13 Why not?!
 //	if (leveltime > 0 && timeinmap <= 0)
@@ -8706,7 +9394,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 
 	if (player->powers[pw_carry] == CR_NIGHTSMODE)
 	{
-		focusangle = player->mo->angle;
+		focusangle = mo->angle;
 		focusaiming = 0;
 	}
 	else if (player == &players[consoleplayer])
@@ -8721,14 +9409,23 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else
 	{
-		focusangle = player->mo->angle;
+		focusangle = mo->angle;
 		focusaiming = player->aiming;
 	}
 
 	if (P_CameraThinker(player, thiscam, resetcalled))
 		return true;
 
-	if (thiscam == &camera)
+	if (tutorialmode)
+	{
+		// force defaults because we have a camera look section
+		camspeed = (INT32)(atof(cv_cam_speed.defaultvalue) * FRACUNIT);
+		camstill = (!stricmp(cv_cam_still.defaultvalue, "off")) ? false : true;
+		camrotate = atoi(cv_cam_rotate.defaultvalue);
+		camdist = FixedMul((INT32)(atof(cv_cam_dist.defaultvalue) * FRACUNIT), mo->scale);
+		camheight = FixedMul((INT32)(atof(cv_cam_height.defaultvalue) * FRACUNIT), FixedMul(player->camerascale, mo->scale));
+	}
+	else if (thiscam == &camera)
 	{
 		camspeed = cv_cam_speed.value;
 		camstill = cv_cam_still.value;
@@ -8768,12 +9465,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			angle = R_PointToAngle2(player->axis1->x, player->axis1->y, player->axis2->x, player->axis2->y);
 			angle += ANGLE_90;
 		}
-		else if (player->mo->target)
+		else if (mo->target)
 		{
-			if (player->mo->target->flags2 & MF2_AMBUSH)
-				angle = R_PointToAngle2(player->mo->target->x, player->mo->target->y, player->mo->x, player->mo->y);
+			if (mo->target->flags2 & MF2_AMBUSH)
+				angle = R_PointToAngle2(mo->target->x, mo->target->y, mo->x, mo->y);
 			else
-				angle = R_PointToAngle2(player->mo->x, player->mo->y, player->mo->target->x, player->mo->target->y);
+				angle = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y);
 		}
 	}
 	else if (P_AnalogMove(player)) // Analog
@@ -8821,12 +9518,11 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		}
 	}
 
-	height = camheight;
-
 	// sets ideal cam pos
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
 		dist = 480<<FRACBITS;
-	else if (player->powers[pw_carry] == CR_NIGHTSMODE)
+	else if (player->powers[pw_carry] == CR_NIGHTSMODE
+		|| ((maptol & TOL_NIGHTS) && player->capsule && player->capsule->reactiontime > 0 && player == &players[player->capsule->reactiontime-1]))
 		dist = 320<<FRACBITS;
 	else
 	{
@@ -8836,14 +9532,14 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		if (splitscreen)
 		{
 			dist = FixedMul(dist, 3*FRACUNIT/2);
-			height = FixedMul(height, 3*FRACUNIT/2);
+			camheight = FixedMul(camheight, 3*FRACUNIT/2);
 		}
 
 		// x1.2 dist for analog
 		if (P_AnalogMove(player))
 		{
 			dist = FixedMul(dist, 6*FRACUNIT/5);
-			height = FixedMul(height, 6*FRACUNIT/5);
+			camheight = FixedMul(camheight, 6*FRACUNIT/5);
 		}
 
 		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_GENERIC || player->powers[pw_carry] == CR_MACESPIN))
@@ -8862,7 +9558,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
 	{
 		// Camera doesn't ALWAYS need to move, only when running...
-		if (abs(player->mo->momx) > 10)
+		if (abs(mo->momx) > 10)
 		{
 			// Move the camera all smooth-like, not jerk it around...
 			if (mo->momx > 0)
@@ -8893,9 +9589,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	pviewheight = FixedMul(41*player->height/48, mo->scale);
 
 	if (mo->eflags & MFE_VERTICALFLIP)
-		z = mo->z + mo->height - pviewheight - height;
+		z = mo->z + mo->height - pviewheight - camheight;
 	else
-		z = mo->z + pviewheight + height;
+		z = mo->z + pviewheight + camheight;
 
 	// move camera down to move under lower ceilings
 	newsubsec = R_IsPointInSubsector(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1));
@@ -9093,7 +9789,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 
 	if (mo->type == MT_EGGTRAP)
-		z = mo->z + 128*FRACUNIT + pviewheight + height;
+		z = mo->z + 128*FRACUNIT + pviewheight + camheight;
 
 	if (thiscam->z < thiscam->floorz && !cameranoclip)
 		thiscam->z = thiscam->floorz;
@@ -9169,24 +9865,20 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	// Make player translucent if camera is too close (only in single player).
 	if (!(multiplayer || netgame) && !splitscreen)
 	{
-		fixed_t vx = 0, vy = 0;
-		if (player->awayviewtics) {
+		fixed_t vx = thiscam->x, vy = thiscam->y;
+		if (player->awayviewtics && player->awayviewmobj != NULL && !P_MobjWasRemoved(player->awayviewmobj))		// Camera must obviously exist
+		{
 			vx = player->awayviewmobj->x;
 			vy = player->awayviewmobj->y;
 		}
-		else
-		{
-			vx = thiscam->x;
-			vy = thiscam->y;
-		}
 
-		if (P_AproxDistance(vx - player->mo->x, vy - player->mo->y) < FixedMul(48*FRACUNIT, mo->scale))
-			player->mo->flags2 |= MF2_SHADOW;
+		if (P_AproxDistance(vx - mo->x, vy - mo->y) < FixedMul(48*FRACUNIT, mo->scale))
+			mo->flags2 |= MF2_SHADOW;
 		else
-			player->mo->flags2 &= ~MF2_SHADOW;
+			mo->flags2 &= ~MF2_SHADOW;
 	}
 	else
-		player->mo->flags2 &= ~MF2_SHADOW;
+		mo->flags2 &= ~MF2_SHADOW;
 
 /*	if (!resetcalled && (player->powers[pw_carry] == CR_NIGHTSMODE && player->exiting))
 	{
@@ -9330,7 +10022,7 @@ static void P_CalcPostImg(player_t *player)
 	else
 		pviewheight = player->mo->z + player->viewheight;
 
-	if (player->awayviewtics)
+	if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj))
 	{
 		sector = player->awayviewmobj->subsector->sector;
 		pviewheight = player->awayviewmobj->z + 20*FRACUNIT;
@@ -9433,15 +10125,427 @@ void P_DoPityCheck(player_t *player)
 	// Apply pity shield if available.
 	if ((player->pity >= 3 || player->pity < 0) && player->powers[pw_shield] == SH_NONE)
 	{
+		P_SwitchShield(player, SH_PITY);
+
 		if (player->pity > 0)
 			S_StartSound(player->mo, mobjinfo[MT_PITY_ICON].seesound);
 
 		player->pity = 0;
-		player->powers[pw_shield] = SH_PITY;
-		P_SpawnShieldOrb(player);
 	}
 }
 
+static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z, fixed_t *nz)
+{
+	sector_t *sec = R_PointInSubsector(x, y)->sector;
+
+	if ((sec->ceilingheight - sec->floorheight) < 64*FRACUNIT)
+		return NULL;
+
+	if (sec->ffloors)
+	{
+		ffloor_t *rover;
+		for (rover = sec->ffloors; rover; rover = rover->next)
+		{
+			if (!(rover->flags & FF_EXISTS))
+				continue;
+
+			*nz = *rover->t_slope ? P_GetZAt(*rover->t_slope, x, y) : *rover->topheight;
+			if (abs(z - *nz) <= 56*FRACUNIT)
+			{
+				sec = &sectors[rover->secnum];
+				return sec;
+			}
+		}
+
+	}
+
+	*nz = sec->f_slope ? P_GetZAt(sec->f_slope, x, y) : sec->floorheight;
+	if (abs(z - *nz) > 56*FRACUNIT)
+		return NULL;
+
+	return sec;
+}
+
+static INT32 P_GetMinecartSpecialLine(sector_t *sec)
+{
+	INT32 line = -1;
+
+	if (!sec)
+		return line;
+
+	if (sec->tag != 0)
+		line = P_FindSpecialLineFromTag(16, sec->tag, -1);
+
+	// Also try for lines facing the sector itself, with tag 0.
+	{
+		UINT32 i;
+		for (i = 0; i < sec->linecount; i++)
+		{
+			line_t *li = sec->lines[i];
+			if (li->tag == 0 && li->special == 16 && li->frontsector == sec)
+				line = li - lines;
+		}
+	}
+
+	return line;
+}
+
+// Get an axis of a certain ID number
+static mobj_t *P_GetAxis(INT32 num)
+{
+	thinker_t *th;
+	mobj_t *mobj;
+
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj = (mobj_t *)th;
+
+		// NiGHTS axes spawn before anything else. If this mobj doesn't have MF2_AXIS, it means we reached the axes' end.
+		if (!(mobj->flags2 & MF2_AXIS))
+			break;
+
+		// Skip if this axis isn't the one we want.
+		if (mobj->health != num)
+			continue;
+
+		return mobj;
+	}
+
+	CONS_Alert(CONS_WARNING, "P_GetAxis: Track segment %d is missing!\n", num);
+	return NULL;
+}
+
+// Auxiliary function. For a given position and axis, it calculates the nearest "valid" snap-on position.
+static void P_GetAxisPosition(fixed_t x, fixed_t y, mobj_t *amo, fixed_t *newx, fixed_t *newy, angle_t *targetangle, angle_t *grind)
+{
+	fixed_t ax = amo->x;
+	fixed_t ay = amo->y;
+	angle_t ang;
+	angle_t gr = 0;
+
+	if (amo->type == MT_AXISTRANSFERLINE)
+	{
+		ang = amo->angle;
+		// Extra security for cardinal directions.
+		if (ang == ANGLE_90 || ang == ANGLE_270) // Vertical lines
+			x = ax;
+		else if (ang == 0 || ang == ANGLE_180) // Horizontal lines
+			y = ay;
+		else // Diagonal lines
+		{
+			fixed_t distance = R_PointToDist2(ax, ay, x, y);
+			angle_t fad = ((R_PointToAngle2(ax, ay, x, y) - ang) >> ANGLETOFINESHIFT) & FINEMASK;
+			fixed_t cosine = FINECOSINE(fad);
+			angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
+			distance = FixedMul(distance, cosine);
+			x = ax + FixedMul(distance, FINECOSINE(fa));
+			y = ay + FixedMul(distance, FINESINE(fa));
+		}
+	}
+	else // Keep minecart to circle
+	{
+		fixed_t rad = amo->radius;
+		fixed_t distfactor = FixedDiv(rad, R_PointToDist2(ax, ay, x, y));
+
+		gr = R_PointToAngle2(ax, ay, x, y);
+		ang = gr + ANGLE_90;
+		x = ax + FixedMul(x - ax, distfactor);
+		y = ay + FixedMul(y - ay, distfactor);
+	}
+
+	*newx = x;
+	*newy = y;
+	*targetangle = ang;
+	*grind = gr;
+}
+
+static void P_SpawnSparks(mobj_t *mo, angle_t maindir)
+{
+	angle_t fa = (mo->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t c = FixedMul(FINECOSINE(fa), mo->radius);
+	fixed_t s = FixedMul(FINESINE(fa), mo->radius);
+	mobj_t *spark;
+	SINT8 b1 = (leveltime & 1) ? 1 : -1;
+	SINT8 b2 = (leveltime & 2) ? 1 : -1;
+	fixed_t r1 = FRACUNIT*P_RandomRange(-1, 1);
+	fixed_t r2 = FRACUNIT*P_RandomRange(-1, 1);
+	fixed_t r3 = FRACUNIT*P_RandomRange(-1, 1);
+	fixed_t fm = (maindir >> ANGLETOFINESHIFT) & FINEMASK;
+
+	spark = P_SpawnMobj(mo->x - b2*s + b1*c, mo->y + b2*c + b1*s, mo->z, MT_MINECARTSPARK);
+	spark->momx = mo->momx + r1 + 8*FINECOSINE(fm);
+	spark->momy = mo->momy + r2 + 8*FINESINE(fm);
+	spark->momz = mo->momz + r3;
+
+	P_Thrust(spark, R_PointToAngle2(mo->x, mo->y, spark->x, spark->y), 8*FRACUNIT);
+	P_SetScale(spark, FRACUNIT/4);
+	spark->destscale = spark->scale;
+	spark->fuse = TICRATE/3;
+}
+
+// Performs a proximity check on a given direction looking for rails.
+static mobj_t *P_LookForRails(mobj_t* mobj, fixed_t c, fixed_t s, angle_t targetangle, fixed_t xcom, fixed_t ycom)
+{
+	INT16 interval = 16;
+	INT16 fwooffset = FixedHypot(mobj->momx, mobj->momy) >> FRACBITS;
+	fixed_t x = mobj->x;
+	fixed_t y = mobj->y;
+	fixed_t z = mobj->z;
+	UINT8 i;
+
+	for (i = 4; i <= 10; i++)
+	{
+		fixed_t nz;
+		INT32 lline;
+
+		x += interval*xcom*i + fwooffset*c*i;
+		y += interval*ycom*i + fwooffset*s*i;
+
+		lline = P_GetMinecartSpecialLine(P_GetMinecartSector(x, y, z, &nz));
+		if (lline != -1)
+		{
+			fixed_t nx, ny;
+			angle_t nang, dummy, angdiff;
+			mobj_t *mark;
+			mobj_t *snax = P_GetAxis(sides[lines[lline].sidenum[0]].textureoffset >> FRACBITS);
+			if (!snax)
+				return NULL;
+			P_GetAxisPosition(x, y, snax, &nx, &ny, &nang, &dummy);
+			angdiff = ((nang - targetangle) + ANG10/2) & ~ANGLE_180;
+			//Axes must be directly parallel or antiparallel, give or take 5 degrees.
+			if (angdiff < ANG10)
+			{
+				mark = P_SpawnMobj(nx, ny, nz, mobj->info->raisestate);
+				return mark;
+			}
+		}
+	}
+	return NULL;
+}
+
+static void P_ParabolicMove(mobj_t *mo, fixed_t x, fixed_t y, fixed_t z, fixed_t g, fixed_t speed)
+{
+	fixed_t dx = x - mo->x;
+	fixed_t dy = y - mo->y;
+	fixed_t dz = z - mo->z;
+	fixed_t dh = P_AproxDistance(dx, dy);
+	fixed_t c = FixedDiv(dx, dh);
+	fixed_t s = FixedDiv(dy, dh);
+	fixed_t fixConst = FixedDiv(speed, g);
+
+	mo->momx = FixedMul(c, speed);
+	mo->momy = FixedMul(s, speed);
+	mo->momz = FixedDiv(dh, 2*fixConst) + FixedDiv(dz, FixedDiv(dh, fixConst/2));
+}
+
+static void P_MinecartThink(player_t *player)
+{
+	mobj_t *minecart = player->mo->tracer;
+	angle_t fa;
+
+	if (!minecart || P_MobjWasRemoved(minecart) || !minecart->health)
+	{
+		// Minecart died on you, so kill yourself.
+		P_KillMobj(player->mo, NULL, NULL, 0);
+		return;
+	}
+
+	//Limit player's angle to a cone.
+#define MINECARTCONEMAX FixedAngle(20*FRACUNIT)
+	{
+		angle_t angdiff = player->mo->angle - minecart->angle;
+		if (angdiff < ANGLE_180 && angdiff > MINECARTCONEMAX)
+			player->mo->angle = minecart->angle + MINECARTCONEMAX;
+		else if (angdiff > ANGLE_180 && angdiff < InvAngle(MINECARTCONEMAX))
+			player->mo->angle = minecart->angle - MINECARTCONEMAX;
+
+		if (angdiff + minecart->angle != player->mo->angle && (!demoplayback || P_AnalogMove(player)))
+		{
+			if (player == &players[consoleplayer])
+				localangle = player->mo->angle;
+			else if (player == &players[secondarydisplayplayer])
+				localangle2 = player->mo->angle;
+		}
+	}
+
+	// Player holding jump?
+	if (!(player->cmd.buttons & BT_JUMP))
+		player->pflags &= ~PF_JUMPDOWN;
+
+	// Handle segments.
+	P_HandleMinecartSegments(minecart);
+
+	// Force 0 friction.
+	minecart->friction = FRACUNIT;
+
+	fa = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	if (!P_TryMove(minecart, minecart->x + FINECOSINE(fa), minecart->y + FINESINE(fa), true))
+	{
+		P_KillMobj(minecart, NULL, NULL, 0);
+		return;
+	}
+
+	if (P_IsObjectOnGround(minecart))
+	{
+		sector_t *sec;
+		INT32 lnum;
+		fixed_t dummy;
+
+		// Just hit floor.
+		if (minecart->eflags & MFE_JUSTHITFLOOR)
+		{
+			S_StopSound(minecart);
+			S_StartSound(minecart, sfx_s3k96);
+		}
+
+		sec = P_GetMinecartSector(minecart->x, minecart->y, minecart->z, &dummy);
+
+		if (sec)
+			lnum = P_GetMinecartSpecialLine(sec);
+
+		// Update axis if the cart is standing on a rail.
+		if (sec && lnum != -1)
+		{
+			mobj_t *axis = P_GetAxis(sides[lines[lnum].sidenum[0]].textureoffset >> FRACBITS);
+			fixed_t newx, newy;
+			angle_t targetangle, grind;
+			angle_t prevangle, angdiff;
+			mobj_t *detleft = NULL;
+			mobj_t *detright = NULL;
+			mobj_t *sidelock = NULL;
+			boolean jumped = false;
+			fixed_t currentSpeed;
+
+			if (!axis)
+			{
+				P_KillMobj(minecart, NULL, NULL, 0);
+				return;
+			}
+
+			minecart->movefactor = 0;
+			P_ResetScore(player);
+			// Handle angle and position
+			P_GetAxisPosition(minecart->x, minecart->y, axis, &newx, &newy, &targetangle, &grind);
+			if (axis->type != MT_AXISTRANSFERLINE)
+				P_SpawnSparks(minecart, grind);
+			P_TryMove(minecart, newx, newy, true);
+
+			// Set angle based on target
+			prevangle = minecart->angle;
+			angdiff = targetangle - minecart->angle;
+			if (angdiff < ANGLE_90 + ANG2 || angdiff > ANGLE_270 - ANG2)
+				minecart->angle = targetangle;
+			else
+				minecart->angle = targetangle + ANGLE_180;
+			angdiff = (minecart->angle - prevangle);
+			if (angdiff && (!demoplayback || P_AnalogMove(player)))  // maintain relative angle on turns
+			{
+				player->mo->angle += angdiff;
+				if (player == &players[consoleplayer])
+					localangle += angdiff;
+				else if (player == &players[secondarydisplayplayer])
+					localangle2 += angdiff;
+			}
+
+			// Sideways detection
+			if (minecart->flags2 & MF2_AMBUSH)
+			{
+				angle_t fa2 = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK;
+				fixed_t c = FINECOSINE(fa2);
+				fixed_t s = FINESINE(fa2);
+
+				detleft = P_LookForRails(minecart, c, s, targetangle, -s, c);
+				detright = P_LookForRails(minecart, c, s, targetangle, s, -c);
+			}
+
+			// How fast are we going?
+			currentSpeed = FixedHypot(minecart->momx, minecart->momy);
+			angdiff = R_PointToAngle2(0, 0, minecart->momx, minecart->momy) - minecart->angle;
+			if (angdiff > ANGLE_90 && angdiff < ANGLE_270)
+				currentSpeed *= -1;
+
+			// Player-specific behavior.
+			if (detleft && player->cmd.sidemove < 0)
+				sidelock = detleft;
+			else if (detright && player->cmd.sidemove > 0)
+				sidelock = detright;
+
+			//if (player->cmd.buttons & BT_USE && currentSpeed > 4*FRACUNIT)
+			//	currentSpeed -= FRACUNIT/8;
+
+			// Jumping
+			if (sidelock || ((player->cmd.buttons & BT_JUMP) && !(player->pflags & PF_JUMPDOWN)))
+			{
+				player->pflags |= PF_JUMPDOWN;
+
+				if (minecart->eflags & MFE_ONGROUND)
+					minecart->eflags &= ~MFE_ONGROUND;
+				minecart->z += P_MobjFlip(minecart);
+				if (sidelock)
+					P_ParabolicMove(minecart, sidelock->x, sidelock->y, sidelock->z, gravity, max(currentSpeed, 10 * FRACUNIT));
+				else
+					minecart->momz = 10 * FRACUNIT;
+
+				S_StartSound(minecart, sfx_s3k51);
+				jumped = true;
+			}
+
+			if (!jumped)
+			{
+				// Natural acceleration and boosters
+				if (currentSpeed < minecart->info->speed)
+					currentSpeed += FRACUNIT/4;
+
+				if (minecart->standingslope)
+				{
+					fixed_t fa2 = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK;
+					fixed_t front = P_GetZAt(minecart->standingslope, minecart->x, minecart->y);
+					fixed_t back = P_GetZAt(minecart->standingslope, minecart->x - FINECOSINE(fa2), minecart->y - FINESINE(fa2));
+
+					if (abs(front - back) < 3*FRACUNIT)
+						currentSpeed += (back - front)/3;
+				}
+
+				// Go forward at our current speed
+				P_InstaThrust(minecart, minecart->angle, currentSpeed);
+
+				// On-track ka-klong sound FX.
+				minecart->movecount += abs(currentSpeed);
+				if (minecart->movecount > 128*FRACUNIT)
+				{
+					minecart->movecount %= 128*FRACUNIT;
+					S_StartSound(minecart, minecart->info->activesound);
+				}
+			}
+		}
+		else
+		{
+			minecart->movefactor++;
+			if ((P_IsObjectOnGround(minecart) && minecart->movefactor >= 5) // off rail
+			|| (abs(minecart->momx) < minecart->scale/2 && abs(minecart->momy) < minecart->scale/2)) // hit a wall
+			{
+				P_KillMobj(minecart, NULL, NULL, 0);
+				return;
+			}
+		}
+	}
+
+	P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+
+	// Move player to minecart.
+	P_TeleportMove(player->mo, minecart->x - minecart->momx, minecart->y - minecart->momy, minecart->z + max(minecart->momz, 0) + 8*FRACUNIT);
+	if (player->powers[pw_carry] != CR_MINECART)
+		return;
+	player->mo->momx = player->mo->momy = player->mo->momz = 0;
+	P_TryMove(player->mo, player->mo->x + minecart->momx, player->mo->y + minecart->momy, true);
+
+	if (player->powers[pw_flashing] == flashingtics)
+		player->powers[pw_flashing]--;
+}
+
 //
 // P_PlayerThink
 //
@@ -9465,8 +10569,11 @@ void P_PlayerThink(player_t *player)
 
 	if (player->bot)
 	{
-		if (player->playerstate == PST_LIVE && B_CheckRespawn(player))
-			player->playerstate = PST_REBORN;
+		if (player->playerstate == PST_LIVE || player->playerstate == PST_DEAD)
+		{
+			if (B_CheckRespawn(player))
+				player->playerstate = PST_REBORN;
+		}
 		if (player->playerstate == PST_REBORN)
 			return;
 	}
@@ -9498,10 +10605,16 @@ void P_PlayerThink(player_t *player)
 	}
 #endif
 
+	if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
+	{
+		P_SetTarget(&player->awayviewmobj, NULL); // remove awayviewmobj asap if invalid
+		player->awayviewtics = 0; // reset to zero
+	}
+
 	if (player->flashcount)
 		player->flashcount--;
 
-	if (player->awayviewtics)
+	if (player->awayviewtics && player->awayviewtics != -1)
 		player->awayviewtics--;
 
 	/// \note do this in the cheat code
@@ -9538,13 +10651,10 @@ void P_PlayerThink(player_t *player)
 		if (i == MAXPLAYERS && player->exiting == 3*TICRATE) // finished
 			player->exiting = (14*TICRATE)/5 + 1;
 
-		// If 10 seconds are left on the timer,
+		// If 11 seconds are left on the timer,
 		// begin the drown music for countdown!
-		if (countdown == 11*TICRATE - 1)
-		{
-			if (P_IsLocalPlayer(player))
-				S_ChangeMusicInternal("_drown", false);
-		}
+		if (countdown == 11*TICRATE - 1 && P_IsLocalPlayer(player))
+			P_PlayJingle(player, JT_DROWN);
 
 		// If you've hit the countdown and you haven't made
 		//  it to the exit, you're a goner!
@@ -9578,6 +10688,44 @@ void P_PlayerThink(player_t *player)
 	if (player->exiting && countdown2)
 		player->exiting = 5;
 
+	// Same check as below, just at 1 second before
+	// so we can fade music
+	if (!exitfadestarted &&
+		player->exiting > 0 && player->exiting <= 1*TICRATE &&
+		(!multiplayer || gametype == GT_COOP ? !mapheaderinfo[gamemap-1]->musinterfadeout : true) &&
+			// don't fade if we're fading during intermission. follows Y_StartIntermission intertype = int_coop
+		(gametype == GT_RACE || gametype == GT_COMPETITION ? countdown2 == 0 : true) && // don't fade on timeout
+		player->lives > 0 && // don't fade on game over (competition)
+		P_IsLocalPlayer(player))
+	{
+		if (cv_playersforexit.value)
+		{
+			INT32 i;
+
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i] || players[i].spectator || players[i].bot)
+					continue;
+				if (players[i].lives <= 0)
+					continue;
+
+				if (!players[i].exiting || players[i].exiting > 1*TICRATE)
+					break;
+			}
+
+			if (i == MAXPLAYERS)
+			{
+				exitfadestarted = true;
+				S_FadeOutStopMusic(1*MUSICRATE);
+			}
+		}
+		else
+		{
+			exitfadestarted = true;
+			S_FadeOutStopMusic(1*MUSICRATE);
+		}
+	}
+
 	if (player->exiting == 2 || countdown2 == 2)
 	{
 		if (cv_playersforexit.value) // Count to be sure everyone's exited
@@ -9644,7 +10792,7 @@ void P_PlayerThink(player_t *player)
 		if (gametype != GT_COOP)
 			player->score = 0;
 		player->mo->health = 1;
-		player->rings = 0;
+		player->rings = player->spheres = 0;
 	}
 	else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP)
 	{
@@ -9690,15 +10838,19 @@ void P_PlayerThink(player_t *player)
 		fixed_t y = player->mo->y;
 		fixed_t z = player->mo->z;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
 
-			if (!(mo2->type == MT_NIGHTSWING || mo2->type == MT_RING || mo2->type == MT_COIN
-			   || mo2->type == MT_BLUEBALL))
+			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))
+				continue;
+
+			if (mo2->flags2 & MF2_NIGHTSPULL)
 				continue;
 
 			if (P_AproxDistance(P_AproxDistance(mo2->x - x, mo2->y - y), mo2->z - z) > FixedMul(128*FRACUNIT, player->mo->scale))
@@ -9707,11 +10859,14 @@ void P_PlayerThink(player_t *player)
 			// Yay! The thing's in reach! Pull it in!
 			mo2->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
 			mo2->flags2 |= MF2_NIGHTSPULL;
+			// New NiGHTS attract speed dummied out because the older behavior
+			// is exploited as a mechanic. Uncomment to enable.
+			mo2->movefactor = 0; // 40*FRACUNIT; // initialize the NightsItemChase timer
 			P_SetTarget(&mo2->tracer, player->mo);
 		}
 	}
 
-	if (player->linktimer && (player->linktimer >= (2*TICRATE - 1) || !player->powers[pw_nights_linkfreeze]))
+	if (player->linktimer && !player->powers[pw_nights_linkfreeze])
 	{
 		if (--player->linktimer <= 0) // Link timer
 			player->linkcount = 0;
@@ -9722,6 +10877,17 @@ void P_PlayerThink(player_t *player)
 	//  for a bit after a teleport.
 	if (player->mo->reactiontime)
 		player->mo->reactiontime--;
+	else if (player->powers[pw_carry] == CR_MINECART)
+	{
+		if (!P_AnalogMove(player))
+			player->mo->angle = (cmd->angleturn << 16 /* not FRACBITS */);
+
+		ticruned++;
+		if ((cmd->angleturn & TICCMD_RECEIVED) == 0)
+			ticmiss++;
+
+		P_MinecartThink(player);
+	}
 	else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_ZOOMTUBE))
 	{
 		if (player->powers[pw_carry] == CR_ROPEHANG)
@@ -9734,8 +10900,6 @@ void P_PlayerThink(player_t *player)
 				ticmiss++;
 
 			P_DoRopeHang(player);
-			if (player->mo->state-states != S_PLAY_RIDE)
-				P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
 			P_DoJumpStuff(player, &player->cmd);
 		}
 		else //if (player->powers[pw_carry] == CR_ZOOMTUBE)
@@ -9748,7 +10912,19 @@ void P_PlayerThink(player_t *player)
 		P_ResetScore(player);
 	}
 	else
+	{
+		if (player->bumpertime == TICRATE/2 && player->mo->hnext)
+		{
+			// Center player to NiGHTS bumper here because if you try to set player's position in
+			// P_TouchSpecialThing case MT_NIGHTSBUMPER, that position is fudged in the time
+			// between that routine in the previous tic
+			// and reaching here in the current tic
+			P_TeleportMove(player->mo, player->mo->hnext->x, player->mo->hnext->y
+				, player->mo->hnext->z + FixedMul(player->mo->hnext->height/4, player->mo->hnext->scale));
+			P_SetTarget(&player->mo->hnext, NULL);
+		}
 		P_MovePlayer(player);
+	}
 
 	if (!player->mo)
 		return; // P_MovePlayer removed player->mo.
@@ -9757,12 +10933,12 @@ void P_PlayerThink(player_t *player)
 	{
 		// Directionchar!
 		// Camera angle stuff.
-		if (player->exiting) // no control, no modification
+		if (player->exiting // no control, no modification
+		|| player->powers[pw_carry] == CR_NIGHTSMODE)
 			;
 		else if (!(player->pflags & PF_DIRECTIONCHAR)
 		|| (player->climbing // stuff where the direction is forced at all times
 		|| (player->pflags & PF_GLIDING))
-		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
 		|| (P_AnalogMove(player) || twodlevel || player->mo->flags2 & MF2_TWOD) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
 		|| G_RingSlingerGametype()) // no firing rings in directions your player isn't aiming
 			player->drawangle = player->mo->angle;
@@ -9773,7 +10949,15 @@ void P_PlayerThink(player_t *player)
 			switch (player->powers[pw_carry])
 			{
 				case CR_PLAYER:
-					player->drawangle = (player->mo->tracer->player ? player->mo->tracer->player->drawangle : player->mo->tracer->angle);
+					if (player->mo->tracer->player)
+					{
+						player->drawangle = player->mo->tracer->player->drawangle;
+						break;
+					}
+					/* FALLTHRU */
+				case CR_MINECART:
+				case CR_GENERIC:
+					player->drawangle = player->mo->tracer->angle;
 					break;
 				/* -- in case we wanted to have the camera freely movable during zoom tubes
 				case CR_ZOOMTUBE:*/
@@ -9851,7 +11035,8 @@ void P_PlayerThink(player_t *player)
 			if (!player->powers[pw_carry]
 			&& ((player->pflags & (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE)) == (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE))
 			&& !(cmd->forwardmove || cmd->sidemove)
-			&& (player->rmomx || player->rmomy))
+			&& (player->rmomx || player->rmomy)
+			&& (!player->capsule || (player->capsule->reactiontime != (player-players)+1)))
 			{
 				fixed_t acceleration = (player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration) * player->thrustfactor * 20;
 				angle_t moveAngle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
@@ -9871,9 +11056,10 @@ void P_PlayerThink(player_t *player)
 			|| player->panim == PA_PAIN
 			|| !player->mo->health
 			|| player->climbing
-			|| player->pflags & (PF_SPINNING|PF_SLIDING))
+			|| player->pflags & (PF_SPINNING|PF_SLIDING)
+			|| player->bumpertime)
 				player->pflags &= ~PF_APPLYAUTOBRAKE;
-			else if (currentlyonground)
+			else if (currentlyonground || player->powers[pw_tailsfly])
 				player->pflags |= PF_APPLYAUTOBRAKE;
 		}
 	}
@@ -9957,9 +11143,12 @@ void P_PlayerThink(player_t *player)
 	if (player->powers[pw_underwater] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER)))
 	{
 		if (player->powers[pw_underwater] <= 12*TICRATE+1)
+		{
+			player->powers[pw_underwater] = 0;
 			P_RestoreMusic(player); //incase they were about to drown
-
-		player->powers[pw_underwater] = 0;
+		}
+		else
+			player->powers[pw_underwater] = 0;
 	}
 	else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer
 		player->powers[pw_underwater]--;
@@ -10187,7 +11376,7 @@ void P_PlayerAfterThink(player_t *player)
 		if (player->followmobj)
 		{
 			P_RemoveMobj(player->followmobj);
-			player->followmobj = NULL;
+			P_SetTarget(&player->followmobj, NULL);
 		}
 		return;
 	}
@@ -10305,7 +11494,7 @@ void P_PlayerAfterThink(player_t *player)
 	if (P_IsLocalPlayer(player) && (player->pflags & PF_WPNDOWN) && player->currentweapon != oldweapon)
 		S_StartSound(NULL, sfx_wepchg);
 
-	if (player->pflags & PF_SLIDING)
+	if ((player->pflags & PF_SLIDING) && ((player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) != PF_JUMPED))
 		P_SetPlayerMobjState(player->mo, player->mo->info->painstate);
 
 	/* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing)
@@ -10421,21 +11610,10 @@ void P_PlayerAfterThink(player_t *player)
 			player->secondjump = 0;
 			player->pflags &= ~PF_THOKKED;
 
-			if (cmd->forwardmove > 0)
-			{
-				if ((player->mo->tracer->tracer->lastlook += 2) > player->mo->tracer->tracer->friction)
-					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->friction;
-			}
-			else if (cmd->forwardmove < 0)
-			{
-				if ((player->mo->tracer->tracer->lastlook -= 2) < player->mo->tracer->tracer->movecount)
-					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->movecount;
-			}
-
 			if ((player->mo->tracer->tracer->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
 			&& !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode?
 			{
-				player->mo->tracer->tracer->health += cmd->sidemove;
+				player->mo->tracer->tracer->angle += cmd->sidemove<<ANGLETOFINESHIFT;
 				player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
 
 				if (!demoplayback || P_AnalogMove(player))
@@ -10483,14 +11661,14 @@ void P_PlayerAfterThink(player_t *player)
 	if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
 	{
 		P_RemoveMobj(player->followmobj);
-		player->followmobj = NULL;
+		P_SetTarget(&player->followmobj, NULL);
 	}
 
 	if (!player->spectator && player->mo->health && player->followitem)
 	{
 		if (!player->followmobj || P_MobjWasRemoved(player->followmobj))
 		{
-			player->followmobj = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem);
+			P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem));
 			P_SetTarget(&player->followmobj->tracer, player->mo);
 			player->followmobj->flags2 |= MF2_LINKDRAW;
 		}
@@ -10664,7 +11842,6 @@ void P_PlayerAfterThink(player_t *player)
 							player->followmobj->threshold = player->mo->z;
 							player->followmobj->movecount = player->panim;
 							player->followmobj->angle = horizangle;
-							player->followmobj->scale = player->mo->scale;
 							P_SetScale(player->followmobj, player->mo->scale);
 							player->followmobj->destscale = player->mo->destscale;
 							player->followmobj->radius = player->mo->radius;
diff --git a/src/r_bsp.c b/src/r_bsp.c
index ad4975cde33b6c86e3ab26c0e10f4deca01afed2..d521d9f4d4943a6ea5e341738a20e2100dadb91b 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,7 @@
 #include "g_game.h"
 #include "r_local.h"
 #include "r_state.h"
+#include "r_portal.h" // Add seg portals
 
 #include "r_splats.h"
 #include "p_local.h" // camera
@@ -26,11 +27,11 @@ side_t *sidedef;
 line_t *linedef;
 sector_t *frontsector;
 sector_t *backsector;
-boolean portalline; // is curline a portal seg?
 
 // very ugly realloc() of drawsegs at run-time, I upped it to 512
 // instead of 256.. and someone managed to send me a level with
 // 896 drawsegs! So too bad here's a limit removal a-la-Boom
+drawseg_t *curdrawsegs = NULL; /**< This is used to handle multiple lists for masked drawsegs. */
 drawseg_t *drawsegs = NULL;
 drawseg_t *ds_p = NULL;
 
@@ -220,10 +221,7 @@ static INT32 R_DoorClosed(void)
 	// preserve a kind of transparent door/lift special effect:
 	&& (backsector->ceilingheight >= frontsector->ceilingheight || curline->sidedef->toptexture)
 
-	&& (backsector->floorheight <= frontsector->floorheight || curline->sidedef->bottomtexture)
-
-	// properly render skies (consider door "open" if both ceilings are sky):
-	&& (backsector->ceilingpic != skyflatnum || frontsector->ceilingpic != skyflatnum);
+	&& (backsector->floorheight <= frontsector->floorheight || curline->sidedef->bottomtexture);
 }
 
 //
@@ -237,8 +235,6 @@ static INT32 R_DoorClosed(void)
 sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 	INT32 *ceilinglightlevel, boolean back)
 {
-	INT32 mapnum = -1;
-
 	if (floorlightlevel)
 		*floorlightlevel = sec->floorlightsec == -1 ?
 			sec->lightlevel : sectors[sec->floorlightsec].lightlevel;
@@ -247,10 +243,10 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 		*ceilinglightlevel = sec->ceilinglightsec == -1 ?
 			sec->lightlevel : sectors[sec->ceilinglightsec].lightlevel;
 
-	// If the sector has a midmap, it's probably from 280 type
-	if (sec->midmap != -1)
-		mapnum = sec->midmap;
-	else if (sec->heightsec != -1)
+	// if (sec->midmap != -1)
+	//	mapnum = sec->midmap;
+	// In original colormap code, this block did not run if sec->midmap was set
+	if (!sec->extra_colormap && sec->heightsec != -1)
 	{
 		const sector_t *s = &sectors[sec->heightsec];
 		mobj_t *viewmobj = viewplayer->mo;
@@ -274,8 +270,6 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 		tempsec->floorheight = s->floorheight;
 		tempsec->ceilingheight = s->ceilingheight;
 
-		mapnum = s->midmap;
-
 		if ((underwater && (tempsec->  floorheight = sec->floorheight,
 			tempsec->ceilingheight = s->floorheight - 1, !back)) || viewz <= s->floorheight)
 		{ // head-below-floor hack
@@ -301,7 +295,6 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 					tempsec->ceiling_yoffs = s->ceiling_yoffs;
 					tempsec->ceilingpic_angle = s->ceilingpic_angle;
 				}
-				mapnum = s->bottommap;
 			}
 
 			tempsec->lightlevel = s->lightlevel;
@@ -325,8 +318,6 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->floor_yoffs = tempsec->ceiling_yoffs = s->ceiling_yoffs;
 			tempsec->floorpic_angle = tempsec->ceilingpic_angle = s->ceilingpic_angle;
 
-			mapnum = s->topmap;
-
 			if (s->floorpic == skyflatnum) // SKYFIX?
 			{
 				tempsec->ceilingheight = tempsec->floorheight-1;
@@ -357,11 +348,6 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 		sec = tempsec;
 	}
 
-	if (mapnum >= 0 && (size_t)mapnum < num_extra_colormaps)
-		sec->extra_colormap = &extra_colormaps[mapnum];
-	else
-		sec->extra_colormap = NULL;
-
 	return sec;
 }
 
@@ -403,17 +389,18 @@ static void R_AddLine(seg_t *line)
 {
 	INT32 x1, x2;
 	angle_t angle1, angle2, span, tspan;
-	static sector_t tempsec; // ceiling/water hack
+	static sector_t tempsec;
+	boolean bothceilingssky = false, bothfloorssky = false;
+
+	portalline = false;
 
 	if (line->polyseg && !(line->polyseg->flags & POF_RENDERSIDES))
 		return;
 
+	// big room fix
+	angle1 = R_PointToAngleEx(viewx, viewy, line->v1->x, line->v1->y);
+	angle2 = R_PointToAngleEx(viewx, viewy, line->v2->x, line->v2->y);
 	curline = line;
-	portalline = false;
-
-	// OPTIMIZE: quickly reject orthogonal back sides.
-	angle1 = R_PointToAngle(line->v1->x, line->v1->y);
-	angle2 = R_PointToAngle(line->v2->x, line->v2->y);
 
 	// Clip to view edges.
 	span = angle1 - angle2;
@@ -473,7 +460,7 @@ static void R_AddLine(seg_t *line)
 				line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, line2);
 			if (line2 >= 0) // found it!
 			{
-				R_AddPortal(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
+				Portal_Add2Lines(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
 				//return; // Don't fill in that space now!
 				goto clipsolid;
 			}
@@ -490,6 +477,25 @@ static void R_AddLine(seg_t *line)
 
 	doorclosed = 0;
 
+	if (backsector->ceilingpic == skyflatnum && frontsector->ceilingpic == skyflatnum)
+		bothceilingssky = true;
+	if (backsector->floorpic == skyflatnum && frontsector->floorpic == skyflatnum)
+		bothfloorssky = true;
+
+	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
+	{
+		if (
+#ifdef POLYOBJECTS
+		!line->polyseg &&
+#endif
+		!line->sidedef->midtexture
+		&& ((!frontsector->ffloors && !backsector->ffloors)
+		|| (frontsector->tag == backsector->tag)))
+			return; // line is empty, don't even bother
+
+		goto clippass; // treat like wide open window instead
+	}
+
 	// Closed door.
 #ifdef ESLOPE
 	if (frontsector->f_slope || frontsector->c_slope || backsector->f_slope || backsector->c_slope)
@@ -508,48 +514,59 @@ static void R_AddLine(seg_t *line)
 		SLOPEPARAMS( backsector->f_slope, backf1,  backf2,  backsector->floorheight)
 		SLOPEPARAMS( backsector->c_slope, backc1,  backc2,  backsector->ceilingheight)
 #undef SLOPEPARAMS
-		if ((backc1 <= frontf1 && backc2 <= frontf2)
-			|| (backf1 >= frontc1 && backf2 >= frontc2))
+		// if both ceilings are skies, consider it always "open"
+		// same for floors
+		if (!bothceilingssky && !bothfloorssky)
 		{
-			goto clipsolid;
-		}
+			if ((backc1 <= frontf1 && backc2 <= frontf2)
+				|| (backf1 >= frontc1 && backf2 >= frontc2))
+			{
+				goto clipsolid;
+			}
 
-		// Check for automap fix. Store in doorclosed for r_segs.c
-		doorclosed = (backc1 <= backf1 && backc2 <= backf2
-		&& ((backc1 >= frontc1 && backc2 >= frontc2) || curline->sidedef->toptexture)
-		&& ((backf1 <= frontf1 && backf2 >= frontf2) || curline->sidedef->bottomtexture)
-		&& (backsector->ceilingpic != skyflatnum || frontsector->ceilingpic != skyflatnum));
+			// Check for automap fix. Store in doorclosed for r_segs.c
+			doorclosed = (backc1 <= backf1 && backc2 <= backf2
+			&& ((backc1 >= frontc1 && backc2 >= frontc2) || curline->sidedef->toptexture)
+			&& ((backf1 <= frontf1 && backf2 >= frontf2) || curline->sidedef->bottomtexture));
 
-		if (doorclosed)
-			goto clipsolid;
+			if (doorclosed)
+				goto clipsolid;
+		}
 
 		// Window.
-		if (backc1 != frontc1 || backc2 != frontc2
-			|| backf1 != frontf1 || backf2 != frontf2)
-		{
-			goto clippass;
-		}
+		if (!bothceilingssky) // ceilings are always the "same" when sky
+			if (backc1 != frontc1 || backc2 != frontc2)
+				goto clippass;
+		if (!bothfloorssky)	// floors are always the "same" when sky
+			if (backf1 != frontf1 || backf2 != frontf2)
+				goto clippass;
 	}
 	else
 #endif
 	{
-		if (backsector->ceilingheight <= frontsector->floorheight
-			|| backsector->floorheight >= frontsector->ceilingheight)
+		// if both ceilings are skies, consider it always "open"
+		// same for floors
+		if (!bothceilingssky && !bothfloorssky)
 		{
-			goto clipsolid;
-		}
+			if (backsector->ceilingheight <= frontsector->floorheight
+				|| backsector->floorheight >= frontsector->ceilingheight)
+			{
+				goto clipsolid;
+			}
 
-		// Check for automap fix. Store in doorclosed for r_segs.c
-		doorclosed = R_DoorClosed();
-		if (doorclosed)
-			goto clipsolid;
+			// Check for automap fix. Store in doorclosed for r_segs.c
+			doorclosed = R_DoorClosed();
+			if (doorclosed)
+				goto clipsolid;
+		}
 
 		// Window.
-		if (backsector->ceilingheight != frontsector->ceilingheight
-			|| backsector->floorheight != frontsector->floorheight)
-		{
-			goto clippass;
-		}
+		if (!bothceilingssky) // ceilings are always the "same" when sky
+			if (backsector->ceilingheight != frontsector->ceilingheight)
+				goto clippass;
+		if (!bothfloorssky)	// floors are always the "same" when sky
+			if (backsector->floorheight != frontsector->floorheight)
+				goto clippass;
 	}
 
 	// Reject empty lines used for triggers and special events.
@@ -592,69 +609,35 @@ INT32 checkcoord[12][4] =
 	{2, 1, 3, 0}
 };
 
-static boolean R_CheckBBox(fixed_t *bspcoord)
+static boolean R_CheckBBox(const fixed_t *bspcoord)
 {
-	INT32 boxpos, sx1, sx2;
-	fixed_t px1, py1, px2, py2;
-	angle_t angle1, angle2, span, tspan;
+	angle_t angle1, angle2;
+	INT32 sx1, sx2, boxpos;
+	const INT32* check;
 	cliprange_t *start;
 
 	// Find the corners of the box that define the edges from current viewpoint.
-	if (viewx <= bspcoord[BOXLEFT])
-		boxpos = 0;
-	else if (viewx < bspcoord[BOXRIGHT])
-		boxpos = 1;
-	else
-		boxpos = 2;
-
-	if (viewy >= bspcoord[BOXTOP])
-		boxpos |= 0;
-	else if (viewy > bspcoord[BOXBOTTOM])
-		boxpos |= 1<<2;
-	else
-		boxpos |= 2<<2;
-
-	if (boxpos == 5)
+	if ((boxpos = (viewx <= bspcoord[BOXLEFT] ? 0 : viewx < bspcoord[BOXRIGHT] ? 1 : 2) + (viewy >= bspcoord[BOXTOP] ? 0 : viewy > bspcoord[BOXBOTTOM] ? 4 : 8)) == 5)
 		return true;
 
-	px1 = bspcoord[checkcoord[boxpos][0]];
-	py1 = bspcoord[checkcoord[boxpos][1]];
-	px2 = bspcoord[checkcoord[boxpos][2]];
-	py2 = bspcoord[checkcoord[boxpos][3]];
+	check = checkcoord[boxpos];
 
-	// check clip list for an open space
-	angle1 = R_PointToAngle2(viewx>>1, viewy>>1, px1>>1, py1>>1) - viewangle;
-	angle2 = R_PointToAngle2(viewx>>1, viewy>>1, px2>>1, py2>>1) - viewangle;
+	// big room fix
+	angle1 = R_PointToAngleEx(viewx, viewy, bspcoord[check[0]], bspcoord[check[1]]) - viewangle;
+	angle2 = R_PointToAngleEx(viewx, viewy, bspcoord[check[2]], bspcoord[check[3]]) - viewangle;
 
-	span = angle1 - angle2;
-
-	// Sitting on a line?
-	if (span >= ANGLE_180)
-		return true;
-
-	tspan = angle1 + clipangle;
-
-	if (tspan > doubleclipangle)
+	if ((signed)angle1 < (signed)angle2)
 	{
-		tspan -= doubleclipangle;
-
-		// Totally off the left edge?
-		if (tspan >= span)
-			return false;
-
-		angle1 = clipangle;
+		if ((angle1 >= ANGLE_180) && (angle1 < ANGLE_270))
+			angle1 = ANGLE_180-1;
+		else
+			angle2 = ANGLE_180;
 	}
-	tspan = clipangle - angle2;
-	if (tspan > doubleclipangle)
-	{
-		tspan -= doubleclipangle;
 
-		// Totally off the left edge?
-		if (tspan >= span)
-			return false;
-
-		angle2 = -(signed)clipangle;
-	}
+	if ((signed)angle2 >= (signed)clipangle) return false;
+	if ((signed)angle1 <= -(signed)clipangle) return false;
+	if ((signed)angle1 >= (signed)clipangle) angle1 = clipangle;
+	if ((signed)angle2 <= -(signed)clipangle) angle2 = 0-clipangle;
 
 	// Find the first clippost that touches the source post (adjacent pixels are touching).
 	angle1 = (angle1+ANGLE_90)>>ANGLETOFINESHIFT;
@@ -663,9 +646,7 @@ static boolean R_CheckBBox(fixed_t *bspcoord)
 	sx2 = viewangletox[angle2];
 
 	// Does not cross a pixel.
-	if (sx1 == sx2)
-		return false;
-	sx2--;
+	if (sx1 >= sx2) return false;
 
 	start = solidsegs;
 	while (start->last < sx2)
@@ -909,11 +890,11 @@ static void R_Subsector(size_t num)
 		light = R_GetPlaneLight(frontsector, floorcenterz, false);
 		if (frontsector->floorlightsec == -1)
 			floorlightlevel = *frontsector->lightlist[light].lightlevel;
-		floorcolormap = frontsector->lightlist[light].extra_colormap;
+		floorcolormap = *frontsector->lightlist[light].extra_colormap;
 		light = R_GetPlaneLight(frontsector, ceilingcenterz, false);
 		if (frontsector->ceilinglightsec == -1)
 			ceilinglightlevel = *frontsector->lightlist[light].lightlevel;
-		ceilingcolormap = frontsector->lightlist[light].extra_colormap;
+		ceilingcolormap = *frontsector->lightlist[light].extra_colormap;
 	}
 
 	sub->sector->extra_colormap = frontsector->extra_colormap;
@@ -922,7 +903,8 @@ static void R_Subsector(size_t num)
 #ifdef ESLOPE
 			frontsector->f_slope ? P_GetZAt(frontsector->f_slope, viewx, viewy) :
 #endif
-		frontsector->floorheight) < viewz || (frontsector->heightsec != -1
+		frontsector->floorheight) < viewz || frontsector->floorpic == skyflatnum
+		|| (frontsector->heightsec != -1
 		&& sectors[frontsector->heightsec].ceilingpic == skyflatnum)))
 	{
 		floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel,
@@ -1009,7 +991,7 @@ static void R_Subsector(size_t num)
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->bottomheight, *rover->bottompic,
 					*frontsector->lightlist[light].lightlevel, *rover->bottomxoffs,
-					*rover->bottomyoffs, *rover->bottomangle, frontsector->lightlist[light].extra_colormap, rover
+					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover
 #ifdef POLYOBJECTS_PLANES
 					, NULL
 #endif
@@ -1055,7 +1037,7 @@ static void R_Subsector(size_t num)
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->topheight, *rover->toppic,
 					*frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle,
-					frontsector->lightlist[light].extra_colormap, rover
+					*frontsector->lightlist[light].extra_colormap, rover
 #ifdef POLYOBJECTS_PLANES
 					, NULL
 #endif
@@ -1105,7 +1087,6 @@ static void R_Subsector(size_t num)
 				&& (viewz < polysec->floorheight))
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
-				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
 					polysec->lightlevel, polysec->floor_xoffs, polysec->floor_yoffs,
 					polysec->floorpic_angle-po->angle,
@@ -1135,7 +1116,6 @@ static void R_Subsector(size_t num)
 				&& (viewz > polysec->ceilingheight))
 			{
 				light = R_GetPlaneLight(frontsector, polysec->ceilingheight, viewz < polysec->ceilingheight);
-				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
 					polysec->lightlevel, polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle-po->angle,
 					NULL, NULL, po
@@ -1208,7 +1188,7 @@ void R_Prep3DFloors(sector_t *sector)
 	ffloor_t *rover;
 	ffloor_t *best;
 	fixed_t bestheight, maxheight;
-	INT32 count, i, mapnum;
+	INT32 count, i;
 	sector_t *sec;
 #ifdef ESLOPE
 	pslope_t *bestslope = NULL;
@@ -1247,7 +1227,7 @@ void R_Prep3DFloors(sector_t *sector)
 #endif
 	sector->lightlist[0].lightlevel = &sector->lightlevel;
 	sector->lightlist[0].caster = NULL;
-	sector->lightlist[0].extra_colormap = sector->extra_colormap;
+	sector->lightlist[0].extra_colormap = &sector->extra_colormap;
 	sector->lightlist[0].flags = 0;
 
 	maxheight = INT32_MAX;
@@ -1313,11 +1293,6 @@ void R_Prep3DFloors(sector_t *sector)
 		sector->lightlist[i].slope = bestslope;
 #endif
 		sec = &sectors[best->secnum];
-		mapnum = sec->midmap;
-		if (mapnum >= 0 && (size_t)mapnum < num_extra_colormaps)
-			sec->extra_colormap = &extra_colormaps[mapnum];
-		else
-			sec->extra_colormap = NULL;
 
 		if (best->flags & FF_NOSHADE)
 		{
@@ -1327,12 +1302,12 @@ void R_Prep3DFloors(sector_t *sector)
 		else if (best->flags & FF_COLORMAPONLY)
 		{
 			sector->lightlist[i].lightlevel = sector->lightlist[i-1].lightlevel;
-			sector->lightlist[i].extra_colormap = sec->extra_colormap;
+			sector->lightlist[i].extra_colormap = &sec->extra_colormap;
 		}
 		else
 		{
 			sector->lightlist[i].lightlevel = best->toplightlevel;
-			sector->lightlist[i].extra_colormap = sec->extra_colormap;
+			sector->lightlist[i].extra_colormap = &sec->extra_colormap;
 		}
 
 		if (best->flags & FF_DOUBLESHADOW)
@@ -1403,13 +1378,5 @@ void R_RenderBSPNode(INT32 bspnum)
 		bspnum = bsp->children[side^1];
 	}
 
-	// PORTAL CULLING
-	if (portalcullsector) {
-		sector_t *sect = subsectors[bspnum & ~NF_SUBSECTOR].sector;
-		if (sect != portalcullsector)
-			return;
-		portalcullsector = NULL;
-	}
-
 	R_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR);
 }
diff --git a/src/r_bsp.h b/src/r_bsp.h
index 80824831b8511fe1f6b02b22b66213bb82e26b31..825be6064b078ba0f5c0966b1d726c72e2546c07 100644
--- a/src/r_bsp.h
+++ b/src/r_bsp.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -29,6 +29,7 @@ extern boolean portalline; // is curline a portal seg?
 
 extern INT32 checkcoord[12][4];
 
+extern drawseg_t *curdrawsegs;
 extern drawseg_t *drawsegs;
 extern drawseg_t *ds_p;
 extern INT32 doorclosed;
@@ -38,7 +39,6 @@ void R_ClearClipSegs(void);
 void R_PortalClearClipSegs(INT32 start, INT32 end);
 void R_ClearDrawSegs(void);
 void R_RenderBSPNode(INT32 bspnum);
-void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2);
 
 #ifdef POLYOBJECTS
 void R_SortPolyObjects(subsector_t *sub);
diff --git a/src/r_data.c b/src/r_data.c
index c4209fad609299fa9bebaee3db530c23e18470d8..6889bdddebf587ce22b3526066fca43aec364ceb 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -439,11 +439,22 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		height = SHORT(realpatch->height);
 		x2 = x1 + width;
 
+		if (x1 > texture->width || x2 < 0)
+			continue; // patch not located within texture's x bounds, ignore
+
+		if (patch->originy > texture->height || (patch->originy + height) < 0)
+			continue; // patch not located within texture's y bounds, ignore
+
+		// patch is actually inside the texture!
+		// now check if texture is partly off-screen and adjust accordingly
+
+		// left edge
 		if (x1 < 0)
 			x = 0;
 		else
 			x = x1;
 
+		// right edge
 		if (x2 > texture->width)
 			x2 = texture->width;
 
@@ -567,6 +578,17 @@ void R_LoadTextures(void)
 	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
 	for (w = 0, numtextures = 0; w < numwadfiles; w++)
 	{
+		// Count the textures from TEXTURES lumps
+
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+
+		// Count single-patch textures
+
 		if (wadfiles[w]->type == RET_PK3)
 		{
 			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
@@ -574,29 +596,34 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
+			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 		}
 
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
+		if (texstart == INT16_MAX || texend == INT16_MAX)
+			continue;
 
-		// Add all the textures between TX_START and TX_END
-		if (texstart != INT16_MAX && texend != INT16_MAX)
+		texstart++; // Do not count the first marker
+
+		// PK3s have subfolders, so we can't just make a simple sum
+		if (wadfiles[w]->type == RET_PK3)
 		{
-			numtextures += (UINT32)(texend - texstart);
+			for (j = texstart; j < texend; j++)
+			{
+				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+					numtextures++;
+			}
 		}
-
-		// If no textures found by this point, bomb out
-		if (!numtextures && w == (numwadfiles - 1))
+		else // Add all the textures between TX_START and TX_END
 		{
-			I_Error("No textures detected in any WADs!\n");
+			numtextures += (UINT32)(texend - texstart);
 		}
 	}
+
+	// If no textures found by this point, bomb out
+	if (!numtextures)
+		I_Error("No textures detected in any WADs!\n");
+
 	// Allocate memory and initialize to 0 for all the textures we are initialising.
 	// There are actually 5 buffers allocated in one for convenience.
 	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
@@ -631,7 +658,7 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
+			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
 			if (texturesLumpPos != INT16_MAX)
@@ -641,9 +668,16 @@ void R_LoadTextures(void)
 		if (texstart == INT16_MAX || texend == INT16_MAX)
 			continue;
 
+		texstart++; // Do not count the first marker
+
 		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); i++, j++)
+		for (j = 0; j < (texend - texstart); j++)
 		{
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder((UINT16)w, texstart + j)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
 			patchlump = W_CacheLumpNumPwad((UINT16)w, texstart + j, PU_CACHE);
 
 			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
@@ -673,6 +707,7 @@ void R_LoadTextures(void)
 
 			texturewidthmask[i] = k - 1;
 			textureheight[i] = texture->height << FRACBITS;
+			i++;
 		}
 	}
 }
@@ -1133,6 +1168,10 @@ void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
 	Z_Free((void *)texturesText);
 }
 
+#ifdef EXTRACOLORMAPLUMPS
+static lumplist_t *colormaplumps = NULL; ///\todo free leak
+static size_t numcolormaplumps = 0;
+
 static inline lumpnum_t R_CheckNumForNameList(const char *name, lumplist_t *list, size_t listsize)
 {
 	size_t i;
@@ -1149,9 +1188,6 @@ static inline lumpnum_t R_CheckNumForNameList(const char *name, lumplist_t *list
 	return LUMPERROR;
 }
 
-static lumplist_t *colormaplumps = NULL; ///\todo free leak
-static size_t numcolormaplumps = 0;
-
 static void R_InitExtraColormaps(void)
 {
 	lumpnum_t startnum, endnum;
@@ -1184,8 +1220,9 @@ static void R_InitExtraColormaps(void)
 	}
 	CONS_Printf(M_GetText("Number of Extra Colormaps: %s\n"), sizeu1(numcolormaplumps));
 }
+#endif
 
-// Search for flat name through all
+// Search for flat name.
 lumpnum_t R_GetFlatNumForName(const char *name)
 {
 	INT32 i;
@@ -1196,39 +1233,30 @@ lumpnum_t R_GetFlatNumForName(const char *name)
 	// Scan wad files backwards so patched flats take preference.
 	for (i = numwadfiles - 1; i >= 0; i--)
 	{
-
-		if (wadfiles[i]->type == RET_PK3)
-		{
-			start = W_CheckNumForFolderStartPK3("Flats/", i, 0);
-			if (start == INT16_MAX)
-				continue;
-			end = W_CheckNumForFolderEndPK3("Flats/", i, start);
-			if (end == INT16_MAX)
-				continue;
-		}
-		else // WAD type? use markers.
+		switch (wadfiles[i]->type)
 		{
-			// Find the ranges to work with.
-			start = W_CheckNumForNamePwad("F_START", (UINT16)i, 0);
-			if (start == INT16_MAX)
+		case RET_WAD:
+			if ((start = W_CheckNumForNamePwad("F_START", (UINT16)i, 0)) == INT16_MAX)
 			{
-				start = W_CheckNumForNamePwad("FF_START", (UINT16)i, 0);
-				if (start == INT16_MAX)
+				if ((start = W_CheckNumForNamePwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
+					continue;
+				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
 					continue;
-				else
-				{
-					end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start);
-					if (end == INT16_MAX)
-						continue;
-				}
 			}
 			else
-			{
-				end = W_CheckNumForNamePwad("F_END", (UINT16)i, start);
-				if (end == INT16_MAX)
+				if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
 					continue;
-			}
+			break;
+		case RET_PK3:
+			if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
+				continue;
+			if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
+				continue;
+			break;
+		default:
+			continue;
 		}
+
 		// Now find lump with specified name in that range.
 		lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
 		if (lump < end)
@@ -1280,31 +1308,37 @@ static void R_InitColormaps(void)
 
 	// Init Boom colormaps.
 	R_ClearColormaps();
+#ifdef EXTRACOLORMAPLUMPS
 	R_InitExtraColormaps();
+#endif
 }
 
 void R_ReInitColormaps(UINT16 num)
 {
 	char colormap[9] = "COLORMAP";
 	lumpnum_t lump;
+	const lumpnum_t basecolormaplump = W_GetNumForName(colormap);
 	if (num > 0 && num <= 10000)
 		snprintf(colormap, 8, "CLM%04u", num-1);
 
 	// Load in the light tables, now 64k aligned for smokie...
 	lump = W_GetNumForName(colormap);
 	if (lump == LUMPERROR)
-		lump = W_GetNumForName("COLORMAP");
-	W_ReadLump(lump, colormaps);
+		lump = basecolormaplump;
+	else
+	{
+		if (W_LumpLength(lump) != W_LumpLength(basecolormaplump))
+		{
+			CONS_Alert(CONS_WARNING, "%s lump size does not match COLORMAP, results may be unexpected.\n", colormap);
+		}
+	}
+
+	W_ReadLumpHeader(lump, colormaps, W_LumpLength(basecolormaplump), 0U);
 
 	// Init Boom colormaps.
 	R_ClearColormaps();
 }
 
-static lumpnum_t foundcolormaps[MAXCOLORMAPS];
-
-static char colormapFixingArray[MAXCOLORMAPS][3][9];
-static size_t carrayindex;
-
 //
 // R_ClearColormaps
 //
@@ -1312,303 +1346,343 @@ static size_t carrayindex;
 //
 void R_ClearColormaps(void)
 {
-	size_t i;
+	// Purged by PU_LEVEL, just overwrite the pointer
+	extra_colormaps = R_CreateDefaultColormap(true);
+}
 
-	num_extra_colormaps = 0;
+//
+// R_CreateDefaultColormap()
+// NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself!
+//
+extracolormap_t *R_CreateDefaultColormap(boolean lighttable)
+{
+	extracolormap_t *exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL);
+	exc->fadestart = 0;
+	exc->fadeend = 31;
+	exc->fog = 0;
+	exc->rgba = 0;
+	exc->fadergba = 0x19000000;
+	exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL;
+#ifdef EXTRACOLORMAPLUMPS
+	exc->lump = LUMPERROR;
+	exc->lumpname[0] = 0;
+#endif
+	exc->next = exc->prev = NULL;
+	return exc;
+}
 
-	carrayindex = 0;
+//
+// R_GetDefaultColormap()
+//
+extracolormap_t *R_GetDefaultColormap(void)
+{
+#ifdef COLORMAPREVERSELIST
+	extracolormap_t *exc;
+#endif
 
-	for (i = 0; i < MAXCOLORMAPS; i++)
-		foundcolormaps[i] = LUMPERROR;
+	if (!extra_colormaps)
+		return (extra_colormaps = R_CreateDefaultColormap(true));
 
-	memset(extra_colormaps, 0, sizeof (extra_colormaps));
+#ifdef COLORMAPREVERSELIST
+	for (exc = extra_colormaps; exc->next; exc = exc->next);
+	return exc;
+#else
+	return extra_colormaps;
+#endif
 }
 
-INT32 R_ColormapNumForName(char *name)
+//
+// R_CopyColormap()
+// NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself!
+//
+extracolormap_t *R_CopyColormap(extracolormap_t *extra_colormap, boolean lighttable)
 {
-	lumpnum_t lump, i;
+	extracolormap_t *exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL);
 
-	if (num_extra_colormaps == MAXCOLORMAPS)
-		I_Error("R_ColormapNumForName: Too many colormaps! the limit is %d\n", MAXCOLORMAPS);
-
-	lump = R_CheckNumForNameList(name, colormaplumps, numcolormaplumps);
-	if (lump == LUMPERROR)
-		I_Error("R_ColormapNumForName: Cannot find colormap lump %.8s\n", name);
+	if (!extra_colormap)
+		extra_colormap = R_GetDefaultColormap();
 
-	for (i = 0; i < num_extra_colormaps; i++)
-		if (lump == foundcolormaps[i])
-			return i;
+	*exc = *extra_colormap;
+	exc->next = exc->prev = NULL;
 
-	foundcolormaps[num_extra_colormaps] = lump;
+#ifdef EXTRACOLORMAPLUMPS
+	strncpy(exc->lumpname, extra_colormap->lumpname, 9);
 
-	// aligned on 8 bit for asm code
-	extra_colormaps[num_extra_colormaps].colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16);
-	W_ReadLump(lump, extra_colormaps[num_extra_colormaps].colormap);
+	if (exc->lump != LUMPERROR && lighttable)
+	{
+		// aligned on 8 bit for asm code
+		exc->colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16);
+		W_ReadLump(lump, exc->colormap);
+	}
+	else
+#endif
+	if (lighttable)
+		exc->colormap = R_CreateLightTable(exc);
+	else
+		exc->colormap = NULL;
 
-	// We set all params of the colormap to normal because there
-	// is no real way to tell how GL should handle a colormap lump anyway..
-	extra_colormaps[num_extra_colormaps].maskcolor = 0xffff;
-	extra_colormaps[num_extra_colormaps].fadecolor = 0x0;
-	extra_colormaps[num_extra_colormaps].maskamt = 0x0;
-	extra_colormaps[num_extra_colormaps].fadestart = 0;
-	extra_colormaps[num_extra_colormaps].fadeend = 33;
-	extra_colormaps[num_extra_colormaps].fog = 0;
-
-	num_extra_colormaps++;
-	return (INT32)num_extra_colormaps - 1;
+	return exc;
 }
 
 //
-// R_CreateColormap
+// R_AddColormapToList
 //
-// This is a more GL friendly way of doing colormaps: Specify colormap
-// data in a special linedef's texture areas and use that to generate
-// custom colormaps at runtime. NOTE: For GL mode, we only need to color
-// data and not the colormap data.
+// Sets prev/next chain for extra_colormaps var
+// Copypasta from P_AddFFloorToList
 //
-static double deltas[256][3], map[256][3];
-
-static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
-static int RoundUp(double number);
-
-INT32 R_CreateColormap(char *p1, char *p2, char *p3)
+void R_AddColormapToList(extracolormap_t *extra_colormap)
 {
-	double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb;
-	double r, g, b, cbrightness, maskamt = 0, othermask = 0;
-	int mask, fog = 0;
-	size_t mapnum = num_extra_colormaps;
-	size_t i;
-	UINT32 cr, cg, cb, maskcolor, fadecolor;
-	UINT32 fadestart = 0, fadeend = 33, fadedist = 33;
+#ifndef COLORMAPREVERSELIST
+	extracolormap_t *exc;
+#endif
 
-#define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
-	if (p1[0] == '#')
+	if (!extra_colormaps)
 	{
-		cr = ((HEX2INT(p1[1]) * 16) + HEX2INT(p1[2]));
-		cmaskr = cr;
-		cg = ((HEX2INT(p1[3]) * 16) + HEX2INT(p1[4]));
-		cmaskg = cg;
-		cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6]));
-		cmaskb = cb;
-		// Create a rough approximation of the color (a 16 bit color)
-		maskcolor = ((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11);
-		if (p1[7] >= 'a' && p1[7] <= 'z')
-			mask = (p1[7] - 'a');
-		else if (p1[7] >= 'A' && p1[7] <= 'Z')
-			mask = (p1[7] - 'A');
-		else
-			mask = 24;
+		extra_colormaps = extra_colormap;
+		extra_colormap->next = 0;
+		extra_colormap->prev = 0;
+		return;
+	}
 
-		maskamt = (double)(mask/24.0l);
+#ifdef COLORMAPREVERSELIST
+	extra_colormaps->prev = extra_colormap;
+	extra_colormap->next = extra_colormaps;
+	extra_colormaps = extra_colormap;
+	extra_colormap->prev = 0;
+#else
+	for (exc = extra_colormaps; exc->next; exc = exc->next);
 
-		othermask = 1 - maskamt;
-		maskamt /= 0xff;
-		cmaskr *= maskamt;
-		cmaskg *= maskamt;
-		cmaskb *= maskamt;
-	}
-	else
-	{
-		cmaskr = cmaskg = cmaskb = 0xff;
-		maskamt = 0;
-		maskcolor = ((0xff) >> 3) + (((0xff) >> 2) << 5) + (((0xff) >> 3) << 11);
-	}
+	exc->next = extra_colormap;
+	extra_colormap->prev = exc;
+	extra_colormap->next = 0;
+#endif
+}
 
-#define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
-	if (p2[0] == '#')
-	{
-		// Get parameters like fadestart, fadeend, and the fogflag
-		fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10);
-		fadeend = NUMFROMCHAR(p2[5]) + (NUMFROMCHAR(p2[4]) * 10);
-		if (fadestart > 32)
-			fadestart = 0;
-		if (fadeend > 33 || fadeend < 1)
-			fadeend = 33;
-		fadedist = fadeend - fadestart;
-		fog = NUMFROMCHAR(p2[1]) ? 1 : 0;
-	}
-#undef getnum
+//
+// R_CheckDefaultColormapByValues()
+//
+#ifdef EXTRACOLORMAPLUMPS
+boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump)
+#else
+boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog)
+#endif
+{
+	return (
+		(!checkparams ? true :
+			(fadestart == 0
+				&& fadeend == 31
+				&& !fog)
+			)
+		&& (!checkrgba ? true : rgba == 0)
+		&& (!checkfadergba ? true : fadergba == 0x19000000)
+#ifdef EXTRACOLORMAPLUMPS
+		&& lump == LUMPERROR
+		&& extra_colormap->lumpname[0] == 0
+#endif
+		);
+}
 
-	if (p3[0] == '#')
-	{
-		cdestr = cr = ((HEX2INT(p3[1]) * 16) + HEX2INT(p3[2]));
-		cdestg = cg = ((HEX2INT(p3[3]) * 16) + HEX2INT(p3[4]));
-		cdestb = cb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6]));
-		fadecolor = (((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11));
-	}
-	else
-		cdestr = cdestg = cdestb = fadecolor = 0;
-#undef HEX2INT
+boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgba, boolean checkfadergba, boolean checkparams)
+{
+	if (!extra_colormap)
+		return true;
+
+#ifdef EXTRACOLORMAPLUMPS
+	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog, extra_colormap->lump);
+#else
+	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog);
+#endif
+}
+
+boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, boolean checkrgba, boolean checkfadergba, boolean checkparams)
+{
+	// Treat NULL as default colormap
+	// We need this because what if one exc is a default colormap, and the other is NULL? They're really both equal.
+	if (!exc_a)
+		exc_a = R_GetDefaultColormap();
+	if (!exc_b)
+		exc_b = R_GetDefaultColormap();
+
+	if (exc_a == exc_b)
+		return true;
+
+	return (
+		(!checkparams ? true :
+			(exc_a->fadestart == exc_b->fadestart
+				&& exc_a->fadeend == exc_b->fadeend
+				&& exc_a->fog == exc_b->fog)
+			)
+		&& (!checkrgba ? true : exc_a->rgba == exc_b->rgba)
+		&& (!checkfadergba ? true : exc_a->fadergba == exc_b->fadergba)
+#ifdef EXTRACOLORMAPLUMPS
+		&& exc_a->lump == exc_b->lump
+		&& !strncmp(exc_a->lumpname, exc_b->lumpname, 9)
+#endif
+		);
+}
 
-	for (i = 0; i < num_extra_colormaps; i++)
+//
+// R_GetColormapFromListByValues()
+// NOTE: Returns NULL if no match is found
+//
+#ifdef EXTRACOLORMAPLUMPS
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump)
+#else
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog)
+#endif
+{
+	extracolormap_t *exc;
+	UINT32 dbg_i = 0;
+
+	for (exc = extra_colormaps; exc; exc = exc->next)
 	{
-		if (foundcolormaps[i] != LUMPERROR)
-			continue;
-		if (maskcolor == extra_colormaps[i].maskcolor
-			&& fadecolor == extra_colormaps[i].fadecolor
-			&& (float)maskamt == (float)extra_colormaps[i].maskamt
-			&& fadestart == extra_colormaps[i].fadestart
-			&& fadeend == extra_colormaps[i].fadeend
-			&& fog == extra_colormaps[i].fog)
+		if (rgba == exc->rgba
+			&& fadergba == exc->fadergba
+			&& fadestart == exc->fadestart
+			&& fadeend == exc->fadeend
+			&& fog == exc->fog
+#ifdef EXTRACOLORMAPLUMPS
+			&& (lump != LUMPERROR && lump == exc->lump)
+#endif
+		)
 		{
-			return (INT32)i;
+			CONS_Debug(DBG_RENDER, "Found Colormap %d: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n",
+				dbg_i, R_GetRgbaR(rgba), R_GetRgbaG(rgba), R_GetRgbaB(rgba), R_GetRgbaA(rgba),
+				R_GetRgbaR(fadergba), R_GetRgbaG(fadergba), R_GetRgbaB(fadergba), R_GetRgbaA(fadergba));
+			return exc;
 		}
+		dbg_i++;
 	}
+	return NULL;
+}
 
-	if (num_extra_colormaps == MAXCOLORMAPS)
-		I_Error("R_CreateColormap: Too many colormaps! the limit is %d\n", MAXCOLORMAPS);
-
-	strncpy(colormapFixingArray[num_extra_colormaps][0], p1, 8);
-	strncpy(colormapFixingArray[num_extra_colormaps][1], p2, 8);
-	strncpy(colormapFixingArray[num_extra_colormaps][2], p3, 8);
-
-	num_extra_colormaps++;
+extracolormap_t *R_GetColormapFromList(extracolormap_t *extra_colormap)
+{
+#ifdef EXTRACOLORMAPLUMPS
+	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog, extra_colormap->lump);
+#else
+	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog);
+#endif
+}
 
-	if (rendermode == render_soft)
-	{
-		for (i = 0; i < 256; i++)
-		{
-			r = pMasterPalette[i].s.red;
-			g = pMasterPalette[i].s.green;
-			b = pMasterPalette[i].s.blue;
-			cbrightness = sqrt((r*r) + (g*g) + (b*b));
+#ifdef EXTRACOLORMAPLUMPS
+extracolormap_t *R_ColormapForName(char *name)
+{
+	lumpnum_t lump;
+	extracolormap_t *exc;
 
-			map[i][0] = (cbrightness * cmaskr) + (r * othermask);
-			if (map[i][0] > 255.0l)
-				map[i][0] = 255.0l;
-			deltas[i][0] = (map[i][0] - cdestr) / (double)fadedist;
+	lump = R_CheckNumForNameList(name, colormaplumps, numcolormaplumps);
+	if (lump == LUMPERROR)
+		I_Error("R_ColormapForName: Cannot find colormap lump %.8s\n", name);
 
-			map[i][1] = (cbrightness * cmaskg) + (g * othermask);
-			if (map[i][1] > 255.0l)
-				map[i][1] = 255.0l;
-			deltas[i][1] = (map[i][1] - cdestg) / (double)fadedist;
+	exc = R_GetColormapFromListByValues(0, 0x19000000, 0, 31, 0, lump);
+	if (exc)
+		return exc;
 
-			map[i][2] = (cbrightness * cmaskb) + (b * othermask);
-			if (map[i][2] > 255.0l)
-				map[i][2] = 255.0l;
-			deltas[i][2] = (map[i][2] - cdestb) / (double)fadedist;
-		}
-	}
+	exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL);
 
-	foundcolormaps[mapnum] = LUMPERROR;
+	exc->lump = lump;
+	strncpy(exc->lumpname, name, 9);
+	exc->lumpname[8] = 0;
 
 	// aligned on 8 bit for asm code
-	extra_colormaps[mapnum].colormap = NULL;
-	extra_colormaps[mapnum].maskcolor = (UINT16)maskcolor;
-	extra_colormaps[mapnum].fadecolor = (UINT16)fadecolor;
-	extra_colormaps[mapnum].maskamt = maskamt;
-	extra_colormaps[mapnum].fadestart = (UINT16)fadestart;
-	extra_colormaps[mapnum].fadeend = (UINT16)fadeend;
-	extra_colormaps[mapnum].fog = fog;
-
-	return (INT32)mapnum;
-}
+	exc->colormap = Z_MallocAlign(W_LumpLength(lump), PU_LEVEL, NULL, 16);
+	W_ReadLump(lump, exc->colormap);
 
-void R_MakeColormaps(void)
-{
-	size_t i;
+	// We set all params of the colormap to normal because there
+	// is no real way to tell how GL should handle a colormap lump anyway..
+	exc->fadestart = 0;
+	exc->fadeend = 31;
+	exc->fog = 0;
+	exc->rgba = 0;
+	exc->fadergba = 0x19000000;
 
-	carrayindex = num_extra_colormaps;
-	num_extra_colormaps = 0;
+	R_AddColormapToList(exc);
 
-	for (i = 0; i < carrayindex; i++)
-		R_CreateColormap2(colormapFixingArray[i][0], colormapFixingArray[i][1],
-			colormapFixingArray[i][2]);
+	return exc;
 }
+#endif
+
+//
+// R_CreateColormap
+//
+// This is a more GL friendly way of doing colormaps: Specify colormap
+// data in a special linedef's texture areas and use that to generate
+// custom colormaps at runtime. NOTE: For GL mode, we only need to color
+// data and not the colormap data.
+//
+static double deltas[256][3], map[256][3];
 
-void R_CreateColormap2(char *p1, char *p2, char *p3)
+static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
+static int RoundUp(double number);
+
+lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 {
 	double cmaskr, cmaskg, cmaskb, cdestr, cdestg, cdestb;
-	double r, g, b, cbrightness;
 	double maskamt = 0, othermask = 0;
-	int mask, p, fog = 0;
-	size_t mapnum = num_extra_colormaps;
-	size_t i;
-	char *colormap_p;
-	UINT32 cr, cg, cb, maskcolor, fadecolor;
-	UINT32 fadestart = 0, fadeend = 33, fadedist = 33;
 
-#define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
-	if (p1[0] == '#')
-	{
-		cr = ((HEX2INT(p1[1]) * 16) + HEX2INT(p1[2]));
-		cmaskr = cr;
-		cg = ((HEX2INT(p1[3]) * 16) + HEX2INT(p1[4]));
-		cmaskg = cg;
-		cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6]));
-		cmaskb = cb;
-		// Create a rough approximation of the color (a 16 bit color)
-		maskcolor = ((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11);
-		if (p1[7] >= 'a' && p1[7] <= 'z')
-			mask = (p1[7] - 'a');
-		else if (p1[7] >= 'A' && p1[7] <= 'Z')
-			mask = (p1[7] - 'A');
-		else
-			mask = 24;
+	UINT8 cr = R_GetRgbaR(extra_colormap->rgba),
+		cg = R_GetRgbaG(extra_colormap->rgba),
+		cb = R_GetRgbaB(extra_colormap->rgba),
+		ca = R_GetRgbaA(extra_colormap->rgba),
+		cfr = R_GetRgbaR(extra_colormap->fadergba),
+		cfg = R_GetRgbaG(extra_colormap->fadergba),
+		cfb = R_GetRgbaB(extra_colormap->fadergba);
+//		cfa = R_GetRgbaA(extra_colormap->fadergba); // unused in software
 
-		maskamt = (double)(mask/24.0l);
-
-		othermask = 1 - maskamt;
-		maskamt /= 0xff;
-		cmaskr *= maskamt;
-		cmaskg *= maskamt;
-		cmaskb *= maskamt;
-	}
-	else
-	{
-		cmaskr = cmaskg = cmaskb = 0xff;
-		maskamt = 0;
-		maskcolor = ((0xff) >> 3) + (((0xff) >> 2) << 5) + (((0xff) >> 3) << 11);
-	}
+	UINT8 fadestart = extra_colormap->fadestart,
+		fadedist = extra_colormap->fadeend - extra_colormap->fadestart;
 
-#define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
-	if (p2[0] == '#')
-	{
-		// Get parameters like fadestart, fadeend, and the fogflag
-		fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10);
-		fadeend = NUMFROMCHAR(p2[5]) + (NUMFROMCHAR(p2[4]) * 10);
-		if (fadestart > 32)
-			fadestart = 0;
-		if (fadeend > 33 || fadeend < 1)
-			fadeend = 33;
-		fadedist = fadeend - fadestart;
-		fog = NUMFROMCHAR(p2[1]) ? 1 : 0;
-	}
-#undef getnum
-
-	if (p3[0] == '#')
-	{
-		cdestr = cr = ((HEX2INT(p3[1]) * 16) + HEX2INT(p3[2]));
-		cdestg = cg = ((HEX2INT(p3[3]) * 16) + HEX2INT(p3[4]));
-		cdestb = cb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6]));
-		fadecolor = (((cb) >> 3) + (((cg) >> 2) << 5) + (((cr) >> 3) << 11));
-	}
-	else
-		cdestr = cdestg = cdestb = fadecolor = 0;
-#undef HEX2INT
-
-	for (i = 0; i < num_extra_colormaps; i++)
-	{
-		if (foundcolormaps[i] != LUMPERROR)
-			continue;
-		if (maskcolor == extra_colormaps[i].maskcolor
-			&& fadecolor == extra_colormaps[i].fadecolor
-			&& (float)maskamt == (float)extra_colormaps[i].maskamt
-			&& fadestart == extra_colormaps[i].fadestart
-			&& fadeend == extra_colormaps[i].fadeend
-			&& fog == extra_colormaps[i].fog)
-		{
-			return;
-		}
-	}
-
-	if (num_extra_colormaps == MAXCOLORMAPS)
-		I_Error("R_CreateColormap: Too many colormaps! the limit is %d\n", MAXCOLORMAPS);
-
-	num_extra_colormaps++;
+	lighttable_t *lighttable = NULL;
+	size_t i;
 
+	/////////////////////
+	// Calc the RGBA mask
+	/////////////////////
+	cmaskr = cr;
+	cmaskg = cg;
+	cmaskb = cb;
+
+	maskamt = (double)(ca/24.0l);
+	othermask = 1 - maskamt;
+	maskamt /= 0xff;
+
+	cmaskr *= maskamt;
+	cmaskg *= maskamt;
+	cmaskb *= maskamt;
+
+	/////////////////////
+	// Calc the RGBA fade mask
+	/////////////////////
+	cdestr = cfr;
+	cdestg = cfg;
+	cdestb = cfb;
+
+	// fade alpha unused in software
+	// maskamt = (double)(cfa/24.0l);
+	// othermask = 1 - maskamt;
+	// maskamt /= 0xff;
+
+	// cdestr *= maskamt;
+	// cdestg *= maskamt;
+	// cdestb *= maskamt;
+
+	/////////////////////
+	// This code creates the colormap array used by software renderer
+	/////////////////////
 	if (rendermode == render_soft)
 	{
+		double r, g, b, cbrightness;
+		int p;
+		char *colormap_p;
+
+		// Initialise the map and delta arrays
+		// map[i] stores an RGB color (as double) for index i,
+		//  which is then converted to SRB2's palette later
+		// deltas[i] stores a corresponding fade delta between the RGB color and the final fade color;
+		//  map[i]'s values are decremented by after each use
 		for (i = 0; i < 256; i++)
 		{
 			r = pMasterPalette[i].s.red;
@@ -1631,25 +1705,14 @@ void R_CreateColormap2(char *p1, char *p2, char *p3)
 				map[i][2] = 255.0l;
 			deltas[i][2] = (map[i][2] - cdestb) / (double)fadedist;
 		}
-	}
-
-	foundcolormaps[mapnum] = LUMPERROR;
 
-	// aligned on 8 bit for asm code
-	extra_colormaps[mapnum].colormap = NULL;
-	extra_colormaps[mapnum].maskcolor = (UINT16)maskcolor;
-	extra_colormaps[mapnum].fadecolor = (UINT16)fadecolor;
-	extra_colormaps[mapnum].maskamt = maskamt;
-	extra_colormaps[mapnum].fadestart = (UINT16)fadestart;
-	extra_colormaps[mapnum].fadeend = (UINT16)fadeend;
-	extra_colormaps[mapnum].fog = fog;
-
-#define ABS2(x) ((x) < 0 ? -(x) : (x))
-	if (rendermode == render_soft)
-	{
+		// Now allocate memory for the actual colormap array itself!
+		// aligned on 8 bit for asm code
 		colormap_p = Z_MallocAlign((256 * 34) + 10, PU_LEVEL, NULL, 8);
-		extra_colormaps[mapnum].colormap = (UINT8 *)colormap_p;
+		lighttable = (UINT8 *)colormap_p;
 
+		// Calculate the palette index for each palette index, for each light level
+		// (as well as the two unused colormap lines we inherited from Doom)
 		for (p = 0; p < 34; p++)
 		{
 			for (i = 0; i < 256; i++)
@@ -1661,7 +1724,7 @@ void R_CreateColormap2(char *p1, char *p2, char *p3)
 
 				if ((UINT32)p < fadestart)
 					continue;
-
+#define ABS2(x) ((x) < 0 ? -(x) : (x))
 				if (ABS2(map[i][0] - cdestr) > ABS2(deltas[i][0]))
 					map[i][0] -= deltas[i][0];
 				else
@@ -1676,12 +1739,290 @@ void R_CreateColormap2(char *p1, char *p2, char *p3)
 					map[i][2] -= deltas[i][2];
 				else
 					map[i][2] = cdestb;
+#undef ABS2
 			}
 		}
 	}
-#undef ABS2
 
-	return;
+	return lighttable;
+}
+
+extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
+{
+	extracolormap_t *extra_colormap, *exc;
+
+	// default values
+	UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25;
+	UINT32 fadestart = 0, fadeend = 31;
+	UINT8 fog = 0;
+	INT32 rgba = 0, fadergba = 0x19000000;
+
+#define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
+#define ALPHA2INT(x) (x >= 'a' && x <= 'z' ? x - 'a' : x >= 'A' && x <= 'Z' ? x - 'A' : x >= '0' && x <= '9' ? 25 : 0)
+
+	// Get base colormap value
+	// First alpha-only, then full value
+	if (p1[0] >= 'a' && p1[0] <= 'z' && !p1[1])
+		ca = (p1[0] - 'a');
+	else if (p1[0] == '#' && p1[1] >= 'a' && p1[1] <= 'z' && !p1[2])
+		ca = (p1[1] - 'a');
+	else if (p1[0] >= 'A' && p1[0] <= 'Z' && !p1[1])
+		ca = (p1[0] - 'A');
+	else if (p1[0] == '#' && p1[1] >= 'A' && p1[1] <= 'Z' && !p1[2])
+		ca = (p1[1] - 'A');
+	else if (p1[0] == '#')
+	{
+		// For each subsequent value, the value before it must exist
+		// If we don't get every value, then set alpha to max
+		if (p1[1] && p1[2])
+		{
+			cr = ((HEX2INT(p1[1]) * 16) + HEX2INT(p1[2]));
+			if (p1[3] && p1[4])
+			{
+				cg = ((HEX2INT(p1[3]) * 16) + HEX2INT(p1[4]));
+				if (p1[5] && p1[6])
+				{
+					cb = ((HEX2INT(p1[5]) * 16) + HEX2INT(p1[6]));
+
+					if (p1[7] >= 'a' && p1[7] <= 'z')
+						ca = (p1[7] - 'a');
+					else if (p1[7] >= 'A' && p1[7] <= 'Z')
+						ca = (p1[7] - 'A');
+					else
+						ca = 25;
+				}
+				else
+					ca = 25;
+			}
+			else
+				ca = 25;
+		}
+		else
+			ca = 25;
+	}
+
+#define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
+
+	// Get parameters like fadestart, fadeend, and the fogflag
+	if (p2[0] == '#')
+	{
+		if (p2[1])
+		{
+			fog = NUMFROMCHAR(p2[1]);
+			if (p2[2] && p2[3])
+			{
+				fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10);
+				if (p2[4] && p2[5])
+					fadeend = NUMFROMCHAR(p2[5]) + (NUMFROMCHAR(p2[4]) * 10);
+			}
+		}
+
+		if (fadestart > 30)
+			fadestart = 0;
+		if (fadeend > 31 || fadeend < 1)
+			fadeend = 31;
+	}
+
+#undef NUMFROMCHAR
+
+	// Get fade (dark) colormap value
+	// First alpha-only, then full value
+	if (p3[0] >= 'a' && p3[0] <= 'z' && !p3[1])
+		cfa = (p3[0] - 'a');
+	else if (p3[0] == '#' && p3[1] >= 'a' && p3[1] <= 'z' && !p3[2])
+		cfa = (p3[1] - 'a');
+	else if (p3[0] >= 'A' && p3[0] <= 'Z' && !p3[1])
+		cfa = (p3[0] - 'A');
+	else if (p3[0] == '#' && p3[1] >= 'A' && p3[1] <= 'Z' && !p3[2])
+		cfa = (p3[1] - 'A');
+	else if (p3[0] == '#')
+	{
+		// For each subsequent value, the value before it must exist
+		// If we don't get every value, then set alpha to max
+		if (p3[1] && p3[2])
+		{
+			cfr = ((HEX2INT(p3[1]) * 16) + HEX2INT(p3[2]));
+			if (p3[3] && p3[4])
+			{
+				cfg = ((HEX2INT(p3[3]) * 16) + HEX2INT(p3[4]));
+				if (p3[5] && p3[6])
+				{
+					cfb = ((HEX2INT(p3[5]) * 16) + HEX2INT(p3[6]));
+
+					if (p3[7] >= 'a' && p3[7] <= 'z')
+						cfa = (p3[7] - 'a');
+					else if (p3[7] >= 'A' && p3[7] <= 'Z')
+						cfa = (p3[7] - 'A');
+					else
+						cfa = 25;
+				}
+				else
+					cfa = 25;
+			}
+			else
+				cfa = 25;
+		}
+		else
+			cfa = 25;
+	}
+#undef ALPHA2INT
+#undef HEX2INT
+
+	// Pack rgba values into combined var
+	// OpenGL also uses this instead of lighttables for rendering
+	rgba = R_PutRgbaRGBA(cr, cg, cb, ca);
+	fadergba = R_PutRgbaRGBA(cfr, cfg, cfb, cfa);
+
+	// Did we just make a default colormap?
+#ifdef EXTRACOLORMAPLUMPS
+	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, fog, LUMPERROR))
+		return NULL;
+#else
+	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, fog))
+		return NULL;
+#endif
+
+	// Look for existing colormaps
+#ifdef EXTRACOLORMAPLUMPS
+	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog, LUMPERROR);
+#else
+	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog);
+#endif
+	if (exc)
+		return exc;
+
+	CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n",
+		cr, cg, cb, ca, cfr, cfg, cfb, cfa);
+
+	extra_colormap = Z_Calloc(sizeof (*extra_colormap), PU_LEVEL, NULL);
+
+	extra_colormap->fadestart = (UINT16)fadestart;
+	extra_colormap->fadeend = (UINT16)fadeend;
+	extra_colormap->fog = fog;
+
+	extra_colormap->rgba = rgba;
+	extra_colormap->fadergba = fadergba;
+
+#ifdef EXTRACOLORMAPLUMPS
+	extra_colormap->lump = LUMPERROR;
+	extra_colormap->lumpname[0] = 0;
+#endif
+
+	// Having lighttables for alpha-only entries is kind of pointless,
+	// but if there happens to be a matching rgba entry that is NOT alpha-only (but has same rgb values),
+	// then it needs this lighttable because we share matching entries.
+	extra_colormap->colormap = R_CreateLightTable(extra_colormap);
+
+	R_AddColormapToList(extra_colormap);
+
+	return extra_colormap;
+}
+
+//
+// R_AddColormaps()
+// NOTE: The result colormap is not added to the extra_colormaps chain. You must do that yourself!
+//
+extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend,
+	boolean subR, boolean subG, boolean subB, boolean subA,
+	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
+	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFog,
+	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
+	boolean lighttable)
+{
+	INT16 red, green, blue, alpha;
+
+	// exc_augend is added (or subtracted) onto by exc_addend
+	// In Rennaisance times, the first number was considered the augend, the second number the addend
+	// But since the commutative property was discovered, today they're both called addends!
+	// So let's be Olde English for a hot second.
+
+	exc_augend = R_CopyColormap(exc_augend, false);
+	if(!exc_addend)
+		exc_addend = R_GetDefaultColormap();
+
+	///////////////////
+	// base rgba
+	///////////////////
+
+	red = max(min(
+		R_GetRgbaR(exc_augend->rgba)
+			+ (subR ? -1 : 1) // subtract R
+			* R_GetRgbaR(exc_addend->rgba)
+		, 255), 0);
+
+	green = max(min(
+		R_GetRgbaG(exc_augend->rgba)
+			+ (subG ? -1 : 1) // subtract G
+			* R_GetRgbaG(exc_addend->rgba)
+		, 255), 0);
+
+	blue = max(min(
+		R_GetRgbaB(exc_augend->rgba)
+			+ (subB ? -1 : 1) // subtract B
+			* R_GetRgbaB(exc_addend->rgba)
+		, 255), 0);
+
+	alpha = useAltAlpha ? altAlpha : R_GetRgbaA(exc_addend->rgba);
+	alpha = max(min(R_GetRgbaA(exc_augend->rgba) + (subA ? -1 : 1) * alpha, 25), 0);
+
+	exc_augend->rgba = R_PutRgbaRGBA(red, green, blue, alpha);
+
+	///////////////////
+	// fade/dark rgba
+	///////////////////
+
+	red = max(min(
+		R_GetRgbaR(exc_augend->fadergba)
+			+ (subFadeR ? -1 : 1) // subtract R
+			* R_GetRgbaR(exc_addend->fadergba)
+		, 255), 0);
+
+	green = max(min(
+		R_GetRgbaG(exc_augend->fadergba)
+			+ (subFadeG ? -1 : 1) // subtract G
+			* R_GetRgbaG(exc_addend->fadergba)
+		, 255), 0);
+
+	blue = max(min(
+		R_GetRgbaB(exc_augend->fadergba)
+			+ (subFadeB ? -1 : 1) // subtract B
+			* R_GetRgbaB(exc_addend->fadergba)
+		, 255), 0);
+
+	alpha = useAltAlpha ? altFadeAlpha : R_GetRgbaA(exc_addend->fadergba);
+	if (alpha == 25 && !useAltAlpha && !R_GetRgbaRGB(exc_addend->fadergba))
+		alpha = 0; // HACK: fadergba A defaults at 25, so don't add anything in this case
+	alpha = max(min(R_GetRgbaA(exc_augend->fadergba) + (subFadeA ? -1 : 1) * alpha, 25), 0);
+
+	exc_augend->fadergba = R_PutRgbaRGBA(red, green, blue, alpha);
+
+	///////////////////
+	// parameters
+	///////////////////
+
+	exc_augend->fadestart = max(min(
+		exc_augend->fadestart
+			+ (subFadeStart ? -1 : 1) // subtract fadestart
+			* exc_addend->fadestart
+		, 31), 0);
+
+	exc_augend->fadeend = max(min(
+		exc_augend->fadeend
+			+ (subFadeEnd ? -1 : 1) // subtract fadeend
+			* (exc_addend->fadeend == 31 && !exc_addend->fadestart ? 0 : exc_addend->fadeend)
+				// HACK: fadeend defaults to 31, so don't add anything in this case
+		, 31), 0);
+
+	if (!ignoreFog) // overwrite fog with new value
+		exc_augend->fog = exc_addend->fog;
+
+	///////////////////
+	// put it together
+	///////////////////
+
+	exc_augend->colormap = lighttable ? R_CreateLightTable(exc_augend) : NULL;
+	exc_augend->next = exc_augend->prev = NULL;
+	return exc_augend;
 }
 
 // Thanks to quake2 source!
@@ -1724,20 +2065,18 @@ static int RoundUp(double number)
 	return (int)number;
 }
 
-const char *R_ColormapNameForNum(INT32 num)
+#ifdef EXTRACOLORMAPLUMPS
+const char *R_NameForColormap(extracolormap_t *extra_colormap)
 {
-	if (num == -1)
+	if (!extra_colormap)
 		return "NONE";
 
-	if (num < 0 || num > MAXCOLORMAPS)
-		I_Error("R_ColormapNameForNum: num %d is invalid!\n", num);
-
-	if (foundcolormaps[num] == LUMPERROR)
+	if (extra_colormap->lump == LUMPERROR)
 		return "INLEVEL";
 
-	return W_CheckNameForNum(foundcolormaps[num]);
+	return extra_colormap->lumpname;
 }
-
+#endif
 
 //
 // build a table for quick conversion from 8bpp to 15bpp
@@ -1936,8 +2275,8 @@ void R_PrecacheLevel(void)
 	spritepresent = calloc(numsprites, sizeof (*spritepresent));
 	if (spritepresent == NULL) I_Error("%s: Out of memory looking up sprites", "R_PrecacheLevel");
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			spritepresent[((mobj_t *)th)->sprite] = 1;
 
 	spritememory = 0;
diff --git a/src/r_data.h b/src/r_data.h
index 53bf27835863e52dde02098321fc115863ebad3b..b6b0a16a15fad21fb90aa0325721d177a1c6eccc 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -96,13 +96,57 @@ void R_ClearTextureNumCache(boolean btell);
 INT32 R_TextureNumForName(const char *name);
 INT32 R_CheckTextureNumForName(const char *name);
 
+// Extra Colormap lumps (C_START/C_END) are not used anywhere
+// Uncomment to enable
+//#define EXTRACOLORMAPLUMPS
+
+// Uncomment to make extra_colormaps order Newest -> Oldest
+//#define COLORMAPREVERSELIST
+
 void R_ReInitColormaps(UINT16 num);
 void R_ClearColormaps(void);
-INT32 R_ColormapNumForName(char *name);
-INT32 R_CreateColormap(char *p1, char *p2, char *p3);
-void R_CreateColormap2(char *p1, char *p2, char *p3);
-void R_MakeColormaps(void);
-const char *R_ColormapNameForNum(INT32 num);
+extracolormap_t *R_CreateDefaultColormap(boolean lighttable);
+extracolormap_t *R_GetDefaultColormap(void);
+extracolormap_t *R_CopyColormap(extracolormap_t *extra_colormap, boolean lighttable);
+void R_AddColormapToList(extracolormap_t *extra_colormap);
+
+#ifdef EXTRACOLORMAPLUMPS
+boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump);
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump);
+#else
+boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog);
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog);
+#endif
+boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgba, boolean checkfadergba, boolean checkparams);
+boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, boolean checkrgba, boolean checkfadergba, boolean checkparams);
+extracolormap_t *R_GetColormapFromList(extracolormap_t *extra_colormap);
+
+lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap);
+extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3);
+extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend,
+	boolean subR, boolean subG, boolean subB, boolean subA,
+	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
+	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFog,
+	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
+	boolean lighttable);
+#ifdef EXTRACOLORMAPLUMPS
+extracolormap_t *R_ColormapForName(char *name);
+const char *R_NameForColormap(extracolormap_t *extra_colormap);
+#endif
+
+#define R_GetRgbaR(rgba) (rgba & 0xFF)
+#define R_GetRgbaG(rgba) ((rgba >> 8) & 0xFF)
+#define R_GetRgbaB(rgba) ((rgba >> 16) & 0xFF)
+#define R_GetRgbaA(rgba) ((rgba >> 24) & 0xFF)
+#define R_GetRgbaRGB(rgba) (rgba & 0xFFFFFF)
+#define R_PutRgbaR(r) (r)
+#define R_PutRgbaG(g) (g << 8)
+#define R_PutRgbaB(b) (b << 16)
+#define R_PutRgbaA(a) (a << 24)
+#define R_PutRgbaRGB(r, g, b) (R_PutRgbaR(r) + R_PutRgbaG(g) + R_PutRgbaB(b))
+#define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
 extern INT32 numtextures;
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 7c8f2a73fc29511268fc8728a6d9700fcfa55447..def7b46f3b8aa5ecdd81c9d1d359848b87268822 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -50,18 +50,25 @@ typedef struct
 typedef UINT8 lighttable_t;
 
 // ExtraColormap type. Use for extra_colormaps from now on.
-typedef struct
+typedef struct extracolormap_s
 {
-	UINT16 maskcolor, fadecolor;
-	double maskamt;
-	UINT16 fadestart, fadeend;
-	INT32 fog;
+	UINT8 fadestart, fadeend;
+	UINT8 fog; // categorical value, not boolean
 
-	// rgba is used in hw mode for colored sector lighting
+	// store rgba values in combined bitwise
+	// also used in OpenGL instead lighttables
 	INT32 rgba; // similar to maskcolor in sw mode
 	INT32 fadergba; // The colour the colourmaps fade to
 
 	lighttable_t *colormap;
+
+#ifdef EXTRACOLORMAPLUMPS
+	lumpnum_t lump; // for colormap lump matching, init to LUMPERROR
+	char lumpname[9]; // for netsyncing
+#endif
+
+	struct extracolormap_s *next;
+	struct extracolormap_s *prev;
 } extracolormap_t;
 
 //
@@ -177,6 +184,8 @@ typedef struct ffloor_s
 	// these are saved for netgames, so do not let Lua touch these!
 	ffloortype_e spawnflags; // flags the 3D floor spawned with
 	INT32 spawnalpha; // alpha the 3D floor spawned with
+
+	void *fadingdata; // fading FOF thinker
 } ffloor_t;
 
 
@@ -187,7 +196,7 @@ typedef struct lightlist_s
 {
 	fixed_t height;
 	INT16 *lightlevel;
-	extracolormap_t *extra_colormap;
+	extracolormap_t **extra_colormap; // pointer-to-a-pointer, so we can react to colormap changes
 	INT32 flags;
 	ffloor_t *caster;
 #ifdef ESLOPE
@@ -228,46 +237,27 @@ typedef struct linechain_s
 // Slopes
 #ifdef ESLOPE
 typedef enum {
-	SL_NOPHYSICS = 1, // Don't do momentum adjustment with this slope
-	SL_NODYNAMIC = 1<<1, // Slope will never need to move during the level, so don't fuss with recalculating it
-	SL_ANCHORVERTEX = 1<<2, // Slope is using a Slope Vertex Thing to anchor its position
-	SL_VERTEXSLOPE = 1<<3, // Slope is built from three Slope Vertex Things
+	SL_NOPHYSICS = 1, /// This plane will have no physics applied besides the positioning.
+	SL_DYNAMIC = 1<<1, /// This plane slope will be assigned a thinker to make it dynamic.
 } slopeflags_t;
 
 typedef struct pslope_s
 {
 	UINT16 id; // The number of the slope, mostly used for netgame syncing purposes
+	struct pslope_s *next; // Make a linked list of dynamic slopes, for easy reference later
 
-	// --- Information used in clipping/projection ---
-	// Origin vector for the plane
-	vector3_t o;
-
-	// 2-Dimentional vector (x, y) normalized. Used to determine distance from
-	// the origin in 2d mapspace. (Basically a thrust of FRACUNIT in xydirection angle)
-	vector2_t d;
-
-	// The rate at which z changes based on distance from the origin plane.
-	fixed_t zdelta;
-
-	// The normal of the slope; will always point upward, and thus be inverted on ceilings. I think it's only needed for physics? -Red
-	vector3_t normal;
+	// The plane's definition.
+	vector3_t o;		/// Plane origin.
+	vector3_t normal;	/// Plane normal.
 
-	// For comparing when a slope should be rendered
-	fixed_t lowz;
-	fixed_t highz;
+	vector2_t d;		/// Precomputed normalized projection of the normal over XY.
+	fixed_t zdelta;		/// Precomputed Z unit increase per XY unit.
 
 	// This values only check and must be updated if the slope itself is modified
-	angle_t zangle; // Angle of the plane going up from the ground (not mesured in degrees)
-	angle_t xydirection; // The direction the slope is facing (north, west, south, etc.)
-
-	struct line_s *sourceline; // The line that generated the slope
-	fixed_t extent; // Distance value used for recalculating zdelta
-	UINT8 refpos; // 1=front floor 2=front ceiling 3=back floor 4=back ceiling (used for dynamic sloping)
+	angle_t zangle;		/// Precomputed angle of the plane going up from the ground (not measured in degrees).
+	angle_t xydirection;/// Precomputed angle of the normal's projection on the XY plane.
 
 	UINT8 flags; // Slope options
-	mapthing_t **vertices; // List should be three long for slopes made by vertex things, or one long for slopes using one vertex thing to anchor
-
-	struct pslope_s *next; // Make a linked list of dynamic slopes, for easy reference later
 } pslope_t;
 #endif
 
@@ -308,6 +298,7 @@ typedef struct sector_s
 	void *floordata; // floor move thinker
 	void *ceilingdata; // ceiling move thinker
 	void *lightingdata; // lighting change thinker
+	void *fadecolormapdata; // fade colormap thinker
 
 	// floor and ceiling texture offsets
 	fixed_t floor_xoffs, floor_yoffs;
@@ -323,8 +314,6 @@ typedef struct sector_s
 	INT32 floorlightsec, ceilinglightsec;
 	INT32 crumblestate; // used for crumbling and bobbing
 
-	INT32 bottommap, midmap, topmap; // dynamic colormaps
-
 	// list of mobjs that are at least partially in the sector
 	// thinglist is a subset of touching_thinglist
 	struct msecnode_s *touching_thinglist;
@@ -383,6 +372,9 @@ typedef struct sector_s
 	boolean hasslope; // The sector, or one of its visible FOFs, contains a slope
 #endif
 
+	// for fade thinker
+	INT16 spawn_lightlevel;
+
 	// these are saved for netgames, so do not let Lua touch these!
 	INT32 spawn_nexttag, spawn_firsttag; // the actual nexttag/firsttag values may differ if the sector's tag was changed
 
@@ -393,6 +385,9 @@ typedef struct sector_s
 	// flag angles sector spawned with (via linedef type 7)
 	angle_t spawn_flrpic_angle;
 	angle_t spawn_ceilpic_angle;
+
+	// colormap structure
+	extracolormap_t *spawn_extra_colormap;
 } sector_t;
 
 //
@@ -468,6 +463,8 @@ typedef struct
 	INT16 repeatcnt; // # of times to repeat midtexture
 
 	char *text; // a concatination 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;
 
 //
@@ -573,6 +570,7 @@ typedef struct seg_s
 	sector_t *frontsector;
 	sector_t *backsector;
 
+	fixed_t length;	// precalculated seg length
 #ifdef HWRENDER
 	// new pointers so that AdjustSegs doesn't mess with v1/v2
 	void *pv1; // polyvertex_t
diff --git a/src/r_draw.c b/src/r_draw.c
index e06d43f676117f013feb9dfc93b4b080fef7902c..f8e4356244c73826bb4cfdc7cb05866e6eb8869e 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -126,10 +126,12 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define BOSS_TT_CACHE_INDEX (MAXSKINS + 1)
 #define METALSONIC_TT_CACHE_INDEX (MAXSKINS + 2)
 #define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3)
+#define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4)
+#define BLINK_TT_CACHE_INDEX (MAXSKINS + 5)
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
-static UINT8** translationtablecache[MAXSKINS + 4] = {NULL};
+static UINT8** translationtablecache[MAXSKINS + 6] = {NULL};
 
 const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
 	// {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // SKINCOLOR_NONE
@@ -257,7 +259,7 @@ const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
 	{0x00, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5}, // SKINCOLOR_SUPERTAN2
 	{0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9}, // SKINCOLOR_SUPERTAN3
 	{0x51, 0x52, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9, 0xfb, 0xed}, // SKINCOLOR_SUPERTAN4
-	{0x52, 0x52, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9, 0xfb, 0xed, 0xee, 0xef, 0xef} // SKINCOLOR_SUPERTAN5
+	{0x52, 0x52, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9, 0xfb, 0xed, 0xee, 0xef, 0xef}  // SKINCOLOR_SUPERTAN5
 };
 
 // See also the enum skincolors_t
@@ -457,17 +459,82 @@ void R_InitTranslationTables(void)
 
 	\return	void
 */
+
+// Define for getting accurate color brightness readings according to how the human eye sees them.
+// https://en.wikipedia.org/wiki/Relative_luminance
+// 0.2126 to red
+// 0.7152 to green
+// 0.0722 to blue
+// (See this same define in hw_md2.c!)
+#define SETBRIGHTNESS(brightness,r,g,b) \
+	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
+
+/** \brief	Generates the rainbow colourmaps that are used when a player has the invincibility power... stolen from kart, with permission
+
+	\param	dest_colormap	colormap to populate
+	\param	skincolor		translation color
+*/
+static void R_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor)
+{
+	INT32 i;
+	RGBA_t color;
+	UINT8 brightness;
+	INT32 j;
+	UINT8 colorbrightnesses[16];
+	UINT16 brightdif;
+	INT32 temp;
+
+	// first generate the brightness of all the colours of that skincolour
+	for (i = 0; i < 16; i++)
+	{
+		color = V_GetColor(Color_Index[skincolor-1][i]);
+		SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue);
+	}
+
+	// next, for every colour in the palette, choose the transcolor that has the closest brightness
+	for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+	{
+		if (i == 0 || i == 31) // pure black and pure white don't change
+		{
+			dest_colormap[i] = (UINT8)i;
+			continue;
+		}
+		color = V_GetColor(i);
+		SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue);
+		brightdif = 256;
+		for (j = 0; j < 16; j++)
+		{
+			temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]);
+			if (temp < brightdif)
+			{
+				brightdif = (UINT16)temp;
+				dest_colormap[i] = Color_Index[skincolor-1][j];
+			}
+		}
+	}
+}
+
+#undef SETBRIGHTNESS
+
 static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color)
 {
 	INT32 i, starttranscolor, skinramplength;
 
 	// Handle a couple of simple special cases
-	if (skinnum == TC_BOSS || skinnum == TC_ALLWHITE || skinnum == TC_METALSONIC || color == SKINCOLOR_NONE)
+	if (skinnum == TC_BOSS
+		|| skinnum == TC_ALLWHITE
+		|| skinnum == TC_METALSONIC
+		|| skinnum == TC_BLINK
+		|| color == SKINCOLOR_NONE)
 	{
-		for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+		if (skinnum == TC_ALLWHITE)
+			memset(dest_colormap, 0, NUM_PALETTE_ENTRIES * sizeof(UINT8));
+		else if (skinnum == TC_BLINK && color != SKINCOLOR_NONE)
+			memset(dest_colormap, Color_Index[color-1][3], NUM_PALETTE_ENTRIES * sizeof(UINT8));
+		else
 		{
-			if (skinnum == TC_ALLWHITE) dest_colormap[i] = 0;
-			else dest_colormap[i] = (UINT8)i;
+			for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+				dest_colormap[i] = (UINT8)i;
 		}
 
 		// White!
@@ -478,6 +545,11 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 
 		return;
 	}
+	else if (skinnum == TC_RAINBOW)
+	{
+		R_RainbowColormap(dest_colormap, color);
+		return;
+	}
 
 	if (color >= MAXTRANSLATIONS)
 		I_Error("Invalid skin color #%hu.", (UINT16)color);
@@ -522,6 +594,8 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolors_t color, UINT8 flags)
 	else if (skinnum == TC_BOSS) skintableindex = BOSS_TT_CACHE_INDEX;
 	else if (skinnum == TC_METALSONIC) skintableindex = METALSONIC_TT_CACHE_INDEX;
 	else if (skinnum == TC_ALLWHITE) skintableindex = ALLWHITE_TT_CACHE_INDEX;
+	else if (skinnum == TC_RAINBOW) skintableindex = RAINBOW_TT_CACHE_INDEX;
+	else if (skinnum == TC_BLINK) skintableindex = BLINK_TT_CACHE_INDEX;
 	else skintableindex = skinnum;
 
 	if (flags & GTC_CACHE)
diff --git a/src/r_draw.h b/src/r_draw.h
index 4a105b5b082e7d2f99a23d952b35a79cd296b172..82498eb11e22d30569e69b2baca059d1acbeb40e 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -105,6 +105,8 @@ extern lumpnum_t viewborderlump[8];
 #define TC_BOSS       -2
 #define TC_METALSONIC -3 // For Metal Sonic battle
 #define TC_ALLWHITE   -4 // For Cy-Brak-demon
+#define TC_RAINBOW    -5 // For single colour
+#define TC_BLINK      -6 // For item blinking, according to kart
 
 // Initialize color translation tables, for player rendering etc.
 void R_InitTranslationTables(void);
diff --git a/src/r_draw16.c b/src/r_draw16.c
index a922f4d0a22ad3df383ecc4724f65287fcda2d0a..918dd356c34bd67e0668031d7423164dbb98f301 100644
--- a/src/r_draw16.c
+++ b/src/r_draw16.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 9340b61c6135059228a8280ccfe58237b9deb6a9..8a2d37fb3d796d81b7bf1a470d8bfa93971f3d0e 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -261,7 +261,7 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void)
 				val = source[frac>>FRACBITS];
 
 				if (val != TRANSPARENTPIXEL)
-					*dest = colormap[*(transmap + (val<<8) + (*dest))];
+					*dest = *(transmap + (colormap[val]<<8) + (*dest));
 
 				dest += vid.width;
 
@@ -281,12 +281,12 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void)
 			{
 				val = source[(frac>>FRACBITS) & heightmask];
 				if (val != TRANSPARENTPIXEL)
-					*dest = colormap[*(transmap + (val<<8) + (*dest))];
+					*dest = *(transmap + (colormap[val]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 				val = source[(frac>>FRACBITS) & heightmask];
 				if (val != TRANSPARENTPIXEL)
-					*dest = colormap[*(transmap + (val<<8) + (*dest))];
+					*dest = *(transmap + (colormap[val]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 			}
@@ -294,7 +294,7 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void)
 			{
 				val = source[(frac>>FRACBITS) & heightmask];
 				if (val != TRANSPARENTPIXEL)
-					*dest = colormap[*(transmap + (val<<8) + (*dest))];
+					*dest = *(transmap + (colormap[val]<<8) + (*dest));
 			}
 		}
 	}
@@ -394,7 +394,7 @@ void R_DrawTranslucentColumn_8(void)
 				// Re-map color indices from wall texture column
 				// using a lighting/special effects LUT.
 				// heightmask is the Tutti-Frutti fix
-				*dest = colormap[*(transmap + (source[frac>>FRACBITS]<<8) + (*dest))];
+				*dest = *(transmap + (colormap[source[frac>>FRACBITS]]<<8) + (*dest));
 				dest += vid.width;
 				if ((frac += fracstep) >= heightmask)
 					frac -= heightmask;
@@ -405,15 +405,15 @@ void R_DrawTranslucentColumn_8(void)
 		{
 			while ((count -= 2) >= 0) // texture height is a power of 2
 			{
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 			}
 			if (count & 1)
-				*dest = colormap[*(transmap + ((source[(frac>>FRACBITS)&heightmask]<<8)) + (*dest))];
+				*dest = *(transmap + (colormap[source[(frac>>FRACBITS)&heightmask]]<<8) + (*dest));
 		}
 	}
 }
@@ -464,8 +464,7 @@ void R_DrawTranslatedTranslucentColumn_8(void)
 				//  using a lighting/special effects LUT.
 				// heightmask is the Tutti-Frutti fix
 
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest));
 
 				dest += vid.width;
 				if ((frac += fracstep) >= heightmask)
@@ -477,17 +476,15 @@ void R_DrawTranslatedTranslucentColumn_8(void)
 		{
 			while ((count -= 2) >= 0) // texture height is a power of 2
 			{
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
-				*dest = dc_colormap[*(dc_transmap
-					+ (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]<<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 				dest += vid.width;
 				frac += fracstep;
 			}
 			if (count & 1)
-				*dest = dc_colormap[*(dc_transmap + (dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]] <<8) + (*dest))];
+				*dest = *(dc_transmap + (dc_colormap[dc_translation[dc_source[(frac>>FRACBITS)&heightmask]]]<<8) + (*dest));
 		}
 	}
 }
@@ -835,8 +832,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		v = (INT64)(vz*z) + viewy;
 
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-
-		*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+		*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		dest++;
 		iz += ds_sz.x;
 		uz += ds_su.x;
@@ -873,7 +869,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		for (i = SPANSIZE-1; i >= 0; i--)
 		{
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 			dest++;
 			u += stepu;
 			v += stepv;
@@ -889,7 +885,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			u = (INT64)(startu);
 			v = (INT64)(startv);
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		}
 		else
 		{
@@ -910,7 +906,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			for (; width != 0; width--)
 			{
 				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-				*dest = colormap[*(ds_transmap + (source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)] << 8) + dest[0])];
+				*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 				dest++;
 				u += stepu;
 				v += stepv;
@@ -1221,49 +1217,49 @@ void R_DrawTranslucentSplat_8 (void)
 		// need!
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[0] = colormap[*(ds_transmap + (val << 8) + dest[0])];
+			dest[0] = *(ds_transmap + (colormap[val] << 8) + dest[0]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[1] = colormap[*(ds_transmap + (val << 8) + dest[1])];
+			dest[1] = *(ds_transmap + (colormap[val] << 8) + dest[1]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[2] = colormap[*(ds_transmap + (val << 8) + dest[2])];
+			dest[2] = *(ds_transmap + (colormap[val] << 8) + dest[2]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[3] = colormap[*(ds_transmap + (val << 8) + dest[3])];
+			dest[3] = *(ds_transmap + (colormap[val] << 8) + dest[3]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[4] = colormap[*(ds_transmap + (val << 8) + dest[4])];
+			dest[4] = *(ds_transmap + (colormap[val] << 8) + dest[4]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[5] = colormap[*(ds_transmap + (val << 8) + dest[5])];
+			dest[5] = *(ds_transmap + (colormap[val] << 8) + dest[5]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[6] = colormap[*(ds_transmap + (val << 8) + dest[6])];
+			dest[6] = *(ds_transmap + (colormap[val] << 8) + dest[6]);
 		xposition += xstep;
 		yposition += ystep;
 
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			dest[7] = colormap[*(ds_transmap + (val << 8) + dest[7])];
+			dest[7] = *(ds_transmap + (colormap[val] << 8) + dest[7]);
 		xposition += xstep;
 		yposition += ystep;
 
@@ -1274,7 +1270,7 @@ void R_DrawTranslucentSplat_8 (void)
 	{
 		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
 		if (val != TRANSPARENTPIXEL)
-			*dest = colormap[*(ds_transmap + (val << 8) + *dest)];
+			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
 
 		dest++;
 		xposition += xstep;
@@ -1317,35 +1313,35 @@ void R_DrawTranslucentSpan_8 (void)
 		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
 		// have the uber complicated math to calculate it now, so that was a memory write we didn't
 		// need!
-		dest[0] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[0])];
+		dest[0] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[0]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[1] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[1])];
+		dest[1] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[1]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[2] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[2])];
+		dest[2] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[2]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[3] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[3])];
+		dest[3] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[3]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[4] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[4])];
+		dest[4] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[4]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[5] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[5])];
+		dest[5] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[5]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[6] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[6])];
+		dest[6] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[6]);
 		xposition += xstep;
 		yposition += ystep;
 
-		dest[7] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + dest[7])];
+		dest[7] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[7]);
 		xposition += xstep;
 		yposition += ystep;
 
@@ -1354,7 +1350,7 @@ void R_DrawTranslucentSpan_8 (void)
 	}
 	while (count--)
 	{
-		*dest = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dest)];
+		*dest = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + *dest);
 		dest++;
 		xposition += xstep;
 		yposition += ystep;
diff --git a/src/r_local.h b/src/r_local.h
index a3dfe7df6a5cccf9f2d53d81d7bc44612755c828..1d3187750ca607111eaacb0a0d41b07f6d1bd2b2 100644
--- a/src/r_local.h
+++ b/src/r_local.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_main.c b/src/r_main.c
index 9d0a8fe6059d5f3c73101849b59afa551a0f0828..db351e9911dc45675f0168b818890dede6fbab93 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -30,6 +30,7 @@
 #include "p_spec.h" // skyboxmo
 #include "z_zone.h"
 #include "m_random.h" // quake camera shake
+#include "r_portal.h"
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -65,37 +66,9 @@ size_t loopcount;
 fixed_t viewx, viewy, viewz;
 angle_t viewangle, aimingangle;
 fixed_t viewcos, viewsin;
-boolean viewsky, skyVisible;
-boolean skyVisible1, skyVisible2; // saved values of skyVisible for P1 and P2, for splitscreen
 sector_t *viewsector;
 player_t *viewplayer;
 
-// PORTALS!
-// You can thank and/or curse JTE for these.
-UINT8 portalrender;
-sector_t *portalcullsector;
-typedef struct portal_pair
-{
-	INT32 line1;
-	INT32 line2;
-	UINT8 pass;
-	struct portal_pair *next;
-
-	fixed_t viewx;
-	fixed_t viewy;
-	fixed_t viewz;
-	angle_t viewangle;
-
-	INT32 start;
-	INT32 end;
-	INT16 *ceilingclip;
-	INT16 *floorclip;
-	fixed_t *frontscale;
-} portal_pair;
-portal_pair *portal_base, *portal_cap;
-line_t *portalclipline;
-INT32 portalclipstart, portalclipend;
-
 //
 // precalculated math tables
 //
@@ -118,15 +91,21 @@ lighttable_t *scalelightfixed[MAXLIGHTSCALE];
 lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 
 // Hack to support extra boom colormaps.
-size_t num_extra_colormaps;
-extracolormap_t extra_colormaps[MAXCOLORMAPS];
+extracolormap_t *extra_colormaps = NULL;
 
 static CV_PossibleValue_t drawdist_cons_t[] = {
 	{256, "256"},	{512, "512"},	{768, "768"},
 	{1024, "1024"},	{1536, "1536"},	{2048, "2048"},
 	{3072, "3072"},	{4096, "4096"},	{6144, "6144"},
 	{8192, "8192"},	{0, "Infinite"},	{0, NULL}};
-static CV_PossibleValue_t precipdensity_cons_t[] = {{0, "None"}, {1, "Light"}, {2, "Moderate"}, {4, "Heavy"}, {6, "Thick"}, {8, "V.Thick"}, {0, NULL}};
+
+//static CV_PossibleValue_t precipdensity_cons_t[] = {{0, "None"}, {1, "Light"}, {2, "Moderate"}, {4, "Heavy"}, {6, "Thick"}, {8, "V.Thick"}, {0, NULL}};
+
+static CV_PossibleValue_t drawdist_precip_cons_t[] = {
+	{256, "256"},	{512, "512"},	{768, "768"},
+	{1024, "1024"},	{1536, "1536"},	{2048, "2048"},
+	{0, "None"},	{0, NULL}};
+
 static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card
 static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}};
@@ -154,8 +133,8 @@ consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_c
 consvar_t cv_translucency = {"translucency", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_drawdist = {"drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_drawdist_nights = {"drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+//consvar_t cv_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // Okay, whoever said homremoval causes a performance hit should be shot.
 consvar_t cv_homremoval = {"homremoval", "No", CV_SAVE, homremoval_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -220,21 +199,11 @@ static void ChaseCam2_OnChange(void)
 
 static void FlipCam_OnChange(void)
 {
-	if (cv_flipcam.value)
-		players[consoleplayer].pflags |= PF_FLIPCAM;
-	else
-		players[consoleplayer].pflags &= ~PF_FLIPCAM;
-
 	SendWeaponPref();
 }
 
 static void FlipCam2_OnChange(void)
 {
-	if (cv_flipcam2.value)
-		players[secondarydisplayplayer].pflags |= PF_FLIPCAM;
-	else
-		players[secondarydisplayplayer].pflags &= ~PF_FLIPCAM;
-
 	SendWeaponPref2();
 }
 
@@ -364,6 +333,29 @@ fixed_t R_PointToDist(fixed_t x, fixed_t y)
 	return R_PointToDist2(viewx, viewy, x, y);
 }
 
+angle_t R_PointToAngleEx(INT32 x2, INT32 y2, INT32 x1, INT32 y1)
+{
+	INT64 dx = x1-x2;
+	INT64 dy = y1-y2;
+	if (dx < INT32_MIN || dx > INT32_MAX || dy < INT32_MIN || dy > INT32_MAX)
+	{
+		x1 = (int)(dx / 2 + x2);
+		y1 = (int)(dy / 2 + y2);
+	}
+	return (y1 -= y2, (x1 -= x2) || y1) ?
+	x1 >= 0 ?
+	y1 >= 0 ?
+		(x1 > y1) ? tantoangle[SlopeDivEx(y1,x1)] :                            // octant 0
+		ANGLE_90-tantoangle[SlopeDivEx(x1,y1)] :                               // octant 1
+		x1 > (y1 = -y1) ? 0-tantoangle[SlopeDivEx(y1,x1)] :                    // octant 8
+		ANGLE_270+tantoangle[SlopeDivEx(x1,y1)] :                              // octant 7
+		y1 >= 0 ? (x1 = -x1) > y1 ? ANGLE_180-tantoangle[SlopeDivEx(y1,x1)] :  // octant 3
+		ANGLE_90 + tantoangle[SlopeDivEx(x1,y1)] :                             // octant 2
+		(x1 = -x1) > (y1 = -y1) ? ANGLE_180+tantoangle[SlopeDivEx(y1,x1)] :    // octant 4
+		ANGLE_270-tantoangle[SlopeDivEx(x1,y1)] :                              // octant 5
+		0;
+}
+
 //
 // R_ScaleFromGlobalAngle
 // Returns the texture mapping scale for the current line (horizontal span)
@@ -560,13 +552,11 @@ void R_SetViewSize(void)
 //
 void R_ExecuteSetViewSize(void)
 {
-	fixed_t cosadj;
 	fixed_t dy;
 	INT32 i;
 	INT32 j;
 	INT32 level;
 	INT32 startmapl;
-	INT32 aspectx;  //added : 02-02-98 : for aspect ratio calc. below...
 
 	setsizeneeded = false;
 
@@ -606,31 +596,22 @@ void R_ExecuteSetViewSize(void)
 	for (i = 0; i < viewwidth; i++)
 		screenheightarray[i] = (INT16)viewheight;
 
-	// setup sky scaling (uses pspriteyscale)
+	// setup sky scaling
 	R_SetSkyScale();
 
 	// planes
-	//aspectx = (((vid.height*centerx*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width);
-	aspectx = centerx;
-
 	if (rendermode == render_soft)
 	{
 		// this is only used for planes rendering in software mode
-		j = viewheight*4;
+		j = viewheight*16;
 		for (i = 0; i < j; i++)
 		{
-			dy = ((i - viewheight*2)<<FRACBITS) + FRACUNIT/2;
+			dy = ((i - viewheight*8)<<FRACBITS) + FRACUNIT/2;
 			dy = abs(dy);
-			yslopetab[i] = FixedDiv(aspectx*FRACUNIT, dy);
+			yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy);
 		}
 	}
 
-	for (i = 0; i < viewwidth; i++)
-	{
-		cosadj = abs(FINECOSINE(xtoviewangle[i]>>ANGLETOFINESHIFT));
-		distscale[i] = FixedDiv(FRACUNIT, cosadj);
-	}
-
 	memset(scalelight, 0xFF, sizeof(scalelight));
 
 	// Calculate the light levels to use for each level/scale combination.
@@ -743,9 +724,135 @@ static mobj_t *viewmobj;
 // WARNING: a should be unsigned but to add with 2048, it isn't!
 #define AIMINGTODY(a) ((FINETANGENT((2048+(((INT32)a)>>ANGLETOFINESHIFT)) & FINEMASK)*160)>>FRACBITS)
 
-void R_SkyboxFrame(player_t *player)
+// recalc necessary stuff for mouseaiming
+// slopes are already calculated for the full possible view (which is 4*viewheight).
+// 18/08/18: (No it's actually 16*viewheight, thanks Jimita for finding this out)
+static void R_SetupFreelook(void)
 {
 	INT32 dy = 0;
+	if (rendermode == render_soft)
+	{
+		// clip it in the case we are looking a hardware 90 degrees full aiming
+		// (lmps, network and use F12...)
+		G_SoftwareClipAimingPitch((INT32 *)&aimingangle);
+		dy = AIMINGTODY(aimingangle) * viewwidth/BASEVIDWIDTH;
+		yslope = &yslopetab[viewheight*8 - (viewheight/2 + dy)];
+	}
+	centery = (viewheight/2) + dy;
+	centeryfrac = centery<<FRACBITS;
+}
+
+#undef AIMINGTODY
+
+void R_SetupFrame(player_t *player)
+{
+	camera_t *thiscam;
+	boolean chasecam = false;
+
+	if (splitscreen && player == &players[secondarydisplayplayer]
+		&& player != &players[consoleplayer])
+	{
+		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 = false;
+
+	if (player->awayviewtics)
+	{
+		// cut-away view stuff
+		viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN
+		I_Assert(viewmobj != NULL);
+		viewz = viewmobj->z + 20*FRACUNIT;
+		aimingangle = player->awayviewaiming;
+		viewangle = viewmobj->angle;
+	}
+	else if (!player->spectator && chasecam)
+	// use outside cam view
+	{
+		viewmobj = NULL;
+		viewz = thiscam->z + (thiscam->height>>1);
+		aimingangle = thiscam->aiming;
+		viewangle = thiscam->angle;
+	}
+	else
+	// use the player's eyes view
+	{
+		viewz = player->viewz;
+
+		viewmobj = player->mo;
+		I_Assert(viewmobj != NULL);
+
+		aimingangle = player->aiming;
+		viewangle = viewmobj->angle;
+
+		if (!demoplayback && player->playerstate != PST_DEAD)
+		{
+			if (player == &players[consoleplayer])
+			{
+				viewangle = localangle; // WARNING: camera uses this
+				aimingangle = localaiming;
+			}
+			else if (player == &players[secondarydisplayplayer])
+			{
+				viewangle = localangle2;
+				aimingangle = localaiming2;
+			}
+		}
+	}
+	viewz += quake.z;
+
+	viewplayer = player;
+
+	if (chasecam && !player->awayviewtics && !player->spectator)
+	{
+		viewx = thiscam->x;
+		viewy = thiscam->y;
+		viewx += quake.x;
+		viewy += quake.y;
+
+		if (thiscam->subsector)
+			viewsector = thiscam->subsector->sector;
+		else
+			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+	}
+	else
+	{
+		viewx = viewmobj->x;
+		viewy = viewmobj->y;
+		viewx += quake.x;
+		viewy += quake.y;
+
+		if (viewmobj->subsector)
+			viewsector = viewmobj->subsector->sector;
+		else
+			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+	}
+
+	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
+	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
+
+	R_SetupFreelook();
+}
+
+void R_SkyboxFrame(player_t *player)
+{
 	camera_t *thiscam;
 
 	if (splitscreen && player == &players[secondarydisplayplayer]
@@ -755,7 +862,6 @@ void R_SkyboxFrame(player_t *player)
 		thiscam = &camera;
 
 	// cut-away view stuff
-	viewsky = true;
 	viewmobj = skyboxmo[0];
 #ifdef PARANOIA
 	if (!viewmobj)
@@ -879,157 +985,11 @@ void R_SkyboxFrame(player_t *player)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	// recalc necessary stuff for mouseaiming
-	// slopes are already calculated for the full possible view (which is 4*viewheight).
-
-	if (rendermode == render_soft)
-	{
-		// clip it in the case we are looking a hardware 90 degrees full aiming
-		// (lmps, network and use F12...)
-		G_SoftwareClipAimingPitch((INT32 *)&aimingangle);
-
-		dy = AIMINGTODY(aimingangle) * viewwidth/BASEVIDWIDTH;
-
-		yslope = &yslopetab[(3*viewheight/2) - dy];
-	}
-	centery = (viewheight/2) + dy;
-	centeryfrac = centery<<FRACBITS;
-}
-
-void R_SetupFrame(player_t *player, boolean skybox)
-{
-	INT32 dy = 0;
-	camera_t *thiscam;
-	boolean chasecam = false;
-
-	if (splitscreen && player == &players[secondarydisplayplayer]
-		&& player != &players[consoleplayer])
-	{
-		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)
-		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;
-
-	viewsky = !skybox;
-	if (player->awayviewtics)
-	{
-		// cut-away view stuff
-		viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN
-		I_Assert(viewmobj != NULL);
-		viewz = viewmobj->z + 20*FRACUNIT;
-		aimingangle = player->awayviewaiming;
-		viewangle = viewmobj->angle;
-	}
-	else if (!player->spectator && chasecam)
-	// use outside cam view
-	{
-		viewmobj = NULL;
-		viewz = thiscam->z + (thiscam->height>>1);
-		aimingangle = thiscam->aiming;
-		viewangle = thiscam->angle;
-	}
-	else
-	// use the player's eyes view
-	{
-		viewz = player->viewz;
-
-		viewmobj = player->mo;
-		I_Assert(viewmobj != NULL);
-
-		aimingangle = player->aiming;
-		viewangle = viewmobj->angle;
-
-		if (!demoplayback && player->playerstate != PST_DEAD)
-		{
-			if (player == &players[consoleplayer])
-			{
-				viewangle = localangle; // WARNING: camera uses this
-				aimingangle = localaiming;
-			}
-			else if (player == &players[secondarydisplayplayer])
-			{
-				viewangle = localangle2;
-				aimingangle = localaiming2;
-			}
-		}
-	}
-	viewz += quake.z;
-
-	viewplayer = player;
-
-	if (chasecam && !player->awayviewtics && !player->spectator)
-	{
-		viewx = thiscam->x;
-		viewy = thiscam->y;
-		viewx += quake.x;
-		viewy += quake.y;
-
-		if (thiscam->subsector)
-			viewsector = thiscam->subsector->sector;
-		else
-			viewsector = R_PointInSubsector(viewx, viewy)->sector;
-	}
-	else
-	{
-		viewx = viewmobj->x;
-		viewy = viewmobj->y;
-		viewx += quake.x;
-		viewy += quake.y;
-
-		if (viewmobj->subsector)
-			viewsector = viewmobj->subsector->sector;
-		else
-			viewsector = R_PointInSubsector(viewx, viewy)->sector;
-	}
-
-	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
-	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
-
-	// recalc necessary stuff for mouseaiming
-	// slopes are already calculated for the full possible view (which is 4*viewheight).
-
-	if (rendermode == render_soft)
-	{
-		// clip it in the case we are looking a hardware 90 degrees full aiming
-		// (lmps, network and use F12...)
-		G_SoftwareClipAimingPitch((INT32 *)&aimingangle);
-
-		dy = AIMINGTODY(aimingangle) * viewwidth/BASEVIDWIDTH;
-
-		yslope = &yslopetab[(3*viewheight/2) - dy];
-	}
-	centery = (viewheight/2) + dy;
-	centeryfrac = centery<<FRACBITS;
+	R_SetupFreelook();
 }
 
-#define ANGLED_PORTALS
-
-static void R_PortalFrame(line_t *start, line_t *dest, portal_pair *portal)
+static void R_PortalFrame(portal_t *portal)
 {
-	vertex_t dest_c, start_c;
-#ifdef ANGLED_PORTALS
-	// delta angle
-	angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0);
-#endif
-
-	//R_SetupFrame(player, false);
 	viewx = portal->viewx;
 	viewy = portal->viewy;
 	viewz = portal->viewz;
@@ -1038,94 +998,35 @@ static void R_PortalFrame(line_t *start, line_t *dest, portal_pair *portal)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	portalcullsector = dest->frontsector;
-	viewsector = dest->frontsector;
-	portalclipline = dest;
 	portalclipstart = portal->start;
 	portalclipend = portal->end;
 
-	// Offset the portal view by the linedef centers
-
-	// looking glass center
-	start_c.x = (start->v1->x + start->v2->x) / 2;
-	start_c.y = (start->v1->y + start->v2->y) / 2;
-
-	// other side center
-	dest_c.x = (dest->v1->x + dest->v2->x) / 2;
-	dest_c.y = (dest->v1->y + dest->v2->y) / 2;
-
-	// Heights!
-	viewz += dest->frontsector->floorheight - start->frontsector->floorheight;
-
-	// calculate the difference in position and rotation!
-#ifdef ANGLED_PORTALS
-	if (dangle == 0)
-#endif
-	{ // the entrance goes straight opposite the exit, so we just need to mess with the offset.
-		viewx += dest_c.x - start_c.x;
-		viewy += dest_c.y - start_c.y;
-		return;
+	if (portal->clipline != -1)
+	{
+		portalclipline = &lines[portal->clipline];
+		viewsector = portalclipline->frontsector;
 	}
-
-#ifdef ANGLED_PORTALS
-	viewangle += dangle;
-	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
-	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
-	//CONS_Printf("dangle == %u\n", AngleFixed(dangle)>>FRACBITS);
-
-	// ????
+	else
 	{
-		fixed_t disttopoint;
-		angle_t angtopoint;
-
-		disttopoint = R_PointToDist2(start_c.x, start_c.y, viewx, viewy);
-		angtopoint = R_PointToAngle2(start_c.x, start_c.y, viewx, viewy);
-		angtopoint += dangle;
-
-		viewx = dest_c.x+FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
-		viewy = dest_c.y+FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+		portalclipline = NULL;
+		viewsector = R_PointInSubsector(viewx, viewy)->sector;
 	}
-#endif
 }
 
-void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2)
+static void Mask_Pre (maskcount_t* m)
 {
-	portal_pair *portal = Z_Malloc(sizeof(portal_pair), PU_LEVEL, NULL);
-	INT16 *ceilingclipsave = Z_Malloc(sizeof(INT16)*(x2-x1), PU_LEVEL, NULL);
-	INT16 *floorclipsave = Z_Malloc(sizeof(INT16)*(x2-x1), PU_LEVEL, NULL);
-	fixed_t *frontscalesave = Z_Malloc(sizeof(fixed_t)*(x2-x1), PU_LEVEL, NULL);
-
-	portal->line1 = line1;
-	portal->line2 = line2;
-	portal->pass = portalrender+1;
-	portal->next = NULL;
-
-	R_PortalStoreClipValues(x1, x2, ceilingclipsave, floorclipsave, frontscalesave);
-
-	portal->ceilingclip = ceilingclipsave;
-	portal->floorclip = floorclipsave;
-	portal->frontscale = frontscalesave;
-
-	portal->start = x1;
-	portal->end = x2;
-
-	portalline = true; // this tells R_StoreWallRange that curline is a portal seg
-
-	portal->viewx = viewx;
-	portal->viewy = viewy;
-	portal->viewz = viewz;
-	portal->viewangle = viewangle;
+	m->drawsegs[0] = ds_p - drawsegs;
+	m->vissprites[0] = visspritecount;
+	m->viewx = viewx;
+	m->viewy = viewy;
+	m->viewz = viewz;
+	m->viewsector = viewsector;
+}
 
-	if (!portal_base)
-	{
-		portal_base = portal;
-		portal_cap = portal;
-	}
-	else
-	{
-		portal_cap->next = portal;
-		portal_cap = portal;
-	}
+static void Mask_Post (maskcount_t* m)
+{
+	m->drawsegs[1] = ds_p - drawsegs;
+	m->vissprites[1] = visspritecount;
 }
 
 // ================
@@ -1140,49 +1041,18 @@ void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2)
 
 void R_RenderPlayerView(player_t *player)
 {
-	portal_pair *portal;
-	const boolean skybox = (skyboxmo[0] && cv_skybox.value);
+	UINT8			nummasks	= 1;
+	maskcount_t*	masks		= malloc(sizeof(maskcount_t));
 
 	if (cv_homremoval.value && player == &players[displayplayer]) // if this is display player 1
 	{
 		if (cv_homremoval.value == 1)
-			V_DrawFill(0, 0, vid.width, vid.height, 31); // No HOM effect!
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // No HOM effect!
 		else //'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
-			V_DrawFill(0, 0, vid.width, vid.height, 32+(timeinmap&15));
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 32+(timeinmap&15));
 	}
 
-	// load previous saved value of skyVisible for the player
-	if (splitscreen && player == &players[secondarydisplayplayer])
-		skyVisible = skyVisible2;
-	else
-		skyVisible = skyVisible1;
-
-	portalrender = 0;
-	portal_base = portal_cap = NULL;
-
-	if (skybox && skyVisible)
-	{
-		R_SkyboxFrame(player);
-
-		R_ClearClipSegs();
-		R_ClearDrawSegs();
-		R_ClearPlanes();
-		R_ClearSprites();
-#ifdef FLOORSPLATS
-		R_ClearVisibleFloorSplats();
-#endif
-
-		R_RenderBSPNode((INT32)numnodes - 1);
-		R_ClipSprites();
-		R_DrawPlanes();
-#ifdef FLOORSPLATS
-		R_DrawVisibleFloorSplats();
-#endif
-		R_DrawMasked();
-	}
-
-	R_SetupFrame(player, skybox);
-	skyVisible = false;
+	R_SetupFrame(player);
 	framecount++;
 	validcount++;
 
@@ -1194,19 +1064,21 @@ void R_RenderPlayerView(player_t *player)
 #ifdef FLOORSPLATS
 	R_ClearVisibleFloorSplats();
 #endif
+	Portal_InitList();
 
 	// check for new console commands.
 	NetUpdate();
 
 	// The head node is the last node output.
 
+	Mask_Pre(&masks[nummasks - 1]);
+	curdrawsegs = ds_p;
 //profile stuff ---------------------------------------------------------
 #ifdef TIMING
 	mytotal = 0;
 	ProfZeroTimer();
 #endif
 	R_RenderBSPNode((INT32)numnodes - 1);
-	R_ClipSprites();
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
 	mytotal += mycount; // 64bit add
@@ -1214,54 +1086,66 @@ void R_RenderPlayerView(player_t *player)
 	CONS_Debug(DBG_RENDER, "RenderBSPNode: 0x%d %d\n", *((INT32 *)&mytotal + 1), (INT32)mytotal);
 #endif
 //profile stuff ---------------------------------------------------------
+	Mask_Post(&masks[nummasks - 1]);
+
+	R_ClipSprites(drawsegs, NULL);
+
 
-	// PORTAL RENDERING
-	for(portal = portal_base; portal; portal = portal_base)
+	// Add skybox portals caused by sky visplanes.
+	if (cv_skybox.value && skyboxmo[0])
+		Portal_AddSkyboxPortals();
+
+	// Portal rendering. Hijacks the BSP traversal.
+	if (portal_base)
 	{
-		// render the portal
-		CONS_Debug(DBG_RENDER, "Rendering portal from line %d to %d\n", portal->line1, portal->line2);
-		portalrender = portal->pass;
+		portal_t *portal;
+
+		for(portal = portal_base; portal; portal = portal_base)
+		{
+			portalrender = portal->pass; // Recursiveness depth.
+
+			R_ClearFFloorClips();
+
+			// Apply the viewpoint stored for the portal.
+			R_PortalFrame(portal);
+
+			// Hack in the clipsegs to delimit the starting
+			// clipping for sprites and possibly other similar
+			// future items.
+			R_PortalClearClipSegs(portal->start, portal->end);
+
+			// Hack in the top/bottom clip values for the window
+			// that were previously stored.
+			Portal_ClipApply(portal);
 
-		R_PortalFrame(&lines[portal->line1], &lines[portal->line2], portal);
+			validcount++;
 
-		R_PortalClearClipSegs(portal->start, portal->end);
+			masks = realloc(masks, (++nummasks)*sizeof(maskcount_t));
 
-		R_PortalRestoreClipValues(portal->start, portal->end, portal->ceilingclip, portal->floorclip, portal->frontscale);
+			Mask_Pre(&masks[nummasks - 1]);
+			curdrawsegs = ds_p;
 
-		validcount++;
+			// Render the BSP from the new viewpoint, and clip
+			// any sprites with the new clipsegs and window.
+			R_RenderBSPNode((INT32)numnodes - 1);
+			Mask_Post(&masks[nummasks - 1]);
 
-		R_RenderBSPNode((INT32)numnodes - 1);
-		R_ClipSprites();
-		//R_DrawPlanes();
-		//R_DrawMasked();
+			R_ClipSprites(ds_p - (masks[nummasks - 1].drawsegs[1] - masks[nummasks - 1].drawsegs[0]), portal);
 
-		// okay done. free it.
-		portalcullsector = NULL; // Just in case...
-		portal_base = portal->next;
-		Z_Free(portal->ceilingclip);
-		Z_Free(portal->floorclip);
-		Z_Free(portal->frontscale);
-		Z_Free(portal);
+			Portal_Remove(portal);
+		}
 	}
-	// END PORTAL RENDERING
 
 	R_DrawPlanes();
 #ifdef FLOORSPLATS
 	R_DrawVisibleFloorSplats();
 #endif
+
 	// draw mid texture and sprite
 	// And now 3D floors/sides!
-	R_DrawMasked();
+	R_DrawMasked(masks, nummasks);
 
-	// Check for new console commands.
-	NetUpdate();
-
-	// save value to skyVisible1 or skyVisible2
-	// this is so that P1 can't affect whether P2 can see a skybox or not, or vice versa
-	if (splitscreen && player == &players[secondarydisplayplayer])
-		skyVisible2 = skyVisible;
-	else
-		skyVisible1 = skyVisible;
+	free(masks);
 }
 
 // =========================================================================
@@ -1281,7 +1165,6 @@ void R_RegisterEngineStuff(void)
 	if (dedicated)
 		return;
 
-	CV_RegisterVar(&cv_precipdensity);
 	CV_RegisterVar(&cv_translucency);
 	CV_RegisterVar(&cv_drawdist);
 	CV_RegisterVar(&cv_drawdist_nights);
@@ -1331,6 +1214,7 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_grcoronasize);
 #endif
 	CV_RegisterVar(&cv_grmd2);
+	CV_RegisterVar(&cv_grspritebillboarding);
 #endif
 
 #ifdef HWRENDER
diff --git a/src/r_main.h b/src/r_main.h
index 2e768cb9c91cfeff09f67f6ce520427aefc292b1..2c9b5cc3d536aa5a53404b7dbf72d3b4464ffd7c 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -58,6 +58,7 @@ INT32 R_PointOnSide(fixed_t x, fixed_t y, node_t *node);
 INT32 R_PointOnSegSide(fixed_t x, fixed_t y, seg_t *line);
 angle_t R_PointToAngle(fixed_t x, fixed_t y);
 angle_t R_PointToAngle2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1);
+angle_t R_PointToAngleEx(INT32 x2, INT32 y2, INT32 x1, INT32 y1);
 fixed_t R_PointToDist(fixed_t x, fixed_t y);
 fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1);
 
@@ -77,7 +78,7 @@ extern consvar_t cv_chasecam, cv_chasecam2;
 extern consvar_t cv_flipcam, cv_flipcam2;
 extern consvar_t cv_shadow, cv_shadowoffs;
 extern consvar_t cv_translucency;
-extern consvar_t cv_precipdensity, cv_drawdist, cv_drawdist_nights, cv_drawdist_precip;
+extern consvar_t cv_drawdist, cv_drawdist_nights, cv_drawdist_precip;
 extern consvar_t cv_skybox;
 extern consvar_t cv_tailspickup;
 
@@ -93,7 +94,7 @@ void R_ExecuteSetViewSize(void);
 
 void R_SkyboxFrame(player_t *player);
 
-void R_SetupFrame(player_t *player, boolean skybox);
+void R_SetupFrame(player_t *player);
 // Called by G_Drawer.
 void R_RenderPlayerView(player_t *player);
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 670152eda24685b6a05529e46b20430b02e50fd8..2f6f97240a0418f3608d1901a8406b80716cf10f 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -16,19 +16,26 @@
 #include "doomdef.h"
 #include "console.h"
 #include "g_game.h"
+#include "p_setup.h" // levelflats
+#include "p_slopes.h"
 #include "r_data.h"
 #include "r_local.h"
 #include "r_state.h"
 #include "r_splats.h" // faB(21jan):testing
 #include "r_sky.h"
+#include "r_portal.h"
+
 #include "v_video.h"
 #include "w_wad.h"
 #include "z_zone.h"
 #include "p_tick.h"
 
-#include "p_setup.h" // levelflats
-
-#include "p_slopes.h"
+#ifdef TIMING
+#include "p5prof.h"
+	INT64 mycount;
+	INT64 mytotal = 0;
+	UINT32 nombre = 100000;
+#endif
 
 //
 // opening
@@ -38,9 +45,8 @@
 //#define QUINCUNX
 
 //SoM: 3/23/2000: Use Boom visplane hashing.
-#define MAXVISPLANES 512
 
-static visplane_t *visplanes[MAXVISPLANES];
+visplane_t *visplanes[MAXVISPLANES];
 static visplane_t *freetail;
 static visplane_t **freehead = &freetail;
 
@@ -48,7 +54,7 @@ visplane_t *floorplane;
 visplane_t *ceilingplane;
 static visplane_t *currentplane;
 
-planemgr_t ffloor[MAXFFLOORS];
+visffloor_t ffloor[MAXFFLOORS];
 INT32 numffloors;
 
 //SoM: 3/23/2000: Boom visplane hashing routine.
@@ -86,10 +92,9 @@ static fixed_t planeheight;
 //                (this is to calculate yslopes only when really needed)
 //                (when mouselookin', yslope is moving into yslopetab)
 //                Check R_SetupFrame, R_SetViewSize for more...
-fixed_t yslopetab[MAXVIDHEIGHT*4];
+fixed_t yslopetab[MAXVIDHEIGHT*16];
 fixed_t *yslope;
 
-fixed_t distscale[MAXVIDWIDTH];
 fixed_t basexscale, baseyscale;
 
 fixed_t cachedheight[MAXVIDHEIGHT];
@@ -108,78 +113,19 @@ void R_InitPlanes(void)
 	// FIXME: unused
 }
 
-// R_PortalStoreClipValues
-// Saves clipping values for later. -Red
-void R_PortalStoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale)
-{
-	INT32 i;
-	for (i = 0; i < end-start; i++)
-	{
-		*ceil = ceilingclip[start+i];
-		ceil++;
-		*floor = floorclip[start+i];
-		floor++;
-		*scale = frontscale[start+i];
-		scale++;
-	}
-}
-
-// R_PortalRestoreClipValues
-// Inverse of the above. Restores the old value!
-void R_PortalRestoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale)
-{
-	INT32 i;
-	for (i = 0; i < end-start; i++)
-	{
-		ceilingclip[start+i] = *ceil;
-		ceil++;
-		floorclip[start+i] = *floor;
-		floor++;
-		frontscale[start+i] = *scale;
-		scale++;
-	}
-
-	// HACKS FOLLOW
-	for (i = 0; i < start; i++)
-	{
-		floorclip[i] = -1;
-		ceilingclip[i] = (INT16)viewheight;
-	}
-	for (i = end; i < vid.width; i++)
-	{
-		floorclip[i] = -1;
-		ceilingclip[i] = (INT16)viewheight;
-	}
-}
-
-
-//profile stuff ---------------------------------------------------------
-//#define TIMING
-#ifdef TIMING
-#include "p5prof.h"
-         INT64 mycount;
-         INT64 mytotal = 0;
-         UINT32 nombre = 100000;
-#endif
-//profile stuff ---------------------------------------------------------
-
-
 //
 // R_MapPlane
 //
 // Uses global vars:
-//  planeheight
-//  ds_source
 //  basexscale
 //  baseyscale
+//  centerx
 //  viewx
 //  viewy
-//  xoffs
-//  yoffs
-//  planeangle
-//
-// BASIC PRIMITIVE
-//
+//  viewsin
+//  viewcos
+//  viewheight
+
 #ifndef NOWATER
 static INT32 bgofs;
 static INT32 wtofs=0;
@@ -187,10 +133,6 @@ static INT32 waterofs;
 static boolean itswater;
 #endif
 
-#ifdef __mips__
-//#define NOWATER
-#endif
-
 #ifndef NOWATER
 static void R_DrawTranslucentWaterSpan_8(void)
 {
@@ -272,8 +214,8 @@ static void R_DrawTranslucentWaterSpan_8(void)
 
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 {
-	angle_t angle;
-	fixed_t distance, length;
+	angle_t angle, planecos, planesin;
+	fixed_t distance, span;
 	size_t pindex;
 
 #ifdef RANGECHECK
@@ -284,12 +226,22 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 	// from r_splats's R_RenderFloorSplat
 	if (x1 >= vid.width) x1 = vid.width - 1;
 
+	angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
+	planecos = FINECOSINE(angle);
+	planesin = FINESINE(angle);
+
 	if (planeheight != cachedheight[y])
 	{
 		cachedheight[y] = planeheight;
 		distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
 		ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale);
 		ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale);
+
+		if ((span = abs(centery-y)))
+		{
+			ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
+			ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
+		}
 	}
 	else
 	{
@@ -298,13 +250,8 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		ds_ystep = cachedystep[y];
 	}
 
-	length = FixedMul (distance,distscale[x1]);
-	angle = (currentplane->viewangle + currentplane->plangle + xtoviewangle[x1])>>ANGLETOFINESHIFT;
-	/// \note Wouldn't it be faster just to add viewx and viewy
-	// to the plane's x/yoffs anyway??
-
-	ds_xfrac = FixedMul(FINECOSINE(angle), length) + xoffs;
-	ds_yfrac = yoffs - FixedMul(FINESINE(angle), length);
+	ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
+	ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
 
 #ifndef NOWATER
 	if (itswater)
@@ -312,8 +259,9 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		const INT32 yay = (wtofs + (distance>>9) ) & 8191;
 		// ripples da water texture
 		bgofs = FixedDiv(FINESINE(yay), (1<<12) + (distance>>11))>>FRACBITS;
+		angle = (currentplane->viewangle + currentplane->plangle + xtoviewangle[x1])>>ANGLETOFINESHIFT;
 
-		angle = (angle + 2048) & 8191;  //90�
+		angle = (angle + 2048) & 8191;  // 90 degrees
 		ds_xfrac += FixedMul(FINECOSINE(angle), (bgofs<<FRACBITS));
 		ds_yfrac += FixedMul(FINESINE(angle), (bgofs<<FRACBITS));
 
@@ -325,7 +273,6 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 #endif
 
 	pindex = distance >> LIGHTZSHIFT;
-
 	if (pindex >= MAXLIGHTZ)
 		pindex = MAXLIGHTZ - 1;
 
@@ -358,12 +305,27 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 #endif
 }
 
+void R_ClearFFloorClips (void)
+{
+	INT32 i, p;
+
+	// opening / clipping determination
+	for (i = 0; i < viewwidth; i++)
+	{
+		for (p = 0; p < MAXFFLOORS; p++)
+		{
+			ffloor[p].f_clip[i] = (INT16)viewheight;
+			ffloor[p].c_clip[i] = -1;
+		}
+	}
+
+	numffloors = 0;
+}
+
 //
 // R_ClearPlanes
 // At begining of frame.
 //
-// NOTE: Uses con_clipviewtop, so that when console is on,
-//       we don't draw the part of the view hidden under the console.
 void R_ClearPlanes(void)
 {
 	INT32 i, p;
@@ -373,17 +335,15 @@ void R_ClearPlanes(void)
 	for (i = 0; i < viewwidth; i++)
 	{
 		floorclip[i] = (INT16)viewheight;
-		ceilingclip[i] = (INT16)con_clipviewtop;
+		ceilingclip[i] = -1;
 		frontscale[i] = INT32_MAX;
 		for (p = 0; p < MAXFFLOORS; p++)
 		{
 			ffloor[p].f_clip[i] = (INT16)viewheight;
-			ffloor[p].c_clip[i] = (INT16)con_clipviewtop;
+			ffloor[p].c_clip[i] = -1;
 		}
 	}
 
-	numffloors = 0;
-
 	for (i = 0; i < MAXVISPLANES; i++)
 	for (*freehead = visplanes[i], visplanes[i] = NULL;
 		freehead && *freehead ;)
@@ -625,7 +585,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop)
 // overlap.
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop)
 {
-	INT32 unionl, unionh;
+//	INT32 unionl, unionh;
 //	INT32 x;
 
 #ifdef POLYOBJECTS_PLANES
@@ -634,6 +594,9 @@ void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop)
 		return;
 #endif
 
+	if (pl->minx > start) pl->minx = start;
+	if (pl->maxx < stop)  pl->maxx = stop;
+/*
 	if (start < pl->minx)
 	{
 		unionl = start;
@@ -651,15 +614,16 @@ void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop)
 	{
 		unionh = pl->maxx;
 	}
-/*
 	for (x = start; x <= stop; x++)
 		if (pl->top[x] != 0xffff || pl->bottom[x] != 0x0000)
 			break;
 
 	if (x <= stop)
 		I_Error("R_ExpandPlane: planes in same subsector overlap?!\nminx: %d, maxx: %d, start: %d, stop: %d\n", pl->minx, pl->maxx, start, stop);
-*/
+
 	pl->minx = unionl, pl->maxx = unionh;
+*/
+
 }
 
 //
@@ -694,10 +658,10 @@ void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2)
 void R_DrawPlanes(void)
 {
 	visplane_t *pl;
-	INT32 x;
-	INT32 angle;
 	INT32 i;
 
+	// Note: are these two lines really needed?
+	// R_DrawSinglePlane and R_DrawSkyPlane do span/column drawer resets themselves anyway
 	spanfunc = basespanfunc;
 	wallcolfunc = walldrawerfunc;
 
@@ -705,45 +669,6 @@ void R_DrawPlanes(void)
 	{
 		for (pl = visplanes[i]; pl; pl = pl->next)
 		{
-			// sky flat
-			if (pl->picnum == skyflatnum)
-			{
-				if (!viewsky)
-				{
-					skyVisible = true;
-					continue;
-				}
-
-				// use correct aspect ratio scale
-				dc_iscale = skyscale;
-
-				// Sky is always drawn full bright,
-				//  i.e. colormaps[0] is used.
-				// Because of this hack, sky is not affected
-				//  by INVUL inverse mapping.
-				dc_colormap = colormaps;
-				dc_texturemid = skytexturemid;
-				dc_texheight = textureheight[skytexture]
-					>>FRACBITS;
-				for (x = pl->minx; x <= pl->maxx; x++)
-				{
-					dc_yl = pl->top[x];
-					dc_yh = pl->bottom[x];
-
-					if (dc_yl <= dc_yh)
-					{
-						angle = (pl->viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT;
-						dc_iscale = FixedMul(skyscale, FINECOSINE(xtoviewangle[x]>>ANGLETOFINESHIFT));
-						dc_x = x;
-						dc_source =
-							R_GetColumn(texturetranslation[skytexture],
-								angle);
-						wallcolfunc();
-					}
-				}
-				continue;
-			}
-
 			if (pl->ffloor != NULL
 #ifdef POLYOBJECTS_PLANES
 			|| pl->polyobj != NULL
@@ -760,6 +685,49 @@ void R_DrawPlanes(void)
 #endif
 }
 
+// R_DrawSkyPlane
+//
+// Draws the sky within the plane's top/bottom bounds
+// Note: this uses column drawers instead of span drawers, since the sky is always a texture
+//
+static void R_DrawSkyPlane(visplane_t *pl)
+{
+	INT32 x;
+	INT32 angle;
+
+	// Reset column drawer function (note: couldn't we just call walldrawerfunc directly?)
+	// (that is, unless we'll need to switch drawers in future for some reason)
+	wallcolfunc = walldrawerfunc;
+
+	// use correct aspect ratio scale
+	dc_iscale = skyscale;
+
+	// Sky is always drawn full bright,
+	//  i.e. colormaps[0] is used.
+	// Because of this hack, sky is not affected
+	//  by sector colormaps (INVUL inverse mapping is not implemented in SRB2 so is irrelevant).
+	dc_colormap = colormaps;
+	dc_texturemid = skytexturemid;
+	dc_texheight = textureheight[skytexture]
+		>>FRACBITS;
+	for (x = pl->minx; x <= pl->maxx; x++)
+	{
+		dc_yl = pl->top[x];
+		dc_yh = pl->bottom[x];
+
+		if (dc_yl <= dc_yh)
+		{
+			angle = (pl->viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT;
+			dc_iscale = FixedMul(skyscale, FINECOSINE(xtoviewangle[x]>>ANGLETOFINESHIFT));
+			dc_x = x;
+			dc_source =
+				R_GetColumn(texturetranslation[skytexture],
+					-angle); // get negative of angle for each column to display sky correct way round! --Monster Iestyn 27/01/18
+			wallcolfunc();
+		}
+	}
+}
+
 void R_DrawSinglePlane(visplane_t *pl)
 {
 	INT32 light = 0;
@@ -771,6 +739,13 @@ void R_DrawSinglePlane(visplane_t *pl)
 	if (!(pl->minx <= pl->maxx))
 		return;
 
+	// sky flat
+	if (pl->picnum == skyflatnum)
+	{
+		R_DrawSkyPlane(pl);
+		return;
+	}
+
 #ifndef NOWATER
 	itswater = false;
 #endif
@@ -788,7 +763,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else // Opaque, but allow transparent flat pixels
 			spanfunc = splatfunc;
 
-		if (pl->extra_colormap && pl->extra_colormap->fog)
+		if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 		else
 			light = LIGHTLEVELS-1;
@@ -842,7 +817,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 			else // Opaque, but allow transparent flat pixels
 				spanfunc = splatfunc;
 
-			if (pl->extra_colormap && pl->extra_colormap->fog)
+			if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
 				light = (pl->lightlevel >> LIGHTSEGSHIFT);
 			else
 				light = LIGHTLEVELS-1;
diff --git a/src/r_plane.h b/src/r_plane.h
index 16c8c12a448e6cd5b905b066391404889b0b8eb9..238fde1827846f0be8278f6ee6a78e21aaf2c52b 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -18,10 +18,11 @@
 #include "r_data.h"
 #include "p_polyobj.h"
 
+#define MAXVISPLANES 512
+
 //
 // Now what is a visplane, anyway?
 // Simple: kinda floor/ceiling polygon optimised for SRB2 rendering.
-// 7764 bytes! (for win32, anyway)
 //
 typedef struct visplane_s
 {
@@ -39,25 +40,12 @@ typedef struct visplane_s
 	extracolormap_t *extra_colormap;
 
 	// leave pads for [minx-1]/[maxx+1]
-
-	// words sucks .. should get rid of that.. but eats memory
-	// THIS IS UNSIGNED! VERY IMPORTANT!!
-	UINT16 pad1;
-	UINT16 top[MAXVIDWIDTH];
-	UINT16 pad2;
-	UINT16 pad3;
-	UINT16 bottom[MAXVIDWIDTH];
-	UINT16 pad4;
-
+	UINT16 padtopstart, top[MAXVIDWIDTH], padtopend;
+	UINT16 padbottomstart, bottom[MAXVIDWIDTH], padbottomend;
 	INT32 high, low; // R_PlaneBounds should set these.
 
 	fixed_t xoffs, yoffs; // Scrolling flats.
 
-	// SoM: frontscale should be stored in the first seg of the subsector
-	// where the planes themselves are stored. I'm doing this now because
-	// the old way caused trouble with the drawseg array was re-sized.
-	INT32 scaleseg;
-
 	struct ffloor_s *ffloor;
 #ifdef POLYOBJECTS_PLANES
 	polyobj_t *polyobj;
@@ -67,6 +55,7 @@ typedef struct visplane_s
 #endif
 } visplane_t;
 
+extern visplane_t *visplanes[MAXVISPLANES];
 extern visplane_t *floorplane;
 extern visplane_t *ceilingplane;
 
@@ -75,22 +64,19 @@ extern INT16 *lastopening, *openings;
 extern size_t maxopenings;
 
 extern INT16 floorclip[MAXVIDWIDTH], ceilingclip[MAXVIDWIDTH];
-extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*4];
+extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16];
 extern fixed_t cachedheight[MAXVIDHEIGHT];
 extern fixed_t cacheddistance[MAXVIDHEIGHT];
 extern fixed_t cachedxstep[MAXVIDHEIGHT];
 extern fixed_t cachedystep[MAXVIDHEIGHT];
 extern fixed_t basexscale, baseyscale;
 
-extern lighttable_t **planezlight;
-
 extern fixed_t *yslope;
-extern fixed_t distscale[MAXVIDWIDTH];
+extern lighttable_t **planezlight;
 
-FUNCMATH void R_InitPlanes(void);
-void R_PortalStoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale);
-void R_PortalRestoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale);
+void R_InitPlanes(void);
 void R_ClearPlanes(void);
+void R_ClearFFloorClips (void);
 
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2);
 void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2);
@@ -134,8 +120,10 @@ typedef struct planemgr_s
 #ifdef POLYOBJECTS_PLANES
 	polyobj_t *polyobj;
 #endif
-} planemgr_t;
+} visffloor_t;
 
-extern planemgr_t ffloor[MAXFFLOORS];
+extern visffloor_t ffloor[MAXFFLOORS];
 extern INT32 numffloors;
+
+void Portal_AddSkyboxPortals (void);
 #endif
diff --git a/src/r_portal.c b/src/r_portal.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea24cd91c49b420df663e847cdbebb651a3f36db
--- /dev/null
+++ b/src/r_portal.c
@@ -0,0 +1,333 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2018 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_portal.c
+/// \brief Software renderer portals.
+
+#include "r_portal.h"
+#include "r_plane.h"
+#include "r_main.h"
+#include "doomstat.h"
+#include "p_spec.h" // Skybox viewpoints
+#include "z_zone.h"
+#include "r_things.h"
+#include "r_sky.h"
+
+UINT8 portalrender;			/**< When rendering a portal, it establishes the depth of the current BSP traversal. */
+
+// Linked list for portals.
+portal_t *portal_base, *portal_cap;
+
+line_t *portalclipline;
+INT32 portalclipstart, portalclipend;
+
+boolean portalline; // is curline a portal seg?
+
+void Portal_InitList (void)
+{
+	portalrender = 0;
+	portal_base = portal_cap = NULL;
+}
+
+/** Store the clipping window for a portal in its given range.
+ *
+ * The window is copied from the current window at the time
+ * the function is called, so it is useful for converting one-sided
+ * lines into portals.
+ */
+void Portal_ClipRange (portal_t* portal)
+{
+	INT32 start	= portal->start;
+	INT32 end	= portal->end;
+	INT16 *ceil		= portal->ceilingclip;
+	INT16 *floor	= portal->floorclip;
+	fixed_t *scale	= portal->frontscale;
+
+	INT32 i;
+	for (i = 0; i < end-start; i++)
+	{
+		*ceil = ceilingclip[start+i];
+		ceil++;
+		*floor = floorclip[start+i];
+		floor++;
+		*scale = frontscale[start+i];
+		scale++;
+	}
+}
+
+/** Apply the clipping window from a portal.
+ */
+void Portal_ClipApply (const portal_t* portal)
+{
+	INT32 i;
+	INT32 start	= portal->start;
+	INT32 end	= portal->end;
+	INT16 *ceil		= portal->ceilingclip;
+	INT16 *floor	= portal->floorclip;
+	fixed_t *scale	= portal->frontscale;
+
+	for (i = 0; i < end-start; i++)
+	{
+		ceilingclip[start+i] = *ceil;
+		ceil++;
+		floorclip[start+i] = *floor;
+		floor++;
+		frontscale[start+i] = *scale;
+		scale++;
+	}
+
+	// HACKS FOLLOW
+	for (i = 0; i < start; i++)
+	{
+		floorclip[i] = -1;
+		ceilingclip[i] = (INT16)viewheight;
+	}
+	for (i = end; i < vid.width; i++)
+	{
+		floorclip[i] = -1;
+		ceilingclip[i] = (INT16)viewheight;
+	}
+}
+
+static portal_t* Portal_Add (const INT16 x1, const INT16 x2)
+{
+	portal_t *portal		= Z_Malloc(sizeof(portal_t), PU_LEVEL, NULL);
+	INT16 *ceilingclipsave	= Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL);
+	INT16 *floorclipsave	= Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL);
+	fixed_t *frontscalesave	= Z_Malloc(sizeof(fixed_t)*(x2-x1 + 1), PU_LEVEL, NULL);
+
+	// Linked list.
+	if (!portal_base)
+	{
+		portal_base	= portal;
+		portal_cap	= portal;
+	}
+	else
+	{
+		portal_cap->next = portal;
+		portal_cap = portal;
+	}
+	portal->next = NULL;
+
+	// Store clipping values so they can be restored once the portal is rendered.
+	portal->ceilingclip	= ceilingclipsave;
+	portal->floorclip	= floorclipsave;
+	portal->frontscale	= frontscalesave;
+	portal->start	= x1;
+	portal->end		= x2;
+
+	// Increase recursion level.
+	portal->pass = portalrender+1;
+
+	return portal;
+}
+
+void Portal_Remove (portal_t* portal)
+{
+	portal_base = portal->next;
+	Z_Free(portal->ceilingclip);
+	Z_Free(portal->floorclip);
+	Z_Free(portal->frontscale);
+	Z_Free(portal);
+}
+
+/** Creates a portal out of two lines and a determined screen range.
+ *
+ * line1 determines the entrance, and line2 the exit.
+ * x1 and x2 determine the screen's column bounds.
+
+ * The view's offset from the entry line center is obtained,
+ * and then rotated&translated to the exit line's center.
+ * When the portal renders, it will create the illusion of
+ * the two lines being seamed together.
+ */
+void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2)
+{
+	portal_t* portal = Portal_Add(x1, x2);
+
+	// Offset the portal view by the linedef centers
+	line_t* start	= &lines[line1];
+	line_t* dest	= &lines[line2];
+
+	angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0);
+
+	fixed_t disttopoint;
+	angle_t angtopoint;
+
+	vertex_t dest_c, start_c;
+
+	// looking glass center
+	start_c.x = (start->v1->x + start->v2->x) / 2;
+	start_c.y = (start->v1->y + start->v2->y) / 2;
+
+	// other side center
+	dest_c.x = (dest->v1->x + dest->v2->x) / 2;
+	dest_c.y = (dest->v1->y + dest->v2->y) / 2;
+
+	disttopoint = R_PointToDist2(start_c.x, start_c.y, viewx, viewy);
+	angtopoint = R_PointToAngle2(start_c.x, start_c.y, viewx, viewy);
+	angtopoint += dangle;
+
+	portal->viewx = dest_c.x + FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+	portal->viewy = dest_c.y + FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+	portal->viewz = viewz + dest->frontsector->floorheight - start->frontsector->floorheight;
+	portal->viewangle = viewangle + dangle;
+
+	portal->clipline = line2;
+
+	Portal_ClipRange(portal);
+
+	portalline = true; // this tells R_StoreWallRange that curline is a portal seg
+}
+
+/** Store the clipping window for a portal using a visplane.
+ *
+ * Since visplanes top/bottom windows work in an identical way,
+ * it can just be copied almost directly.
+ */
+static void Portal_ClipVisplane (const visplane_t* plane, portal_t* portal)
+{
+	INT16 start	= portal->start;
+	INT16 end	= portal->end;
+	INT32 i;
+
+	for (i = 0; i < end - start; i++)
+	{
+		// Invalid column.
+		if (plane->top[i + start] == 65535)
+		{
+			portal->ceilingclip[i] = -1;
+			portal->floorclip[i] = -1;
+			continue;
+		}
+		portal->ceilingclip[i] = plane->top[i + start] - 1;
+		portal->floorclip[i] = plane->bottom[i + start] + 1;
+		portal->frontscale[i] = INT32_MAX;
+	}
+}
+
+extern INT32 viewwidth;
+
+static boolean TrimVisplaneBounds (const visplane_t* plane, INT16* start, INT16* end)
+{
+	*start = plane->minx;
+	*end = plane->maxx + 1;
+
+	// Visplanes have 1-px pads on their sides (extra columns).
+	// Trim them, else it may render out of bounds.
+	if (*end > viewwidth)
+		*end = viewwidth;
+
+	if (!(*start < *end))
+		return true;
+
+
+	/** Trims a visplane's horizontal gap to match its render area.
+	 *
+	 * Visplanes' minx/maxx may sometimes exceed the area they're
+	 * covering. This merely adjusts the boundaries to the next
+	 * valid area.
+	 */
+
+	while (plane->bottom[*start] == 0 && plane->top[*start] == 65535 && *start < *end)
+	{
+		(*start)++;
+	}
+
+
+	while (plane->bottom[*end - 1] == 0 && plane->top[*start] == 65535 && *end > *start)
+	{
+		(*end)--;
+	}
+
+	return false;
+}
+
+/** Creates a skybox portal out of a visplane.
+ *
+ * Applies the necessary offsets and rotation to give
+ * a depth illusion to the skybox.
+ */
+void Portal_AddSkybox (const visplane_t* plane)
+{
+	INT16 start, end;
+	mapheader_t *mh;
+	portal_t* portal;
+
+	if (TrimVisplaneBounds(plane, &start, &end))
+		return;
+
+	portal = Portal_Add(start, end);
+
+	Portal_ClipVisplane(plane, portal);
+
+	portal->viewx = skyboxmo[0]->x;
+	portal->viewy = skyboxmo[0]->y;
+	portal->viewz = skyboxmo[0]->z;
+	portal->viewangle = viewangle + skyboxmo[0]->angle;
+
+	mh = mapheaderinfo[gamemap-1];
+
+	// If a relative viewpoint exists, offset the viewpoint.
+	if (skyboxmo[1])
+	{
+		fixed_t x = 0, y = 0;
+		angle_t ang = skyboxmo[0]->angle>>ANGLETOFINESHIFT;
+
+		if (mh->skybox_scalex > 0)
+			x = (viewx - skyboxmo[1]->x) / mh->skybox_scalex;
+		else if (mh->skybox_scalex < 0)
+			x = (viewx - skyboxmo[1]->x) * -mh->skybox_scalex;
+
+		if (mh->skybox_scaley > 0)
+			y = (viewy - skyboxmo[1]->y) / mh->skybox_scaley;
+		else if (mh->skybox_scaley < 0)
+			y = (viewy - skyboxmo[1]->y) * -mh->skybox_scaley;
+
+		// Apply transform to account for the skybox viewport angle.
+		portal->viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
+		portal->viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
+	}
+
+	if (mh->skybox_scalez > 0)
+		portal->viewz += viewz / mh->skybox_scalez;
+	else if (mh->skybox_scalez < 0)
+		portal->viewz += viewz * -mh->skybox_scalez;
+
+	portal->clipline = -1;
+}
+
+/** Creates portals for the currently existing sky visplanes.
+ * The visplanes are also removed and cleared from the list.
+ */
+void Portal_AddSkyboxPortals (void)
+{
+	visplane_t *pl;
+	INT32 i;
+	UINT16 count = 0;
+
+	for (i = 0; i < MAXVISPLANES; i++, pl++)
+	{
+		for (pl = visplanes[i]; pl; pl = pl->next)
+		{
+			if (pl->picnum == skyflatnum)
+			{
+				Portal_AddSkybox(pl);
+
+				pl->minx = 0;
+				pl->maxx = -1;
+
+				count++;
+			}
+		}
+	}
+
+	CONS_Debug(DBG_RENDER, "Skybox portals: %d\n", count);
+}
diff --git a/src/r_portal.h b/src/r_portal.h
new file mode 100644
index 0000000000000000000000000000000000000000..e8f9119e84a6eddc41e2bf651cf1ac408772bc2d
--- /dev/null
+++ b/src/r_portal.h
@@ -0,0 +1,59 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2018 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_portal.h
+/// \brief Software renderer portal struct, functions, linked list extern.
+
+#ifndef __R_PORTAL__
+#define __R_PORTAL__
+
+#include "r_data.h"
+#include "r_plane.h" // visplanes
+
+/** Portal structure for the software renderer.
+ */
+typedef struct portal_s
+{
+	struct portal_s *next;
+
+	// Viewport.
+	fixed_t viewx;
+	fixed_t viewy;
+	fixed_t viewz;
+	angle_t viewangle;
+
+	UINT8 pass;			/**< Keeps track of the portal's recursion depth. */
+	INT32 clipline;		/**< Optional clipline for line-based portals. */
+
+	// Clipping information.
+	INT32 start;		/**< First horizontal pixel coordinate to draw at. */
+	INT32 end;			/**< Last horizontal pixel coordinate to draw at. */
+	INT16 *ceilingclip; /**< Temporary screen top clipping array. */
+	INT16 *floorclip;	/**< Temporary screen bottom clipping array. */
+	fixed_t *frontscale;/**< Temporary screen bottom clipping array. */
+} portal_t;
+
+extern portal_t* portal_base;
+extern portal_t* portal_cap;
+extern UINT8 portalrender;
+
+extern line_t *portalclipline;
+extern INT32 portalclipstart, portalclipend;
+
+void Portal_InitList	(void);
+void Portal_Remove		(portal_t* portal);
+void Portal_Add2Lines	(const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2);
+void Portal_AddSkybox	(const visplane_t* plane);
+
+void Portal_ClipRange (portal_t* portal);
+void Portal_ClipApply (const portal_t* portal);
+
+void Portal_AddSkyboxPortals (void);
+#endif
diff --git a/src/r_segs.c b/src/r_segs.c
index ecf707e0f5f6132b4d0ed5df0143ccb8778a9a08..6eb81ce7a4a64a78a3cda0765bd64149fc0eb280 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,7 @@
 #include "r_local.h"
 #include "r_sky.h"
 
+#include "r_portal.h"
 #include "r_splats.h"
 
 #include "w_wad.h"
@@ -413,7 +414,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 #endif
 			rlight->startheight = rlight->height; // keep starting value here to reset for each repeat
 			rlight->lightlevel = *light->lightlevel;
-			rlight->extra_colormap = light->extra_colormap;
+			rlight->extra_colormap = *light->extra_colormap;
 			rlight->flags = light->flags;
 
 			if (rlight->flags & FF_FOG || (rlight->extra_colormap && rlight->extra_colormap->fog))
@@ -607,8 +608,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 						else
 							rlight->rcolormap = xwalllights[pindex];
 
-						rlight->height += rlight->heightstep;
 						height = rlight->height;
+						rlight->height += rlight->heightstep;
 
 						if (height <= windowtop)
 						{
@@ -858,49 +859,46 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			light = &frontsector->lightlist[i];
 			rlight = &dc_lightlist[p];
 #ifdef ESLOPE
-			if (light->slope) {
-				leftheight = P_GetZAt(light->slope, ds->leftpos.x, ds->leftpos.y);
-				rightheight = P_GetZAt(light->slope, ds->rightpos.x, ds->rightpos.y);
-			} else
-				leftheight = rightheight = light->height;
 
-			if (*pfloor->b_slope) {
-				pfloorleft = P_GetZAt(*pfloor->b_slope, ds->leftpos.x, ds->leftpos.y);
-				pfloorright = P_GetZAt(*pfloor->b_slope, ds->rightpos.x, ds->rightpos.y);
-			} else
-				pfloorleft = pfloorright = *pfloor->bottomheight;
+#define SLOPEPARAMS(slope, end1, end2, normalheight) \
+	if (slope) { \
+		end1 = P_GetZAt(slope, ds->leftpos.x, ds->leftpos.y); \
+		end2 = P_GetZAt(slope, ds->rightpos.x, ds->rightpos.y); \
+	} else \
+		end1 = end2 = normalheight;
+
+			SLOPEPARAMS(light->slope,     leftheight, rightheight, light->height)
+			SLOPEPARAMS(*pfloor->b_slope, pfloorleft, pfloorright, *pfloor->bottomheight)
 
 			if (leftheight < pfloorleft && rightheight < pfloorright)
 				continue;
 
-			if (*pfloor->t_slope) {
-				pfloorleft = P_GetZAt(*pfloor->t_slope, ds->leftpos.x, ds->leftpos.y);
-				pfloorright = P_GetZAt(*pfloor->t_slope, ds->rightpos.x, ds->rightpos.y);
-			} else
-				pfloorleft = pfloorright = *pfloor->topheight;
+			SLOPEPARAMS(*pfloor->t_slope, pfloorleft, pfloorright, *pfloor->topheight)
 
 			if (leftheight > pfloorleft && rightheight > pfloorright && i+1 < dc_numlights)
 			{
 				lightlist_t *nextlight = &frontsector->lightlist[i+1];
-				if (nextlight->slope ? P_GetZAt(nextlight->slope, ds->leftpos.x, ds->leftpos.y) : nextlight->height > pfloorleft
-				 && nextlight->slope ? P_GetZAt(nextlight->slope, ds->rightpos.x, ds->rightpos.y) : nextlight->height > pfloorright)
+				if ((nextlight->slope ? P_GetZAt(nextlight->slope, ds->leftpos.x, ds->leftpos.y) : nextlight->height) > pfloorleft
+				 && (nextlight->slope ? P_GetZAt(nextlight->slope, ds->rightpos.x, ds->rightpos.y) : nextlight->height) > pfloorright)
 					continue;
 			}
 
 			leftheight -= viewz;
 			rightheight -= viewz;
 
+#define CLAMPMAX INT32_MAX
+#define CLAMPMIN (-INT32_MAX) // This is not INT32_MIN on purpose! INT32_MIN makes the drawers freak out.
+			// Monster Iestyn (25/03/18): do not skip these lights if they fail overflow test, just clamp them instead so they behave.
 			overflow_test = (INT64)centeryfrac - (((INT64)leftheight*ds->scale1)>>FRACBITS);
-			if (overflow_test < 0) overflow_test = -overflow_test;
-			if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) continue;
-			overflow_test = (INT64)centeryfrac - (((INT64)rightheight*ds->scale2)>>FRACBITS);
-			if (overflow_test < 0) overflow_test = -overflow_test;
-			if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) continue;
+			if      (overflow_test > (INT64)CLAMPMAX) rlight->height = CLAMPMAX;
+			else if (overflow_test > (INT64)CLAMPMIN) rlight->height = (fixed_t)overflow_test;
+			else                                      rlight->height = CLAMPMIN;
 
-			rlight->height = (centeryfrac) - FixedMul(leftheight, ds->scale1);
-			rlight->heightstep = (centeryfrac) - FixedMul(rightheight, ds->scale2);
+			overflow_test = (INT64)centeryfrac - (((INT64)rightheight*ds->scale2)>>FRACBITS);
+			if      (overflow_test > (INT64)CLAMPMAX) rlight->heightstep = CLAMPMAX;
+			else if (overflow_test > (INT64)CLAMPMIN) rlight->heightstep = (fixed_t)overflow_test;
+			else                                      rlight->heightstep = CLAMPMIN;
 			rlight->heightstep = (rlight->heightstep-rlight->height)/(range);
-			rlight->height -= rlight->heightstep;
 #else
 			if (light->height < *pfloor->bottomheight)
 				continue;
@@ -910,41 +908,37 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 			lheight = light->height;// > *pfloor->topheight ? *pfloor->topheight + FRACUNIT : light->height;
 			rlight->heightstep = -FixedMul (rw_scalestep, (lheight - viewz));
-			rlight->height = (centeryfrac) - FixedMul((lheight - viewz), spryscale) - rlight->heightstep;
+			rlight->height = (centeryfrac) - FixedMul((lheight - viewz), spryscale);
 #endif
 			rlight->flags = light->flags;
 			if (light->flags & FF_CUTLEVEL)
 			{
 #ifdef ESLOPE
-				if (*light->caster->b_slope) {
-					leftheight = P_GetZAt(*light->caster->b_slope, ds->leftpos.x, ds->leftpos.y);
-					rightheight = P_GetZAt(*light->caster->b_slope, ds->rightpos.x, ds->rightpos.y);
-				} else
-					leftheight = rightheight = *light->caster->bottomheight;
-
+				SLOPEPARAMS(*light->caster->b_slope, leftheight, rightheight, *light->caster->bottomheight)
+#undef SLOPEPARAMS
 				leftheight -= viewz;
 				rightheight -= viewz;
 
+				// Monster Iestyn (25/03/18): do not skip these lights if they fail overflow test, just clamp them instead so they behave.
 				overflow_test = (INT64)centeryfrac - (((INT64)leftheight*ds->scale1)>>FRACBITS);
-				if (overflow_test < 0) overflow_test = -overflow_test;
-				if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) continue;
-				overflow_test = (INT64)centeryfrac - (((INT64)rightheight*ds->scale2)>>FRACBITS);
-				if (overflow_test < 0) overflow_test = -overflow_test;
-				if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) continue;
+				if      (overflow_test > (INT64)CLAMPMAX) rlight->botheight = CLAMPMAX;
+				else if (overflow_test > (INT64)CLAMPMIN) rlight->botheight = (fixed_t)overflow_test;
+				else                                      rlight->botheight = CLAMPMIN;
 
-				rlight->botheight = (centeryfrac) - FixedMul(leftheight, ds->scale1);
-				rlight->botheightstep = (centeryfrac) - FixedMul(rightheight, ds->scale2);
+				overflow_test = (INT64)centeryfrac - (((INT64)rightheight*ds->scale2)>>FRACBITS);
+				if      (overflow_test > (INT64)CLAMPMAX) rlight->botheightstep = CLAMPMAX;
+				else if (overflow_test > (INT64)CLAMPMIN) rlight->botheightstep = (fixed_t)overflow_test;
+				else                                      rlight->botheightstep = CLAMPMIN;
 				rlight->botheightstep = (rlight->botheightstep-rlight->botheight)/(range);
-				rlight->botheight -= rlight->botheightstep;
 #else
 				lheight = *light->caster->bottomheight;// > *pfloor->topheight ? *pfloor->topheight + FRACUNIT : *light->caster->bottomheight;
 				rlight->botheightstep = -FixedMul (rw_scalestep, (lheight - viewz));
-				rlight->botheight = (centeryfrac) - FixedMul((lheight - viewz), spryscale) - rlight->botheightstep;
+				rlight->botheight = (centeryfrac) - FixedMul((lheight - viewz), spryscale);
 #endif
 			}
 
 			rlight->lightlevel = *light->lightlevel;
-			rlight->extra_colormap = light->extra_colormap;
+			rlight->extra_colormap = *light->extra_colormap;
 
 			// Check if the current light effects the colormap/lightlevel
 			if (pfloor->flags & FF_FOG)
@@ -1115,9 +1109,6 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	}
 #endif
 
-#define CLAMPMAX INT32_MAX
-#define CLAMPMIN (-INT32_MAX) // This is not INT32_MIN on purpose! INT32_MIN makes the drawers freak out.
-
 	// draw the columns
 	for (dc_x = x1; dc_x <= x2; dc_x++)
 	{
@@ -1130,33 +1121,28 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 				oldx = dc_x;
 			}
 #endif
-			// 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;
+			// Calculate bounds
+			// clamp the values if necessary to avoid overflows and rendering glitches caused by them
 
 #ifdef ESLOPE
-				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;
+			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;
 #else
-				sprtopscreen = windowtop = (centeryfrac - FixedMul((dc_texturemid - offsetvalue), spryscale));
-				sprbotscreen = windowbottom = FixedMul(*pfloor->topheight - *pfloor->bottomheight, spryscale) + sprtopscreen;
+			sprtopscreen = windowtop = (centeryfrac - FixedMul((dc_texturemid - offsetvalue), spryscale));
+			sprbotscreen = windowbottom = FixedMul(*pfloor->topheight - *pfloor->bottomheight, spryscale) + sprtopscreen;
 #endif
 
-				// 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)
 				{
 					for (i = 0; i < dc_numlights; i++)
 					{
@@ -1165,14 +1151,25 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 						if (rlight->flags & FF_CUTLEVEL)
 							rlight->botheight += rlight->botheightstep;
 					}
-					spryscale += rw_scalestep;
-					continue;
 				}
+				spryscale += rw_scalestep;
+				continue;
+			}
 
-				dc_iscale = 0xffffffffu / (unsigned)spryscale;
+			dc_iscale = 0xffffffffu / (unsigned)spryscale;
 
-				// draw the texture
-				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]) - 3);
+
+			// SoM: New code does not rely on R_DrawColumnShadowed_8 which
+			// will (hopefully) put less strain on the stack.
+			if (dc_numlights)
+			{
+				lighttable_t **xwalllights;
+				fixed_t height;
+				fixed_t bheight = 0;
+				INT32 solid = 0;
+				INT32 lighteffect = 0;
 
 				for (i = 0; i < dc_numlights; i++)
 				{
@@ -1192,7 +1189,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 						pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
 
-						if (pindex >=  MAXLIGHTSCALE)
+						if (pindex >= MAXLIGHTSCALE)
 							pindex = MAXLIGHTSCALE-1;
 
 						if (pfloor->flags & FF_FOG)
@@ -1231,13 +1228,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 					else
 						solid = 0;
 
-					rlight->height += rlight->heightstep;
 					height = rlight->height;
+					rlight->height += rlight->heightstep;
 
 					if (solid)
 					{
-						rlight->botheight += rlight->botheightstep;
 						bheight = rlight->botheight - (FRACUNIT >> 1);
+						rlight->botheight += rlight->botheightstep;
 					}
 
 					if (height <= windowtop)
@@ -1253,6 +1250,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 					if (windowbottom >= sprbotscreen)
 					{
 						windowbottom = sprbotscreen;
+						// draw the texture
 						colfunc_2s (col);
 						for (i++; i < dc_numlights; i++)
 						{
@@ -1263,6 +1261,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 						}
 						continue;
 					}
+					// draw the texture
 					colfunc_2s (col);
 					if (solid)
 						windowtop = bheight;
@@ -1272,6 +1271,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 						dc_colormap = rlight->rcolormap;
 				}
 				windowbottom = sprbotscreen;
+				// draw the texture, if there is any space left
 				if (windowtop < windowbottom)
 					colfunc_2s (col);
 
@@ -1286,31 +1286,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 				pindex = MAXLIGHTSCALE - 1;
 
 			dc_colormap = walllights[pindex];
-			if (frontsector->extra_colormap)
-				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
+
 			if (pfloor->flags & FF_FOG && pfloor->master->frontsector->extra_colormap)
 				dc_colormap = pfloor->master->frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
-
-#ifdef ESLOPE
-			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;
-#else
-			sprtopscreen = windowtop = (centeryfrac - FixedMul((dc_texturemid - offsetvalue), spryscale));
-			sprbotscreen = windowbottom = FixedMul(*pfloor->topheight - *pfloor->bottomheight, spryscale) + sprtopscreen;
-#endif
-
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
+			else if (frontsector->extra_colormap)
+				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
 
 			// draw the texture
-			col = (column_t *)((UINT8 *)R_GetColumn(texnum,maskedtexturecol[dc_x]) - 3);
-
 			colfunc_2s (col);
 			spryscale += rw_scalestep;
 		}
@@ -1321,6 +1303,18 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 #undef CLAMPMIN
 }
 
+// R_ExpandPlaneY
+//
+// A simple function to modify a vsplane's top and bottom for a particular column
+// Sort of like R_ExpandPlane in r_plane.c, except this is vertical expansion
+static inline void R_ExpandPlaneY(visplane_t *pl, INT32 x, INT16 top, INT16 bottom)
+{
+	// Expand the plane, don't shrink it!
+	// note: top and bottom default to 0xFFFF and 0x0000 respectively, which is totally compatible with this
+	if (pl->top[x] > top)       pl->top[x] = top;
+	if (pl->bottom[x] < bottom) pl->bottom[x] = bottom;
+}
+
 //
 // R_RenderSegLoop
 // Draws zero, one, or two textures (and possibly a masked
@@ -1344,7 +1338,6 @@ UINT32 nombre = 100000;
 #endif
 //profile stuff ---------------------------------------------------------
 
-
 static void R_RenderSegLoop (void)
 {
 	angle_t angle;
@@ -1366,7 +1359,6 @@ static void R_RenderSegLoop (void)
 		// mark floor / ceiling areas
 		yl = (topfrac+HEIGHTUNIT-1)>>HEIGHTBITS;
 
-		// no space above wall?
 		top = ceilingclip[rw_x]+1;
 
 		// no space above wall?
@@ -1375,16 +1367,19 @@ static void R_RenderSegLoop (void)
 
 		if (markceiling)
 		{
+#if 0
 			bottom = yl-1;
 
 			if (bottom >= floorclip[rw_x])
 				bottom = floorclip[rw_x]-1;
 
 			if (top <= bottom)
-			{
-				ceilingplane->top[rw_x] = (INT16)top;
-				ceilingplane->bottom[rw_x] = (INT16)bottom;
-			}
+#else
+			bottom = yl > floorclip[rw_x] ? floorclip[rw_x] : yl;
+
+			if (top <= --bottom && ceilingplane)
+#endif
+				R_ExpandPlaneY(ceilingplane, rw_x, top, bottom);
 		}
 
 
@@ -1397,24 +1392,10 @@ static void R_RenderSegLoop (void)
 
 		if (markfloor)
 		{
-#if 0 // Old Doom Legacy code
-			bottom = floorclip[rw_x]-1;
-			if (top <= ceilingclip[rw_x])
-				top = ceilingclip[rw_x]+1;
-			if (top <= bottom && floorplane)
-			{
-				floorplane->top[rw_x] = (INT16)top;
-				floorplane->bottom[rw_x] = (INT16)bottom;
-			}
-#else // Spiffy new PRBoom code
-			top  = yh < ceilingclip[rw_x] ? ceilingclip[rw_x] : yh;
+			top = yh < ceilingclip[rw_x] ? ceilingclip[rw_x] : yh;
 
 			if (++top <= bottom && floorplane)
-			{
-				floorplane->top[rw_x] = (INT16)top;
-				floorplane->bottom[rw_x] = (INT16)bottom;
-			}
-#endif
+				R_ExpandPlaneY(floorplane, rw_x, top, bottom);
 		}
 
 		if (numffloors)
@@ -1428,14 +1409,6 @@ static void R_RenderSegLoop (void)
 #ifdef POLYOBJECTS_PLANES
 				if (ffloor[i].polyobj && (!curline->polyseg || ffloor[i].polyobj != curline->polyseg))
 					continue;
-
-				// FIXME hack to fix planes disappearing when a seg goes behind the camera. This NEEDS to be changed to be done properly. -Red
-				if (curline->polyseg) {
-					if (ffloor[i].plane->minx > rw_x)
-						ffloor[i].plane->minx = rw_x;
-					else if (ffloor[i].plane->maxx < rw_x)
-						ffloor[i].plane->maxx = rw_x;
-				}
 #endif
 
 				if (ffloor[i].height < viewz)
@@ -1451,8 +1424,9 @@ static void R_RenderSegLoop (void)
 
 #ifdef POLYOBJECTS_PLANES
 					// Polyobject-specific hack to fix plane leaking -Red
-					if (curline->polyseg && ffloor[i].polyobj && ffloor[i].polyobj == curline->polyseg && top_w >= bottom_w) {
-						ffloor[i].plane->top[rw_x] = ffloor[i].plane->bottom[rw_x] = 0xFFFF;
+					if (ffloor[i].polyobj && top_w >= bottom_w) {
+						ffloor[i].plane->top[rw_x] = 0xFFFF;
+						ffloor[i].plane->bottom[rw_x] = 0x0000; // fix for sky plane drawing crashes - Monster Iestyn 25/05/18
 					} else
 #endif
 
@@ -1475,8 +1449,9 @@ static void R_RenderSegLoop (void)
 
 #ifdef POLYOBJECTS_PLANES
 					// Polyobject-specific hack to fix plane leaking -Red
-					if (curline->polyseg && ffloor[i].polyobj && ffloor[i].polyobj == curline->polyseg && top_w >= bottom_w) {
-						ffloor[i].plane->top[rw_x] = ffloor[i].plane->bottom[rw_x] = 0xFFFF;
+					if (ffloor[i].polyobj && top_w >= bottom_w) {
+						ffloor[i].plane->top[rw_x] = 0xFFFF;
+						ffloor[i].plane->bottom[rw_x] = 0x0000; // fix for sky plane drawing crashes - Monster Iestyn 25/05/18
 					} else
 #endif
 
@@ -1698,26 +1673,11 @@ static void R_RenderSegLoop (void)
 		}
 
 		for (i = 0; i < numffloors; i++)
-		{
-#ifdef POLYOBJECTS_PLANES
-			if (ffloor[i].polyobj && (!curline->polyseg || ffloor[i].polyobj != curline->polyseg))
-				continue;
-#endif
-
 			ffloor[i].f_frac += ffloor[i].f_step;
-		}
 
 		for (i = 0; i < numbackffloors; i++)
 		{
-			INT32 y_w;
-
-#ifdef POLYOBJECTS_PLANES
-			if (ffloor[i].polyobj && (!curline->polyseg || ffloor[i].polyobj != curline->polyseg))
-				continue;
-#endif
-			y_w = ffloor[i].b_frac >> HEIGHTBITS;
-
-			ffloor[i].f_clip[rw_x] = ffloor[i].c_clip[rw_x] = (INT16)(y_w & 0xFFFF);
+			ffloor[i].f_clip[rw_x] = ffloor[i].c_clip[rw_x] = (INT16)((ffloor[i].b_frac >> HEIGHTBITS) & 0xFFFF);
 			ffloor[i].b_frac += ffloor[i].b_step;
 		}
 
@@ -1727,6 +1687,23 @@ static void R_RenderSegLoop (void)
 	}
 }
 
+// Uses precalculated seg->length
+static INT64 R_CalcSegDist(seg_t* seg, INT64 x2, INT64 y2)
+{
+	if (!seg->linedef->dy)
+		return llabs(y2 - seg->v1->y);
+	else if (!seg->linedef->dx)
+		return llabs(x2 - seg->v1->x);
+	else
+	{
+		INT64 dx = (seg->v2->x)-(seg->v1->x);
+		INT64 dy = (seg->v2->y)-(seg->v1->y);
+		INT64 vdx = x2-(seg->v1->x);
+		INT64 vdy = y2-(seg->v1->y);
+		return ((dy*vdx)-(dx*vdy))/(seg->length);
+	}
+}
+
 //
 // R_StoreWallRange
 // A wall segment will be drawn
@@ -1737,6 +1714,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	fixed_t       hyp;
 	fixed_t       sineval;
 	angle_t       distangle, offsetangle;
+	boolean longboi;
 #ifndef ESLOPE
 	fixed_t       vtop;
 #endif
@@ -1760,6 +1738,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 	if (ds_p == drawsegs+maxdrawsegs)
 	{
+		size_t curpos = curdrawsegs - drawsegs;
 		size_t pos = ds_p - drawsegs;
 		size_t newmax = maxdrawsegs ? maxdrawsegs*2 : 128;
 		if (firstseg)
@@ -1767,6 +1746,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		drawsegs = Z_Realloc(drawsegs, newmax*sizeof (*drawsegs), PU_STATIC, NULL);
 		ds_p = drawsegs + pos;
 		maxdrawsegs = newmax;
+		curdrawsegs = drawsegs + curpos;
 		if (firstseg)
 			firstseg = drawsegs + (size_t)firstseg;
 	}
@@ -1782,10 +1762,15 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		offsetangle = ANGLE_90;
 
 	distangle = ANGLE_90 - offsetangle;
-	hyp = R_PointToDist (curline->v1->x, curline->v1->y);
 	sineval = FINESINE(distangle>>ANGLETOFINESHIFT);
-	rw_distance = FixedMul (hyp, sineval);
 
+	hyp = R_PointToDist(curline->v1->x, curline->v1->y);
+	rw_distance = FixedMul(hyp, sineval);
+	longboi = (hyp >= INT32_MAX);
+
+	// big room fix
+	if (longboi)
+		rw_distance = (fixed_t)R_CalcSegDist(curline,viewx,viewy);
 
 	ds_p->x1 = rw_x = start;
 	ds_p->x2 = stop;
@@ -1908,29 +1893,26 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		}
 	}
 
-	if (frontsector->c_slope) {
-		worldtop = P_GetZAt(frontsector->c_slope, segleft.x, segleft.y) - viewz;
-		worldtopslope = P_GetZAt(frontsector->c_slope, segright.x, segright.y) - viewz;
-	} else {
-		worldtopslope =
-#else
-	{
-#endif
-		worldtop = frontsector->ceilingheight - viewz;
-	}
 
-
-#ifdef ESLOPE
-	if (frontsector->f_slope) {
-		worldbottom = P_GetZAt(frontsector->f_slope, segleft.x, segleft.y) - viewz;
-		worldbottomslope = P_GetZAt(frontsector->f_slope, segright.x, segright.y) - viewz;
-	} else {
-		worldbottomslope =
+#define SLOPEPARAMS(slope, end1, end2, normalheight) \
+	if (slope) { \
+		end1 = P_GetZAt(slope, segleft.x, segleft.y); \
+		end2 = P_GetZAt(slope, segright.x, segright.y); \
+	} else \
+		end1 = end2 = normalheight;
+
+	SLOPEPARAMS(frontsector->c_slope, worldtop,    worldtopslope,    frontsector->ceilingheight)
+	SLOPEPARAMS(frontsector->f_slope, worldbottom, worldbottomslope, frontsector->floorheight)
+	// subtract viewz from these to turn them into
+	// positions relative to the camera's z position
+	worldtop -= viewz;
+	worldtopslope -= viewz;
+	worldbottom -= viewz;
+	worldbottomslope -= viewz;
 #else
-	{
+	worldtop = frontsector->ceilingheight - viewz;
+	worldbottom = frontsector->floorheight - viewz;
 #endif
-		worldbottom = frontsector->floorheight - viewz;
-	}
 
 	midtexture = toptexture = bottomtexture = maskedtexture = 0;
 	ds_p->maskedtexturecol = NULL;
@@ -2032,125 +2014,128 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	else
 	{
 		// two sided line
+		boolean bothceilingssky = false; // turned on if both back and front ceilings are sky
+		boolean bothfloorssky = false; // likewise, but for floors
 
 #ifdef ESLOPE
-		if (backsector->c_slope) {
-			worldhigh = P_GetZAt(backsector->c_slope, segleft.x, segleft.y) - viewz;
-			worldhighslope = P_GetZAt(backsector->c_slope, segright.x, segright.y) - viewz;
-		} else {
-			worldhighslope =
+		SLOPEPARAMS(backsector->c_slope, worldhigh, worldhighslope, backsector->ceilingheight)
+		SLOPEPARAMS(backsector->f_slope, worldlow,  worldlowslope,  backsector->floorheight)
+		worldhigh -= viewz;
+		worldhighslope -= viewz;
+		worldlow -= viewz;
+		worldlowslope -= viewz;
 #else
-		{
+		worldhigh = backsector->ceilingheight - viewz;
+		worldlow = backsector->floorheight - viewz;
 #endif
-			worldhigh = backsector->ceilingheight - viewz;
-		}
-
-
-#ifdef ESLOPE
-		if (backsector->f_slope) {
-			worldlow = P_GetZAt(backsector->f_slope, segleft.x, segleft.y) - viewz;
-			worldlowslope = P_GetZAt(backsector->f_slope, segright.x, segright.y) - viewz;
-		} else {
-			worldlowslope =
-#else
-		{
-#endif
-			worldlow = backsector->floorheight - viewz;
-		}
-
 
 		// hack to allow height changes in outdoor areas
+		// This is what gets rid of the upper textures if there should be sky
 		if (frontsector->ceilingpic == skyflatnum
 			&& backsector->ceilingpic == skyflatnum)
 		{
-#ifdef ESLOPE
-			worldtopslope = worldhighslope =
-#endif
-			worldtop = worldhigh;
+			bothceilingssky = true;
+		}
+
+		// likewise, but for floors and upper textures
+		if (frontsector->floorpic == skyflatnum
+			&& backsector->floorpic == skyflatnum)
+		{
+			bothfloorssky = true;
 		}
 
 		ds_p->sprtopclip = ds_p->sprbottomclip = NULL;
 		ds_p->silhouette = 0;
 
-		if (
+		if (!bothfloorssky)
+		{
+			if (
 #ifdef ESLOPE
-			worldbottomslope > worldlowslope ||
+				worldbottomslope > worldlowslope ||
 #endif
-			worldbottom > worldlow)
-		{
-			ds_p->silhouette = SIL_BOTTOM;
+				worldbottom > worldlow)
+			{
+				ds_p->silhouette = SIL_BOTTOM;
 #ifdef ESLOPE
-			if ((backsector->f_slope ? P_GetZAt(backsector->f_slope, viewx, viewy) : backsector->floorheight) > viewz)
-				ds_p->bsilheight = INT32_MAX;
-			else
-				ds_p->bsilheight = (frontsector->f_slope ? INT32_MAX : frontsector->floorheight);
+				if ((backsector->f_slope ? P_GetZAt(backsector->f_slope, viewx, viewy) : backsector->floorheight) > viewz)
+					ds_p->bsilheight = INT32_MAX;
+				else
+					ds_p->bsilheight = (frontsector->f_slope ? INT32_MAX : frontsector->floorheight);
 #else
-			ds_p->bsilheight = frontsector->floorheight;
+				ds_p->bsilheight = frontsector->floorheight;
 #endif
-		}
+			}
 #ifdef ESLOPE
-		else if ((backsector->f_slope ? P_GetZAt(backsector->f_slope, viewx, viewy) : backsector->floorheight) > viewz)
+			else if ((backsector->f_slope ? P_GetZAt(backsector->f_slope, viewx, viewy) : backsector->floorheight) > viewz)
 #else
-		else if (backsector->floorheight > viewz)
+			else if (backsector->floorheight > viewz)
 #endif
-		{
-			ds_p->silhouette = SIL_BOTTOM;
-			ds_p->bsilheight = INT32_MAX;
-			// ds_p->sprbottomclip = negonearray;
+			{
+				ds_p->silhouette = SIL_BOTTOM;
+				ds_p->bsilheight = INT32_MAX;
+				// ds_p->sprbottomclip = negonearray;
+			}
 		}
 
-		if (
+		if (!bothceilingssky)
+		{
+			if (
 #ifdef ESLOPE
-			worldtopslope < worldhighslope ||
+				worldtopslope < worldhighslope ||
 #endif
-			worldtop < worldhigh)
-		{
-			ds_p->silhouette |= SIL_TOP;
+				worldtop < worldhigh)
+			{
+				ds_p->silhouette |= SIL_TOP;
 #ifdef ESLOPE
-			if ((backsector->c_slope ? P_GetZAt(backsector->c_slope, viewx, viewy) : backsector->ceilingheight) < viewz)
-				ds_p->tsilheight = INT32_MIN;
-			else
-				ds_p->tsilheight = (frontsector->c_slope ? INT32_MIN : frontsector->ceilingheight);
+				if ((backsector->c_slope ? P_GetZAt(backsector->c_slope, viewx, viewy) : backsector->ceilingheight) < viewz)
+					ds_p->tsilheight = INT32_MIN;
+				else
+					ds_p->tsilheight = (frontsector->c_slope ? INT32_MIN : frontsector->ceilingheight);
 #else
-			ds_p->tsilheight = frontsector->ceilingheight;
+				ds_p->tsilheight = frontsector->ceilingheight;
 #endif
-		}
+			}
 #ifdef ESLOPE
-		else if ((backsector->c_slope ? P_GetZAt(backsector->c_slope, viewx, viewy) : backsector->ceilingheight) < viewz)
+			else if ((backsector->c_slope ? P_GetZAt(backsector->c_slope, viewx, viewy) : backsector->ceilingheight) < viewz)
 #else
-		else if (backsector->ceilingheight < viewz)
+			else if (backsector->ceilingheight < viewz)
 #endif
-		{
-			ds_p->silhouette |= SIL_TOP;
-			ds_p->tsilheight = INT32_MIN;
-			// ds_p->sprtopclip = screenheightarray;
+			{
+				ds_p->silhouette |= SIL_TOP;
+				ds_p->tsilheight = INT32_MIN;
+				// ds_p->sprtopclip = screenheightarray;
+			}
 		}
 
+		if (!bothceilingssky && !bothfloorssky)
+		{
 #ifdef ESLOPE
-		if (worldhigh <= worldbottom && worldhighslope <= worldbottomslope)
+			if (worldhigh <= worldbottom && worldhighslope <= worldbottomslope)
 #else
-		if (worldhigh <= worldbottom)
+			if (worldhigh <= worldbottom)
 #endif
-		{
-			ds_p->sprbottomclip = negonearray;
-			ds_p->bsilheight = INT32_MAX;
-			ds_p->silhouette |= SIL_BOTTOM;
-		}
+			{
+				ds_p->sprbottomclip = negonearray;
+				ds_p->bsilheight = INT32_MAX;
+				ds_p->silhouette |= SIL_BOTTOM;
+			}
 
 #ifdef ESLOPE
-		if (worldlow >= worldtop && worldlowslope >= worldtopslope)
+			if (worldlow >= worldtop && worldlowslope >= worldtopslope)
 #else
-		if (worldlow >= worldtop)
+			if (worldlow >= worldtop)
 #endif
-		{
-			ds_p->sprtopclip = screenheightarray;
-			ds_p->tsilheight = INT32_MIN;
-			ds_p->silhouette |= SIL_TOP;
+			{
+				ds_p->sprtopclip = screenheightarray;
+				ds_p->tsilheight = INT32_MIN;
+				ds_p->silhouette |= SIL_TOP;
+			}
 		}
 
 		//SoM: 3/25/2000: This code fixes an automap bug that didn't check
 		// frontsector->ceiling and backsector->floor to see if a door was closed.
 		// Without the following code, sprites get displayed behind closed doors.
+		if (!bothceilingssky && !bothfloorssky)
 		{
 #ifdef ESLOPE
 			if (doorclosed || (worldhigh <= worldbottom && worldhighslope <= worldbottomslope))
@@ -2174,7 +2159,13 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 		}
 
-		if (worldlow != worldbottom
+		if (bothfloorssky)
+		{
+			// see double ceiling skies comment
+			// this is the same but for upside down thok barriers where the floor is sky and the ceiling is normal
+			markfloor = false;
+		}
+		else if (worldlow != worldbottom
 #ifdef ESLOPE
 			|| worldlowslope != worldbottomslope
 			|| backsector->f_slope != frontsector->f_slope
@@ -2186,7 +2177,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->floor_yoffs != frontsector->floor_yoffs
 		    || backsector->floorpic_angle != frontsector->floorpic_angle
 		    //SoM: 3/22/2000: Prevents bleeding.
-		    || frontsector->heightsec != -1
+		    || (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
 		    || backsector->floorlightsec != frontsector->floorlightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
@@ -2200,7 +2191,14 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			markfloor = false;
 		}
 
-		if (worldhigh != worldtop
+		if (bothceilingssky)
+		{
+			// double ceiling skies are special
+			// we don't want to lower the ceiling clipping, (no new plane is drawn anyway)
+			// so we can see the floor of thok barriers always regardless of sector properties
+			markceiling = false;
+		}
+		else if (worldhigh != worldtop
 #ifdef ESLOPE
 			|| worldhighslope != worldtopslope
 			|| backsector->c_slope != frontsector->c_slope
@@ -2226,19 +2224,28 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			markceiling = false;
 		}
 
-		if (backsector->ceilingheight <= frontsector->floorheight ||
-		    backsector->floorheight >= frontsector->ceilingheight)
+		if (!bothceilingssky && !bothfloorssky)
 		{
-			// closed door
-			markceiling = markfloor = true;
+#ifdef ESLOPE
+			if ((worldhigh <= worldbottom && worldhighslope <= worldbottomslope)
+			 || (worldlow >= worldtop && worldlowslope >= worldtopslope))
+#else
+			if (backsector->ceilingheight <= frontsector->floorheight
+			 || backsector->floorheight >= frontsector->ceilingheight)
+#endif
+			{
+				// closed door
+				markceiling = markfloor = true;
+			}
 		}
 
 		// check TOP TEXTURE
-		if (worldhigh < worldtop
+		if (!bothceilingssky // never draw the top texture if on
+			&& (worldhigh < worldtop
 #ifdef ESLOPE
 				|| worldhighslope < worldtopslope
 #endif
-			)
+			))
 		{
 			fixed_t texheight;
 			// top texture
@@ -2287,11 +2294,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 		}
 		// check BOTTOM TEXTURE
-		if (worldlow > worldbottom
+		if (!bothfloorssky // never draw the bottom texture if on
+			&& (worldlow > worldbottom
 #ifdef ESLOPE
 				|| worldlowslope > worldbottomslope
 #endif
-			)     //seulement si VISIBLE!!!
+			))     //seulement si VISIBLE!!!
 		{
 			// bottom texture
 			bottomtexture = R_GetTextureNum(sidedef->bottomtexture);
@@ -2364,16 +2372,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 						continue;
 
 #ifdef ESLOPE
-					if (*rover->t_slope) {
-						high1 = P_GetZAt(*rover->t_slope, segleft.x, segleft.y);
-						highslope1 = P_GetZAt(*rover->t_slope, segright.x, segright.y);
-					} else
-						high1 = highslope1 = *rover->topheight;
-					if (*rover->b_slope) {
-						low1 = P_GetZAt(*rover->b_slope, segleft.x, segleft.y);
-						lowslope1 = P_GetZAt(*rover->b_slope, segright.x, segright.y);
-					} else
-						low1 = lowslope1 = *rover->bottomheight;
+					SLOPEPARAMS(*rover->t_slope, high1, highslope1, *rover->topheight)
+					SLOPEPARAMS(*rover->b_slope, low1,  lowslope1,  *rover->bottomheight)
 
 					if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope))
 						continue;
@@ -2405,16 +2405,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 						}
 
 #ifdef ESLOPE
-						if (*r2->t_slope) {
-							high2 = P_GetZAt(*r2->t_slope, segleft.x, segleft.y);
-							highslope2 = P_GetZAt(*r2->t_slope, segright.x, segright.y);
-						} else
-							high2 = highslope2 = *r2->topheight;
-						if (*r2->b_slope) {
-							low2 = P_GetZAt(*r2->b_slope, segleft.x, segleft.y);
-							lowslope2 = P_GetZAt(*r2->b_slope, segright.x, segright.y);
-						} else
-							low2 = lowslope2 = *r2->bottomheight;
+						SLOPEPARAMS(*r2->t_slope, high2, highslope2, *r2->topheight)
+						SLOPEPARAMS(*r2->b_slope, low2,  lowslope2,  *r2->bottomheight)
 
 						if ((high2 < lowcut || highslope2 < lowcutslope) || (low2 > highcut || lowslope2 > highcutslope))
 							continue;
@@ -2447,16 +2439,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 						continue;
 
 #ifdef ESLOPE
-					if (*rover->t_slope) {
-						high1 = P_GetZAt(*rover->t_slope, segleft.x, segleft.y);
-						highslope1 = P_GetZAt(*rover->t_slope, segright.x, segright.y);
-					} else
-						high1 = highslope1 = *rover->topheight;
-					if (*rover->b_slope) {
-						low1 = P_GetZAt(*rover->b_slope, segleft.x, segleft.y);
-						lowslope1 = P_GetZAt(*rover->b_slope, segright.x, segright.y);
-					} else
-						low1 = lowslope1 = *rover->bottomheight;
+					SLOPEPARAMS(*rover->t_slope, high1, highslope1, *rover->topheight)
+					SLOPEPARAMS(*rover->b_slope, low1,  lowslope1,  *rover->bottomheight)
 
 					if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope))
 						continue;
@@ -2488,17 +2472,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 						}
 
 #ifdef ESLOPE
-						if (*r2->t_slope) {
-							high2 = P_GetZAt(*r2->t_slope, segleft.x, segleft.y);
-							highslope2 = P_GetZAt(*r2->t_slope, segright.x, segright.y);
-						} else
-							high2 = highslope2 = *r2->topheight;
-						if (*r2->b_slope) {
-							low2 = P_GetZAt(*r2->b_slope, segleft.x, segleft.y);
-							lowslope2 = P_GetZAt(*r2->b_slope, segright.x, segright.y);
-						} else
-							low2 = lowslope2 = *r2->bottomheight;
-
+						SLOPEPARAMS(*r2->t_slope, high2, highslope2, *r2->topheight)
+						SLOPEPARAMS(*r2->b_slope, low2,  lowslope2,  *r2->bottomheight)
+#undef SLOPEPARAMS
 						if ((high2 < lowcut || highslope2 < lowcutslope) || (low2 > highcut || lowslope2 > highcutslope))
 							continue;
 						if ((high1 > high2 || highslope1 > highslope2) || (low1 < low2 || lowslope1 < lowslope2))
@@ -2642,8 +2618,18 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		if (offsetangle > ANGLE_90)
 			offsetangle = ANGLE_90;
 
-		sineval = FINESINE(offsetangle >>ANGLETOFINESHIFT);
-		rw_offset = FixedMul (hyp, sineval);
+		sineval = FINESINE(offsetangle>>ANGLETOFINESHIFT);
+		rw_offset = FixedMul(hyp, sineval);
+
+		// big room fix
+		if (longboi)
+		{
+			INT64 dx = (curline->v2->x)-(curline->v1->x);
+			INT64 dy = (curline->v2->y)-(curline->v1->y);
+			INT64 vdx = viewx-(curline->v1->x);
+			INT64 vdy = viewy-(curline->v1->y);
+			rw_offset = ((dx*vdx-dy*vdy))/(curline->length);
+		}
 
 		if (rw_normalangle-rw_angle1 < ANGLE_180)
 			rw_offset = -rw_offset;
@@ -2677,7 +2663,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	//  and doesn't need to be marked.
 	if (frontsector->heightsec == -1)
 	{
-		if ((
+		if (frontsector->floorpic != skyflatnum
+		&& (
 #ifdef ESLOPE
 			frontsector->f_slope ? P_GetZAt(frontsector->f_slope, viewx, viewy) :
 #endif
@@ -2687,12 +2674,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			markfloor = false;
 		}
 
-		if ((
+		if (frontsector->ceilingpic != skyflatnum
+		&& (
 #ifdef ESLOPE
 			frontsector->c_slope ? P_GetZAt(frontsector->c_slope, viewx, viewy) :
 #endif
-			frontsector->ceilingheight) <= viewz &&
-		    frontsector->ceilingpic != skyflatnum)
+			frontsector->ceilingheight) <= viewz)
 		{
 			// below view plane
 			markceiling = false;
@@ -2710,6 +2697,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	if (linedef->special == 41) { // HORIZON LINES
 		topstep = bottomstep = 0;
 		topfrac = bottomfrac = (centeryfrac>>4);
+		topfrac++; // Prevent 1px HOM
 	} else {
 		topstep = -FixedMul (rw_scalestep, worldtop);
 		topfrac = (centeryfrac>>4) - FixedMul (worldtop, rw_scale);
@@ -2822,7 +2810,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 
 			rlight->lightlevel = *light->lightlevel;
-			rlight->extra_colormap = light->extra_colormap;
+			rlight->extra_colormap = *light->extra_colormap;
 			p++;
 		}
 
@@ -2833,20 +2821,26 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	{
 		for (i = 0; i < numffloors; i++)
 		{
-#ifdef POLYOBJECTS_PLANES
-			if (ffloor[i].polyobj && (!curline->polyseg || ffloor[i].polyobj != curline->polyseg))
-				continue;
-#endif
-
 			ffloor[i].f_pos >>= 4;
 #ifdef ESLOPE
 			ffloor[i].f_pos_slope >>= 4;
-			ffloor[i].f_frac = (centeryfrac>>4) - FixedMul(ffloor[i].f_pos, rw_scale);
-			ffloor[i].f_step = ((centeryfrac>>4) - FixedMul(ffloor[i].f_pos_slope, ds_p->scale2) - ffloor[i].f_frac)/(range);
+#endif
+			if (linedef->special == 41) // Horizon lines extend FOFs in contact with them too.
+			{
+				ffloor[i].f_step = 0;
+				ffloor[i].f_frac = (centeryfrac>>4);
+				topfrac++; // Prevent 1px HOM
+			}
+			else
+			{
+#ifdef ESLOPE
+				ffloor[i].f_frac = (centeryfrac>>4) - FixedMul(ffloor[i].f_pos, rw_scale);
+				ffloor[i].f_step = ((centeryfrac>>4) - FixedMul(ffloor[i].f_pos_slope, ds_p->scale2) - ffloor[i].f_frac)/(range);
 #else
-			ffloor[i].f_step = FixedMul(-rw_scalestep, ffloor[i].f_pos);
-			ffloor[i].f_frac = (centeryfrac>>4) - FixedMul(ffloor[i].f_pos, rw_scale);
+				ffloor[i].f_step = FixedMul(-rw_scalestep, ffloor[i].f_pos);
+				ffloor[i].f_frac = (centeryfrac>>4) - FixedMul(ffloor[i].f_pos, rw_scale);
 #endif
+			}
 		}
 	}
 
@@ -3118,7 +3112,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		if (ceilingplane) //SoM: 3/29/2000: Check for null ceiling planes
 			ceilingplane = R_CheckPlane (ceilingplane, rw_x, rw_stopx-1);
 		else
-			markceiling = 0;
+			markceiling = false;
+
+		// Don't mark ceiling flat lines for polys unless this line has an upper texture, otherwise we get flat leakage pulling downward
+		// (If it DOES have an upper texture and we do this, the ceiling won't render at all)
+		if (curline->polyseg && !curline->sidedef->toptexture)
+			markceiling = false;
 	}
 
 	// get a new or use the same visplane
@@ -3127,7 +3126,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		if (floorplane) //SoM: 3/29/2000: Check for null planes
 			floorplane = R_CheckPlane (floorplane, rw_x, rw_stopx-1);
 		else
-			markfloor = 0;
+			markfloor = false;
+
+		// Don't mark floor flat lines for polys unless this line has a lower texture, otherwise we get flat leakage pulling upward
+		// (If it DOES have a lower texture and we do this, the floor won't render at all)
+		if (curline->polyseg && !curline->sidedef->bottomtexture)
+			markfloor = false;
 	}
 
 	ds_p->numffloorplanes = 0;
@@ -3150,6 +3154,22 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			for (i = 0; i < numffloors; i++)
 				R_ExpandPlane(ffloor[i].plane, rw_x, rw_stopx - 1);
 		}
+#ifdef POLYOBJECTS_PLANES
+		// FIXME hack to fix planes disappearing when a seg goes behind the camera. This NEEDS to be changed to be done properly. -Red
+		if (curline->polyseg)
+		{
+			for (i = 0; i < numffloors; i++)
+			{
+				if (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg)
+					continue;
+				if (ffloor[i].plane->minx > rw_x)
+					ffloor[i].plane->minx = rw_x;
+
+				if (ffloor[i].plane->maxx < rw_stopx - 1)
+					ffloor[i].plane->maxx = rw_stopx - 1;
+			}
+		}
+#endif
 	}
 
 #ifdef WALLSPLATS
diff --git a/src/r_segs.h b/src/r_segs.h
index 4187d36e020c093bf42972e12bce85d97b80b56b..92d0100e2ee49765bfe4e733257d72b5122d6bc3 100644
--- a/src/r_segs.h
+++ b/src/r_segs.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_sky.c b/src/r_sky.c
index 898424a9925d1f1fadb89f208eaf4c4a6d9d88f8..817e8368a722b2cd81a2328a4b11f3e1ff3b77dd 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_sky.h b/src/r_sky.h
index aa4bda375229f1d127c855a15f8a377f6d54452b..86b615595d22506b99a582e683c4a3f87290f902 100644
--- a/src/r_sky.h
+++ b/src/r_sky.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_splats.c b/src/r_splats.c
index f6d7e78f312bc71611014326db1eafb111b2bd67..9ab671274403f1f7dfea967271f286d8aafef1cd 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -365,9 +365,8 @@ static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTe
 #else
 	lighttable_t **planezlight;
 	fixed_t planeheight;
-	angle_t angle;
-	fixed_t distance;
-	fixed_t length;
+	angle_t angle, planecos, planesin;
+	fixed_t distance, span;
 	size_t indexr;
 	INT32 light;
 #endif
@@ -473,12 +472,22 @@ static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTe
 		if (x2 >= vid.width)
 			x2 = vid.width - 1;
 
+		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
+		planecos = FINECOSINE(angle);
+		planesin = FINESINE(angle);
+
 		if (planeheight != cachedheight[y])
 		{
 			cachedheight[y] = planeheight;
 			distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
 			ds_xstep = cachedxstep[y] = FixedMul(distance,basexscale);
 			ds_ystep = cachedystep[y] = FixedMul(distance,baseyscale);
+
+			if ((span = abs(centery-y)))
+			{
+				ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
+				ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
+			}
 		}
 		else
 		{
@@ -486,10 +495,9 @@ static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTe
 			ds_xstep = cachedxstep[y];
 			ds_ystep = cachedystep[y];
 		}
-		length = FixedMul(distance, distscale[x1]);
-		angle = (viewangle + xtoviewangle[x1])>>ANGLETOFINESHIFT;
-		ds_xfrac = viewx + FixedMul(FINECOSINE(angle), length);
-		ds_yfrac = -viewy - FixedMul(FINESINE(angle), length);
+
+		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
+		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
 		ds_xfrac -= offsetx;
 		ds_yfrac += offsety;
 
diff --git a/src/r_splats.h b/src/r_splats.h
index c0ba6881c7b384126796a30b9a9c8b5526419844..387b2958295eaa8f09945c0a2d05b79377627192 100644
--- a/src/r_splats.h
+++ b/src/r_splats.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -63,11 +63,7 @@ typedef struct floorsplat_s
 fixed_t P_SegLength(seg_t *seg);
 
 // call at P_SetupLevel()
-#if !(defined (WALLSPLATS) || defined (FLOORSPLATS))
-FUNCMATH void R_ClearLevelSplats(void);
-#else
 void R_ClearLevelSplats(void);
-#endif
 
 #ifdef WALLSPLATS
 void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
diff --git a/src/r_state.h b/src/r_state.h
index ac3e1fa42576a050c4edfdf177b560defc08eead..da9425bdf0405160810e7102a4d53ce1241eb881 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -40,11 +40,7 @@ extern sprcache_t *spritecachedinfo;
 extern lighttable_t *colormaps;
 
 // Boom colormaps.
-// Had to put a limit on colormaps :(
-#define MAXCOLORMAPS 60
-
-extern size_t num_extra_colormaps;
-extern extracolormap_t extra_colormaps[MAXCOLORMAPS];
+extern extracolormap_t *extra_colormaps;
 
 // for global animation
 extern INT32 *texturetranslation;
@@ -84,14 +80,8 @@ extern side_t *sides;
 //
 extern fixed_t viewx, viewy, viewz;
 extern angle_t viewangle, aimingangle;
-extern boolean viewsky, skyVisible;
-extern boolean skyVisible1, skyVisible2; // saved values of skyVisible for P1 and P2, for splitscreen
 extern sector_t *viewsector;
 extern player_t *viewplayer;
-extern UINT8 portalrender;
-extern sector_t *portalcullsector;
-extern line_t *portalclipline;
-extern INT32 portalclipstart, portalclipend;
 
 extern consvar_t cv_allowmlook;
 extern consvar_t cv_maxportals;
diff --git a/src/r_things.c b/src/r_things.c
index d201786a596b87cb87ee47b40a454df2262616a6..92f2b946020bde6317faf9f52102cdab58f10d2d 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -24,6 +24,7 @@
 #include "i_video.h" // rendermode
 #include "r_things.h"
 #include "r_plane.h"
+#include "r_portal.h"
 #include "p_tick.h"
 #include "p_local.h"
 #include "p_slopes.h"
@@ -380,10 +381,9 @@ void R_AddSpriteDefs(UINT16 wadnum)
 	char wadname[MAX_WADPATH];
 
 	// Find the sprites section in this resource file.
-	if (wadfiles[wadnum]->type == RET_PK3)
-		start = W_CheckNumForFolderStartPK3("Sprites/", wadnum, 0);
-	else
+	switch (wadfiles[wadnum]->type)
 	{
+	case RET_WAD:
 		start = W_CheckNumForNamePwad("S_START", wadnum, 0);
 		if (start == INT16_MAX)
 			start = W_CheckNumForNamePwad("SS_START", wadnum, 0); //deutex compatib.
@@ -391,21 +391,23 @@ void R_AddSpriteDefs(UINT16 wadnum)
 			start = 0; //let say S_START is lump 0
 		else
 			start++;   // just after S_START
-	}
-
-	// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
-	if (start == 0 && W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
-		return;
 
-	if (wadfiles[wadnum]->type == RET_PK3)
-		end = W_CheckNumForFolderEndPK3("Sprites/", wadnum, start);
-	else
-	{
 		end = W_CheckNumForNamePwad("S_END",wadnum,start);
 		if (end == INT16_MAX)
 			end = W_CheckNumForNamePwad("SS_END",wadnum,start);     //deutex compatib.
+		break;
+	case RET_PK3:
+		start = W_CheckNumForFolderStartPK3("Sprites/", wadnum, 0);
+		end = W_CheckNumForFolderEndPK3("Sprites/", wadnum, start);
+		break;
+	default:
+		return;
 	}
 
+	// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
+	if (start == 0 && W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
+		return;
+
 	if (end == INT16_MAX)
 	{
 		CONS_Debug(DBG_SETUP, "no sprites in pwad %d\n", wadnum);
@@ -443,7 +445,7 @@ void R_AddSpriteDefs(UINT16 wadnum)
 //
 // GAME FUNCTIONS
 //
-static UINT32 visspritecount;
+UINT32 visspritecount;
 static UINT32 clippedvissprites;
 static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
@@ -484,7 +486,11 @@ void R_InitSprites(void)
 	// it can be is do before loading config for skin cvar possible value
 	R_InitSkins();
 	for (i = 0; i < numwadfiles; i++)
+	{
 		R_AddSkins((UINT16)i);
+		R_PatchSkins((UINT16)i);
+	}
+	ST_ReloadSkinFaceGraphics();
 
 	//
 	// check if all sprites have frames
@@ -717,11 +723,11 @@ static void R_DrawVisSprite(vissprite_t *vis)
 
 	colfunc = basecolfunc; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
-	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
+	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
 	{
 		// translate certain pixels to white
 		colfunc = transcolfunc;
-		if (vis->mobj->type == MT_CYBRAKDEMON)
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
 			dc_translation = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
 		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
 			dc_translation = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
@@ -732,7 +738,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	{
 		colfunc = transtransfunc;
 		dc_transmap = vis->transmap;
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -751,7 +759,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		colfunc = transcolfunc;
 
 		// New colormap stuff for skins Tails 06-07-2002
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -981,9 +991,10 @@ static void R_SplitSprite(vissprite_t *sprite)
 			else
 				spritelights = scalelight[lightnum];
 
-			newsprite->extra_colormap = sector->lightlist[i].extra_colormap;
+			newsprite->extra_colormap = *sector->lightlist[i].extra_colormap;
 
-			if (!((newsprite->cut & SC_FULLBRIGHT) && (!newsprite->extra_colormap || !newsprite->extra_colormap->fog)))
+			if (!((newsprite->cut & SC_FULLBRIGHT)
+				&& (!newsprite->extra_colormap || !(newsprite->extra_colormap->fog & 1))))
 			{
 				lindex = FixedMul(sprite->xscale, FixedDiv(640, vid.width))>>(LIGHTSCALESHIFT);
 
@@ -1247,7 +1258,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	}
 
 	// PORTAL SPRITE CLIPPING
-	if (portalrender)
+	if (portalrender && portalclipline)
 	{
 		if (x2 < portalclipstart || x1 > portalclipend)
 			return;
@@ -1349,8 +1360,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	{
 		if (vis->x1 < portalclipstart)
 			vis->x1 = portalclipstart;
-		if (vis->x2 > portalclipend)
-			vis->x2 = portalclipend;
+		if (vis->x2 >= portalclipend)
+			vis->x2 = portalclipend-1;
 	}
 
 	vis->xscale = xscale; //SoM: 4/17/2000
@@ -1359,7 +1370,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, sortscale))>>FRACBITS);
 	vis->cut = cut;
 	if (thing->subsector->sector->numlights)
-		vis->extra_colormap = thing->subsector->sector->lightlist[light].extra_colormap;
+		vis->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
 	else
 		vis->extra_colormap = thing->subsector->sector->extra_colormap;
 
@@ -1403,7 +1414,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		vis->cut |= SC_FULLBRIGHT;
 
 	if (vis->cut & SC_FULLBRIGHT
-		&& (!vis->extra_colormap || !vis->extra_colormap->fog))
+		&& (!vis->extra_colormap || !(vis->extra_colormap->fog & 1)))
 	{
 		// full bright: goggles
 		vis->colormap = colormaps;
@@ -1515,7 +1526,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 		return;
 
 	// PORTAL SPRITE CLIPPING
-	if (portalrender)
+	if (portalrender && portalclipline)
 	{
 		if (x2 < portalclipstart || x1 > portalclipend)
 			return;
@@ -1524,6 +1535,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 			return;
 	}
 
+
 	//SoM: 3/17/2000: Disregard sprites that are out of view..
 	gzt = thing->z + spritecachedinfo[lump].topoffset;
 	gz = gzt - spritecachedinfo[lump].height;
@@ -1531,7 +1543,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	if (thing->subsector->sector->cullheight)
 	{
 		if (R_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, viewz, gz, gzt))
-			return;
+			goto weatherthink;
 	}
 
 	// store information in a vissprite
@@ -1556,8 +1568,8 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	{
 		if (vis->x1 < portalclipstart)
 			vis->x1 = portalclipstart;
-		if (vis->x2 > portalclipend)
-			vis->x2 = portalclipend;
+		if (vis->x2 >= portalclipend)
+			vis->x2 = portalclipend-1;
 	}
 
 	vis->xscale = xscale; //SoM: 4/17/2000
@@ -1591,6 +1603,17 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	// Fullbright
 	vis->colormap = colormaps;
+
+weatherthink:
+	// okay... this is a hack, but weather isn't networked, so it should be ok
+	if (!(thing->precipflags & PCF_THUNK))
+	{
+		if (thing->precipflags & PCF_RAIN)
+			P_RainThinker(thing);
+		else
+			P_SnowThinker(thing);
+		thing->precipflags |= PCF_THUNK;
+	}
 }
 
 // R_AddSprites
@@ -1641,8 +1664,10 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
 
-			if (approx_dist <= limit_dist)
-				R_ProjectSprite(thing);
+			if (approx_dist > limit_dist)
+				continue;
+
+			R_ProjectSprite(thing);
 		}
 	}
 	else
@@ -1653,7 +1678,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 				R_ProjectSprite(thing);
 	}
 
-	// Someone seriously wants infinite draw distance for precipitation?
+	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
 	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
@@ -1663,25 +1688,18 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 
 			approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
 
-			if (approx_dist <= limit_dist)
-				R_ProjectPrecipitationSprite(precipthing);
+			if (approx_dist > limit_dist)
+				continue;
+
+			R_ProjectPrecipitationSprite(precipthing);
 		}
 	}
-	else
-	{
-		// Draw everything in sector, no checks
-		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
-			if (!(precipthing->precipflags & PCF_INVISIBLE))
-				R_ProjectPrecipitationSprite(precipthing);
-	}
 }
 
 //
 // R_SortVisSprites
 //
-static vissprite_t vsprsortedhead;
-
-void R_SortVisSprites(void)
+static void R_SortVisSprites(vissprite_t* vsprsortedhead, UINT32 start, UINT32 end)
 {
 	UINT32       i, linkedvissprites = 0;
 	vissprite_t *ds, *dsprev, *dsnext, *dsfirst;
@@ -1690,20 +1708,17 @@ void R_SortVisSprites(void)
 	fixed_t      bestscale;
 	INT32        bestdispoffset;
 
-	if (!visspritecount)
-		return;
-
 	unsorted.next = unsorted.prev = &unsorted;
 
-	dsfirst = R_GetVisSprite(0);
+	dsfirst = R_GetVisSprite(start);
 
 	// The first's prev and last's next will be set to
 	// nonsense, but are fixed in a moment
-	for (i = 0, dsnext = dsfirst, ds = NULL; i < visspritecount; i++)
+	for (i = start, dsnext = dsfirst, ds = NULL; i < end; i++)
 	{
 		dsprev = ds;
 		ds = dsnext;
-		if (i < visspritecount - 1) dsnext = R_GetVisSprite(i + 1);
+		if (i < end - 1) dsnext = R_GetVisSprite(i + 1);
 
 		ds->next = dsnext;
 		ds->prev = dsprev;
@@ -1781,8 +1796,8 @@ void R_SortVisSprites(void)
 	}
 
 	// pull the vissprites out by scale
-	vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead;
-	for (i = 0; i < visspritecount-linkedvissprites; i++)
+	vsprsortedhead->next = vsprsortedhead->prev = vsprsortedhead;
+	for (i = start; i < end-linkedvissprites; i++)
 	{
 		bestscale = bestdispoffset = INT32_MAX;
 		for (ds = unsorted.next; ds != &unsorted; ds = ds->next)
@@ -1807,10 +1822,10 @@ void R_SortVisSprites(void)
 		}
 		best->next->prev = best->prev;
 		best->prev->next = best->next;
-		best->next = &vsprsortedhead;
-		best->prev = vsprsortedhead.prev;
-		vsprsortedhead.prev->next = best;
-		vsprsortedhead.prev = best;
+		best->next = vsprsortedhead;
+		best->prev = vsprsortedhead->prev;
+		vsprsortedhead->prev->next = best;
+		vsprsortedhead->prev = best;
 	}
 }
 
@@ -1820,28 +1835,28 @@ void R_SortVisSprites(void)
 static drawnode_t *R_CreateDrawNode(drawnode_t *link);
 
 static drawnode_t nodebankhead;
-static drawnode_t nodehead;
 
-static void R_CreateDrawNodes(void)
+static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean tempskip)
 {
 	drawnode_t *entry;
 	drawseg_t *ds;
 	INT32 i, p, best, x1, x2;
 	fixed_t bestdelta, delta;
 	vissprite_t *rover;
+	static vissprite_t vsprsortedhead;
 	drawnode_t *r2;
 	visplane_t *plane;
 	INT32 sintersect;
 	fixed_t scale = 0;
 
 	// Add the 3D floors, thicksides, and masked textures...
-	for (ds = ds_p; ds-- > drawsegs ;)
+	for (ds = drawsegs + mask->drawsegs[1]; ds-- > drawsegs + mask->drawsegs[0];)
 	{
 		if (ds->numthicksides)
 		{
 			for (i = 0; i < ds->numthicksides; i++)
 			{
-				entry = R_CreateDrawNode(&nodehead);
+				entry = R_CreateDrawNode(head);
 				entry->thickseg = ds;
 				entry->ffloor = ds->thicksides[i];
 			}
@@ -1852,11 +1867,11 @@ static void R_CreateDrawNodes(void)
 			plane = ds->curline->polyseg->visplane;
 			R_PlaneBounds(plane);
 
-			if (plane->low < con_clipviewtop || plane->high > vid.height || plane->high > plane->low)
+			if (plane->low < 0 || plane->high > vid.height || plane->high > plane->low)
 				;
 			else {
 				// Put it in!
-				entry = R_CreateDrawNode(&nodehead);
+				entry = R_CreateDrawNode(head);
 				entry->plane = plane;
 				entry->seg = ds;
 			}
@@ -1865,7 +1880,7 @@ static void R_CreateDrawNodes(void)
 #endif
 		if (ds->maskedtexturecol)
 		{
-			entry = R_CreateDrawNode(&nodehead);
+			entry = R_CreateDrawNode(head);
 			entry->seg = ds;
 		}
 		if (ds->numffloorplanes)
@@ -1881,7 +1896,7 @@ static void R_CreateDrawNodes(void)
 					plane = ds->ffloorplanes[p];
 					R_PlaneBounds(plane);
 
-					if (plane->low < con_clipviewtop || plane->high > vid.height || plane->high > plane->low || plane->polyobj)
+					if (plane->low < 0 || plane->high > vid.height || plane->high > plane->low || plane->polyobj)
 					{
 						ds->ffloorplanes[p] = NULL;
 						continue;
@@ -1896,7 +1911,7 @@ static void R_CreateDrawNodes(void)
 				}
 				if (best != -1)
 				{
-					entry = R_CreateDrawNode(&nodehead);
+					entry = R_CreateDrawNode(head);
 					entry->plane = ds->ffloorplanes[best];
 					entry->seg = ds;
 					ds->ffloorplanes[best] = NULL;
@@ -1907,6 +1922,9 @@ static void R_CreateDrawNodes(void)
 		}
 	}
 
+	if (tempskip)
+		return;
+
 #ifdef POLYOBJECTS_PLANES
 	// find all the remaining polyobject planes and add them on the end of the list
 	// probably this is a terrible idea if we wanted them to be sorted properly
@@ -1918,22 +1936,24 @@ static void R_CreateDrawNodes(void)
 		plane = PolyObjects[i].visplane;
 		R_PlaneBounds(plane);
 
-		if (plane->low < con_clipviewtop || plane->high > vid.height || plane->high > plane->low)
+		if (plane->low < 0 || plane->high > vid.height || plane->high > plane->low)
 		{
 			PolyObjects[i].visplane = NULL;
 			continue;
 		}
-		entry = R_CreateDrawNode(&nodehead);
+		entry = R_CreateDrawNode(head);
 		entry->plane = plane;
 		// note: no seg is set, for what should be obvious reasons
 		PolyObjects[i].visplane = NULL;
 	}
 #endif
 
-	if (visspritecount == 0)
+	// No vissprites in this mask?
+	if (mask->vissprites[1] - mask->vissprites[0] == 0)
 		return;
 
-	R_SortVisSprites();
+	R_SortVisSprites(&vsprsortedhead, mask->vissprites[0], mask->vissprites[1]);
+
 	for (rover = vsprsortedhead.prev; rover != &vsprsortedhead; rover = rover->prev)
 	{
 		if (rover->szt > vid.height || rover->sz < 0)
@@ -1941,7 +1961,7 @@ static void R_CreateDrawNodes(void)
 
 		sintersect = (rover->x1 + rover->x2) / 2;
 
-		for (r2 = nodehead.next; r2 != &nodehead; r2 = r2->next)
+		for (r2 = head->next; r2 != head; r2 = r2->next)
 		{
 			if (r2->plane)
 			{
@@ -2080,9 +2100,9 @@ static void R_CreateDrawNodes(void)
 				}
 			}
 		}
-		if (r2 == &nodehead)
+		if (r2 == head)
 		{
-			entry = R_CreateDrawNode(&nodehead);
+			entry = R_CreateDrawNode(head);
 			entry->sprite = rover;
 		}
 	}
@@ -2124,25 +2144,24 @@ static void R_DoneWithNode(drawnode_t *node)
 	(node->prev = &nodebankhead)->next = node;
 }
 
-static void R_ClearDrawNodes(void)
+static void R_ClearDrawNodes(drawnode_t* head)
 {
 	drawnode_t *rover;
 	drawnode_t *next;
 
-	for (rover = nodehead.next; rover != &nodehead ;)
+	for (rover = head->next; rover != head;)
 	{
 		next = rover->next;
 		R_DoneWithNode(rover);
 		rover = next;
 	}
 
-	nodehead.next = nodehead.prev = &nodehead;
+	head->next = head->prev = head;
 }
 
 void R_InitDrawNodes(void)
 {
 	nodebankhead.next = nodebankhead.prev = &nodebankhead;
-	nodehead.next = nodehead.prev = &nodehead;
 }
 
 //
@@ -2168,7 +2187,7 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 
 // R_ClipSprites
 // Clips vissprites without drawing, so that portals can work. -Red
-void R_ClipSprites(void)
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
 {
 	vissprite_t *spr;
 	for (; clippedvissprites < visspritecount; clippedvissprites++)
@@ -2194,7 +2213,7 @@ void R_ClipSprites(void)
 		// 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-- > drawsegs ;)
+		for (ds = ds_p; ds-- > dsstart;)
 		{
 			// determine if the drawseg obscures the sprite
 			if (ds->x1 > spr->x2 ||
@@ -2206,33 +2225,36 @@ void R_ClipSprites(void)
 				continue;
 			}
 
-			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
-				continue; // is a portal
+			if (ds->portalpass != 66)
+			{
+				if (ds->portalpass > 0 && ds->portalpass <= portalrender)
+					continue; // is a portal
 
-			r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
-			r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
+				if (ds->scale1 > ds->scale2)
+				{
+					lowscale = ds->scale2;
+					scale = ds->scale1;
+				}
+				else
+				{
+					lowscale = ds->scale1;
+					scale = ds->scale2;
+				}
 
-			if (ds->scale1 > ds->scale2)
-			{
-				lowscale = ds->scale2;
-				scale = ds->scale1;
-			}
-			else
-			{
-				lowscale = ds->scale1;
-				scale = ds->scale2;
+				if (scale < spr->sortscale ||
+					(lowscale < spr->sortscale &&
+					 !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
+				{
+					// masked mid texture?
+					/*if (ds->maskedtexturecol)
+						R_RenderMaskedSegRange (ds, r1, r2);*/
+					// seg is behind sprite
+					continue;
+				}
 			}
 
-			if (scale < spr->sortscale ||
-			    (lowscale < spr->sortscale &&
-			     !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
-			{
-				// masked mid texture?
-				/*if (ds->maskedtexturecol)
-					R_RenderMaskedSegRange (ds, r1, r2);*/
-				// seg is behind sprite
-				continue;
-			}
+			r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
+			r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
 
 			// clip this piece of the sprite
 			silhouette = ds->silhouette;
@@ -2350,20 +2372,29 @@ void R_ClipSprites(void)
 				//Fab : 26-04-98: was -1, now clips against console bottom
 				spr->cliptop[x] = (INT16)con_clipviewtop;
 		}
+
+		if (portal)
+		{
+			for (x = spr->x1; x <= spr->x2; x++)
+			{
+				if (spr->clipbot[x] > portal->floorclip[x - portal->start])
+					spr->clipbot[x] = portal->floorclip[x - portal->start];
+				if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
+					spr->cliptop[x] = portal->ceilingclip[x - portal->start];
+			}
+		}
 	}
 }
 
 //
 // R_DrawMasked
 //
-void R_DrawMasked(void)
+static void R_DrawMaskedList (drawnode_t* head)
 {
 	drawnode_t *r2;
 	drawnode_t *next;
 
-	R_CreateDrawNodes();
-
-	for (r2 = nodehead.next; r2 != &nodehead; r2 = r2->next)
+	for (r2 = head->next; r2 != head; r2 = r2->next)
 	{
 		if (r2->plane)
 		{
@@ -2415,7 +2446,42 @@ void R_DrawMasked(void)
 			r2 = next;
 		}
 	}
-	R_ClearDrawNodes();
+}
+
+void R_DrawMasked(maskcount_t* masks, UINT8 nummasks)
+{
+	drawnode_t *heads;	/**< Drawnode lists; as many as number of views/portals. */
+	SINT8 i;
+
+	heads = calloc(nummasks, sizeof(drawnode_t));
+
+	for (i = 0; i < nummasks; i++)
+	{
+		heads[i].next = heads[i].prev = &heads[i];
+
+		viewx = masks[i].viewx;
+		viewy = masks[i].viewy;
+		viewz = masks[i].viewz;
+		viewsector = masks[i].viewsector;
+
+		R_CreateDrawNodes(&masks[i], &heads[i], false);
+	}
+
+	//for (i = 0; i < nummasks; i++)
+	//	CONS_Printf("Mask no.%d:\ndrawsegs: %d\n vissprites: %d\n\n", i, masks[i].drawsegs[1] - masks[i].drawsegs[0], masks[i].vissprites[1] - masks[i].vissprites[0]);
+
+	for (; nummasks > 0; nummasks--)
+	{
+		viewx = masks[nummasks - 1].viewx;
+		viewy = masks[nummasks - 1].viewy;
+		viewz = masks[nummasks - 1].viewz;
+		viewsector = masks[nummasks - 1].viewsector;
+
+		R_DrawMaskedList(&heads[nummasks - 1]);
+		R_ClearDrawNodes(&heads[nummasks - 1]);
+	}
+
+	free(heads);
 }
 
 // ==========================================================================
@@ -2425,7 +2491,7 @@ void R_DrawMasked(void)
 // ==========================================================================
 
 INT32 numskins = 0;
-skin_t skins[MAXSKINS+1];
+skin_t skins[MAXSKINS];
 // FIXTHIS: don't work because it must be inistilised before the config load
 //#define SKINVALUES
 #ifdef SKINVALUES
@@ -2445,6 +2511,9 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 	if (!skin)
 		return 0;
 
+	if ((unsigned)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
+		return 0;
+
 	while (!(skin->sprites[spr2].numframes)
 		&& spr2 != SPR2_STND
 		&& ++i < 32) // recursion limiter
@@ -2458,7 +2527,6 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 
 		switch(spr2)
 		{
-
 		// Normal special cases.
 		case SPR2_JUMP:
 			spr2 = ((player
@@ -2467,9 +2535,11 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
 		case SPR2_TIRE:
-			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			spr2 = ((player
+					? player->charability
+					: skin->ability)
+					== CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 			break;
-
 		// Use the handy list, that's what it's there for!
 		default:
 			spr2 = spr2defaults[spr2];
@@ -2479,6 +2549,9 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 		spr2 |= super;
 	}
 
+	if (i >= 32) // probably an infinite loop...
+		return 0;
+
 	return spr2;
 }
 
@@ -2497,14 +2570,7 @@ static void Sk_SetDefaultValue(skin_t *skin)
 	skin->flags = 0;
 
 	strcpy(skin->realname, "Someone");
-#ifdef SKINNAMEPADDING
-	strcpy(skin->hudname, "  ???");
-#else
 	strcpy(skin->hudname, "???");
-#endif
-	strncpy(skin->charsel, "CHRSONIC", 8);
-	strncpy(skin->face, "MISSING", 8);
-	strncpy(skin->superface, "MISSING", 8);
 
 	skin->starttranscolor = 96;
 	skin->prefcolor = SKINCOLOR_GREEN;
@@ -2619,7 +2685,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 
 	if (P_IsLocalPlayer(player))
 		CONS_Alert(CONS_WARNING, M_GetText("Skin '%s' not found.\n"), skinname);
-	else if(server || adminplayer == consoleplayer)
+	else if(server || IsPlayerAdmin(consoleplayer))
 		CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname);
 
 	SetPlayerSkinByNum(playernum, 0);
@@ -2650,6 +2716,9 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 		player->revitem = skin->revitem < 0 ? (mobjtype_t)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
 		player->followitem = skin->followitem;
 
+		if (((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK) && (player->revitem == MT_LHRT || player->spinitem == MT_LHRT || player->thokitem == MT_LHRT)) // Healers can't keep their buff.
+			player->powers[pw_shield] &= SH_STACK;
+
 		player->actionspd = skin->actionspd;
 		player->mindash = skin->mindash;
 		player->maxdash = skin->maxdash;
@@ -2677,7 +2746,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 		if (player->followmobj)
 		{
 			P_RemoveMobj(player->followmobj);
-			player->followmobj = NULL;
+			P_SetTarget(&player->followmobj, NULL);
 		}
 
 		if (player->mo)
@@ -2703,7 +2772,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 
 	if (P_IsLocalPlayer(player))
 		CONS_Alert(CONS_WARNING, M_GetText("Requested skin not found\n"));
-	else if(server || adminplayer == consoleplayer)
+	else if(server || IsPlayerAdmin(consoleplayer))
 		CONS_Alert(CONS_WARNING, "Player %d (%s) skin not found\n", playernum, player_names[playernum]);
 	SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin
 }
@@ -2733,11 +2802,7 @@ static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 	return INT16_MAX; // not found
 }
 
-#ifdef SKINNAMEPADDING
-#define HUDNAMEWRITE(value) snprintf(skin->hudname, sizeof(skin->hudname), "%5s", value)
-#else
 #define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, value)
-#endif
 
 // turn _ into spaces and . into katana dot
 #define SYMBOLCONVERT(name) for (value = name; *value; value++)\
@@ -2935,7 +3000,7 @@ void R_AddSkins(UINT16 wadnum)
 	char *value;
 	size_t size;
 	skin_t *skin;
-	boolean hudname, realname, superface;
+	boolean hudname, realname;
 
 	//
 	// search for all skin markers in pwad
@@ -2946,7 +3011,7 @@ void R_AddSkins(UINT16 wadnum)
 		// advance by default
 		lastlump = lump + 1;
 
-		if (numskins > MAXSKINS)
+		if (numskins >= MAXSKINS)
 		{
 			CONS_Debug(DBG_RENDER, "ignored skin (%d skins maximum)\n", MAXSKINS);
 			continue; // so we know how many skins couldn't be added
@@ -2965,7 +3030,7 @@ void R_AddSkins(UINT16 wadnum)
 		skin = &skins[numskins];
 		Sk_SetDefaultValue(skin);
 		skin->wadnum = wadnum;
-		hudname = realname = superface = false;
+		hudname = realname = false;
 		// parse
 		stoken = strtok (buf2, "\r\n= ");
 		while (stoken)
@@ -3041,24 +3106,6 @@ void R_AddSkins(UINT16 wadnum)
 				if (!realname)
 					STRBUFCPY(skin->realname, skin->hudname);
 			}
-			else if (!stricmp(stoken, "charsel"))
-			{
-				strupr(value);
-				strncpy(skin->charsel, value, sizeof skin->charsel);
-			}
-			else if (!stricmp(stoken, "face"))
-			{
-				strupr(value);
-				strncpy(skin->face, value, sizeof skin->face);
-				if (!superface)
-					strncpy(skin->superface, value, sizeof skin->superface);
-			}
-			else if (!stricmp(stoken, "superface"))
-			{
-				superface = true;
-				strupr(value);
-				strncpy(skin->superface, value, sizeof skin->superface);
-			}
 			else if (!stricmp(stoken, "availability"))
 			{
 				skin->availability = atoi(value);
@@ -3077,6 +3124,7 @@ next_token:
 
 		// Add sprites
 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		//ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere
 
 		R_FlushTranslationColormapCache();
 
@@ -3087,9 +3135,6 @@ next_token:
 		skin_cons_t[numskins].strvalue = skin->name;
 #endif
 
-		// add face graphics
-		ST_LoadFaceGraphics(skin->face, skin->superface, numskins);
-
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
 			HWR_AddPlayerMD2(numskins);
@@ -3212,6 +3257,9 @@ next_token:
 
 		// Patch sprites
 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		//ST_LoadFaceGraphics(skinnum); -- nah let's do this elsewhere
+
+		R_FlushTranslationColormapCache();
 
 		if (!skin->availability) // Safe to print...
 			CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
diff --git a/src/r_things.h b/src/r_things.h
index 9a9761d5bf288b095b1262cb54393bb3b546f68b..9c3d16ab018b27abdb610e4ee5eefe7a6b5e66b3 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -16,6 +16,7 @@
 
 #include "sounds.h"
 #include "r_plane.h"
+#include "r_portal.h"
 
 // "Left" and "Right" character symbols for additional rotation functionality
 #define ROT_L ('L' - '0')
@@ -45,7 +46,6 @@ extern fixed_t windowbottom;
 
 void R_DrawMaskedColumn(column_t *column);
 void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight);
-void R_SortVisSprites(void);
 
 //faB: find sprites in wadfile, replace existing, add new ones
 //     (only sprites from namelist are added or replaced)
@@ -55,8 +55,21 @@ void R_AddSpriteDefs(UINT16 wadnum);
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
-void R_ClipSprites(void);
-void R_DrawMasked(void);
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
+
+/** Used to count the amount of masked elements
+ * per portal to later group them in separate
+ * drawnode lists.
+ */
+typedef struct
+{
+	size_t drawsegs[2];
+	size_t vissprites[2];
+	fixed_t viewx, viewy, viewz;			/**< View z stored at the time of the BSP traversal for the view/portal. Masked sorting/drawing needs it. */
+	sector_t* viewsector;
+} maskcount_t;
+
+void R_DrawMasked(maskcount_t* masks, UINT8 nummasks);
 
 // -----------
 // SKINS STUFF
@@ -75,7 +88,6 @@ typedef struct
 
 	char realname[SKINNAMESIZE+1]; // Display name for level completion.
 	char hudname[SKINNAMESIZE+1]; // HUD name to display (officially exactly 5 characters long)
-	char charsel[8], face[8], superface[8]; // Arbitrarily named patch lumps
 
 	UINT8 ability; // ability definition
 	UINT8 ability2; // secondary ability definition
@@ -206,7 +218,8 @@ typedef struct drawnode_s
 } drawnode_t;
 
 extern INT32 numskins;
-extern skin_t skins[MAXSKINS + 1];
+extern skin_t skins[MAXSKINS];
+extern UINT32 visspritecount;
 
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
@@ -246,6 +259,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE char R_Frame2Char(UINT8 frame)
 FUNCMATH FUNCINLINE static ATTRINLINE UINT8 R_Char2Frame(char cn)
 {
 #if 0 // 2.1 compat
+	if (cn == '+') return '\\' - 'A'; // PK3 can't use backslash, so use + instead
 	return cn - 'A';
 #else
 	if (cn >= 'A' && cn <= 'Z') return cn - 'A';
diff --git a/src/s_sound.c b/src/s_sound.c
index 0ac3a2c76a3516eb4eb45c1fab50e6a12632816b..1c6cd5ef1a4d6e18419c436f250258aa3c923427 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -37,6 +37,11 @@ extern INT32 msg_id;
 #include "r_sky.h" // skyflatnum
 #include "p_local.h" // camera info
 #include "fastcmp.h"
+#include "m_misc.h" // for tunes command
+
+#if defined(HAVE_BLUA) && defined(HAVE_LUA_MUSICPLUS)
+#include "lua_hook.h" // MusicChange hook
+#endif
 
 #ifdef HW3SOUND
 // 3D Sound Interface
@@ -47,6 +52,17 @@ static INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, I
 
 CV_PossibleValue_t soundvolume_cons_t[] = {{0, "MIN"}, {31, "MAX"}, {0, NULL}};
 static void SetChannelsNum(void);
+static void Command_Tunes_f(void);
+static void Command_RestartAudio_f(void);
+
+// Sound system toggles
+static void GameMIDIMusic_OnChange(void);
+static void GameSounds_OnChange(void);
+static void GameDigiMusic_OnChange(void);
+
+static void ModFilter_OnChange(void);
+
+static lumpnum_t S_GetMusicLumpNum(const char *mname);
 
 // commands for music and sound servers
 #ifdef MUSSERV
@@ -75,7 +91,7 @@ consvar_t stereoreverse = {"stereoreverse", "Off", CV_SAVE, CV_OnOff, NULL, 0, N
 static consvar_t precachesound = {"precachesound", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // actual general (maximum) sound & music volume, saved into the config
-consvar_t cv_soundvolume = {"soundvolume", "31", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_soundvolume = {"soundvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_digmusicvolume = {"digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_midimusicvolume = {"midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -92,6 +108,17 @@ consvar_t cv_closedcaptioning = {"closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_
 consvar_t cv_numChannels = {"snd_channels", "32", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL};
 
 static consvar_t surround = {"surround", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_resetmusic = {"resetmusic", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// Sound system toggles, saved into the config
+consvar_t cv_gamedigimusic = {"digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gamemidimusic = {"midimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameMIDIMusic_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gamesounds = {"sounds", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameSounds_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+#ifdef HAVE_OPENMPT
+static CV_PossibleValue_t interpolationfilter_cons_t[] = {{0, "Default"}, {1, "None"}, {2, "Linear"}, {4, "Cubic"}, {8, "Windowed sinc"}, {0, NULL}};
+consvar_t cv_modfilter = {"modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange, 0, NULL, NULL, 0, 0, NULL};
+#endif
 
 #define S_MAX_VOLUME 127
 
@@ -230,7 +257,7 @@ void S_RegisterSoundStuff(void)
 {
 	if (dedicated)
 	{
-		nosound = true;
+		sound_disabled = true;
 		return;
 	}
 
@@ -247,6 +274,21 @@ void S_RegisterSoundStuff(void)
 #endif
 	CV_RegisterVar(&surround);
 	CV_RegisterVar(&cv_samplerate);
+	CV_RegisterVar(&cv_resetmusic);
+	CV_RegisterVar(&cv_gamesounds);
+	CV_RegisterVar(&cv_gamedigimusic);
+	CV_RegisterVar(&cv_gamemidimusic);
+#ifdef HAVE_OPENMPT
+	CV_RegisterVar(&cv_modfilter);
+#endif
+#ifdef HAVE_MIXERX
+	CV_RegisterVar(&cv_midiplayer);
+	CV_RegisterVar(&cv_midisoundfontpath);
+	CV_RegisterVar(&cv_miditimiditypath);
+#endif
+
+	COM_AddCommand("tunes", Command_Tunes_f);
+	COM_AddCommand("restartaudio", Command_RestartAudio_f);
 
 #if defined (macintosh) && !defined (HAVE_SDL) // mp3 playlist stuff
 	{
@@ -484,6 +526,7 @@ void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan)
 void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 {
 	INT32 sep, pitch, priority, cnum;
+	const sfxenum_t actual_id = sfx_id;
 	sfxinfo_t *sfx;
 
 	const mobj_t *origin = (const mobj_t *)origin_p;
@@ -494,7 +537,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 	mobj_t *listenmobj = players[displayplayer].mo;
 	mobj_t *listenmobj2 = NULL;
 
-	if (sound_disabled || !sound_started || nosound)
+	if (sound_disabled || !sound_started)
 		return;
 
 	// Don't want a sound? Okay then...
@@ -622,11 +665,11 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 #endif
 
 		// Handle closed caption input.
-		S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+		S_StartCaption(actual_id, cnum, MAXCAPTIONTICS);
 
 		// Assigns the handle to one of the channels in the
 		// mix/output buffer.
-		channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority);
+		channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum);
 	}
 
 dontplay:
@@ -675,11 +718,11 @@ dontplay:
 #endif
 
 	// Handle closed caption input.
-	S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+	S_StartCaption(actual_id, cnum, MAXCAPTIONTICS);
 
 	// Assigns the handle to one of the channels in the
 	// mix/output buffer.
-	channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority);
+	channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum);
 }
 
 void S_StartSound(const void *origin, sfxenum_t sfx_id)
@@ -824,7 +867,7 @@ void S_UpdateSounds(void)
 		goto notinlevel;
 	}
 
-	if (dedicated || nosound)
+	if (dedicated || sound_disabled)
 		return;
 
 	if (players[displayplayer].awayviewtics)
@@ -1283,6 +1326,43 @@ void S_StartSoundName(void *mo, const char *soundname)
 	S_StartSound(mo, soundnum);
 }
 
+//
+// Initializes sound stuff, including volume
+// Sets channels, SFX volume,
+//  allocates channel buffer, sets S_sfx lookup.
+//
+void S_InitSfxChannels(INT32 sfxVolume)
+{
+	INT32 i;
+
+	if (dedicated)
+		return;
+
+	S_SetSfxVolume(sfxVolume);
+
+	SetChannelsNum();
+
+	// Note that sounds have not been cached (yet).
+	for (i = 1; i < NUMSFX; i++)
+	{
+		S_sfx[i].usefulness = -1; // for I_GetSfx()
+		S_sfx[i].lumpnum = LUMPERROR;
+	}
+
+	// precache sounds if requested by cmdline, or precachesound var true
+	if (!sound_disabled && (M_CheckParm("-precachesound") || precachesound.value))
+	{
+		// Initialize external data (all sounds) at start, keep static.
+		CONS_Printf(M_GetText("Loading sounds... "));
+
+		for (i = 1; i < NUMSFX; i++)
+			if (S_sfx[i].name)
+				S_sfx[i].data = I_GetSfx(&S_sfx[i]);
+
+		CONS_Printf(M_GetText(" pre-cached all sound data\n"));
+	}
+}
+
 /// ------------------------
 /// Music
 /// ------------------------
@@ -1290,50 +1370,449 @@ void S_StartSoundName(void *mo, const char *soundname)
 #ifdef MUSICSLOT_COMPATIBILITY
 const char *compat_special_music_slots[16] =
 {
-	"titles", // 1036  title screen
-	"read_m", // 1037  intro
-	"lclear", // 1038  level clear
-	"invinc", // 1039  invincibility
-	"shoes",  // 1040  super sneakers
-	"minvnc", // 1041  Mario invincibility
-	"drown",  // 1042  drowning
-	"gmover", // 1043  game over
-	"xtlife", // 1044  extra life
-	"contsc", // 1045  continue screen
-	"supers", // 1046  Super Sonic
-	"chrsel", // 1047  character select
-	"credit", // 1048  credits
-	"racent", // 1049  Race Results
-	"stjr",   // 1050  Sonic Team Jr. Presents
+	"_title", // 1036  title screen
+	"_intro", // 1037  intro
+	"_clear", // 1038  level clear
+	"_inv", // 1039  invincibility
+	"_shoes",  // 1040  super sneakers
+	"_minv", // 1041  Mario invincibility
+	"_drown",  // 1042  drowning
+	"_gover", // 1043  game over
+	"_1up", // 1044  extra life
+	"_conti", // 1045  continue screen
+	"_super", // 1046  Super Sonic
+	"_chsel", // 1047  character select
+	"_creds", // 1048  credits
+	"_inter", // 1049  Race Results
+	"_stjr",   // 1050  Sonic Team Jr. Presents
 	""
 };
 #endif
 
-#define music_playing (music_name[0]) // String is empty if no music is playing
-
 static char      music_name[7]; // up to 6-character name
-static lumpnum_t music_lumpnum; // lump number of music (used??)
-static void     *music_data;    // music raw data
-static INT32     music_handle;  // once registered, the handle for the music
+static void      *music_data;
+static UINT16    music_flags;
+static boolean   music_looping;
+
+static char      queue_name[7];
+static UINT16    queue_flags;
+static boolean   queue_looping;
+static UINT32    queue_position;
+static UINT32    queue_fadeinms;
+
+static tic_t     pause_starttic;
+
+/// ------------------------
+/// Music Status
+/// ------------------------
+
+boolean S_DigMusicDisabled(void)
+{
+	return digital_disabled;
+}
+
+boolean S_MIDIMusicDisabled(void)
+{
+	return midi_disabled;
+}
+
+boolean S_MusicDisabled(void)
+{
+	return (midi_disabled && digital_disabled);
+}
+
+boolean S_MusicPlaying(void)
+{
+	return I_SongPlaying();
+}
+
+boolean S_MusicPaused(void)
+{
+	return I_SongPaused();
+}
+
+musictype_t S_MusicType(void)
+{
+	return I_SongType();
+}
+
+const char *S_MusicName(void)
+{
+	return music_name;
+}
+
+boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping)
+{
+	if (!I_SongPlaying())
+		return false;
+
+	strncpy(mname, music_name, 7);
+	mname[6] = 0;
+	*mflags = music_flags;
+	*looping = music_looping;
+
+	return (boolean)mname[0];
+}
+
+boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi)
+{
+	return (
+		(checkDigi ? W_CheckNumForName(va("O_%s", mname)) != LUMPERROR : false)
+		|| (checkMIDI ? W_CheckNumForName(va("D_%s", mname)) != LUMPERROR : false)
+	);
+}
+
+/// ------------------------
+/// Music Effects
+/// ------------------------
+
+boolean S_SpeedMusic(float speed)
+{
+	return I_SetSongSpeed(speed);
+}
+
+/// ------------------------
+/// Music Seeking
+/// ------------------------
+
+UINT32 S_GetMusicLength(void)
+{
+	return I_GetSongLength();
+}
+
+boolean S_SetMusicLoopPoint(UINT32 looppoint)
+{
+	return I_SetSongLoopPoint(looppoint);
+}
+
+UINT32 S_GetMusicLoopPoint(void)
+{
+	return I_GetSongLoopPoint();
+}
+
+boolean S_SetMusicPosition(UINT32 position)
+{
+	return I_SetSongPosition(position);
+}
+
+UINT32 S_GetMusicPosition(void)
+{
+	return I_GetSongPosition();
+}
+
+/// ------------------------
+/// Music Stacking (Jingles)
+/// In this section: mazmazz doesn't know how to do dynamic arrays or struct pointers!
+/// ------------------------
+
+static musicstack_t *music_stacks = NULL;
+static musicstack_t *last_music_stack = NULL;
+
+void S_SetStackAdjustmentStart(void)
+{
+	if (!pause_starttic)
+		pause_starttic = gametic;
+}
+
+void S_AdjustMusicStackTics(void)
+{
+	if (pause_starttic)
+	{
+		musicstack_t *mst;
+		for (mst = music_stacks; mst; mst = mst->next)
+			mst->tic += gametic - pause_starttic;
+		pause_starttic = 0;
+	}
+}
+
+static void S_ResetMusicStack(void)
+{
+	musicstack_t *mst, *mst_next;
+	for (mst = music_stacks; mst; mst = mst_next)
+	{
+		mst_next = mst->next;
+		Z_Free(mst);
+	}
+	music_stacks = last_music_stack = NULL;
+}
+
+static void S_RemoveMusicStackEntry(musicstack_t *entry)
+{
+	musicstack_t *mst;
+	for (mst = music_stacks; mst; mst = mst->next)
+	{
+		if (mst == entry)
+		{
+			// Remove ourselves from the chain and link
+			// prev and next together
+
+			if (mst->prev)
+				mst->prev->next = mst->next;
+			else
+				music_stacks = mst->next;
+
+			if (mst->next)
+				mst->next->prev = mst->prev;
+			else
+				last_music_stack = mst->prev;
+
+			break;
+		}
+	}
+	Z_Free(entry);
+}
+
+static void S_RemoveMusicStackEntryByStatus(UINT16 status)
+{
+	musicstack_t *mst, *mst_next;
+
+	if (!status)
+		return;
+
+	for (mst = music_stacks; mst; mst = mst_next)
+	{
+		mst_next = mst->next;
+		if (mst->status == status)
+			S_RemoveMusicStackEntry(mst);
+	}
+}
+
+static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status)
+{
+	musicstack_t *mst, *new_mst;
+
+	// if the first entry is empty, force master onto it
+	if (!music_stacks)
+	{
+		music_stacks = Z_Calloc(sizeof (*mst), PU_MUSIC, NULL);
+		strncpy(music_stacks->musname, (status == JT_MASTER ? mname : mapmusname), 7);
+		music_stacks->musflags = (status == JT_MASTER ? mflags : mapmusflags);
+		music_stacks->looping = (status == JT_MASTER ? looping : true);
+		music_stacks->position = (status == JT_MASTER ? position : S_GetMusicPosition());
+		music_stacks->tic = gametic;
+		music_stacks->status = JT_MASTER;
+		music_stacks->mlumpnum = S_GetMusicLumpNum(music_stacks->musname);
+
+		if (status == JT_MASTER)
+			return; // we just added the user's entry here
+	}
+
+	// look for an empty slot to park ourselves
+	for (mst = music_stacks; mst->next; mst = mst->next);
+
+	// create our new entry
+	new_mst = Z_Calloc(sizeof (*new_mst), PU_MUSIC, NULL);
+	strncpy(new_mst->musname, mname, 7);
+	new_mst->musname[6] = 0;
+	new_mst->musflags = mflags;
+	new_mst->looping = looping;
+	new_mst->position = position;
+	new_mst->tic = gametic;
+	new_mst->status = status;
+	new_mst->mlumpnum = S_GetMusicLumpNum(new_mst->musname);
+
+	mst->next = new_mst;
+	new_mst->prev = mst;
+	new_mst->next = NULL;
+	last_music_stack = new_mst;
+}
+
+static musicstack_t *S_GetMusicStackEntry(UINT16 status, boolean fromfirst, INT16 startindex)
+{
+	musicstack_t *mst, *start_mst = NULL, *mst_next;
+
+	// if the first entry is empty, force master onto it
+	// fixes a memory corruption bug
+	if (!music_stacks && status != JT_MASTER)
+		S_AddMusicStackEntry(mapmusname, mapmusflags, true, S_GetMusicPosition(), JT_MASTER);
+
+	if (startindex >= 0)
+	{
+		INT16 i = 0;
+		for (mst = music_stacks; mst && i <= startindex; mst = mst->next, i++)
+			start_mst = mst;
+	}
+	else
+		start_mst = (fromfirst ? music_stacks : last_music_stack);
 
-static boolean mus_paused     = 0;  // whether songs are mus_paused
+	for (mst = start_mst; mst; mst = mst_next)
+	{
+		mst_next = (fromfirst ? mst->next : mst->prev);
+
+		if (!status || mst->status == status)
+		{
+			if (P_EvaluateMusicStatus(mst->status))
+			{
+				if (!S_MusicExists(mst->musname, !midi_disabled, !digital_disabled)) // paranoia
+					S_RemoveMusicStackEntry(mst); // then continue
+				else
+					return mst;
+			}
+			else
+				S_RemoveMusicStackEntry(mst); // then continue
+		}
+	}
+
+	return NULL;
+}
+
+void S_RetainMusic(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status)
+{
+	musicstack_t *mst;
+
+	if (!status) // we use this as a null indicator, don't push
+	{
+		CONS_Alert(CONS_ERROR, "Music stack entry must have a nonzero status.\n");
+		return;
+	}
+	else if (status == JT_MASTER) // enforce only one JT_MASTER
+	{
+		for (mst = music_stacks; mst; mst = mst->next)
+		{
+			if (mst->status == JT_MASTER)
+			{
+				CONS_Alert(CONS_ERROR, "Music stack can only have one JT_MASTER entry.\n");
+				return;
+			}
+		}
+	}
+	else // remove any existing status
+		S_RemoveMusicStackEntryByStatus(status);
+
+	S_AddMusicStackEntry(mname, mflags, looping, position, status);
+}
+
+boolean S_RecallMusic(UINT16 status, boolean fromfirst)
+{
+	UINT32 newpos = 0;
+	boolean mapmuschanged = false;
+	musicstack_t *result;
+	musicstack_t *entry = Z_Calloc(sizeof (*result), PU_MUSIC, NULL);
+
+	if (status)
+		result = S_GetMusicStackEntry(status, fromfirst, -1);
+	else
+		result = S_GetMusicStackEntry(JT_NONE, false, -1);
+
+	if (result && !S_MusicExists(result->musname, !midi_disabled, !digital_disabled))
+	{
+		Z_Free(entry);
+		return false; // music doesn't exist, so don't do anything
+	}
+
+	// make a copy of result, since we make modifications to our copy
+	if (result)
+	{
+		*entry = *result;
+		strncpy(entry->musname, result->musname, 7);
+	}
+
+	// no result, just grab mapmusname
+	if (!result || !entry->musname[0] || ((status == JT_MASTER || (music_stacks ? !music_stacks->status : false)) && !entry->status))
+	{
+		strncpy(entry->musname, mapmusname, 7);
+		entry->musflags = mapmusflags;
+		entry->looping = true;
+		entry->position = mapmusposition;
+		entry->tic = gametic;
+		entry->status = JT_MASTER;
+		entry->mlumpnum = S_GetMusicLumpNum(entry->musname);
+	}
+
+	if (entry->status == JT_MASTER)
+	{
+		mapmuschanged = strnicmp(entry->musname, mapmusname, 7);
+		S_ResetMusicStack();
+	}
+	else if (!entry->status)
+	{
+		Z_Free(entry);
+		return false;
+	}
+
+	if (!mapmuschanged && strncmp(entry->musname, S_MusicName(), 7)) // don't restart music if we're already playing it
+	{
+		if (music_stack_fadeout)
+			S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, music_stack_fadeout, 0);
+		else
+		{
+			S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, 0, music_stack_fadein);
+
+			if (!music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize
+			{
+				UINT32 poslapse = 0;
+
+				// To prevent the game from jumping past the end of the music, we need
+				// to check if we can get the song's length. Otherwise, if the lapsed resume time goes
+				// over a LOOPPOINT, mixer_sound.c will be unable to calculate the new resume position.
+				if (S_GetMusicLength())
+					poslapse = (UINT32)((float)(gametic - entry->tic)/(float)TICRATE*(float)MUSICRATE);
+
+				newpos = entry->position + poslapse;
+			}
+
+			// If the newly recalled music lumpnum does not match the lumpnum that we stored in stack,
+			// then discard the new position. That way, we will not recall an invalid position
+			// when the music is replaced or digital/MIDI is toggled.
+			if (newpos > 0 && S_MusicPlaying() && S_GetMusicLumpNum(entry->musname) == entry->mlumpnum)
+				S_SetMusicPosition(newpos);
+			else
+			{
+				S_StopFadingMusic();
+				S_SetInternalMusicVolume(100);
+			}
+		}
+		music_stack_noposition = false;
+		music_stack_fadeout = 0;
+		music_stack_fadein = JINGLEPOSTFADE;
+	}
+
+	Z_Free(entry);
+	return true;
+}
+
+/// ------------------------
+/// Music Playback
+/// ------------------------
+
+static lumpnum_t S_GetMusicLumpNum(const char *mname)
+{
+	if (!S_DigMusicDisabled() && S_DigExists(mname))
+		return W_GetNumForName(va("o_%s", mname));
+	else if (!S_MIDIMusicDisabled() && S_MIDIExists(mname))
+		return W_GetNumForName(va("d_%s", mname));
+	else if (S_DigMusicDisabled() && S_DigExists(mname))
+	{
+		CONS_Alert(CONS_NOTICE, "Digital music is disabled!\n");
+		return LUMPERROR;
+	}
+	else if (S_MIDIMusicDisabled() && S_MIDIExists(mname))
+	{
+		CONS_Alert(CONS_NOTICE, "MIDI music is disabled!\n");
+		return LUMPERROR;
+	}
+	else
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Music lump %.6s not found!\n"), mname);
+		return LUMPERROR;
+	}
+}
 
-static boolean S_MIDIMusic(const char *mname, boolean looping)
+static boolean S_LoadMusic(const char *mname)
 {
 	lumpnum_t mlumpnum;
 	void *mdata;
-	INT32 mhandle;
 
-	if (nomidimusic || music_disabled)
-		return false; // didn't search.
+	if (S_MusicDisabled())
+		return false;
+
+	mlumpnum = S_GetMusicLumpNum(mname);
 
-	if (W_CheckNumForName(va("d_%s", mname)) == LUMPERROR)
+	if (mlumpnum == LUMPERROR)
 		return false;
-	mlumpnum = W_GetNumForName(va("d_%s", mname));
 
 	// load & register it
 	mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC);
-	mhandle = I_RegisterSong(mdata, W_LumpLength(mlumpnum));
 
 #ifdef MUSSERV
 	if (msg_id != -1)
@@ -1347,221 +1826,482 @@ static boolean S_MIDIMusic(const char *mname, boolean looping)
 	}
 #endif
 
-	// play it
-	if (!I_PlaySong(mhandle, looping))
+	if (I_LoadSong(mdata, W_LumpLength(mlumpnum)))
+	{
+		strncpy(music_name, mname, 7);
+		music_name[6] = 0;
+		music_data = mdata;
+		return true;
+	}
+	else
 		return false;
+}
 
-	strncpy(music_name, mname, 7);
-	music_name[6] = 0;
-	music_lumpnum = mlumpnum;
-	music_data = mdata;
-	music_handle = mhandle;
-	return true;
+static void S_UnloadMusic(void)
+{
+	I_UnloadSong();
+
+#ifndef HAVE_SDL //SDL uses RWOPS
+	Z_ChangeTag(music_data, PU_CACHE);
+#endif
+	music_data = NULL;
+
+	music_name[0] = 0;
+	music_flags = 0;
+	music_looping = false;
 }
 
-static boolean S_DigMusic(const char *mname, boolean looping)
+static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
 {
-	if (nodigimusic || digital_disabled)
-		return false; // try midi
+	if (S_MusicDisabled())
+		return false;
 
-	if (!I_StartDigSong(mname, looping))
+	if ((!fadeinms && !I_PlaySong(looping)) ||
+		(fadeinms && !I_FadeInPlaySong(fadeinms, looping)))
+	{
+		S_UnloadMusic();
 		return false;
+	}
 
-	strncpy(music_name, mname, 7);
-	music_name[6] = 0;
-	music_lumpnum = LUMPERROR;
-	music_data = NULL;
-	music_handle = 0;
+	S_InitMusicVolume(); // switch between digi and sequence volume
 	return true;
 }
 
-void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping)
+static void S_QueueMusic(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 fadeinms)
+{
+	strncpy(queue_name, mmusic, 7);
+	queue_flags = mflags;
+	queue_looping = looping;
+	queue_position = position;
+	queue_fadeinms = fadeinms;
+}
+
+static void S_ClearQueue(void)
+{
+	queue_name[0] = queue_flags = queue_looping = queue_position = queue_fadeinms = 0;
+}
+
+static void S_ChangeMusicToQueue(void)
+{
+	S_ChangeMusicEx(queue_name, queue_flags, queue_looping, queue_position, 0, queue_fadeinms);
+	S_ClearQueue();
+}
+
+void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms)
 {
-	if ((nomidimusic || music_disabled) && (nodigimusic || digital_disabled))
+	char newmusic[7];
+
+	if (S_MusicDisabled())
 		return;
 
-	// No Music (empty string)
-	if (mmusic[0] == 0)
-	{
-		S_StopMusic();
+	strncpy(newmusic, mmusic, 7);
+#if defined(HAVE_BLUA) && defined(HAVE_LUA_MUSICPLUS)
+	if(LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
+		return;
+#endif
+	newmusic[6] = 0;
+
+ 	// No Music (empty string)
+	if (newmusic[0] == 0)
+ 	{
+		if (prefadems)
+			I_FadeSong(0, prefadems, &S_StopMusic);
+		else
+			S_StopMusic();
 		return;
 	}
 
-	if (strncmp(music_name, mmusic, 6))
+	if (prefadems) // queue music change for after fade // allow even if the music is the same
+		// && S_MusicPlaying() // Let the delay happen even if we're not playing music
 	{
-		S_StopMusic(); // shutdown old music
-		if (!S_DigMusic(mmusic, looping) && !S_MIDIMusic(mmusic, looping))
+		CONS_Debug(DBG_DETAILED, "Now fading out song %s\n", music_name);
+		S_QueueMusic(newmusic, mflags, looping, position, fadeinms);
+		I_FadeSong(0, prefadems, S_ChangeMusicToQueue);
+		return;
+	}
+	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET))
+ 	{
+		CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic);
+
+		S_StopMusic();
+
+		if (!S_LoadMusic(newmusic))
 		{
-			CONS_Alert(CONS_ERROR, M_GetText("Music lump %.6s not found!\n"), mmusic);
+			CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded!\n", newmusic);
 			return;
 		}
-	}
-	I_SetSongTrack(mflags & MUSIC_TRACKMASK);
-}
 
-boolean S_SpeedMusic(float speed)
-{
-	return I_SetSongSpeed(speed);
+		music_flags = mflags;
+		music_looping = looping;
+
+		if (!S_PlayMusic(looping, fadeinms))
+ 		{
+			CONS_Alert(CONS_ERROR, "Music %.6s could not be played!\n", newmusic);
+			return;
+		}
+
+		if (position)
+			I_SetSongPosition(position);
+
+		I_SetSongTrack(mflags & MUSIC_TRACKMASK);
+	}
+	else if (fadeinms) // let fades happen with same music
+	{
+		I_SetSongPosition(position);
+		I_FadeSong(100, fadeinms, NULL);
+ 	}
+	else // reset volume to 100 with same music
+	{
+		I_StopFadingSong();
+		I_FadeSong(100, 500, NULL);
+	}
 }
 
 void S_StopMusic(void)
 {
-	if (!music_playing)
+	if (!I_SongPlaying())
 		return;
 
-	if (mus_paused)
-		I_ResumeSong(music_handle);
-
-	if (!nodigimusic)
-		I_StopDigSong();
+	if (I_SongPaused())
+		I_ResumeSong();
 
 	S_SpeedMusic(1.0f);
-	I_StopSong(music_handle);
-	I_UnRegisterSong(music_handle);
-
-#ifndef HAVE_SDL //SDL uses RWOPS
-	Z_ChangeTag(music_data, PU_CACHE);
-#endif
-
-	music_data = NULL;
-	music_name[0] = 0;
+	I_StopSong();
+	S_UnloadMusic(); // for now, stopping also means you unload the song
 
 	if (cv_closedcaptioning.value)
 	{
 		if (closedcaptions[0].s-S_sfx == sfx_None)
-			closedcaptions[0].t = CAPTIONFADETICS;
+		{
+			if (gamestate != wipegamestate)
+			{
+				closedcaptions[0].c = NULL;
+				closedcaptions[0].s = NULL;
+				closedcaptions[0].t = 0;
+				closedcaptions[0].b = 0;
+			}
+			else
+				closedcaptions[0].t = CAPTIONFADETICS;
+		}
 	}
 }
 
-void S_SetDigMusicVolume(INT32 volume)
+//
+// Stop and resume music, during game PAUSE.
+//
+void S_PauseAudio(void)
 {
-	if (volume < 0 || volume > 31)
-		CONS_Alert(CONS_WARNING, "musicvolume should be between 0-31\n");
-
-	CV_SetValue(&cv_digmusicvolume, volume&31);
-	actualdigmusicvolume = cv_digmusicvolume.value;   //check for change of var
+	if (I_SongPlaying() && !I_SongPaused())
+		I_PauseSong();
 
-#ifdef DJGPPDOS
-	I_SetDigMusicVolume(31); // Trick for buggy dos drivers. Win32 doesn't need this.
+	// pause cd music
+#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
+	I_PauseCD();
+#else
+	I_StopCD();
 #endif
-	I_SetDigMusicVolume(volume&31);
+
+	S_SetStackAdjustmentStart();
 }
 
-void S_SetMIDIMusicVolume(INT32 volume)
+void S_ResumeAudio(void)
 {
-	if (volume < 0 || volume > 31)
-		CONS_Alert(CONS_WARNING, "musicvolume should be between 0-31\n");
+	if (I_SongPlaying() && I_SongPaused())
+		I_ResumeSong();
 
-	CV_SetValue(&cv_midimusicvolume, volume&0x1f);
+	// resume cd music
+	I_ResumeCD();
+
+	S_AdjustMusicStackTics();
+}
+
+void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume)
+{
+	if (digvolume < 0)
+		digvolume = cv_digmusicvolume.value;
+	if (seqvolume < 0)
+		seqvolume = cv_midimusicvolume.value;
+
+	if (digvolume < 0 || digvolume > 31)
+		CONS_Alert(CONS_WARNING, "digmusicvolume should be between 0-31\n");
+	CV_SetValue(&cv_digmusicvolume, digvolume&31);
+	actualdigmusicvolume = cv_digmusicvolume.value;   //check for change of var
+
+	if (seqvolume < 0 || seqvolume > 31)
+		CONS_Alert(CONS_WARNING, "midimusicvolume should be between 0-31\n");
+	CV_SetValue(&cv_midimusicvolume, seqvolume&31);
 	actualmidimusicvolume = cv_midimusicvolume.value;   //check for change of var
 
 #ifdef DJGPPDOS
-	I_SetMIDIMusicVolume(31); // Trick for buggy dos drivers. Win32 doesn't need this.
+	digvolume = seqvolume = 31;
 #endif
-	I_SetMIDIMusicVolume(volume&0x1f);
+
+	switch(I_SongType())
+	{
+		case MU_MID:
+		case MU_MID_EX:
+		//case MU_MOD:
+		//case MU_GME:
+			I_SetMusicVolume(seqvolume&31);
+			break;
+		default:
+			I_SetMusicVolume(digvolume&31);
+			break;
+	}
 }
 
 /// ------------------------
-/// Init & Others
+/// Music Fading
 /// ------------------------
 
-//
-// Initializes sound stuff, including volume
-// Sets channels, SFX and music volume,
-//  allocates channel buffer, sets S_sfx lookup.
-//
-void S_Init(INT32 sfxVolume, INT32 digMusicVolume, INT32 midiMusicVolume)
+void S_SetInternalMusicVolume(INT32 volume)
 {
-	INT32 i;
-
-	if (dedicated)
-		return;
-
-	S_SetSfxVolume(sfxVolume);
-	S_SetDigMusicVolume(digMusicVolume);
-	S_SetMIDIMusicVolume(midiMusicVolume);
-
-	SetChannelsNum();
-
-	// no sounds are playing, and they are not mus_paused
-	mus_paused = 0;
-
-	// Note that sounds have not been cached (yet).
-	for (i = 1; i < NUMSFX; i++)
-	{
-		S_sfx[i].usefulness = -1; // for I_GetSfx()
-		S_sfx[i].lumpnum = LUMPERROR;
-	}
+	I_SetInternalMusicVolume(min(max(volume, 0), 100));
+}
 
-	// precache sounds if requested by cmdline, or precachesound var true
-	if (!nosound && (M_CheckParm("-precachesound") || precachesound.value))
-	{
-		// Initialize external data (all sounds) at start, keep static.
-		CONS_Printf(M_GetText("Loading sounds... "));
+void S_StopFadingMusic(void)
+{
+	I_StopFadingSong();
+}
 
-		for (i = 1; i < NUMSFX; i++)
-			if (S_sfx[i].name)
-				S_sfx[i].data = I_GetSfx(&S_sfx[i]);
+boolean S_FadeMusicFromVolume(UINT8 target_volume, INT16 source_volume, UINT32 ms)
+{
+	if (source_volume < 0)
+		return I_FadeSong(target_volume, ms, NULL);
+	else
+		return I_FadeSongFromVolume(target_volume, source_volume, ms, NULL);
+}
 
-		CONS_Printf(M_GetText(" pre-cached all sound data\n"));
-	}
+boolean S_FadeOutStopMusic(UINT32 ms)
+{
+	return I_FadeSong(0, ms, &S_StopMusic);
 }
 
+/// ------------------------
+/// Init & Others
+/// ------------------------
 
 //
 // Per level startup code.
 // Kills playing sounds at start of level,
 //  determines music if any, changes music.
 //
-void S_Start(void)
+void S_StartEx(boolean reset)
 {
 	if (mapmusflags & MUSIC_RELOADRESET)
 	{
 		strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
 		mapmusname[6] = 0;
 		mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
+		mapmusposition = mapheaderinfo[gamemap-1]->muspos;
+	}
+
+	if (cv_resetmusic.value || reset)
+		S_StopMusic();
+	S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+
+	S_ResetMusicStack();
+	music_stack_noposition = false;
+	music_stack_fadeout = 0;
+	music_stack_fadein = JINGLEPOSTFADE;
+}
+
+static void Command_Tunes_f(void)
+{
+	const char *tunearg;
+	UINT16 tunenum, track = 0;
+	UINT32 position = 0;
+	const size_t argc = COM_Argc();
+
+	if (argc < 2) //tunes slot ...
+	{
+		CONS_Printf("tunes <name/num> [track] [speed] [position] / <-show> / <-default> / <-none>:\n");
+		CONS_Printf(M_GetText("Play an arbitrary music lump. If a map number is used, 'MAP##M' is played.\n"));
+		CONS_Printf(M_GetText("If the format supports multiple songs, you can specify which one to play.\n\n"));
+		CONS_Printf(M_GetText("* With \"-show\", shows the currently playing tune and track.\n"));
+		CONS_Printf(M_GetText("* With \"-default\", returns to the default music for the map.\n"));
+		CONS_Printf(M_GetText("* With \"-none\", any music playing will be stopped.\n"));
+		return;
 	}
 
-	mus_paused = 0;
+	tunearg = COM_Argv(1);
+	tunenum = (UINT16)atoi(tunearg);
+	track = 0;
 
-	if (cv_resetmusic.value)
+	if (!strcasecmp(tunearg, "-show"))
+	{
+		CONS_Printf(M_GetText("The current tune is: %s [track %d]\n"),
+			mapmusname, (mapmusflags & MUSIC_TRACKMASK));
+		return;
+	}
+	if (!strcasecmp(tunearg, "-none"))
+	{
 		S_StopMusic();
-	S_ChangeMusic(mapmusname, mapmusflags, true);
+		return;
+	}
+	else if (!strcasecmp(tunearg, "-default"))
+	{
+		tunearg = mapheaderinfo[gamemap-1]->musname;
+		track = mapheaderinfo[gamemap-1]->mustrack;
+	}
+	else if (!tunearg[2] && toupper(tunearg[0]) >= 'A' && toupper(tunearg[0]) <= 'Z')
+		tunenum = (UINT16)M_MapNumber(tunearg[0], tunearg[1]);
+
+	if (tunenum && tunenum >= 1036)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Valid music slots are 1 to 1035.\n"));
+		return;
+	}
+	if (!tunenum && strlen(tunearg) > 6) // This is automatic -- just show the error just in case
+		CONS_Alert(CONS_NOTICE, M_GetText("Music name too long - truncated to six characters.\n"));
+
+	if (argc > 2)
+		track = (UINT16)atoi(COM_Argv(2))-1;
+
+	if (tunenum)
+		snprintf(mapmusname, 7, "%sM", G_BuildMapName(tunenum));
+	else
+		strncpy(mapmusname, tunearg, 7);
+
+	if (argc > 4)
+		position = (UINT32)atoi(COM_Argv(4));
+
+	mapmusname[6] = 0;
+	mapmusflags = (track & MUSIC_TRACKMASK);
+	mapmusposition = position;
+
+	S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+
+	if (argc > 3)
+	{
+		float speed = (float)atof(COM_Argv(3));
+		if (speed > 0.0f)
+			S_SpeedMusic(speed);
+	}
 }
 
-//
-// Stop and resume music, during game PAUSE.
-//
-void S_PauseAudio(void)
+static void Command_RestartAudio_f(void)
+{
+	S_StopMusic();
+	S_StopSounds();
+	I_ShutdownMusic();
+	I_ShutdownSound();
+	I_StartupSound();
+	I_InitMusic();
+
+// These must be called or no sound and music until manually set.
+
+	I_SetSfxVolume(cv_soundvolume.value);
+	S_SetMusicVolume(cv_digmusicvolume.value, cv_midimusicvolume.value);
+	if (Playing()) // Gotta make sure the player is in a level
+		P_RestoreMusic(&players[consoleplayer]);
+}
+
+void GameSounds_OnChange(void)
 {
-	if (!nodigimusic)
-		I_PauseSong(0);
+	if (M_CheckParm("-nosound"))
+		return;
 
-	if (music_playing && !mus_paused)
+	if (sound_disabled)
 	{
-		I_PauseSong(music_handle);
-		mus_paused = true;
+		sound_disabled = false;
+		S_InitSfxChannels(cv_soundvolume.value);
+		S_StartSound(NULL, sfx_strpst);
 	}
+	else
+	{
+		sound_disabled = true;
+		S_StopSounds();
+	}
+}
 
-	// pause cd music
-#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	I_PauseCD();
-#else
-	I_StopCD();
-#endif
+void GameDigiMusic_OnChange(void)
+{
+	if (M_CheckParm("-nomusic"))
+		return;
+	else if (M_CheckParm("-nodigmusic"))
+		return;
+
+	if (digital_disabled)
+	{
+		digital_disabled = false;
+		I_InitMusic();
+		S_StopMusic();
+		if (Playing())
+			P_RestoreMusic(&players[consoleplayer]);
+		else
+			S_ChangeMusicInternal("_clear", false);
+	}
+	else
+	{
+		digital_disabled = true;
+		if (S_MusicType() != MU_MID)
+		{
+			if (midi_disabled)
+				S_StopMusic();
+			else
+			{
+				char mmusic[7];
+				UINT16 mflags;
+				boolean looping;
+
+				if (S_MusicInfo(mmusic, &mflags, &looping) && S_MIDIExists(mmusic))
+				{
+					S_StopMusic();
+					S_ChangeMusic(mmusic, mflags, looping);
+				}
+				else
+					S_StopMusic();
+			}
+		}
+	}
 }
 
-void S_ResumeAudio(void)
+void GameMIDIMusic_OnChange(void)
 {
-	if (!nodigimusic)
-		I_ResumeSong(0);
+	if (M_CheckParm("-nomusic"))
+		return;
+	else if (M_CheckParm("-nomidimusic"))
+		return;
+
+	if (midi_disabled)
+	{
+		midi_disabled = false;
+		I_InitMusic();
+		if (Playing())
+			P_RestoreMusic(&players[consoleplayer]);
+		else
+			S_ChangeMusicInternal("_clear", false);
+	}
 	else
-	if (music_playing && mus_paused)
 	{
-		I_ResumeSong(music_handle);
-		mus_paused = false;
+		midi_disabled = true;
+		if (S_MusicType() == MU_MID)
+		{
+			if (digital_disabled)
+				S_StopMusic();
+			else
+			{
+				char mmusic[7];
+				UINT16 mflags;
+				boolean looping;
+
+				if (S_MusicInfo(mmusic, &mflags, &looping) && S_DigExists(mmusic))
+				{
+					S_StopMusic();
+					S_ChangeMusic(mmusic, mflags, looping);
+				}
+				else
+					S_StopMusic();
+			}
+		}
 	}
+}
 
-	// resume cd music
-	I_ResumeCD();
+#ifdef HAVE_OPENMPT
+void ModFilter_OnChange(void)
+{
+	if (openmpt_mhandle)
+		openmpt_module_set_render_param(openmpt_mhandle, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, cv_modfilter.value);
 }
+#endif
diff --git a/src/s_sound.h b/src/s_sound.h
index 4b9735480b7ea1cd666431e5f6dac592d2bc6ec5..e0737eff743643f90683d293aec483d2e4360a17 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -14,17 +14,37 @@
 #ifndef __S_SOUND__
 #define __S_SOUND__
 
+#include "i_sound.h" // musictype_t
 #include "sounds.h"
 #include "m_fixed.h"
 #include "command.h"
 #include "tables.h" // angle_t
 
+#ifdef HAVE_OPENMPT
+#include "libopenmpt/libopenmpt.h"
+openmpt_module *openmpt_mhandle;
+#endif
+
 // mask used to indicate sound origin is player item pickup
 #define PICKUP_SOUND 0x8000
 
 extern consvar_t stereoreverse;
 extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume, cv_midimusicvolume;
 extern consvar_t cv_numChannels;
+extern consvar_t cv_resetmusic;
+extern consvar_t cv_gamedigimusic;
+extern consvar_t cv_gamemidimusic;
+extern consvar_t cv_gamesounds;
+
+#ifdef HAVE_OPENMPT
+extern consvar_t cv_modfilter;
+#endif
+
+#ifdef HAVE_MIXERX
+extern consvar_t cv_midiplayer;
+extern consvar_t cv_midisoundfontpath;
+extern consvar_t cv_miditimiditypath;
+#endif
 
 #ifdef SNDSERV
 extern consvar_t sndserver_cmd, sndserver_arg;
@@ -97,9 +117,9 @@ void S_RegisterSoundStuff(void);
 
 //
 // Initializes sound stuff, including volume
-// Sets channels, SFX and music volume, allocates channel buffer, sets S_sfx lookup.
+// Sets channels, SFX, allocates channel buffer, sets S_sfx lookup.
 //
-void S_Init(INT32 sfxVolume, INT32 digMusicVolume, INT32 midiMusicVolume);
+void S_InitSfxChannels(INT32 sfxVolume);
 
 //
 // Per level startup code.
@@ -107,7 +127,8 @@ void S_Init(INT32 sfxVolume, INT32 digMusicVolume, INT32 midiMusicVolume);
 //
 void S_StopSounds(void);
 void S_ClearSfx(void);
-void S_Start(void);
+void S_StartEx(boolean reset);
+#define S_Start() S_StartEx(false)
 
 //
 // Basically a W_GetNumForName that adds "ds" at the beginning of the string. Returns a lumpnum.
@@ -125,15 +146,87 @@ void S_StartSoundAtVolume(const void *origin, sfxenum_t sound_id, INT32 volume);
 // Stop sound for thing at <origin>
 void S_StopSound(void *origin);
 
+//
+// Music Status
+//
+
+boolean S_DigMusicDisabled(void);
+boolean S_MIDIMusicDisabled(void);
+boolean S_MusicDisabled(void);
+boolean S_MusicPlaying(void);
+boolean S_MusicPaused(void);
+musictype_t S_MusicType(void);
+const char *S_MusicName(void);
+boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping);
+boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi);
+#define S_DigExists(a) S_MusicExists(a, false, true)
+#define S_MIDIExists(a) S_MusicExists(a, true, false)
+
+//
+// Music Effects
+//
+
+// Set Speed of Music
+boolean S_SpeedMusic(float speed);
+
+//
+// Music Seeking
+//
+
+// Get Length of Music
+UINT32 S_GetMusicLength(void);
+
+// Set LoopPoint of Music
+boolean S_SetMusicLoopPoint(UINT32 looppoint);
+
+// Get LoopPoint of Music
+UINT32 S_GetMusicLoopPoint(void);
+
+// Set Position of Music
+boolean S_SetMusicPosition(UINT32 position);
+
+// Get Position of Music
+UINT32 S_GetMusicPosition(void);
+
+//
+// Music Stacking (Jingles)
+//
+
+typedef struct musicstack_s
+{
+	char musname[7];
+	UINT16 musflags;
+	boolean looping;
+	UINT32 position;
+	tic_t tic;
+	UINT16 status;
+	lumpnum_t mlumpnum;
+
+    struct musicstack_s *prev;
+    struct musicstack_s *next;
+} musicstack_t;
+
+char music_stack_nextmusname[7];
+boolean music_stack_noposition;
+UINT32 music_stack_fadeout;
+UINT32 music_stack_fadein;
+
+void S_SetStackAdjustmentStart(void);
+void S_AdjustMusicStackTics(void);
+void S_RetainMusic(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status);
+boolean S_RecallMusic(UINT16 status, boolean fromfirst);
+
+//
+// Music Playback
+//
+
 // Start music track, arbitrary, given its name, and set whether looping
 // note: music flags 12 bits for tracknum (gme, other formats with more than one track)
 //       13-15 aren't used yet
 //       and the last bit we ignore (internal game flag for resetting music on reload)
-#define S_ChangeMusicInternal(a,b) S_ChangeMusic(a,0,b)
-void S_ChangeMusic(const char *mmusic, UINT16 mflags, boolean looping);
-
-// Set Speed of Music
-boolean S_SpeedMusic(float speed);
+void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms);
+#define S_ChangeMusicInternal(a,b) S_ChangeMusicEx(a,0,b,0,0,0)
+#define S_ChangeMusic(a,b,c) S_ChangeMusicEx(a,b,c,0,0,0)
 
 // Stops the music.
 void S_StopMusic(void);
@@ -142,6 +235,17 @@ void S_StopMusic(void);
 void S_PauseAudio(void);
 void S_ResumeAudio(void);
 
+//
+// Music Fading
+//
+
+void S_SetInternalMusicVolume(INT32 volume);
+void S_StopFadingMusic(void);
+boolean S_FadeMusicFromVolume(UINT8 target_volume, INT16 source_volume, UINT32 ms);
+#define S_FadeMusic(a, b) S_FadeMusicFromVolume(a, -1, b)
+#define S_FadeInChangeMusic(a,b,c,d) S_ChangeMusicEx(a,b,c,0,0,d)
+boolean S_FadeOutStopMusic(UINT32 ms);
+
 //
 // Updates music & sounds
 //
@@ -149,9 +253,11 @@ void S_UpdateSounds(void);
 
 FUNCMATH fixed_t S_CalculateSoundDistance(fixed_t px1, fixed_t py1, fixed_t pz1, fixed_t px2, fixed_t py2, fixed_t pz2);
 
-void S_SetDigMusicVolume(INT32 volume);
-void S_SetMIDIMusicVolume(INT32 volume);
 void S_SetSfxVolume(INT32 volume);
+void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume);
+#define S_SetDigMusicVolume(a) S_SetMusicVolume(a,-1)
+#define S_SetMIDIMusicVolume(a) S_SetMusicVolume(-1,a)
+#define S_InitMusicVolume() S_SetMusicVolume(-1,-1)
 
 INT32 S_OriginPlaying(void *origin);
 INT32 S_IdPlaying(sfxenum_t id);
diff --git a/src/screen.c b/src/screen.c
index 28765785bda30f2071c14e7176f022371a20ddad..fc3f5b8e87f863112cdd88a5e7d0cd1f9faea95f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -69,11 +69,7 @@ consvar_t cv_scr_height = {"scr_height", "800", CV_SAVE, CV_Unsigned, NULL, 0, N
 consvar_t cv_scr_depth = {"scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_renderview = {"renderview", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-#ifdef DIRECTFULLSCREEN
-static FUNCMATH void SCR_ChangeFullscreen (void);
-#else
 static void SCR_ChangeFullscreen (void);
-#endif
 
 consvar_t cv_fullscreen = {"fullscreen", "Yes", CV_SAVE|CV_CALL, CV_YesNo, SCR_ChangeFullscreen, 0, NULL, NULL, 0, 0, NULL};
 
@@ -285,7 +281,10 @@ void SCR_Recalc(void)
 	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
 
 #ifdef HWRENDER
-	if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
+	//if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
+	// 13/11/18:
+	// The above is no longer necessary, since we want OpenGL to be just like software now
+	// -- Monster Iestyn
 #endif
 		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
 
@@ -311,14 +310,6 @@ void SCR_Recalc(void)
 	if (automapactive)
 		AM_Stop();
 
-	// r_plane stuff: visplanes, openings, floorclip, ceilingclip, spanstart,
-	//                spanstop, yslope, distscale, cachedheight, cacheddistance,
-	//                cachedxstep, cachedystep
-	//             -> allocated at the maximum vidsize, static.
-
-	// r_main: xtoviewangle, allocated at the maximum size.
-	// r_things: negonearray, screenheightarray allocated max. size.
-
 	// set the screen[x] ptrs on the new vidbuffers
 	V_Init();
 
@@ -414,7 +405,7 @@ void SCR_DisplayTicRate(void)
 	tic_t ontic = I_GetTime();
 	tic_t totaltics = 0;
 	INT32 ticcntcolor = 0;
-	INT32 offs = (cv_debug ? 8 : 0);
+	const INT32 h = vid.height-(8*vid.dupy);
 
 	for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i)
 		fpsgraph[i % TICRATE] = false;
@@ -428,9 +419,9 @@ void SCR_DisplayTicRate(void)
 	if (totaltics <= TICRATE/2) ticcntcolor = V_REDMAP;
 	else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP;
 
-	V_DrawString(vid.width-((24+(6*offs))*vid.dupx), vid.height-((16-offs)*vid.dupy),
-		V_YELLOWMAP|V_NOSCALESTART, "FPS");
-	V_DrawString(vid.width-(40*vid.dupx), vid.height-(8*vid.dupy),
+	V_DrawString(vid.width-(72*vid.dupx), h,
+		V_YELLOWMAP|V_NOSCALESTART, "FPS:");
+	V_DrawString(vid.width-(40*vid.dupx), h,
 		ticcntcolor|V_NOSCALESTART, va("%02d/%02u", totaltics, TICRATE));
 
 	lasttic = ontic;
@@ -440,6 +431,23 @@ void SCR_ClosedCaptions(void)
 {
 	UINT8 i;
 	boolean gamestopped = (paused || P_AutoPause());
+	INT32 basey = BASEVIDHEIGHT;
+
+	if (gamestate != wipegamestate)
+		return;
+
+	if (gamestate == GS_LEVEL)
+	{
+		if (promptactive)
+			basey -= 28;
+		else if (splitscreen)
+			basey -= 8;
+		else if ((modeattacking == ATTACKING_NIGHTS)
+		|| (!(maptol & TOL_NIGHTS)
+		&& ((cv_powerupdisplay.value == 2) // "Always"
+		 || (cv_powerupdisplay.value == 1 && !camera.chase)))) // "First-person only"
+			basey -= 16;
+	}
 
 	for (i = 0; i < NUMCAPTIONS; i++)
 	{
@@ -455,9 +463,8 @@ void SCR_ClosedCaptions(void)
 		if (music && !gamestopped && (closedcaptions[i].t < flashingtics) && (closedcaptions[i].t & 1))
 			continue;
 
-		flags = V_NOSCALESTART|V_ALLOWLOWERCASE;
-		y = vid.height-((i + 2)*10*vid.dupy);
-		dot = ' ';
+		flags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_ALLOWLOWERCASE;
+		y = basey-((i + 2)*10);
 
 		if (closedcaptions[i].b)
 			y -= (closedcaptions[i].b--)*vid.dupy;
@@ -469,8 +476,10 @@ void SCR_ClosedCaptions(void)
 			dot = '\x19';
 		else if (closedcaptions[i].c && closedcaptions[i].c->origin)
 			dot = '\x1E';
+		else
+			dot = ' ';
 
-		V_DrawRightAlignedString(vid.width-(20*vid.dupx), y,
-		flags, va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
+		V_DrawRightAlignedString(BASEVIDWIDTH - 20, y, flags,
+			va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
 	}
 }
diff --git a/src/screen.h b/src/screen.h
index 4d4fbb88b8cc62ccf4363e41a5b8a639af0b4e68..7aa6fdb638a1f68fa70373bb258777f71b701282 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt
index 7f6771262dcbd2415639f41d20ad7a15a3092da4..f1aa135b9eafd520e0700835b9c509898f22bef3 100644
--- a/src/sdl/CMakeLists.txt
+++ b/src/sdl/CMakeLists.txt
@@ -3,7 +3,18 @@
 set(SRB2_CONFIG_SDL2_USEMIXER ON CACHE BOOL "Use SDL2_mixer or regular sdl sound")
 
 if(${SRB2_CONFIG_SDL2_USEMIXER})
-	find_package(SDL2_mixer)
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(SDL2_MIXER_FOUND ON)
+		if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(SDL2_MIXER_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2)
+			set(SDL2_MIXER_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/x86_64-w64-mingw32/lib -lSDL2_mixer")
+		else() # 32-bit
+			set(SDL2_MIXER_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/i686-w64-mingw32/include/SDL2)
+			set(SDL2_MIXER_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/i686-w64-mingw32/lib -lSDL2_mixer")
+		endif()
+	else()
+		find_package(SDL2_mixer)
+	endif()
 	if(${SDL2_MIXER_FOUND})
 		set(SRB2_HAVE_MIXER ON)
 		set(SRB2_SDL2_SOUNDIMPL mixer_sound.c)
@@ -42,12 +53,25 @@ set(SRB2_SDL2_HEADERS
 source_group("Interface Code" FILES ${SRB2_SDL2_SOURCES} ${SRB2_SDL2_HEADERS})
 
 # Dependency
-find_package(SDL2)
+if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+	set(SDL2_FOUND ON)
+	if(${SRB2_SYSTEM_BITS} EQUAL 64)
+		set(SDL2_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/include/SDL2)
+		set(SDL2_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/lib -lSDL2")
+	else() # 32-bit
+		set(SDL2_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/include/SDL2)
+		set(SDL2_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/lib -lSDL2")
+	endif()
+else()
+	find_package(SDL2)
+endif()
 
 if(${SDL2_FOUND})
 	set(SRB2_SDL2_TOTAL_SOURCES
 		${SRB2_CORE_SOURCES}
 		${SRB2_CORE_HEADERS}
+		${SRB2_PNG_SOURCES}
+		${SRB2_PNG_HEADERS}
 		${SRB2_CORE_RENDER_SOURCES}
 		${SRB2_CORE_GAME_SOURCES}
 		${SRB2_LUA_SOURCES}
@@ -58,7 +82,8 @@ if(${SDL2_FOUND})
 		${SRB2_SDL2_HEADERS}
 	)
 
-	source_group("Main" FILES ${SRB2_CORE_SOURCES} ${SRB2_CORE_HEADERS})
+	source_group("Main" FILES ${SRB2_CORE_SOURCES} ${SRB2_CORE_HEADERS}
+		${SRB2_PNG_SOURCES} ${SRB2_PNG_HEADERS})
 	source_group("Renderer" FILES ${SRB2_CORE_RENDER_SOURCES})
 	source_group("Game" FILES ${SRB2_CORE_GAME_SOURCES})
 	source_group("Assembly" FILES ${SRB2_ASM_SOURCES} ${SRB2_NASM_SOURCES})
@@ -124,16 +149,18 @@ if(${SDL2_FOUND})
 			SDL2
 			SDL2_mixer
 			${GME_LIBRARIES}
+			${OPENMPT_LIBRARIES}
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
 		)
-		set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "Sonic Robo Blast 2")
+		set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
 	else()
 		target_link_libraries(SRB2SDL2 PRIVATE
 			${SDL2_LIBRARIES}
 			${SDL2_MIXER_LIBRARIES}
 			${GME_LIBRARIES}
+			${OPENMPT_LIBRARIES}
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
@@ -185,7 +212,18 @@ if(${SDL2_FOUND})
 	endif()
 
 	if(MSVC)
-		find_package(SDL2_MAIN REQUIRED)
+		if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+			set(SDL2_MAIN_FOUND ON)
+			if(${SRB2_SYSTEM_BITS} EQUAL 64)
+				set(SDL2_MAIN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/include/SDL2)
+				set(SDL2_MAIN_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/lib -lSDL2main")
+			else() # 32-bit
+				set(SDL2_MAIN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/include/SDL2)
+				set(SDL2_MAIN_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/lib -lSDL2main")
+			endif()
+		else()
+			find_package(SDL2_MAIN REQUIRED)
+		endif()
 		target_link_libraries(SRB2SDL2 PRIVATE
 			${SDL2_MAIN_LIBRARIES}
 		)
@@ -202,6 +240,7 @@ if(${SDL2_FOUND})
 		${SDL2_INCLUDE_DIRS}
 		${SDL2_MIXER_INCLUDE_DIRS}
 		${GME_INCLUDE_DIRS}
+		${OPENMPT_INCLUDE_DIRS}
 		${PNG_INCLUDE_DIRS}
 		${ZLIB_INCLUDE_DIRS}
 		${OPENGL_INCLUDE_DIRS}
@@ -241,21 +280,60 @@ if(${SDL2_FOUND})
 	if(${CMAKE_SYSTEM} MATCHES Windows)
 		set(win_extra_dll_list "")
 		macro(getwinlib dllname defaultname)
-			find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}")
-			list(APPEND win_extra_dll_list ${SRB2_SDL2_DLL_${dllname}})
+			if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+				if (${CMAKE_GENERATOR} STREQUAL "MinGW Makefiles")
+					if(${SRB2_SYSTEM_BITS} EQUAL 64)
+						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
+							HINTS ${CMAKE_SOURCE_DIR}/libs/dll-binaries/x86_64
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/bin
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/x86_64-w64-mingw32/bin
+							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86_64/mingw
+						)
+					else()
+						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
+							HINTS ${CMAKE_SOURCE_DIR}/libs/dll-binaries/i686
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/bin
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/i686-w64-mingw32/bin
+							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86/mingw
+						)
+					endif()
+				else()
+					if(${SRB2_SYSTEM_BITS} EQUAL 64)
+						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
+							HINTS ${CMAKE_SOURCE_DIR}/libs/dll-binaries/x86_64
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/lib/x64
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/lib/x64
+							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86_64/mingw
+						)
+					else()
+						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
+							HINTS ${CMAKE_SOURCE_DIR}/libs/dll-binaries/i686
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/lib/x86
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/lib/x86
+							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86/mingw
+						)
+					endif()
+				endif()
+
+				list(APPEND win_extra_dll_list ${SRB2_SDL2_DLL_${dllname}})
+			else()
+				find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}")
+				list(APPEND win_extra_dll_list ${SRB2_SDL2_DLL_${dllname}})
+	endif()
 		endmacro()
 		getwinlib(SDL2 "SDL2.dll")
 		if(${SRB2_CONFIG_SDL2_USEMIXER})
 			getwinlib(SDL2_mixer "SDL2_mixer.dll")
-			getwinlib(libmikmod-2 "libmikmod-2.dll")
 			getwinlib(libogg_0 "libogg-0.dll")
 			getwinlib(libvorbis_0 "libvorbis-0.dll")
 			getwinlib(libvorbisfile_3 "libvorbisfile-3.dll")
-			getwinlib(smpeg2 "smpeg2.dll")
 		endif()
 		if(${SRB2_CONFIG_HAVE_GME})
 			getwinlib(libgme "libgme.dll")
 		endif()
+		if(${SRB2_CONFIG_HAVE_OPENMPT})
+			getwinlib(libopenmpt "libopenmpt.dll")
+		endif()
 
 		install(PROGRAMS
 			${win_extra_dll_list}
@@ -269,10 +347,19 @@ if(${SDL2_FOUND})
 
 
 	# Mac bundle fixup
+	# HACK: THIS IS IMPORTANT! See the escaped \${CMAKE_INSTALL_PREFIX}? This
+	# makes it so that var is evaluated LATER during cpack, not right now!
+	# This fixes the quirk where the bundled libraries don't land in the final package
+	# https://cmake.org/pipermail/cmake/2011-March/043532.html
+	#
+	# HOWEVER: ${CPACK_PACKAGE_DESCRIPTION_SUMMARY} is NOT escaped, because that var
+	# is only available to us at this step. Read the link: ${CMAKE_INSTALL_PREFIX} at
+	# this current step points to the CMAKE build folder, NOT the folder that CPACK uses.
+	# Therefore, it makes sense to escape that var, but not the other.
 	if(${CMAKE_SYSTEM} MATCHES Darwin)
 		install(CODE "
 			include(BundleUtilities)
-			fixup_bundle(\"${CMAKE_INSTALL_PREFIX}/Sonic Robo Blast 2.app\"
+			fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${CPACK_PACKAGE_DESCRIPTION_SUMMARY}.app\"
 				\"\"
 				/Library/Frameworks
 			)"
diff --git a/src/sdl/IMG_xpm.c b/src/sdl/IMG_xpm.c
index e08736d66515b89367f29022afd4ed663e570497..af76ec1ddd1d2dfe8ee0b02f22293c7db7696cdd 100644
--- a/src/sdl/IMG_xpm.c
+++ b/src/sdl/IMG_xpm.c
@@ -1,27 +1,24 @@
 /*
-    SDL_image:  An example image loading library for use with SDL
-    Copyright (C) 1999-2004 Sam Lantinga
+  SDL_image:  An example image loading library for use with SDL
+  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
 
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Library General Public
-    License as published by the Free Software Foundation; either
-    version 2 of the License, or (at your option) any later version.
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
 
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Library General Public License for more details.
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
 
-    You should have received a copy of the GNU Library General Public
-    License along with this library; if not, write to the Free
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-    Sam Lantinga
-    slouken@libsdl.org
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
 */
 
-/* $Id: IMG_xpm.c,v 1.10 2004/01/04 22:04:38 slouken Exp $ */
-
 /*
  * XPM (X PixMap) image loader:
  *
@@ -45,98 +42,111 @@
  * requires about 13K in binary form.
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-
-//#include "SDL_image.h"
-
+#if 0
+#include "SDL_image.h"
+#else
+// SDLCALL terms removed from original SDL_image declarations
+int IMG_isXPM(SDL_RWops *src);
+SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src);
+SDL_Surface *IMG_ReadXPMFromArray(const char **xpm);
+#define IMG_SetError    SDL_SetError
+#define IMG_GetError    SDL_GetError
+#endif
 
 #ifdef LOAD_XPM
 
 /* See if an image is contained in a data source */
-#if 0
 int IMG_isXPM(SDL_RWops *src)
 {
-        char magic[9];
+    Sint64 start;
+    int is_XPM;
+    char magic[9];
 
-        return (SDL_RWread(src, magic, sizeof (magic), 1)
-                && memcmp(magic, "/* XPM */", 9) == 0);
+    if ( !src )
+        return 0;
+    start = SDL_RWtell(src);
+    is_XPM = 0;
+    if ( SDL_RWread(src, magic, sizeof(magic), 1) ) {
+        if ( SDL_memcmp(magic, "/* XPM */", sizeof(magic)) == 0 ) {
+            is_XPM = 1;
+        }
+    }
+    SDL_RWseek(src, start, RW_SEEK_SET);
+    return(is_XPM);
 }
-#endif
 
 /* Hash table to look up colors from pixel strings */
 #define STARTING_HASH_SIZE 256
 
 struct hash_entry {
-        char *key;
-        Uint32 color;
-        struct hash_entry *next;
+    const char *key;
+    Uint32 color;
+    struct hash_entry *next;
 };
 
 struct color_hash {
-        struct hash_entry **table;
-        struct hash_entry *entries; /* array of all entries */
-        struct hash_entry *next_free;
-        size_t size;
-        int maxnum;
+    struct hash_entry **table;
+    struct hash_entry *entries; /* array of all entries */
+    struct hash_entry *next_free;
+    int size;
+    int maxnum;
 };
 
-static int hash_key(const char *key, int cpp, size_t size)
+static int hash_key(const char *key, int cpp, int size)
 {
-        int hash;
+    int hash;
 
-        hash = 0;
-        while ( cpp-- > 0 ) {
-                hash = hash * 33 + *key++;
-        }
-        return (int)(hash & (size - 1));
+    hash = 0;
+    while ( cpp-- > 0 ) {
+        hash = hash * 33 + *key++;
+    }
+    return hash & (size - 1);
 }
 
 static struct color_hash *create_colorhash(int maxnum)
 {
-        size_t bytes;
-		int s;
-        struct color_hash *hash;
-
-        /* we know how many entries we need, so we can allocate
-           everything here */
-        hash = malloc(sizeof *hash);
-        if (!hash)
-                return NULL;
+    int bytes, s;
+    struct color_hash *hash;
 
-        /* use power-of-2 sized hash table for decoding speed */
-        for (s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
-                ;
-        hash->size = s;
-        hash->maxnum = maxnum;
-        bytes = hash->size * sizeof (struct hash_entry **);
-        hash->entries = NULL;        /* in case malloc fails */
-        hash->table = malloc(bytes);
-        if (!hash->table)
-                return NULL;
-        memset(hash->table, 0, bytes);
-        hash->entries = malloc(maxnum * sizeof (struct hash_entry));
-        if (!hash->entries)
-        {
-                free(hash->table);
-                return NULL;
-        }
-        hash->next_free = hash->entries;
-        return hash;
+    /* we know how many entries we need, so we can allocate
+       everything here */
+    hash = (struct color_hash *)SDL_malloc(sizeof *hash);
+    if (!hash)
+        return NULL;
+
+    /* use power-of-2 sized hash table for decoding speed */
+    for (s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
+        ;
+    hash->size = s;
+    hash->maxnum = maxnum;
+    bytes = hash->size * sizeof(struct hash_entry **);
+    hash->entries = NULL;   /* in case malloc fails */
+    hash->table = (struct hash_entry **)SDL_malloc(bytes);
+    if (!hash->table) {
+        SDL_free(hash);
+        return NULL;
+    }
+    SDL_memset(hash->table, 0, bytes);
+    hash->entries = (struct hash_entry *)SDL_malloc(maxnum * sizeof(struct hash_entry));
+    if (!hash->entries) {
+        SDL_free(hash->table);
+        SDL_free(hash);
+        return NULL;
+    }
+    hash->next_free = hash->entries;
+    return hash;
 }
 
 static int add_colorhash(struct color_hash *hash,
                          char *key, int cpp, Uint32 color)
 {
-        const int indexkey = hash_key(key, cpp, hash->size);
-        struct hash_entry *e = hash->next_free++;
-        e->color = color;
-        e->key = key;
-        e->next = hash->table[indexkey];
-        hash->table[indexkey] = e;
-        return 1;
+    int index = hash_key(key, cpp, hash->size);
+    struct hash_entry *e = hash->next_free++;
+    e->color = color;
+    e->key = key;
+    e->next = hash->table[index];
+    hash->table[index] = e;
+    return 1;
 }
 
 /* fast lookup that works if cpp == 1 */
@@ -144,88 +154,756 @@ static int add_colorhash(struct color_hash *hash,
 
 static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp)
 {
-        struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
-        while (entry) {
-                if (memcmp(key, entry->key, cpp) == 0)
-                        return entry->color;
-                entry = entry->next;
-        }
-        return 0;                /* garbage in - garbage out */
+    struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
+    while (entry) {
+        if (SDL_memcmp(key, entry->key, cpp) == 0)
+            return entry->color;
+        entry = entry->next;
+    }
+    return 0;       /* garbage in - garbage out */
 }
 
 static void free_colorhash(struct color_hash *hash)
 {
-        if (hash && hash->table) {
-                free(hash->table);
-                free(hash->entries);
-                free(hash);
-        }
-}
-
-/* portable case-insensitive string comparison */
-static int string_equal(const char *a, const char *b, size_t n)
-{
-        while (*a && *b && n) {
-                if (toupper((unsigned char)*a) != toupper((unsigned char)*b))
-                        return 0;
-                a++;
-                b++;
-                n--;
-        }
-        return *a == *b;
+    if (hash) {
+        if (hash->table)
+            SDL_free(hash->table);
+        if (hash->entries)
+            SDL_free(hash->entries);
+        SDL_free(hash);
+    }
 }
 
-#undef ARRAYSIZE
-#define ARRAYSIZE(a) (int)(sizeof (a) / sizeof ((a)[0]))
-
 /*
  * convert colour spec to RGB (in 0xrrggbb format).
  * return 1 if successful.
  */
-static int color_to_rgb(const char *spec, size_t speclen, Uint32 *rgb)
+static int color_to_rgb(const char *spec, int speclen, Uint32 *rgb)
 {
-        /* poor man's rgb.txt */
-        static struct { const char *name; Uint32 rgb; } known[] = {
-                {"none",  0xffffffff},
-                {"black", 0x00000000},
-                {"white", 0x00ffffff},
-                {"red",   0x00ff0000},
-                {"green", 0x0000ff00},
-                {"blue",  0x000000ff}
-        };
-
-        if (spec[0] == '#') {
-                char buf[7];
-                switch (speclen) {
-                case 4:
-                        buf[0] = buf[1] = spec[1];
-                        buf[2] = buf[3] = spec[2];
-                        buf[4] = buf[5] = spec[3];
-                        break;
-                case 7:
-                        memcpy(buf, spec + 1, 6);
-                        break;
-                case 13:
-                        buf[0] = spec[1];
-                        buf[1] = spec[2];
-                        buf[2] = spec[5];
-                        buf[3] = spec[6];
-                        buf[4] = spec[9];
-                        buf[5] = spec[10];
-                        break;
-                }
-                buf[6] = '\0';
-                *rgb = (Uint32)strtol(buf, NULL, 16);
+    /* poor man's rgb.txt */
+    static struct { const char *name; Uint32 rgb; } known[] = {
+        { "none",                 0xFFFFFFFF },
+        { "black",                0x000000 },
+        { "white",                0xFFFFFF },
+        { "red",                  0xFF0000 },
+        { "green",                0x00FF00 },
+        { "blue",                 0x0000FF },
+/* This table increases the size of the library by 40K, so it's disabled by default */
+#ifdef EXTENDED_XPM_COLORS
+        { "aliceblue",            0xf0f8ff },
+        { "antiquewhite",         0xfaebd7 },
+        { "antiquewhite1",        0xffefdb },
+        { "antiquewhite2",        0xeedfcc },
+        { "antiquewhite3",        0xcdc0b0 },
+        { "antiquewhite4",        0x8b8378 },
+        { "aqua",                 0x00ffff },
+        { "aquamarine",           0x7fffd4 },
+        { "aquamarine1",          0x7fffd4 },
+        { "aquamarine2",          0x76eec6 },
+        { "aquamarine3",          0x66cdaa },
+        { "aquamarine4",          0x458b74 },
+        { "azure",                0xf0ffff },
+        { "azure1",               0xf0ffff },
+        { "azure2",               0xe0eeee },
+        { "azure3",               0xc1cdcd },
+        { "azure4",               0x838b8b },
+        { "beige",                0xf5f5dc },
+        { "bisque",               0xffe4c4 },
+        { "bisque1",              0xffe4c4 },
+        { "bisque2",              0xeed5b7 },
+        { "bisque3",              0xcdb79e },
+        { "bisque4",              0x8b7d6b },
+        { "black",                0x000000 },
+        { "blanchedalmond",       0xffebcd },
+        { "blue",                 0x0000ff },
+        { "blue1",                0x0000ff },
+        { "blue2",                0x0000ee },
+        { "blue3",                0x0000cd },
+        { "blue4",                0x00008B },
+        { "blueviolet",           0x8a2be2 },
+        { "brown",                0xA52A2A },
+        { "brown1",               0xFF4040 },
+        { "brown2",               0xEE3B3B },
+        { "brown3",               0xCD3333 },
+        { "brown4",               0x8B2323 },
+        { "burlywood",            0xDEB887 },
+        { "burlywood1",           0xFFD39B },
+        { "burlywood2",           0xEEC591 },
+        { "burlywood3",           0xCDAA7D },
+        { "burlywood4",           0x8B7355 },
+        { "cadetblue",            0x5F9EA0 },
+        { "cadetblue",            0x5f9ea0 },
+        { "cadetblue1",           0x98f5ff },
+        { "cadetblue2",           0x8ee5ee },
+        { "cadetblue3",           0x7ac5cd },
+        { "cadetblue4",           0x53868b },
+        { "chartreuse",           0x7FFF00 },
+        { "chartreuse1",          0x7FFF00 },
+        { "chartreuse2",          0x76EE00 },
+        { "chartreuse3",          0x66CD00 },
+        { "chartreuse4",          0x458B00 },
+        { "chocolate",            0xD2691E },
+        { "chocolate1",           0xFF7F24 },
+        { "chocolate2",           0xEE7621 },
+        { "chocolate3",           0xCD661D },
+        { "chocolate4",           0x8B4513 },
+        { "coral",                0xFF7F50 },
+        { "coral1",               0xFF7256 },
+        { "coral2",               0xEE6A50 },
+        { "coral3",               0xCD5B45 },
+        { "coral4",               0x8B3E2F },
+        { "cornflowerblue",       0x6495ed },
+        { "cornsilk",             0xFFF8DC },
+        { "cornsilk1",            0xFFF8DC },
+        { "cornsilk2",            0xEEE8CD },
+        { "cornsilk3",            0xCDC8B1 },
+        { "cornsilk4",            0x8B8878 },
+        { "crimson",              0xDC143C },
+        { "cyan",                 0x00FFFF },
+        { "cyan1",                0x00FFFF },
+        { "cyan2",                0x00EEEE },
+        { "cyan3",                0x00CDCD },
+        { "cyan4",                0x008B8B },
+        { "darkblue",             0x00008b },
+        { "darkcyan",             0x008b8b },
+        { "darkgoldenrod",        0xb8860b },
+        { "darkgoldenrod1",       0xffb90f },
+        { "darkgoldenrod2",       0xeead0e },
+        { "darkgoldenrod3",       0xcd950c },
+        { "darkgoldenrod4",       0x8b6508 },
+        { "darkgray",             0xa9a9a9 },
+        { "darkgreen",            0x006400 },
+        { "darkgrey",             0xa9a9a9 },
+        { "darkkhaki",            0xbdb76b },
+        { "darkmagenta",          0x8b008b },
+        { "darkolivegreen",       0x556b2f },
+        { "darkolivegreen1",      0xcaff70 },
+        { "darkolivegreen2",      0xbcee68 },
+        { "darkolivegreen3",      0xa2cd5a },
+        { "darkolivegreen4",      0x6e8b3d },
+        { "darkorange",           0xff8c00 },
+        { "darkorange1",          0xff7f00 },
+        { "darkorange2",          0xee7600 },
+        { "darkorange3",          0xcd6600 },
+        { "darkorange4",          0x8b4500 },
+        { "darkorchid",           0x9932cc },
+        { "darkorchid1",          0xbf3eff },
+        { "darkorchid2",          0xb23aee },
+        { "darkorchid3",          0x9a32cd },
+        { "darkorchid4",          0x68228b },
+        { "darkred",              0x8b0000 },
+        { "darksalmon",           0xe9967a },
+        { "darkseagreen",         0x8fbc8f },
+        { "darkseagreen1",        0xc1ffc1 },
+        { "darkseagreen2",        0xb4eeb4 },
+        { "darkseagreen3",        0x9bcd9b },
+        { "darkseagreen4",        0x698b69 },
+        { "darkslateblue",        0x483d8b },
+        { "darkslategray",        0x2f4f4f },
+        { "darkslategray1",       0x97ffff },
+        { "darkslategray2",       0x8deeee },
+        { "darkslategray3",       0x79cdcd },
+        { "darkslategray4",       0x528b8b },
+        { "darkslategrey",        0x2f4f4f },
+        { "darkturquoise",        0x00ced1 },
+        { "darkviolet",           0x9400D3 },
+        { "darkviolet",           0x9400d3 },
+        { "deeppink",             0xff1493 },
+        { "deeppink1",            0xff1493 },
+        { "deeppink2",            0xee1289 },
+        { "deeppink3",            0xcd1076 },
+        { "deeppink4",            0x8b0a50 },
+        { "deepskyblue",          0x00bfff },
+        { "deepskyblue1",         0x00bfff },
+        { "deepskyblue2",         0x00b2ee },
+        { "deepskyblue3",         0x009acd },
+        { "deepskyblue4",         0x00688b },
+        { "dimgray",              0x696969 },
+        { "dimgrey",              0x696969 },
+        { "dodgerblue",           0x1e90ff },
+        { "dodgerblue1",          0x1e90ff },
+        { "dodgerblue2",          0x1c86ee },
+        { "dodgerblue3",          0x1874cd },
+        { "dodgerblue4",          0x104e8b },
+        { "firebrick",            0xB22222 },
+        { "firebrick1",           0xFF3030 },
+        { "firebrick2",           0xEE2C2C },
+        { "firebrick3",           0xCD2626 },
+        { "firebrick4",           0x8B1A1A },
+        { "floralwhite",          0xfffaf0 },
+        { "forestgreen",          0x228b22 },
+        { "fractal",              0x808080 },
+        { "fuchsia",              0xFF00FF },
+        { "gainsboro",            0xDCDCDC },
+        { "ghostwhite",           0xf8f8ff },
+        { "gold",                 0xFFD700 },
+        { "gold1",                0xFFD700 },
+        { "gold2",                0xEEC900 },
+        { "gold3",                0xCDAD00 },
+        { "gold4",                0x8B7500 },
+        { "goldenrod",            0xDAA520 },
+        { "goldenrod1",           0xFFC125 },
+        { "goldenrod2",           0xEEB422 },
+        { "goldenrod3",           0xCD9B1D },
+        { "goldenrod4",           0x8B6914 },
+        { "gray",                 0x7E7E7E },
+        { "gray",                 0xBEBEBE },
+        { "gray0",                0x000000 },
+        { "gray1",                0x030303 },
+        { "gray10",               0x1A1A1A },
+        { "gray100",              0xFFFFFF },
+        { "gray11",               0x1C1C1C },
+        { "gray12",               0x1F1F1F },
+        { "gray13",               0x212121 },
+        { "gray14",               0x242424 },
+        { "gray15",               0x262626 },
+        { "gray16",               0x292929 },
+        { "gray17",               0x2B2B2B },
+        { "gray18",               0x2E2E2E },
+        { "gray19",               0x303030 },
+        { "gray2",                0x050505 },
+        { "gray20",               0x333333 },
+        { "gray21",               0x363636 },
+        { "gray22",               0x383838 },
+        { "gray23",               0x3B3B3B },
+        { "gray24",               0x3D3D3D },
+        { "gray25",               0x404040 },
+        { "gray26",               0x424242 },
+        { "gray27",               0x454545 },
+        { "gray28",               0x474747 },
+        { "gray29",               0x4A4A4A },
+        { "gray3",                0x080808 },
+        { "gray30",               0x4D4D4D },
+        { "gray31",               0x4F4F4F },
+        { "gray32",               0x525252 },
+        { "gray33",               0x545454 },
+        { "gray34",               0x575757 },
+        { "gray35",               0x595959 },
+        { "gray36",               0x5C5C5C },
+        { "gray37",               0x5E5E5E },
+        { "gray38",               0x616161 },
+        { "gray39",               0x636363 },
+        { "gray4",                0x0A0A0A },
+        { "gray40",               0x666666 },
+        { "gray41",               0x696969 },
+        { "gray42",               0x6B6B6B },
+        { "gray43",               0x6E6E6E },
+        { "gray44",               0x707070 },
+        { "gray45",               0x737373 },
+        { "gray46",               0x757575 },
+        { "gray47",               0x787878 },
+        { "gray48",               0x7A7A7A },
+        { "gray49",               0x7D7D7D },
+        { "gray5",                0x0D0D0D },
+        { "gray50",               0x7F7F7F },
+        { "gray51",               0x828282 },
+        { "gray52",               0x858585 },
+        { "gray53",               0x878787 },
+        { "gray54",               0x8A8A8A },
+        { "gray55",               0x8C8C8C },
+        { "gray56",               0x8F8F8F },
+        { "gray57",               0x919191 },
+        { "gray58",               0x949494 },
+        { "gray59",               0x969696 },
+        { "gray6",                0x0F0F0F },
+        { "gray60",               0x999999 },
+        { "gray61",               0x9C9C9C },
+        { "gray62",               0x9E9E9E },
+        { "gray63",               0xA1A1A1 },
+        { "gray64",               0xA3A3A3 },
+        { "gray65",               0xA6A6A6 },
+        { "gray66",               0xA8A8A8 },
+        { "gray67",               0xABABAB },
+        { "gray68",               0xADADAD },
+        { "gray69",               0xB0B0B0 },
+        { "gray7",                0x121212 },
+        { "gray70",               0xB3B3B3 },
+        { "gray71",               0xB5B5B5 },
+        { "gray72",               0xB8B8B8 },
+        { "gray73",               0xBABABA },
+        { "gray74",               0xBDBDBD },
+        { "gray75",               0xBFBFBF },
+        { "gray76",               0xC2C2C2 },
+        { "gray77",               0xC4C4C4 },
+        { "gray78",               0xC7C7C7 },
+        { "gray79",               0xC9C9C9 },
+        { "gray8",                0x141414 },
+        { "gray80",               0xCCCCCC },
+        { "gray81",               0xCFCFCF },
+        { "gray82",               0xD1D1D1 },
+        { "gray83",               0xD4D4D4 },
+        { "gray84",               0xD6D6D6 },
+        { "gray85",               0xD9D9D9 },
+        { "gray86",               0xDBDBDB },
+        { "gray87",               0xDEDEDE },
+        { "gray88",               0xE0E0E0 },
+        { "gray89",               0xE3E3E3 },
+        { "gray9",                0x171717 },
+        { "gray90",               0xE5E5E5 },
+        { "gray91",               0xE8E8E8 },
+        { "gray92",               0xEBEBEB },
+        { "gray93",               0xEDEDED },
+        { "gray94",               0xF0F0F0 },
+        { "gray95",               0xF2F2F2 },
+        { "gray96",               0xF5F5F5 },
+        { "gray97",               0xF7F7F7 },
+        { "gray98",               0xFAFAFA },
+        { "gray99",               0xFCFCFC },
+        { "green",                0x008000 },
+        { "green",                0x00FF00 },
+        { "green1",               0x00FF00 },
+        { "green2",               0x00EE00 },
+        { "green3",               0x00CD00 },
+        { "green4",               0x008B00 },
+        { "greenyellow",          0xadff2f },
+        { "grey",                 0xBEBEBE },
+        { "grey0",                0x000000 },
+        { "grey1",                0x030303 },
+        { "grey10",               0x1A1A1A },
+        { "grey100",              0xFFFFFF },
+        { "grey11",               0x1C1C1C },
+        { "grey12",               0x1F1F1F },
+        { "grey13",               0x212121 },
+        { "grey14",               0x242424 },
+        { "grey15",               0x262626 },
+        { "grey16",               0x292929 },
+        { "grey17",               0x2B2B2B },
+        { "grey18",               0x2E2E2E },
+        { "grey19",               0x303030 },
+        { "grey2",                0x050505 },
+        { "grey20",               0x333333 },
+        { "grey21",               0x363636 },
+        { "grey22",               0x383838 },
+        { "grey23",               0x3B3B3B },
+        { "grey24",               0x3D3D3D },
+        { "grey25",               0x404040 },
+        { "grey26",               0x424242 },
+        { "grey27",               0x454545 },
+        { "grey28",               0x474747 },
+        { "grey29",               0x4A4A4A },
+        { "grey3",                0x080808 },
+        { "grey30",               0x4D4D4D },
+        { "grey31",               0x4F4F4F },
+        { "grey32",               0x525252 },
+        { "grey33",               0x545454 },
+        { "grey34",               0x575757 },
+        { "grey35",               0x595959 },
+        { "grey36",               0x5C5C5C },
+        { "grey37",               0x5E5E5E },
+        { "grey38",               0x616161 },
+        { "grey39",               0x636363 },
+        { "grey4",                0x0A0A0A },
+        { "grey40",               0x666666 },
+        { "grey41",               0x696969 },
+        { "grey42",               0x6B6B6B },
+        { "grey43",               0x6E6E6E },
+        { "grey44",               0x707070 },
+        { "grey45",               0x737373 },
+        { "grey46",               0x757575 },
+        { "grey47",               0x787878 },
+        { "grey48",               0x7A7A7A },
+        { "grey49",               0x7D7D7D },
+        { "grey5",                0x0D0D0D },
+        { "grey50",               0x7F7F7F },
+        { "grey51",               0x828282 },
+        { "grey52",               0x858585 },
+        { "grey53",               0x878787 },
+        { "grey54",               0x8A8A8A },
+        { "grey55",               0x8C8C8C },
+        { "grey56",               0x8F8F8F },
+        { "grey57",               0x919191 },
+        { "grey58",               0x949494 },
+        { "grey59",               0x969696 },
+        { "grey6",                0x0F0F0F },
+        { "grey60",               0x999999 },
+        { "grey61",               0x9C9C9C },
+        { "grey62",               0x9E9E9E },
+        { "grey63",               0xA1A1A1 },
+        { "grey64",               0xA3A3A3 },
+        { "grey65",               0xA6A6A6 },
+        { "grey66",               0xA8A8A8 },
+        { "grey67",               0xABABAB },
+        { "grey68",               0xADADAD },
+        { "grey69",               0xB0B0B0 },
+        { "grey7",                0x121212 },
+        { "grey70",               0xB3B3B3 },
+        { "grey71",               0xB5B5B5 },
+        { "grey72",               0xB8B8B8 },
+        { "grey73",               0xBABABA },
+        { "grey74",               0xBDBDBD },
+        { "grey75",               0xBFBFBF },
+        { "grey76",               0xC2C2C2 },
+        { "grey77",               0xC4C4C4 },
+        { "grey78",               0xC7C7C7 },
+        { "grey79",               0xC9C9C9 },
+        { "grey8",                0x141414 },
+        { "grey80",               0xCCCCCC },
+        { "grey81",               0xCFCFCF },
+        { "grey82",               0xD1D1D1 },
+        { "grey83",               0xD4D4D4 },
+        { "grey84",               0xD6D6D6 },
+        { "grey85",               0xD9D9D9 },
+        { "grey86",               0xDBDBDB },
+        { "grey87",               0xDEDEDE },
+        { "grey88",               0xE0E0E0 },
+        { "grey89",               0xE3E3E3 },
+        { "grey9",                0x171717 },
+        { "grey90",               0xE5E5E5 },
+        { "grey91",               0xE8E8E8 },
+        { "grey92",               0xEBEBEB },
+        { "grey93",               0xEDEDED },
+        { "grey94",               0xF0F0F0 },
+        { "grey95",               0xF2F2F2 },
+        { "grey96",               0xF5F5F5 },
+        { "grey97",               0xF7F7F7 },
+        { "grey98",               0xFAFAFA },
+        { "grey99",               0xFCFCFC },
+        { "honeydew",             0xF0FFF0 },
+        { "honeydew1",            0xF0FFF0 },
+        { "honeydew2",            0xE0EEE0 },
+        { "honeydew3",            0xC1CDC1 },
+        { "honeydew4",            0x838B83 },
+        { "hotpink",              0xff69b4 },
+        { "hotpink1",             0xff6eb4 },
+        { "hotpink2",             0xee6aa7 },
+        { "hotpink3",             0xcd6090 },
+        { "hotpink4",             0x8b3a62 },
+        { "indianred",            0xcd5c5c },
+        { "indianred1",           0xff6a6a },
+        { "indianred2",           0xee6363 },
+        { "indianred3",           0xcd5555 },
+        { "indianred4",           0x8b3a3a },
+        { "indigo",               0x4B0082 },
+        { "ivory",                0xFFFFF0 },
+        { "ivory1",               0xFFFFF0 },
+        { "ivory2",               0xEEEEE0 },
+        { "ivory3",               0xCDCDC1 },
+        { "ivory4",               0x8B8B83 },
+        { "khaki",                0xF0E68C },
+        { "khaki1",               0xFFF68F },
+        { "khaki2",               0xEEE685 },
+        { "khaki3",               0xCDC673 },
+        { "khaki4",               0x8B864E },
+        { "lavender",             0xE6E6FA },
+        { "lavenderblush",        0xfff0f5 },
+        { "lavenderblush1",       0xfff0f5 },
+        { "lavenderblush2",       0xeee0e5 },
+        { "lavenderblush3",       0xcdc1c5 },
+        { "lavenderblush4",       0x8b8386 },
+        { "lawngreen",            0x7cfc00 },
+        { "lemonchiffon",         0xfffacd },
+        { "lemonchiffon1",        0xfffacd },
+        { "lemonchiffon2",        0xeee9bf },
+        { "lemonchiffon3",        0xcdc9a5 },
+        { "lemonchiffon4",        0x8b8970 },
+        { "lightblue",            0xadd8e6 },
+        { "lightblue1",           0xbfefff },
+        { "lightblue2",           0xb2dfee },
+        { "lightblue3",           0x9ac0cd },
+        { "lightblue4",           0x68838b },
+        { "lightcoral",           0xf08080 },
+        { "lightcyan",            0xe0ffff },
+        { "lightcyan1",           0xe0ffff },
+        { "lightcyan2",           0xd1eeee },
+        { "lightcyan3",           0xb4cdcd },
+        { "lightcyan4",           0x7a8b8b },
+        { "lightgoldenrod",       0xeedd82 },
+        { "lightgoldenrod1",      0xffec8b },
+        { "lightgoldenrod2",      0xeedc82 },
+        { "lightgoldenrod3",      0xcdbe70 },
+        { "lightgoldenrod4",      0x8b814c },
+        { "lightgoldenrodyellow", 0xfafad2 },
+        { "lightgray",            0xd3d3d3 },
+        { "lightgreen",           0x90ee90 },
+        { "lightgrey",            0xd3d3d3 },
+        { "lightpink",            0xffb6c1 },
+        { "lightpink1",           0xffaeb9 },
+        { "lightpink2",           0xeea2ad },
+        { "lightpink3",           0xcd8c95 },
+        { "lightpink4",           0x8b5f65 },
+        { "lightsalmon",          0xffa07a },
+        { "lightsalmon1",         0xffa07a },
+        { "lightsalmon2",         0xee9572 },
+        { "lightsalmon3",         0xcd8162 },
+        { "lightsalmon4",         0x8b5742 },
+        { "lightseagreen",        0x20b2aa },
+        { "lightskyblue",         0x87cefa },
+        { "lightskyblue1",        0xb0e2ff },
+        { "lightskyblue2",        0xa4d3ee },
+        { "lightskyblue3",        0x8db6cd },
+        { "lightskyblue4",        0x607b8b },
+        { "lightslateblue",       0x8470ff },
+        { "lightslategray",       0x778899 },
+        { "lightslategrey",       0x778899 },
+        { "lightsteelblue",       0xb0c4de },
+        { "lightsteelblue1",      0xcae1ff },
+        { "lightsteelblue2",      0xbcd2ee },
+        { "lightsteelblue3",      0xa2b5cd },
+        { "lightsteelblue4",      0x6e7b8b },
+        { "lightyellow",          0xffffe0 },
+        { "lightyellow1",         0xffffe0 },
+        { "lightyellow2",         0xeeeed1 },
+        { "lightyellow3",         0xcdcdb4 },
+        { "lightyellow4",         0x8b8b7a },
+        { "lime",                 0x00FF00 },
+        { "limegreen",            0x32cd32 },
+        { "linen",                0xFAF0E6 },
+        { "magenta",              0xFF00FF },
+        { "magenta1",             0xFF00FF },
+        { "magenta2",             0xEE00EE },
+        { "magenta3",             0xCD00CD },
+        { "magenta4",             0x8B008B },
+        { "maroon",               0x800000 },
+        { "maroon",               0xB03060 },
+        { "maroon1",              0xFF34B3 },
+        { "maroon2",              0xEE30A7 },
+        { "maroon3",              0xCD2990 },
+        { "maroon4",              0x8B1C62 },
+        { "mediumaquamarine",     0x66cdaa },
+        { "mediumblue",           0x0000cd },
+        { "mediumforestgreen",    0x32814b },
+        { "mediumgoldenrod",      0xd1c166 },
+        { "mediumorchid",         0xba55d3 },
+        { "mediumorchid1",        0xe066ff },
+        { "mediumorchid2",        0xd15fee },
+        { "mediumorchid3",        0xb452cd },
+        { "mediumorchid4",        0x7a378b },
+        { "mediumpurple",         0x9370db },
+        { "mediumpurple1",        0xab82ff },
+        { "mediumpurple2",        0x9f79ee },
+        { "mediumpurple3",        0x8968cd },
+        { "mediumpurple4",        0x5d478b },
+        { "mediumseagreen",       0x3cb371 },
+        { "mediumslateblue",      0x7b68ee },
+        { "mediumspringgreen",    0x00fa9a },
+        { "mediumturquoise",      0x48d1cc },
+        { "mediumvioletred",      0xc71585 },
+        { "midnightblue",         0x191970 },
+        { "mintcream",            0xf5fffa },
+        { "mistyrose",            0xffe4e1 },
+        { "mistyrose1",           0xffe4e1 },
+        { "mistyrose2",           0xeed5d2 },
+        { "mistyrose3",           0xcdb7b5 },
+        { "mistyrose4",           0x8b7d7b },
+        { "moccasin",             0xFFE4B5 },
+        { "navajowhite",          0xffdead },
+        { "navajowhite1",         0xffdead },
+        { "navajowhite2",         0xeecfa1 },
+        { "navajowhite3",         0xcdb38b },
+        { "navajowhite4",         0x8b795e },
+        { "navy",                 0x000080 },
+        { "navyblue",             0x000080 },
+        { "none",                 0x0000FF },
+        { "oldlace",              0xfdf5e6 },
+        { "olive",                0x808000 },
+        { "olivedrab",            0x6b8e23 },
+        { "olivedrab1",           0xc0ff3e },
+        { "olivedrab2",           0xb3ee3a },
+        { "olivedrab3",           0x9acd32 },
+        { "olivedrab4",           0x698b22 },
+        { "opaque",               0x000000 },
+        { "orange",               0xFFA500 },
+        { "orange1",              0xFFA500 },
+        { "orange2",              0xEE9A00 },
+        { "orange3",              0xCD8500 },
+        { "orange4",              0x8B5A00 },
+        { "orangered",            0xff4500 },
+        { "orangered1",           0xff4500 },
+        { "orangered2",           0xee4000 },
+        { "orangered3",           0xcd3700 },
+        { "orangered4",           0x8b2500 },
+        { "orchid",               0xDA70D6 },
+        { "orchid1",              0xFF83FA },
+        { "orchid2",              0xEE7AE9 },
+        { "orchid3",              0xCD69C9 },
+        { "orchid4",              0x8B4789 },
+        { "palegoldenrod",        0xeee8aa },
+        { "palegreen",            0x98fb98 },
+        { "palegreen1",           0x9aff9a },
+        { "palegreen2",           0x90ee90 },
+        { "palegreen3",           0x7ccd7c },
+        { "palegreen4",           0x548b54 },
+        { "paleturquoise",        0xafeeee },
+        { "paleturquoise1",       0xbbffff },
+        { "paleturquoise2",       0xaeeeee },
+        { "paleturquoise3",       0x96cdcd },
+        { "paleturquoise4",       0x668b8b },
+        { "palevioletred",        0xdb7093 },
+        { "palevioletred1",       0xff82ab },
+        { "palevioletred2",       0xee799f },
+        { "palevioletred3",       0xcd6889 },
+        { "palevioletred4",       0x8b475d },
+        { "papayawhip",           0xffefd5 },
+        { "peachpuff",            0xffdab9 },
+        { "peachpuff1",           0xffdab9 },
+        { "peachpuff2",           0xeecbad },
+        { "peachpuff3",           0xcdaf95 },
+        { "peachpuff4",           0x8b7765 },
+        { "peru",                 0xCD853F },
+        { "pink",                 0xFFC0CB },
+        { "pink1",                0xFFB5C5 },
+        { "pink2",                0xEEA9B8 },
+        { "pink3",                0xCD919E },
+        { "pink4",                0x8B636C },
+        { "plum",                 0xDDA0DD },
+        { "plum1",                0xFFBBFF },
+        { "plum2",                0xEEAEEE },
+        { "plum3",                0xCD96CD },
+        { "plum4",                0x8B668B },
+        { "powderblue",           0xb0e0e6 },
+        { "purple",               0x800080 },
+        { "purple",               0xA020F0 },
+        { "purple1",              0x9B30FF },
+        { "purple2",              0x912CEE },
+        { "purple3",              0x7D26CD },
+        { "purple4",              0x551A8B },
+        { "red",                  0xFF0000 },
+        { "red1",                 0xFF0000 },
+        { "red2",                 0xEE0000 },
+        { "red3",                 0xCD0000 },
+        { "red4",                 0x8B0000 },
+        { "rosybrown",            0xbc8f8f },
+        { "rosybrown1",           0xffc1c1 },
+        { "rosybrown2",           0xeeb4b4 },
+        { "rosybrown3",           0xcd9b9b },
+        { "rosybrown4",           0x8b6969 },
+        { "royalblue",            0x4169e1 },
+        { "royalblue1",           0x4876ff },
+        { "royalblue2",           0x436eee },
+        { "royalblue3",           0x3a5fcd },
+        { "royalblue4",           0x27408b },
+        { "saddlebrown",          0x8b4513 },
+        { "salmon",               0xFA8072 },
+        { "salmon1",              0xFF8C69 },
+        { "salmon2",              0xEE8262 },
+        { "salmon3",              0xCD7054 },
+        { "salmon4",              0x8B4C39 },
+        { "sandybrown",           0xf4a460 },
+        { "seagreen",             0x2e8b57 },
+        { "seagreen1",            0x54ff9f },
+        { "seagreen2",            0x4eee94 },
+        { "seagreen3",            0x43cd80 },
+        { "seagreen4",            0x2e8b57 },
+        { "seashell",             0xFFF5EE },
+        { "seashell1",            0xFFF5EE },
+        { "seashell2",            0xEEE5DE },
+        { "seashell3",            0xCDC5BF },
+        { "seashell4",            0x8B8682 },
+        { "sienna",               0xA0522D },
+        { "sienna1",              0xFF8247 },
+        { "sienna2",              0xEE7942 },
+        { "sienna3",              0xCD6839 },
+        { "sienna4",              0x8B4726 },
+        { "silver",               0xC0C0C0 },
+        { "skyblue",              0x87ceeb },
+        { "skyblue1",             0x87ceff },
+        { "skyblue2",             0x7ec0ee },
+        { "skyblue3",             0x6ca6cd },
+        { "skyblue4",             0x4a708b },
+        { "slateblue",            0x6a5acd },
+        { "slateblue1",           0x836fff },
+        { "slateblue2",           0x7a67ee },
+        { "slateblue3",           0x6959cd },
+        { "slateblue4",           0x473c8b },
+        { "slategray",            0x708090 },
+        { "slategray1",           0xc6e2ff },
+        { "slategray2",           0xb9d3ee },
+        { "slategray3",           0x9fb6cd },
+        { "slategray4",           0x6c7b8b },
+        { "slategrey",            0x708090 },
+        { "snow",                 0xFFFAFA },
+        { "snow1",                0xFFFAFA },
+        { "snow2",                0xEEE9E9 },
+        { "snow3",                0xCDC9C9 },
+        { "snow4",                0x8B8989 },
+        { "springgreen",          0x00ff7f },
+        { "springgreen1",         0x00ff7f },
+        { "springgreen2",         0x00ee76 },
+        { "springgreen3",         0x00cd66 },
+        { "springgreen4",         0x008b45 },
+        { "steelblue",            0x4682b4 },
+        { "steelblue1",           0x63b8ff },
+        { "steelblue2",           0x5cacee },
+        { "steelblue3",           0x4f94cd },
+        { "steelblue4",           0x36648b },
+        { "tan",                  0xD2B48C },
+        { "tan1",                 0xFFA54F },
+        { "tan2",                 0xEE9A49 },
+        { "tan3",                 0xCD853F },
+        { "tan4",                 0x8B5A2B },
+        { "teal",                 0x008080 },
+        { "thistle",              0xD8BFD8 },
+        { "thistle1",             0xFFE1FF },
+        { "thistle2",             0xEED2EE },
+        { "thistle3",             0xCDB5CD },
+        { "thistle4",             0x8B7B8B },
+        { "tomato",               0xFF6347 },
+        { "tomato1",              0xFF6347 },
+        { "tomato2",              0xEE5C42 },
+        { "tomato3",              0xCD4F39 },
+        { "tomato4",              0x8B3626 },
+        { "transparent",          0x0000FF },
+        { "turquoise",            0x40E0D0 },
+        { "turquoise1",           0x00F5FF },
+        { "turquoise2",           0x00E5EE },
+        { "turquoise3",           0x00C5CD },
+        { "turquoise4",           0x00868B },
+        { "violet",               0xEE82EE },
+        { "violetred",            0xd02090 },
+        { "violetred1",           0xff3e96 },
+        { "violetred2",           0xee3a8c },
+        { "violetred3",           0xcd3278 },
+        { "violetred4",           0x8b2252 },
+        { "wheat",                0xF5DEB3 },
+        { "wheat1",               0xFFE7BA },
+        { "wheat2",               0xEED8AE },
+        { "wheat3",               0xCDBA96 },
+        { "wheat4",               0x8B7E66 },
+        { "white",                0xFFFFFF },
+        { "whitesmoke",           0xf5f5f5 },
+        { "yellow",               0xFFFF00 },
+        { "yellow1",              0xFFFF00 },
+        { "yellow2",              0xEEEE00 },
+        { "yellow3",              0xCDCD00 },
+        { "yellow4",              0x8B8B00 },
+        { "yellowgreen",          0x9acd32 },
+#endif /* EXTENDED_XPM_COLORS */
+        {"none",                  0xFFFFFF}
+    };
+
+    if (spec[0] == '#') {
+        char buf[7];
+        switch(speclen) {
+        case 4:
+            buf[0] = buf[1] = spec[1];
+            buf[2] = buf[3] = spec[2];
+            buf[4] = buf[5] = spec[3];
+            break;
+        case 7:
+            SDL_memcpy(buf, spec + 1, 6);
+            break;
+        case 13:
+            buf[0] = spec[1];
+            buf[1] = spec[2];
+            buf[2] = spec[5];
+            buf[3] = spec[6];
+            buf[4] = spec[9];
+            buf[5] = spec[10];
+            break;
+        }
+        buf[6] = '\0';
+        *rgb = (Uint32)SDL_strtol(buf, NULL, 16);
+        return 1;
+    } else {
+        size_t i;
+        for (i = 0; i < SDL_arraysize(known); i++) {
+            if (SDL_strncasecmp(known[i].name, spec, speclen) == 0) {
+                *rgb = known[i].rgb;
                 return 1;
-        } else {
-                int i;
-                for (i = 0; i < ARRAYSIZE(known); i++)
-                        if (string_equal(known[i].name, spec, speclen)) {
-                                *rgb = known[i].rgb;
-                                return 1;
-                        }
-                return 0;
+            }
         }
+        return 0;
+    }
 }
 
 #ifndef MAX
@@ -243,263 +921,278 @@ static const char *error;
  */
 static const char *get_next_line(const char ***lines, SDL_RWops *src, int len)
 {
-        char *linebufnew;
-        if (lines) {
-                return *(*lines)++;
+    char *linebufnew;
+
+    if (lines) {
+        return *(*lines)++;
+    } else {
+        char c;
+        int n;
+        do {
+            if (SDL_RWread(src, &c, 1, 1) <= 0) {
+                error = "Premature end of data";
+                return NULL;
+            }
+        } while (c != '"');
+        if (len) {
+            len += 4;   /* "\",\n\0" */
+            if (len > buflen){
+                buflen = len;
+                linebufnew = (char *)SDL_realloc(linebuf, buflen);
+                if (!linebufnew) {
+                    SDL_free(linebuf);
+                    error = "Out of memory";
+                    return NULL;
+                }
+                linebuf = linebufnew;
+            }
+            if (SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
+                error = "Premature end of data";
+                return NULL;
+            }
+            n = len - 2;
         } else {
-                char c;
-                int n;
-                do {
-                        if (SDL_RWread(src, &c, 1, 1) <= 0) {
-                                error = "Premature end of data";
-                                return NULL;
-                        }
-                } while (c != '"');
-                if (len) {
-                        len += 4;        /* "\",\n\0" */
-                        if (len > buflen){
-                                buflen = len;
-                                linebufnew = realloc(linebuf, buflen);
-                                if(!linebufnew) {
-                                        free(linebuf);
-                                        error = "Out of memory";
-                                        return NULL;
-                                }
-                                linebuf = linebufnew;
-                        }
-                        if (SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
-                                error = "Premature end of data";
-                                return NULL;
-                        }
-                        n = len - 2;
-                } else {
-                        n = 0;
-                        do {
-                                if (n >= buflen - 1) {
-                                        if (buflen == 0)
-                                                buflen = 16;
-                                        buflen *= 2;
-                                        linebufnew = realloc(linebuf, buflen);
-                                        if(!linebufnew) {
-                                                free(linebuf);
-                                                error = "Out of memory";
-                                                return NULL;
-                                        }
-                                        linebuf = linebufnew;
-                                }
-                                if (SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
-                                        error = "Premature end of data";
-                                        return NULL;
-                                }
-                        } while (linebuf[n++] != '"');
-                        n--;
+            n = 0;
+            do {
+                if (n >= buflen - 1) {
+                    if (buflen == 0)
+                        buflen = 16;
+                    buflen *= 2;
+                    linebufnew = (char *)SDL_realloc(linebuf, buflen);
+                    if (!linebufnew) {
+                        SDL_free(linebuf);
+                        error = "Out of memory";
+                        return NULL;
+                    }
+                    linebuf = linebufnew;
                 }
-                linebuf[n] = '\0';
-                return linebuf;
+                if (SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
+                    error = "Premature end of data";
+                    return NULL;
+                }
+            } while (linebuf[n++] != '"');
+            n--;
         }
+        linebuf[n] = '\0';
+        return linebuf;
+    }
 }
 
-#define SKIPSPACE(p)                            \
-do {                                            \
-        while (isspace((unsigned char)*(p)))        \
-              ++(p);                                \
+#define SKIPSPACE(p)                \
+do {                        \
+    while (SDL_isspace((unsigned char)*(p))) \
+          ++(p);                \
 } while (0)
 
-#define SKIPNONSPACE(p)                                 \
-do {                                                    \
-        while (!isspace((unsigned char)*(p)) && *p)        \
-              ++(p);                                        \
+#define SKIPNONSPACE(p)                 \
+do {                            \
+    while (!SDL_isspace((unsigned char)*(p)) && *p)  \
+          ++(p);                    \
 } while (0)
 
 /* read XPM from either array or RWops */
 static SDL_Surface *load_xpm(const char **xpm, SDL_RWops *src)
 {
-        SDL_Surface *image = NULL;
-        int indexc;
-        int x, y;
-        int w, h, ncolors, cpp;
-        int indexed;
-        Uint8 *dst;
-        struct color_hash *colors = NULL;
-        SDL_Color *im_colors = NULL;
-        char *keystrings = NULL, *nextkey;
-        const char *line;
-        const char ***xpmlines = NULL;
-        int pixels_len;
-
-        error = NULL;
-        linebuf = NULL;
-        buflen = 0;
-
-        if (xpm)
-                xpmlines = &xpm;
+    Sint64 start = 0;
+    SDL_Surface *image = NULL;
+    int index;
+    int x, y;
+    int w, h, ncolors, cpp;
+    int indexed;
+    Uint8 *dst;
+    struct color_hash *colors = NULL;
+    SDL_Color *im_colors = NULL;
+    char *keystrings = NULL, *nextkey;
+    const char *line;
+    const char ***xpmlines = NULL;
+    int pixels_len;
+
+    error = NULL;
+    linebuf = NULL;
+    buflen = 0;
+
+    if (src)
+        start = SDL_RWtell(src);
+
+    if (xpm)
+        xpmlines = &xpm;
 
+    line = get_next_line(xpmlines, src, 0);
+    if (!line)
+        goto done;
+    /*
+     * The header string of an XPMv3 image has the format
+     *
+     * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
+     *
+     * where the hotspot coords are intended for mouse cursors.
+     * Right now we don't use the hotspots but it should be handled
+     * one day.
+     */
+    if (SDL_sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
+       || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
+        error = "Invalid format description";
+        goto done;
+    }
+
+    keystrings = (char *)SDL_malloc(ncolors * cpp);
+    if (!keystrings) {
+        error = "Out of memory";
+        goto done;
+    }
+    nextkey = keystrings;
+
+    /* Create the new surface */
+    if (ncolors <= 256) {
+        indexed = 1;
+        image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
+                         0, 0, 0, 0);
+        im_colors = image->format->palette->colors;
+        image->format->palette->ncolors = ncolors;
+    } else {
+        indexed = 0;
+        image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
+                         0xff0000, 0x00ff00, 0x0000ff, 0);
+    }
+    if (!image) {
+        /* Hmm, some SDL error (out of memory?) */
+        goto done;
+    }
+
+    /* Read the colors */
+    colors = create_colorhash(ncolors);
+    if (!colors) {
+        error = "Out of memory";
+        goto done;
+    }
+    for (index = 0; index < ncolors; ++index ) {
+        const char *p;
         line = get_next_line(xpmlines, src, 0);
         if (!line)
-                goto done;
-        /*
-         * The header string of an XPMv3 image has the format
-         *
-         * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
-         *
-         * where the hotspot coords are intended for mouse cursors.
-         * Right now we don't use the hotspots but it should be handled
-         * one day.
-         */
-        if (sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
-           || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
-                error = "Invalid format description";
-                goto done;
-        }
+            goto done;
 
-        keystrings = malloc(ncolors * cpp);
-        if (!keystrings) {
-                error = "Out of memory";
-                goto done;
-        }
-        nextkey = keystrings;
-
-        /* Create the new surface */
-        if (ncolors <= 256) {
-                indexed = 1;
-                image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
-                                             0, 0, 0, 0);
-                im_colors = image->format->palette->colors;
-                image->format->palette->ncolors = ncolors;
-        } else {
-                indexed = 0;
-                image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
-                                             0xff0000, 0x00ff00, 0x0000ff, 0);
-        }
-        if (!image) {
-                /* Hmm, some SDL error (out of memory?) */
-                goto done;
-        }
+        p = line + cpp + 1;
 
-        /* Read the colors */
-        colors = create_colorhash(ncolors);
-        if (!colors) {
-                error = "Out of memory";
+        /* parse a colour definition */
+        for (;;) {
+            char nametype;
+            const char *colname;
+            Uint32 rgb, pixel;
+
+            SKIPSPACE(p);
+            if (!*p) {
+                error = "colour parse error";
                 goto done;
+            }
+            nametype = *p;
+            SKIPNONSPACE(p);
+            SKIPSPACE(p);
+            colname = p;
+            SKIPNONSPACE(p);
+            if (nametype == 's')
+                continue;      /* skip symbolic colour names */
+
+            if (!color_to_rgb(colname, (int)(p - colname), &rgb))
+                continue;
+
+            SDL_memcpy(nextkey, line, cpp);
+            if (indexed) {
+                SDL_Color *c = im_colors + index;
+                c->r = (Uint8)(rgb >> 16);
+                c->g = (Uint8)(rgb >> 8);
+                c->b = (Uint8)(rgb);
+                pixel = index;
+            } else
+                pixel = rgb;
+            add_colorhash(colors, nextkey, cpp, pixel);
+            nextkey += cpp;
+            if (rgb == 0xffffffff)
+                SDL_SetColorKey(image, SDL_TRUE, pixel);
+            break;
         }
-        for (indexc = 0; indexc < ncolors; ++indexc ) {
-                const char *p;
-                line = get_next_line(xpmlines, src, 0);
-                if (!line)
-                        goto done;
-
-                p = line + cpp + 1;
-
-                /* parse a colour definition */
-                for (;;) {
-                        char nametype;
-                        const char *colname;
-                        Uint32 rgb, pixel;
-
-                        SKIPSPACE(p);
-                        if (!*p) {
-                                error = "colour parse error";
-                                goto done;
-                        }
-                        nametype = *p;
-                        SKIPNONSPACE(p);
-                        SKIPSPACE(p);
-                        colname = p;
-                        SKIPNONSPACE(p);
-                        if (nametype == 's')
-                                continue;      /* skip symbolic colour names */
-
-                        if (!color_to_rgb(colname, p - colname, &rgb))
-                                continue;
-
-                        memcpy(nextkey, line, cpp);
-                        if (indexed) {
-                                SDL_Color *c = im_colors + indexc;
-                                c->r = (Uint8)(rgb >> 16);
-                                c->g = (Uint8)(rgb >> 8);
-                                c->b = (Uint8)(rgb);
-                                pixel = indexc;
-                        } else
-                                pixel = rgb;
-                        add_colorhash(colors, nextkey, cpp, pixel);
-                        nextkey += cpp;
-                        if (rgb == 0xffffffff)
-                                SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
-                        break;
-                }
-        }
+    }
 
-        /* Read the pixels */
-        pixels_len = w * cpp;
-        dst = image->pixels;
-        for (y = 0; y < h; y++) {
-                line = get_next_line(xpmlines, src, pixels_len);
-                if (indexed) {
-                        /* optimization for some common cases */
-                        if (cpp == 1)
-                                for (x = 0; x < w; x++)
-                                        dst[x] = (Uint8)QUICK_COLORHASH(colors,
-                                                                 line + x);
-                        else
-                                for (x = 0; x < w; x++)
-                                        dst[x] = (Uint8)get_colorhash(colors,
-                                                               line + x * cpp,
-                                                               cpp);
-                } else {
-                        for (x = 0; x < w; x++)
-                                ((Uint32*)dst)[x] = get_colorhash(colors,
-                                                                line + x * cpp,
-                                                                  cpp);
-                }
-                dst += image->pitch;
+    /* Read the pixels */
+    pixels_len = w * cpp;
+    dst = (Uint8 *)image->pixels;
+    for (y = 0; y < h; y++) {
+        line = get_next_line(xpmlines, src, pixels_len);
+        if (!line)
+            goto done;
+
+        if (indexed) {
+            /* optimization for some common cases */
+            if (cpp == 1)
+                for (x = 0; x < w; x++)
+                    dst[x] = (Uint8)QUICK_COLORHASH(colors,
+                                 line + x);
+            else
+                for (x = 0; x < w; x++)
+                    dst[x] = (Uint8)get_colorhash(colors,
+                                   line + x * cpp,
+                                   cpp);
+        } else {
+            for (x = 0; x < w; x++)
+                ((Uint32*)dst)[x] = get_colorhash(colors,
+                                line + x * cpp,
+                                  cpp);
         }
+        dst += image->pitch;
+    }
 
 done:
-        if (error) {
-                SDL_FreeSurface(image);
-                image = NULL;
-                SDL_SetError(error);
+    if (error) {
+        if ( src )
+            SDL_RWseek(src, start, RW_SEEK_SET);
+        if ( image ) {
+            SDL_FreeSurface(image);
+            image = NULL;
         }
-        free(keystrings);
-        free_colorhash(colors);
-        free(linebuf);
-        return(image);
+        IMG_SetError("%s", error);
+    }
+    if (keystrings)
+        SDL_free(keystrings);
+    free_colorhash(colors);
+    if (linebuf)
+        SDL_free(linebuf);
+    return(image);
 }
 
 /* Load a XPM type image from an RWops datasource */
-#if 0
 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
 {
-        if ( !src ) {
-                /* The error message has been set in SDL_RWFromFile */
-                return NULL;
-        }
-        return load_xpm(NULL, src);
+    if ( !src ) {
+        /* The error message has been set in SDL_RWFromFile */
+        return NULL;
+    }
+    return load_xpm(NULL, src);
 }
-#endif
 
-static inline SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
+SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
 {
-        return load_xpm(xpm, NULL);
+    if (!xpm) {
+        IMG_SetError("array is NULL");
+        return NULL;
+    }
+    return load_xpm(xpm, NULL);
 }
 
 #else  /* not LOAD_XPM */
 
 /* See if an image is contained in a data source */
-#if 0
 int IMG_isXPM(SDL_RWops *src)
 {
-        return(0);
+    return(0);
 }
 
+
 /* Load a XPM type image from an SDL datasource */
 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
 {
-        return(NULL);
+    return(NULL);
 }
-#endif
 
-static inline SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
+SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
 {
     return NULL;
 }
diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg
index 58c4d0861f048c1b5f50e9c55aacaf26cb6557e1..05b60f7a328ab917363e0a89d870511179e297d3 100644
--- a/src/sdl/Makefile.cfg
+++ b/src/sdl/Makefile.cfg
@@ -80,8 +80,13 @@ ifdef NOMIXER
 else
 	i_sound_o=$(OBJDIR)/mixer_sound.o
 	OPTS+=-DHAVE_MIXER
+ifdef HAVE_MIXERX
+	OPTS+=-DHAVE_MIXERX
+	SDL_LDFLAGS+=-lSDL2_mixer_ext
+else
 	SDL_LDFLAGS+=-lSDL2_mixer
 endif
+endif
 
 ifdef SDL_TTF
 	OPTS+=-DHAVE_TTF
diff --git a/src/sdl/SDL_icon.xpm b/src/sdl/SDL_icon.xpm
index cf72960dfc9048d1ab23b3c59ed4d29d65f4c404..1d0f9d314a98ff081671715292b5e4b26d2c54a9 100644
--- a/src/sdl/SDL_icon.xpm
+++ b/src/sdl/SDL_icon.xpm
@@ -1,425 +1,163 @@
 /* XPM */
-static char * C:\Repo\srb2\src\sdl\SDL_icon_xpm[] = {
-"32 32 390 2",
-"  	c None",
-". 	c #4F4F70",
-"+ 	c #4D4D87",
-"@ 	c #4D4D84",
-"# 	c #4E4E6C",
-"$ 	c #6C6C95",
-"% 	c #5E5EB2",
-"& 	c #6B6BE7",
-"* 	c #7373F9",
-"= 	c #7C7CFF",
-"- 	c #6F70E7",
-"; 	c #494BB2",
-"> 	c #4F4FA3",
-", 	c #6464D4",
-"' 	c #7979F5",
-") 	c #5F5FCA",
-"! 	c #5D5D93",
-"~ 	c #3A3A9F",
-"{ 	c #6060AC",
-"] 	c #777793",
-"^ 	c #5C5CB3",
-"/ 	c #7373EA",
-"( 	c #7A7AFF",
-"_ 	c #7575FF",
-": 	c #7979FF",
-"< 	c #6264DD",
-"[ 	c #47478C",
-"} 	c #564567",
-"| 	c #4647D0",
-"1 	c #5C5CAE",
-"2 	c #5E5EFF",
-"3 	c #2929FF",
-"4 	c #1D1DFF",
-"5 	c #1919D1",
-"6 	c #4F4F90",
-"7 	c #1E1ECE",
-"8 	c #5858FF",
-"9 	c #6767A8",
-"0 	c #4949A0",
-"a 	c #7070FB",
-"b 	c #7D7DFF",
-"c 	c #7777FF",
-"d 	c #7373FF",
-"e 	c #7272FF",
-"f 	c #7878FF",
-"g 	c #6465D8",
-"h 	c #363886",
-"i 	c #9F7655",
-"j 	c #C89B5C",
-"k 	c #1D1CB7",
-"l 	c #3031B1",
-"m 	c #1919F4",
-"n 	c #1111FF",
-"o 	c #1818FF",
-"p 	c #1B1BFF",
-"q 	c #1C1CFF",
-"r 	c #2626B3",
-"s 	c #1E1EC8",
-"t 	c #1A1AE8",
-"u 	c #24249F",
-"v 	c #2F2FD2",
-"w 	c #7676FF",
-"x 	c #6869E2",
-"y 	c #414290",
-"z 	c #8C6751",
-"A 	c #FCBA68",
-"B 	c #E9BD7D",
-"C 	c #201EB8",
-"D 	c #090AB8",
-"E 	c #1616EB",
-"F 	c #1818FD",
-"G 	c #1414EE",
-"H 	c #1010E1",
-"I 	c #0E0EE2",
-"J 	c #0E0EF4",
-"K 	c #0606B2",
-"L 	c #7A7A89",
-"M 	c #0C0C9A",
-"N 	c #0A0AA7",
-"O 	c #2424E4",
-"P 	c #6669E6",
-"Q 	c #4F4A8F",
-"R 	c #BF853B",
-"S 	c #FFD98D",
-"T 	c #CDAB76",
-"U 	c #1717C4",
-"V 	c #0F10BA",
-"W 	c #0909B6",
-"X 	c #0505C3",
-"Y 	c #0000B6",
-"Z 	c #0000BE",
-"` 	c #0000AD",
-" .	c #1D1D83",
-"..	c #63638E",
-"+.	c #090975",
-"@.	c #1414F3",
-"#.	c #5B5BFF",
-"$.	c #7B7BFF",
-"%.	c #7070FF",
-"&.	c #6E6EFF",
-"*.	c #7172F6",
-"=.	c #625DAF",
-"-.	c #BA9E6C",
-";.	c #887167",
-">.	c #090DF2",
-",.	c #1313BE",
-"'.	c #000085",
-").	c #0000AC",
-"!.	c #0202AA",
-"~.	c #242488",
-"{.	c #1414C7",
-"].	c #1717FF",
-"^.	c #5959FF",
-"/.	c #7F7FFF",
-"(.	c #7474FF",
-"_.	c #7171FF",
-":.	c #8686FF",
-"<.	c #7574FF",
-"[.	c #797CFF",
-"}.	c #5756B8",
-"|.	c #1C19A4",
-"1.	c #1617FF",
-"2.	c #1212BD",
-"3.	c #040485",
-"4.	c #0707A4",
-"5.	c #1B1B71",
-"6.	c #373797",
-"7.	c #1616FF",
-"8.	c #5050FF",
-"9.	c #8080FF",
-"0.	c #AAAAFF",
-"a.	c #AEAEF6",
-"b.	c #8A8AEF",
-"c.	c #6969FB",
-"d.	c #2728FF",
-"e.	c #1314FF",
-"f.	c #1919FF",
-"g.	c #1313E8",
-"h.	c #1F1FF4",
-"i.	c #5454FF",
-"j.	c #6D6DF0",
-"k.	c #6868B5",
-"l.	c #0B0BB8",
-"m.	c #1212C5",
-"n.	c #1616FC",
-"o.	c #1515FF",
-"p.	c #1212FF",
-"q.	c #2323FF",
-"r.	c #3636FF",
-"s.	c #4040FF",
-"t.	c #4343F9",
-"u.	c #5D5DB8",
-"v.	c #7F7F92",
-"w.	c #878793",
-"x.	c #4B4B94",
-"y.	c #0B0CE2",
-"z.	c #1313FF",
-"A.	c #4C4CFF",
-"B.	c #8282FF",
-"C.	c #7171ED",
-"D.	c #636394",
-"E.	c #575785",
-"F.	c #A9A99C",
-"G.	c #1414BC",
-"H.	c #1414FF",
-"I.	c #0707FD",
-"J.	c #2525AA",
-"K.	c #A8A8A4",
-"L.	c #EBEBE2",
-"M.	c #F9F9F2",
-"N.	c #E1E1CC",
-"O.	c #4D4D9F",
-"P.	c #0B0BF7",
-"Q.	c #2121FF",
-"R.	c #3232FF",
-"S.	c #5555FF",
-"T.	c #6161B4",
-"U.	c #B5B5B2",
-"V.	c #FFFFF8",
-"W.	c #4F4F9A",
-"X.	c #0B0BF5",
-"Y.	c #1616C5",
-"Z.	c #A8A8A1",
-"`.	c #FFFFFC",
-" +	c #FFFFFF",
-".+	c #C0C0C4",
-"++	c #1212D4",
-"@+	c #4444FF",
-"#+	c #6464FF",
-"$+	c #8383FF",
-"%+	c #6767C3",
-"&+	c #E4E4E4",
-"*+	c #9494AE",
-"=+	c #0808DF",
-"-+	c #0D0DF2",
-";+	c #61619A",
-">+	c #F1F1E0",
-",+	c #E8E8DD",
-"'+	c #2424BB",
-")+	c #1010FF",
-"!+	c #3434FF",
-"~+	c #6161FF",
-"{+	c #6969D2",
-"]+	c #EFEFF0",
-"^+	c #C2C2BA",
-"/+	c #1010B6",
-"(+	c #0909AC",
-"_+	c #A4A49A",
-":+	c #EAEADE",
-"<+	c #2525B8",
-"[+	c #2F2FFF",
-"}+	c #3C3CB5",
-"|+	c #EEEEEE",
-"1+	c #BBBBAD",
-"2+	c #0B0B56",
-"3+	c #0B0BFC",
-"4+	c #1212EF",
-"5+	c #0C0C3E",
-"6+	c #919187",
-"7+	c #DEDED6",
-"8+	c #1F1FC0",
-"9+	c #1A1AFF",
-"0+	c #1717FA",
-"a+	c #1515F8",
-"b+	c #1111FC",
-"c+	c #494992",
-"d+	c #999998",
-"e+	c #3E3E3B",
-"f+	c #3C3C99",
-"g+	c #535397",
-"h+	c #5A5A4D",
-"i+	c #6F6F70",
-"j+	c #BFBFC9",
-"k+	c #1111D6",
-"l+	c #1515F1",
-"m+	c #0F0FE2",
-"n+	c #0D0DD9",
-"o+	c #0909CD",
-"p+	c #0808C7",
-"q+	c #0505C7",
-"r+	c #0303CB",
-"s+	c #0101C0",
-"t+	c #0202AF",
-"u+	c #0606AC",
-"v+	c #121283",
-"w+	c #BBBBBB",
-"x+	c #BEBEBE",
-"y+	c #2F2F2E",
-"z+	c #C7C8BB",
-"A+	c #D8DAD1",
-"B+	c #272828",
-"C+	c #929292",
-"D+	c #8688C7",
-"E+	c #0506F6",
-"F+	c #1616F5",
-"G+	c #0B0BD3",
-"H+	c #0202B6",
-"I+	c #0000AF",
-"J+	c #0000B4",
-"K+	c #0000BD",
-"L+	c #0000BB",
-"M+	c #00009E",
-"N+	c #2C2C7E",
-"O+	c #6A6A8B",
-"P+	c #959595",
-"Q+	c #F0F0F1",
-"R+	c #E1E1E1",
-"S+	c #8C8E90",
-"T+	c #BEBEBF",
-"U+	c #C9C7C5",
-"V+	c #939699",
-"W+	c #E7EAED",
-"X+	c #CBCBC7",
-"Y+	c #413B9B",
-"Z+	c #0607DD",
-"`+	c #0C0CE2",
-" @	c #0303B9",
-".@	c #0000A8",
-"+@	c #181888",
-"@@	c #6A6A6A",
-"#@	c #626263",
-"$@	c #4B4B4C",
-"%@	c #3E3B36",
-"&@	c #9B805C",
-"*@	c #D9B07D",
-"=@	c #C9AE89",
-"-@	c #B9AF9E",
-";@	c #C7C5C4",
-">@	c #CBCCCF",
-",@	c #C7C6C6",
-"'@	c #AEA59A",
-")@	c #B69974",
-"!@	c #D8B87F",
-"~@	c #9B8272",
-"{@	c #0E0B9B",
-"]@	c #0000B7",
-"^@	c #0000B8",
-"/@	c #000082",
-"(@	c #00007A",
-"_@	c #636379",
-":@	c #62533E",
-"<@	c #B59B6C",
-"[@	c #DEB07B",
-"}@	c #FECC90",
-"|@	c #FFCE92",
-"1@	c #FEC98C",
-"2@	c #F1BD82",
-"3@	c #D1A979",
-"4@	c #BC9E73",
-"5@	c #CCA777",
-"6@	c #EAB980",
-"7@	c #FFCD90",
-"8@	c #FFD595",
-"9@	c #FDD782",
-"0@	c #413678",
-"a@	c #0000AE",
-"b@	c #000077",
-"c@	c #010193",
-"d@	c #0C0CE4",
-"e@	c #38389E",
-"f@	c #EEC585",
-"g@	c #FFDA9D",
-"h@	c #FFC992",
-"i@	c #FFC88F",
-"j@	c #FFC990",
-"k@	c #FFCE93",
-"l@	c #FFD094",
-"m@	c #FFCC92",
-"n@	c #C9A174",
-"o@	c #EDBD88",
-"p@	c #FAD287",
-"q@	c #3A2F7F",
-"r@	c #0000BA",
-"s@	c #0000B0",
-"t@	c #0101B2",
-"u@	c #1111ED",
-"v@	c #1919C1",
-"w@	c #95887C",
-"x@	c #DCAC6E",
-"y@	c #FFD393",
-"z@	c #FFCD94",
-"A@	c #FFCA93",
-"B@	c #FFC991",
-"C@	c #FFC78E",
-"D@	c #FFCB91",
-"E@	c #E0B581",
-"F@	c #BB9A6F",
-"G@	c #FFDC97",
-"H@	c #C1A173",
-"I@	c #0E0B9A",
-"J@	c #0000B5",
-"K@	c #0101B6",
-"L@	c #1010E0",
-"M@	c #1616EC",
-"N@	c #A68156",
-"O@	c #E7AC6B",
-"P@	c #FFC582",
-"Q@	c #FFCF8F",
-"R@	c #FFD195",
-"S@	c #FFD296",
-"T@	c #FFD396",
-"U@	c #FFD193",
-"V@	c #FFD28F",
-"W@	c #D2A96B",
-"X@	c #2F2482",
-"Y@	c #0000C1",
-"Z@	c #0000C0",
-"`@	c #0000BF",
-" #	c #0101BF",
-".#	c #1212F0",
-"+#	c #767698",
-"@#	c #9C866E",
-"##	c #A9865D",
-"$#	c #C0915D",
-"%#	c #C89760",
-"&#	c #C29360",
-"*#	c #AD8A61",
-"=#	c #9D8971",
-"-#	c #7F7A7A",
-";#	c #70708F",
-">#	c #6F6F91",
-",#	c #575788",
-"'#	c #464687",
-")#	c #2F2F87",
-"!#	c #15158F",
-"~#	c #0101A8",
-"{#	c #1313FB",
-"]#	c #57579F",
-"^#	c #343487",
-"/#	c #434388",
-"                                                                ",
-"                                                                ",
-"                                                                ",
-"                              . + @ #                           ",
-"                      $ % & * = - ; > , ' ) !                   ",
-"      ~ {       ] ^ / = ( _ : < [ } | 1 2 3 4 5 6               ",
-"      7 8 9   0 a b c d e f g h i j k l m n o p q r             ",
-"      s t u v _ f d d d w x y z A B C D E F G H I J K L         ",
-"      M N O _ c e d d d _ P Q R S T U V W X Y Z `  ...          ",
-"      +.@.#.$.d d d d %.&._ *.=.-.;.>.,.'.).!.~.                ",
-"      {.].^./.(.d d _.$.:._ <.[.}.|.1.2.3.4.5.                  ",
-"    6.7.7.4 8.e : w 9.0.a.b.c.2 d.e.f.g.h.i.j.k.                ",
-"    l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.o o z.A.B./.b C.D.            ",
-"    E.F.G.].o H.z.I.J.K.L.M.N.O.P.o o o Q.R.S._.b B.T.          ",
-"    U.V.W.X.f.f.7.Y.Z.`. + + +.+++].o o o.n z.q.@+#+$+%+        ",
-"    &+ +*+=+].o -+;+>+ + + + +,+'+H.o o o o o H.)+o !+~+{+      ",
-"    ]+ +^+/+H.o.(+_+ + + + + +:+<+z.o o o o o o o 7.n H.[+}+    ",
-"    |+ +1+2+3+4+5+6+ + + + + +7+8+H.o o f.9+f.9+f.F 0+a+b+o.c+  ",
-"    &+ +d+e+f+g+h+i+ + + + + +j+k+].f.9+l+m+n+o+p+q+r+s+t+u+v+  ",
-"    w+ +x+y+z+A+B+C+ + + + + +D+E+9+F+G+H+I+J+K+L+M+N+O+        ",
-"    P+Q+R+S+T+U+V+W+ + + + +X+Y+Z+`+ @I+J+Z .@+@E.              ",
-"    @@#@$@%@&@*@=@-@;@>@,@'@)@!@~@{@]@^@I+/@(@_@                ",
-"      :@<@[@}@|@1@2@3@4@5@6@7@8@9@0@L+a@b@c@d@e@                ",
-"        f@g@h@i@i@j@k@l@|@m@n@o@p@q@r@s@t@u@p v@                ",
-"        w@x@y@z@A@B@i@C@D@E@F@G@H@I@L+J@K@L@p M@                ",
-"            N@O@P@Q@R@S@T@U@V@W@X@Y@Z@Y@`@ #.#p +#              ",
-"                @###$#%#&#*#=#-#;#>#,#'#)#!#~#{#]#              ",
-"                                              ^#/#              ",
-"                                                                ",
-"                                                                ",
-"                                                                ",
-"                                                                "};
+const char * SDL_icon_xpm[] = {
+"96 96 64 1",
+" 	c None",
+".	c #040656",
+"+	c #0100B2",
+"@	c #04056E",
+"#	c #0000BD",
+"$	c #0B0C09",
+"%	c #0B0D26",
+"&	c #090C42",
+"*	c #060AA7",
+"=	c #1604DA",
+"-	c #020CD5",
+";	c #100F8D",
+">	c #040DE4",
+",	c #11129B",
+"'	c #1D1A83",
+")	c #2A10FD",
+"!	c #1318FA",
+"~	c #25225B",
+"{	c #252271",
+"]	c #312E2B",
+"^	c #33334D",
+"/	c #363775",
+"(	c #3D3B69",
+"_	c #3A3B8B",
+":	c #373AFF",
+"<	c #4142AA",
+"[	c #4B4864",
+"}	c #4D4B4A",
+"|	c #60492F",
+"1	c #4F4C57",
+"2	c #4A4A9E",
+"3	c #4F4E85",
+"4	c #474ADE",
+"5	c #4E4FFE",
+"6	c #5D5CB3",
+"7	c #686663",
+"8	c #666682",
+"9	c #676875",
+"0	c #66659E",
+"a	c #8B6538",
+"b	c #6465D5",
+"c	c #7F694F",
+"d	c #6767FF",
+"e	c #7272FF",
+"f	c #91795C",
+"g	c #7677FD",
+"h	c #828396",
+"i	c #A78153",
+"j	c #888989",
+"k	c #8D897E",
+"l	c #9190FD",
+"m	c #CA9048",
+"n	c #C09968",
+"o	c #A9A8A1",
+"p	c #A6A8B0",
+"q	c #B0B1FB",
+"r	c #EEAC61",
+"s	c #E3B478",
+"t	c #C3C4BE",
+"u	c #FFC68C",
+"v	c #FCCD90",
+"w	c #D4D7D3",
+"x	c #E3E5E0",
+"y	c #FCFFFB",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                            ttj7777777joot                                      ",
+"                                           9hh8830000088hh9                                     ",
+"                                       9888(//__<bbbb2////3[888hpp                              ",
+"                                 oj}^/_6bbbbgggggggb2///_bgbbbbb631kt                           ",
+"                                (80066bgeeegggggggb22262/bbggeggb66081                          ",
+"         p9^jj              pp8(_2bgggggeeeeeeeegb2~_bgb//6geegged5*'(hp                        ",
+"         ^2<3[7           j^/2bbggggeeeeeeeeeeggb2_({'4eb/2ggge5:!!!>-*{^kt                     ",
+"         &,5b60^         (02<beggggeeeeeeeeeegb62__7}~:5g/_bgd5!))))))=+;20k                    ",
+"         @#:egb3^     pp({4dgggeeeeeeeeeeeeegg6/__3im}+:e//bd:!)))))))))!#;87                   ",
+"        p'-!:dgb3]   7['4egeeeeeeeeeeeeeeeegg2/__[armc,-523<:!)))))))))))!>*{}                  ",
+"       tp,-)!5egb3} ~_<4dgggeeeeeeeeeeeeeegb6/_2[amusf'#!<_'>))))))))))))!)>+{~                 ",
+"        p;-))!5gb2^^'#5eggeeeeeeeeeeeeeeegg6/_23amrusi{#!+;;>))))))))))))))!!-'8p               ",
+"       tp'#!)):d6(@*>5egeeeeeeeeeeeeeeeegg6_/<(amrrvvn{+)-,;>))))))!!!!!!)))!!>,~j              ",
+"        p;#!))-'{'+-5eggeeeeeeeeeeeeeeeegb222(cmrruvvn{+)>,@>!)!!)!!>>>>======>-,/8             ",
+"         ;#)!-*.;-!5eggeeeeeeeeeeeeeeeegb2_<6|mrrsvvvn{+)!,.-!!!!>>=--######+++-#@(k            ",
+"        h@-)+@.*>!5egeeeeeeeeeeeeeeeeeegb_</]mrrruvvvn{+))*@->>--###++++++###+;@{(9j            ",
+"       kh,#+@@,>!:dggeeeeeeeeeeeeeeeeeeebbb_]mrruuvvsf'#)!*.+-###+++++++##+*;'3(&^9             ",
+"        8*,@@*)):dggeeeeeeeeeeeeeeeeeeeeggg<(|iruvvvsc,=!!*.;*++++++++###+,@&1o                 ",
+"        8@@@-!)!5eeeeeeeeeeeeeeeeeeeeeeeeeggb2[csvvvn^#)!!+@;*#+++++###*@~[                     ",
+"        9&@*!)):5geeeeeeeeeeeeeeeeeeeeeeeeegge637nsvf{>))!+;;*-######*;{.^                      ",
+"        9%;!!)):dgeeeeeeeeeeeeeeeeeeeeeeeeeeeggb_1ir7;>))!+;;,++++++*'(}                        ",
+"        9{+!))!5egeeeeeeeeeeeeeeeeeddddeeeeeeeege2}|~#!))!#;@...@@@.^hp                         ",
+"        8,=!))):dggeeeeeeeeeeeeeeeeggggeeeeeeeeggb_~,>!))!+@@@;;;;@&^o                          ",
+"       }(-)))))!:eegeeeeeeeeeeeeeegllllgeeeeeeeegd5+=))))!+;,#>--#,'/hj                         ",
+"      o8.>))))))!:dgggeeeeeeeeeeellqqqqlgeeeeggg5:!!!)))))-*+>)!:55db631                        ",
+"     p8<*!)))))))!:5deggggggeeeegqqqqqqqqlggged5:!))))))))>->!!:5ddeegb3/                       ",
+"    oh'#!))))))))))!:ddeeeeeeeeglqqqqqqqqlgedd:!)))))))))))))!:dggggeggg239                     ",
+"     ^*>!))!)))))))))!::55dddeegglll600333_4:!!)))))))))))))):dggeeeeeeggb6(9o                  ",
+"     ~+=-+#>))))))))))!!!:::::5554<3889988[/,=)))))))))))))):5gggeeeeeeeggb6087                 ",
+"     ~**@~'+>!))))))))))))))))!!>*{1kkooook7(,-!)))))))))))!:5deeeeeeeeeeeggb289                ",
+"     ~,'1o7(*>!))))))))))))))))=,[jtttwxxxwto^;>!))))))))))!!!::5deggeeeeeeegbb3]               ",
+"     ~@/oxt7'#))))))))))))))))=,3ktwxxyyyyyyxk/+!))))))))))))))!:::5degggeeegggb3^              ",
+"     ^&8xyyt^,)))))))))))))))>,3otwxyyyyyyyyyxh'>)))))))))))))))))):5ddeeeeeeeggb3^             ",
+"    771pyyyx7'=!)))))))))))!!#(jtxxyyyyyyyyyyyt3-)))))))))))))))))))!!::degggeeegb2[o           ",
+"     77tyyyxk/+!!)))))))))))-;9owxyyyyyyyyyyyywh*>)))))))))))))))))))))!::5ddgggggb68j          ",
+"      owyyyyt8;>))))))))))))*(otwyyyyyyyyyyyyyxp'-)))))))))))))))))))))))!!:5deeeggg_8j         ",
+"     jtxyyyyxh'>)))))))))!!#_ktxyyyyyyyyyyyyyyyt_+))))))))))))))))))))))))))!!:5deggg63j        ",
+"    7jwyyyyyyp/=))))))))))>,3owxyyyyyyyyyyyyyyyw/+))))))))))))))))))))))))))))!::5degb689       ",
+"     7xyyyyyyo[#))))))))))-/jtwyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))!:5dgg_/       ",
+"     }xyyyyyyt9*=))))))))=*9owyyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))))!!:5d3}      ",
+"     }xyyyyyywj'#!))))))!#@7oxyyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))))))!!:4/7     ",
+"     7xyyyyyyxj&,!!))))!!,%}oyyyyyyyyyyyyyyyyyyw/*))))))))))))))))))))))))))))))))))))))>487    ",
+"     7xyyyyyywk$@!!)))!!-.$]oyyyyyyyyyyyyyyyyyyw/+))))))))))))))))))))))))))))))!!!!))))!>'     ",
+"     }xyyyyyywj$&+!!)!)>;%$]jyyyyyyyyyyyyyyyyyyt{#)))))))))))))))))))))!!!!!!))!)!!!!!!))!#'    ",
+"     7xyyyyyyt7$%@-!)!>*[]$$jyyyyyyyyyyyyyyyyyxp;-))))))))))))))))))!!!!!!!!!!!!>>>>>>>>>>!,^   ",
+"     7xyyyyyyt}$][;-)=,(o7$$7yyyyyyyyyyyyyyyyyxp,-)))))))))))!!!!)!!!!>>>>=-----########--=+'9  ",
+"     jwyyyyyyo}$}o(';@~7wj$$7yyyyyyyyyyyyyyyyywh*>)))))))))))!>>>=>=---#####+########+++***;@17 ",
+"     otxyyyyyt}$7t7}1}7kw7$$7yyyyyyyyyyyyyyyyyt0-)))))))))!!!>--####+++++++++++++##+***,;''.&]  ",
+"    ooowyyyyyt}$}j7owwojo}$$jyyyyyyyyyyyyyyyyyp2>)))))))!!!=##++++++++++++++###+*;@.~[8[9hph    ",
+"     ojtyyyyywj$$}jwyyxo}$$]jyyyyyyyyyyyyyyyyyp'>))))))!>>-#++++++++++++####+,;'_3/&^}77kot     ",
+"      7tyyyyyxo]$$oxyyyt]$$}tyyyyyyyyyyyyyyyyx0*!)))!!!>-#++++++++++++#+##+*;.&1ko              ",
+"      7tyyyyyyx7]}xyyyyxj}]oxyyyyyyyyyyyyyyyyp<=)!!!!>-#++++++++++++####*;.(8h                  ",
+"       owxyxxyytooywptwwtppxyyyyyyyyyyyyyyyxp3,-=!)!>-#++++++++++###+*,'_{&1k                   ",
+"        jtwwttwtwwtj7kjowxyyyyyyyyyyyyyyyyxt7~'',+>=#+++++++++++###*;@&^j                       ",
+"        ]joojj7}]}]|innfc7jtwyyyyyyyyyyyxtjcfnnnf[@*#+++++++++###+@.&%%                         ",
+"       ]$}77}}$$$$]fsssnnifkkotwwwwwwwtpjkfinvvvsi}@*#++++++###*;@.@@&[                         ",
+"      o7$]]]]]$$]|isvvvvvusifckopppopok7cisvvvvvvvn(,#++++++#+*@.&@*#;3o                        ",
+"       }}$]|||fnnsvvvuvvvuuvvsniffffffnnsvvvvuuuvvvc{*+#++##*@&.@*+#--<7                        ",
+"        }]cninsuvvvvuuuuuuvvvvusnnnnnssuvvvvvuuuuvvc~*+#+++*@.@;*##=>>,^                        ",
+"         7fvvvvvvuuuuuuuuuuuuvvvvvvvvvvvuuuuuuuuuvvc~*+#+#+,.@*###->!!*~                        ",
+"         pkivvvvuuuuuuuuuuuuuuuvvvvvvvvuuuvsnsuuuvvf~*+#++++*+++->!!)!#.                        ",
+"          kfsuvvuuuuuuuuuuuuuuuuuuuuuuuuuvvnfsuvuvvc{++#++++###->!!))!-;h                       ",
+"           kisvvvuuuuuuuuuuuuuuuuuuuuuuuvvvicsvvvvs1@##+++++++#>!!))))=,ho                      ",
+"            7imuvvvuuuuuuuuuuuuuuuuuuuuvusfcivvuvvn~;##+++++++#>!!))))!#8k                      ",
+"             cimruuuuuvuuuuuuuuuuuuuuuuvsnfisuvvvsc@*#+++++++++#>!!))))-3}                      ",
+"              7amrruuuuuuuuuuuuuuuuuuuuvsnnsvvuvvi^,##++++++++++#>!!)))>/^                      ",
+"               kfamrruuuuvvvuuuuuuuuuuuuuvvvvvvvn1@+#++++++++++++#>!)))>{~                      ",
+"                7|iimrrruuuuuuuuuuuuuuuuvvvvuusn1'+#########++++++->!))>;                       ",
+"                  7cammrrrrruuuuuuvvvvvuuuuurrm|.*-#+#######+###+++->!!!*'                      ",
+"                   ookcaimmrrrrrruuuuurrrrrmi|]%.@@@@@;,*,*+########->!!*6o                     ",
+"                    p7}|ainiimmmmmmmmmmminnia|$%.....{3322_{''',,**+#=!!#6k                     ",
+"                          j7||aaiiiiiaa||7j           ookok711^&.';,*+=!><k                     ",
+"                               koooook                          hph[~@+>><k                     ",
+"                                ppppp                            tk7^3_,+<j                     ",
+"                                                                     o7^@3j                     ",
+"                                                                        9jj                     ",
+"                                                                         o                      ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                ",
+"                                                                                                "};
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index abf6d76d2935d7546f445cf9b53880963d0b1433..72c38b3dc090061f370d6822d7050912ed2f34ca 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -23,27 +39,50 @@
     <ProjectGuid>{61BA7D3C-F77D-4D31-B718-1177FE482CF2}</ProjectGuid>
     <Keyword>Win32Proj</Keyword>
     <RootNamespace>Srb2SDL</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
     <ProjectName>Srb2Win</ProjectName>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
-  <PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <PlatformToolset>v140</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
     <UseDebugLibraries>true</UseDebugLibraries>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
     <UseDebugLibraries>false</UseDebugLibraries>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
     <UseDebugLibraries>true</UseDebugLibraries>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
     <UseDebugLibraries>false</UseDebugLibraries>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <PlatformToolset>v141</PlatformToolset>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -54,29 +93,67 @@
     <Import Project="..\..\libs\libpng.props" />
     <Import Project="..\..\libs\SDL2.props" />
     <Import Project="..\..\libs\SDL_mixer.props" />
+    <Import Project="..\..\libs\libgme.props" />
+    <Import Project="..\..\libs\libopenmpt.props" />
     <Import Project="Srb2SDL.props" />
   </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Debug.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Debug.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Release.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Release.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Debug.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Debug.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Release.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Release.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <RunCodeAnalysis>false</RunCodeAnalysis>
   </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <ClCompile>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;ole32.lib;advapi32.lib;shell32.lib;gdi32.lib;oleaut32.lib;uuid.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <ClCompile>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <Link>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;ole32.lib;advapi32.lib;shell32.lib;gdi32.lib;oleaut32.lib;uuid.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\libs\libpng-src\projects\visualc10\libpng.vcxproj">
       <Project>{72b01aca-7a1a-4f7b-acef-2607299cf052}</Project>
@@ -89,6 +166,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\am_map.h" />
+    <ClInclude Include="..\apng.h" />
     <ClInclude Include="..\blua\lapi.h" />
     <ClInclude Include="..\blua\lauxlib.h" />
     <ClInclude Include="..\blua\lcode.h" />
@@ -141,6 +219,7 @@
     <ClInclude Include="..\hardware\hw3dsdrv.h" />
     <ClInclude Include="..\hardware\hw3sound.h" />
     <ClInclude Include="..\hardware\hws_data.h" />
+    <ClInclude Include="..\hardware\hw_clip.h" />
     <ClInclude Include="..\hardware\hw_data.h" />
     <ClInclude Include="..\hardware\hw_defs.h" />
     <ClInclude Include="..\hardware\hw_dll.h" />
@@ -198,6 +277,7 @@
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
     <ClInclude Include="..\r_plane.h" />
+    <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
@@ -240,6 +320,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\am_map.c" />
+    <ClCompile Include="..\apng.c" />
     <ClCompile Include="..\blua\lapi.c" />
     <ClCompile Include="..\blua\lauxlib.c" />
     <ClCompile Include="..\blua\lbaselib.c" />
@@ -282,6 +363,7 @@
     <ClCompile Include="..\hardware\hw3sound.c" />
     <ClCompile Include="..\hardware\hw_bsp.c" />
     <ClCompile Include="..\hardware\hw_cache.c" />
+    <ClCompile Include="..\hardware\hw_clip.c" />
     <ClCompile Include="..\hardware\hw_draw.c" />
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
@@ -349,6 +431,7 @@
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
     <ClCompile Include="..\r_plane.c" />
+    <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index bdb029cf9437e8c960a84a111bbcbfaf5dc9b82b..9e442000fdc1a5ad0a2e6643cd06eb99ddd8cf11 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -216,6 +216,9 @@
     <ClInclude Include="..\hardware\hws_data.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_clip.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\hardware\hw_data.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -291,6 +294,9 @@
     <ClInclude Include="..\lua_script.h">
       <Filter>LUA</Filter>
     </ClInclude>
+    <ClInclude Include="..\apng.h">
+      <Filter>M_Misc</Filter>
+    </ClInclude>
     <ClInclude Include="..\md5.h">
       <Filter>M_Misc</Filter>
     </ClInclude>
@@ -444,6 +450,12 @@
     <ClInclude Include="sdlmain.h">
       <Filter>SDLApp</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_clip.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_portal.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <CustomBuild Include="..\tmap.nas">
@@ -603,6 +615,9 @@
     <ClCompile Include="..\hardware\hw_cache.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_clip.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_draw.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
@@ -879,6 +894,13 @@
     <ClCompile Include="SDL_main\SDL_windows_main.c">
       <Filter>SDLApp</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_clip.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\apng.c" />
+    <ClCompile Include="..\r_portal.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <Image Include="Srb2SDL.ico">
diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj
index d2a268f8d44f501b141ffa4de48eea5fed1e5f32..3898aeba4efc53c9bd06fe38834d164ae31d64c1 100644
--- a/src/sdl/Srb2SDL-vc9.vcproj
+++ b/src/sdl/Srb2SDL-vc9.vcproj
@@ -2834,6 +2834,50 @@
 				RelativePath="..\m_argv.h"
 				>
 			</File>
+			<File
+				RelativePath="..\apng.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\apng.h"
+				>
+			</File>
 			<File
 				RelativePath="..\m_bbox.c"
 				>
diff --git a/src/sdl/Srb2SDL.props b/src/sdl/Srb2SDL.props
index 260f81eed79d762e0f079810d30ea0e0d35d4dcb..17b41bddc1ae2f4f3d12ec2d0ce0160ccf541e72 100644
--- a/src/sdl/Srb2SDL.props
+++ b/src/sdl/Srb2SDL.props
@@ -5,7 +5,10 @@
   <PropertyGroup />
   <ItemDefinitionGroup>
     <ClCompile>
-      <PreprocessorDefinitions>USE_WGL_SWAP;DIRECTFULLSCREEN;HAVE_SDL;HWRENDER;HW3SOUND;HAVE_FILTER;HAVE_MIXER;SDLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <!-- x86/x64 defines: has specific libraries that ARM does not -->
+      <PreprocessorDefinitions Condition="'$(Platform)' == 'Win32' OR '$(Platform)' == 'x64'">HAVE_ZLIB;HAVE_LIBGME;USE_WGL_SWAP;DIRECTFULLSCREEN;HAVE_SDL;HWRENDER;HW3SOUND;HAVE_FILTER;HAVE_MIXER;HAVE_OPENMPT;SDLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <!-- ARM defines -->
+      <PreprocessorDefinitions Condition="'$(Platform)' != 'Win32' AND '$(Platform)' != 'x64'">USE_WGL_SWAP;DIRECTFULLSCREEN;HAVE_SDL;HWRENDER;HW3SOUND;HAVE_FILTER;HAVE_MIXER;SDLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
   </ItemDefinitionGroup>
   <ItemGroup />
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index f4686d2bf4f3e95626ba6a69322ce44b379ce8ec..05ac6450e2267c0c08182775cece05eeaefabeab 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -94,6 +94,7 @@ void *hwSym(const char *funcName,void *handle)
 #ifdef SHUFFLE
 	GETFUNC(PostImgRedraw);
 #endif //SHUFFLE
+	GETFUNC(FlushScreenTextures);
 	GETFUNC(StartScreenWipe);
 	GETFUNC(EndScreenWipe);
 	GETFUNC(DoScreenWipe);
diff --git a/src/sdl/i_cdmus.c b/src/sdl/i_cdmus.c
index 3105f512278e98536de12c9f27e02c5a884e4321..5d086e73a05fdc7d28b23818a9141aa9edc650dc 100644
--- a/src/sdl/i_cdmus.c
+++ b/src/sdl/i_cdmus.c
@@ -12,19 +12,19 @@ consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NUL
 consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 
-FUNCMATH void I_InitCD(void){}
+void I_InitCD(void){}
 
-FUNCMATH void I_StopCD(void){}
+void I_StopCD(void){}
 
-FUNCMATH void I_PauseCD(void){}
+void I_PauseCD(void){}
 
-FUNCMATH void I_ResumeCD(void){}
+void I_ResumeCD(void){}
 
-FUNCMATH void I_ShutdownCD(void){}
+void I_ShutdownCD(void){}
 
-FUNCMATH void I_UpdateCD(void){}
+void I_UpdateCD(void){}
 
-FUNCMATH void I_PlayCD(UINT8 track, UINT8 looping)
+void I_PlayCD(UINT8 track, UINT8 looping)
 {
 	(void)track;
 	(void)looping;
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index 65b7b5ffe0d97addbb99239956077aa8895b6ddf..f54f0d7c5496226d84c84a9636bdb899dbdd4414 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -103,6 +103,10 @@ static inline VOID MakeCodeWritable(VOID)
 
 	\return	int
 */
+#if defined (__GNUC__) && (__GNUC__ >= 4)
+#pragma GCC diagnostic ignored "-Wmissing-noreturn"
+#endif
+
 #ifdef FORCESDLMAIN
 int SDL_main(int argc, char **argv)
 #else
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 7d0eabe31ccdf809f82e7e241c6af93ee3df957a..98166a1cef1719d1584880890943226ee3f8c1d8 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -1,8 +1,11 @@
 // Emacs style mode select   -*- C++ -*-
+//
+// SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 //
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Portions Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -124,7 +127,11 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include "macosx/mac_resources.h"
 #endif
 
-// Locations for searching the srb2.srb
+#ifndef errno
+#include <errno.h>
+#endif
+
+// Locations for searching the srb2.pk3
 #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
 #define DEFAULTWADLOCATION1 "/usr/local/share/games/SRB2"
 #define DEFAULTWADLOCATION2 "/usr/local/games/SRB2"
@@ -143,7 +150,6 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 /**	\brief WAD file to look for
 */
 #define WADKEYWORD1 "srb2.pk3"
-#define WADKEYWORD2 "srb2.wad"
 /**	\brief holds wad path
 */
 static char returnWadPath[256];
@@ -887,8 +893,8 @@ void I_GetJoystickEvents(void)
 	UINT64 joyhats = 0;
 #if 0
 	UINT64 joybuttons = 0;
-#endif
 	Sint16 axisx, axisy;
+#endif
 
 	if (!joystick_started) return;
 
@@ -956,6 +962,7 @@ void I_GetJoystickEvents(void)
 		}
 	}
 
+#if 0
 	// send joystick axis positions
 	event.type = ev_joystick;
 
@@ -1006,6 +1013,7 @@ void I_GetJoystickEvents(void)
 		}
 		D_PostEvent(&event);
 	}
+#endif
 }
 
 /**	\brief	Open joystick handle
@@ -1150,6 +1158,7 @@ static void I_ShutdownJoystick2(void)
 		D_PostEvent(&event);
 	}
 
+	joystick2_started = 0;
 	JoyReset(&JoyInfo2);
 	if (!joystick_started && !joystick2_started && SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK)
 	{
@@ -1168,8 +1177,8 @@ void I_GetJoystick2Events(void)
 	UINT64 joyhats = 0;
 #if 0
 	INT64 joybuttons = 0;
-#endif
 	INT32 axisx, axisy;
+#endif
 
 	if (!joystick2_started)
 		return;
@@ -1239,6 +1248,7 @@ void I_GetJoystick2Events(void)
 		}
 	}
 
+#if 0
 	// send joystick axis positions
 	event.type = ev_joystick2;
 
@@ -1289,7 +1299,7 @@ void I_GetJoystick2Events(void)
 		}
 		D_PostEvent(&event);
 	}
-
+#endif
 }
 
 /**	\brief	Open joystick handle
@@ -1391,7 +1401,13 @@ static int joy_open2(const char *fname)
 void I_InitJoystick(void)
 {
 	I_ShutdownJoystick();
-	SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE);
+
+	if (M_CheckParm("-noxinput"))
+		SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE);
+
+	if (M_CheckParm("-nohidapi"))
+		SDL_SetHintWithPriority("SDL_JOYSTICK_HIDAPI", "0", SDL_HINT_OVERRIDE);
+
 	if (!strcmp(cv_usejoystick.string, "0") || M_CheckParm("-nojoy"))
 		return;
 	if (joy_open(cv_usejoystick.string) != -1)
@@ -1407,7 +1423,13 @@ void I_InitJoystick(void)
 void I_InitJoystick2(void)
 {
 	I_ShutdownJoystick2();
-	SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE);
+
+	if (M_CheckParm("-noxinput"))
+		SDL_SetHintWithPriority("SDL_XINPUT_ENABLED", "0", SDL_HINT_OVERRIDE);
+
+	if (M_CheckParm("-nohidapi"))
+		SDL_SetHintWithPriority("SDL_JOYSTICK_HIDAPI", "0", SDL_HINT_OVERRIDE);
+
 	if (!strcmp(cv_usejoystick2.string, "0") || M_CheckParm("-nojoy"))
 		return;
 	if (joy_open2(cv_usejoystick2.string) != -1)
@@ -1445,18 +1467,28 @@ INT32 I_NumJoys(void)
 	return numjoy;
 }
 
+static char joyname[255]; // MAX_PATH; joystick name is straight from the driver
+
 const char *I_GetJoyName(INT32 joyindex)
 {
-	const char *joyname = "NA";
+	const char *tempname = NULL;
 	joyindex--; //SDL's Joystick System starts at 0, not 1
 	if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0)
 	{
 		if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != -1)
-			joyname = SDL_JoystickNameForIndex(joyindex);
+		{
+			tempname = SDL_JoystickNameForIndex(joyindex);
+			if (tempname)
+				strncpy(joyname, tempname, 254);
+		}
 		SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
 	}
 	else
-		joyname = SDL_JoystickNameForIndex(joyindex);
+	{
+		tempname = SDL_JoystickNameForIndex(joyindex);
+		if (tempname)
+			strncpy(joyname, tempname, 254);
+	}
 	return joyname;
 }
 
@@ -1679,7 +1711,7 @@ static void I_ShutdownMouse2(void)
 	EscapeCommFunction(mouse2filehandle, CLRRTS);
 
 	PurgeComm(mouse2filehandle, PURGE_TXABORT | PURGE_RXABORT |
-	          PURGE_TXCLEAR | PURGE_RXCLEAR);
+			  PURGE_TXCLEAR | PURGE_RXCLEAR);
 
 	CloseHandle(mouse2filehandle);
 
@@ -1872,11 +1904,11 @@ void I_StartupMouse2(void)
 	{
 		// COM file handle
 		mouse2filehandle = CreateFileA(cv_mouse2port.string, GENERIC_READ | GENERIC_WRITE,
-		                               0,                     // exclusive access
-		                               NULL,                  // no security attrs
-		                               OPEN_EXISTING,
-		                               FILE_ATTRIBUTE_NORMAL,
-		                               NULL);
+									   0,                     // exclusive access
+									   NULL,                  // no security attrs
+									   OPEN_EXISTING,
+									   FILE_ATTRIBUTE_NORMAL,
+									   NULL);
 		if (mouse2filehandle == INVALID_HANDLE_VALUE)
 		{
 			INT32 e = GetLastError();
@@ -1896,7 +1928,7 @@ void I_StartupMouse2(void)
 
 	// purge buffers
 	PurgeComm(mouse2filehandle, PURGE_TXABORT | PURGE_RXABORT
-	          | PURGE_TXCLEAR | PURGE_RXCLEAR);
+			  | PURGE_TXCLEAR | PURGE_RXCLEAR);
 
 	// setup port to 1200 7N1
 	dcb.DCBlength = sizeof (DCB);
@@ -1922,14 +1954,14 @@ void I_StartupMouse2(void)
 //
 // I_Tactile
 //
-FUNCMATH void I_Tactile(FFType pFFType, const JoyFF_t *FFEffect)
+void I_Tactile(FFType pFFType, const JoyFF_t *FFEffect)
 {
 	// UNUSED.
 	(void)pFFType;
 	(void)FFEffect;
 }
 
-FUNCMATH void I_Tactile2(FFType pFFType, const JoyFF_t *FFEffect)
+void I_Tactile2(FFType pFFType, const JoyFF_t *FFEffect)
 {
 	// UNUSED.
 	(void)pFFType;
@@ -1940,7 +1972,7 @@ FUNCMATH void I_Tactile2(FFType pFFType, const JoyFF_t *FFEffect)
 */
 static ticcmd_t emptycmd;
 
-FUNCMATH ticcmd_t *I_BaseTiccmd(void)
+ticcmd_t *I_BaseTiccmd(void)
 {
 	return &emptycmd;
 }
@@ -1949,7 +1981,7 @@ FUNCMATH ticcmd_t *I_BaseTiccmd(void)
 */
 static ticcmd_t emptycmd2;
 
-FUNCMATH ticcmd_t *I_BaseTiccmd2(void)
+ticcmd_t *I_BaseTiccmd2(void)
 {
 	return &emptycmd2;
 }
@@ -2010,7 +2042,7 @@ static void I_ShutdownTimer(void)
 	pfntimeGetTime = NULL;
 	if (winmm)
 	{
-		p_timeEndPeriod pfntimeEndPeriod = (p_timeEndPeriod)GetProcAddress(winmm, "timeEndPeriod");
+		p_timeEndPeriod pfntimeEndPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeEndPeriod");
 		if (pfntimeEndPeriod)
 			pfntimeEndPeriod(1);
 		FreeLibrary(winmm);
@@ -2024,8 +2056,8 @@ static void I_ShutdownTimer(void)
 //
 tic_t I_GetTime (void)
 {
-	static Uint32 basetime = 0;
-	       Uint32 ticks = SDL_GetTicks();
+	static Uint64 basetime = 0;
+		   Uint64 ticks = SDL_GetTicks();
 
 	if (!basetime)
 		basetime = ticks;
@@ -2043,7 +2075,7 @@ tic_t I_GetTime (void)
 //
 //I_StartupTimer
 //
-FUNCMATH void I_StartupTimer(void)
+void I_StartupTimer(void)
 {
 #ifdef _WIN32
 	// for win2k time bug
@@ -2055,10 +2087,10 @@ FUNCMATH void I_StartupTimer(void)
 	winmm = LoadLibraryA("winmm.dll");
 	if (winmm)
 	{
-		p_timeEndPeriod pfntimeBeginPeriod = (p_timeEndPeriod)GetProcAddress(winmm, "timeBeginPeriod");
+		p_timeEndPeriod pfntimeBeginPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeBeginPeriod");
 		if (pfntimeBeginPeriod)
 			pfntimeBeginPeriod(1);
-		pfntimeGetTime = (p_timeGetTime)GetProcAddress(winmm, "timeGetTime");
+		pfntimeGetTime = (p_timeGetTime)(LPVOID)GetProcAddress(winmm, "timeGetTime");
 	}
 	I_AddExitFunc(I_ShutdownTimer);
 #endif
@@ -2091,7 +2123,6 @@ INT32 I_StartupSystem(void)
 	return 0;
 }
 
-
 //
 // I_Quit
 //
@@ -2132,6 +2163,8 @@ void I_Quit(void)
 		printf("\r");
 		ShowEndTxt();
 	}
+	if (myargmalloc)
+		free(myargv); // Deallocate allocated memory
 death:
 	W_Shutdown();
 	exit(0);
@@ -2143,11 +2176,11 @@ void I_WaitVBL(INT32 count)
 	SDL_Delay(count);
 }
 
-FUNCMATH void I_BeginRead(void)
+void I_BeginRead(void)
 {
 }
 
-FUNCMATH void I_EndRead(void)
+void I_EndRead(void)
 {
 }
 
@@ -2356,7 +2389,7 @@ void I_GetDiskFreeSpace(INT64 *freespace)
 
 	if (!testwin95)
 	{
-		pfnGetDiskFreeSpaceEx = (p_GetDiskFreeSpaceExA)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetDiskFreeSpaceExA");
+		pfnGetDiskFreeSpaceEx = (p_GetDiskFreeSpaceExA)(LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetDiskFreeSpaceExA");
 		testwin95 = true;
 	}
 	if (pfnGetDiskFreeSpaceEx)
@@ -2370,7 +2403,7 @@ void I_GetDiskFreeSpace(INT64 *freespace)
 	{
 		DWORD SectorsPerCluster, BytesPerSector, NumberOfFreeClusters, TotalNumberOfClusters;
 		GetDiskFreeSpace(NULL, &SectorsPerCluster, &BytesPerSector,
-		                 &NumberOfFreeClusters, &TotalNumberOfClusters);
+						 &NumberOfFreeClusters, &TotalNumberOfClusters);
 		*freespace = BytesPerSector*SectorsPerCluster*NumberOfFreeClusters;
 	}
 #else // Dummy for platform independent; 1GB should be enough
@@ -2380,7 +2413,7 @@ void I_GetDiskFreeSpace(INT64 *freespace)
 
 char *I_GetUserName(void)
 {
-	static char username[MAXPLAYERNAME];
+	static char username[MAXPLAYERNAME+1];
 	char *p;
 #ifdef _WIN32
 	DWORD i = MAXPLAYERNAME;
@@ -2511,14 +2544,6 @@ static boolean isWadPathOk(const char *path)
 		return true;
 	}
 
-	sprintf(wad3path, pandf, path, WADKEYWORD2);
-
-	if (FIL_ReadFileOK(wad3path))
-	{
-		free(wad3path);
-		return true;
-	}
-
 	free(wad3path);
 	return false;
 }
@@ -2536,7 +2561,7 @@ static void pathonly(char *s)
 		}
 }
 
-/**	\brief	search for srb2.srb in the given path
+/**	\brief	search for srb2.pk3 in the given path
 
 	\param	searchDir	starting path
 
@@ -2557,19 +2582,12 @@ static const char *searchWad(const char *searchDir)
 		return tempsw;
 	}
 
-	strcpy(tempsw, WADKEYWORD2);
-	fstemp = filesearch(tempsw, searchDir, NULL, true, 20);
-	if (fstemp == FS_FOUND)
-	{
-		pathonly(tempsw);
-		return tempsw;
-	}
 	return NULL;
 }
 
-/**	\brief go through all possible paths and look for srb2.srb
+/**	\brief go through all possible paths and look for srb2.pk3
 
-  \return path to srb2.srb if any
+  \return path to srb2.pk3 if any
 */
 static const char *locateWad(void)
 {
@@ -2592,22 +2610,22 @@ static const char *locateWad(void)
 
 #ifdef CMAKECONFIG
 #ifndef NDEBUG
-    I_OutputMsg(","CMAKE_ASSETS_DIR);
-    strcpy(returnWadPath, CMAKE_ASSETS_DIR);
-    if (isWadPathOk(returnWadPath))
-    {
-        return returnWadPath;
-    }
+	I_OutputMsg(","CMAKE_ASSETS_DIR);
+	strcpy(returnWadPath, CMAKE_ASSETS_DIR);
+	if (isWadPathOk(returnWadPath))
+	{
+		return returnWadPath;
+	}
 #endif
 #endif
 
 #ifdef __APPLE__
-    OSX_GetResourcesPath(returnWadPath);
-    I_OutputMsg(",%s", returnWadPath);
-    if (isWadPathOk(returnWadPath))
-    {
-        return returnWadPath;
-    }
+	OSX_GetResourcesPath(returnWadPath);
+	I_OutputMsg(",%s", returnWadPath);
+	if (isWadPathOk(returnWadPath))
+	{
+		return returnWadPath;
+	}
 #endif
 
 	// examine default dirs
@@ -2698,7 +2716,7 @@ const char *I_LocateWad(void)
 
 	if (waddir)
 	{
-		// change to the directory where we found srb2.srb
+		// change to the directory where we found srb2.pk3
 #if defined (_WIN32)
 		SetCurrentDirectoryA(waddir);
 #else
@@ -2709,10 +2727,33 @@ const char *I_LocateWad(void)
 	return waddir;
 }
 
-#if defined(LINUX) || defined(LINUX64)
+#ifdef __linux__
 #define MEMINFO_FILE "/proc/meminfo"
 #define MEMTOTAL "MemTotal:"
+#define MEMAVAILABLE "MemAvailable:"
 #define MEMFREE "MemFree:"
+#define CACHED "Cached:"
+#define BUFFERS "Buffers:"
+#define SHMEM "Shmem:"
+
+/* Parse the contents of /proc/meminfo (in buf), return value of "name"
+ * (example: MemTotal) */
+static long get_entry(const char* name, const char* buf)
+{
+	long val;
+	char* hit = strstr(buf, name);
+	if (hit == NULL) {
+		return -1;
+	}
+
+	errno = 0;
+	val = strtol(hit + strlen(name), NULL, 10);
+	if (errno != 0) {
+		CONS_Alert(CONS_ERROR, M_GetText("get_entry: strtol() failed: %s\n"), strerror(errno));
+		return -1;
+	}
+	return val;
+}
 #endif
 
 // quick fix for compil
@@ -2729,20 +2770,23 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	};
 	if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL)
 	{
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	if (kvm_nlist(kd, namelist) != 0)
 	{
 		kvm_close (kd);
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	if (kvm_read(kd, namelist[X_SUM].n_value, &sum,
 		sizeof (sum)) != sizeof (sum))
 	{
 		kvm_close(kd);
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	kvm_close(kd);
@@ -2763,7 +2807,7 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (total)
 		*total = (UINT32)info.dwTotalPhys;
 	return (UINT32)info.dwAvailPhys;
-#elif defined (LINUX) || defined (LINUX64)
+#elif defined (__linux__)
 	/* Linux */
 	char buf[1024];
 	char *memTag;
@@ -2771,6 +2815,11 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	UINT32 totalKBytes;
 	INT32 n;
 	INT32 meminfo_fd = -1;
+	long Cached;
+	long MemFree;
+	long Buffers;
+	long Shmem;
+	long MemAvailable = -1;
 
 	meminfo_fd = open(MEMINFO_FILE, O_RDONLY);
 	n = read(meminfo_fd, buf, 1023);
@@ -2779,30 +2828,45 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (n < 0)
 	{
 		// Error
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 
 	buf[n] = '\0';
-	if (NULL == (memTag = strstr(buf, MEMTOTAL)))
+	if ((memTag = strstr(buf, MEMTOTAL)) == NULL)
 	{
 		// Error
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 
 	memTag += sizeof (MEMTOTAL);
 	totalKBytes = atoi(memTag);
 
-	if (NULL == (memTag = strstr(buf, MEMFREE)))
+	if ((memTag = strstr(buf, MEMAVAILABLE)) == NULL)
 	{
-		// Error
-		*total = 0L;
-		return 0;
-	}
+		Cached = get_entry(CACHED, buf);
+		MemFree = get_entry(MEMFREE, buf);
+		Buffers = get_entry(BUFFERS, buf);
+		Shmem = get_entry(SHMEM, buf);
+		MemAvailable = Cached + MemFree + Buffers - Shmem;
 
-	memTag += sizeof (MEMFREE);
-	freeKBytes = atoi(memTag);
+		if (MemAvailable == -1)
+		{
+			// Error
+			if (total)
+				*total = 0L;
+			return 0;
+		}
+		freeKBytes = MemAvailable;
+	}
+	else
+	{
+		memTag += sizeof (MEMAVAILABLE);
+		freeKBytes = atoi(memTag);
+	}
 
 	if (total)
 		*total = totalKBytes << 10;
@@ -2812,7 +2876,7 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (total)
 		*total = 48<<20;
 	return 48<<20;
-#endif /* LINUX */
+#endif
 }
 
 const CPUInfoFlags *I_CPUInfo(void)
@@ -2820,7 +2884,7 @@ const CPUInfoFlags *I_CPUInfo(void)
 #if defined (_WIN32)
 	static CPUInfoFlags WIN_CPUInfo;
 	SYSTEM_INFO SI;
-	p_IsProcessorFeaturePresent pfnCPUID = (p_IsProcessorFeaturePresent)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsProcessorFeaturePresent");
+	p_IsProcessorFeaturePresent pfnCPUID = (p_IsProcessorFeaturePresent)(LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsProcessorFeaturePresent");
 
 	ZeroMemory(&WIN_CPUInfo,sizeof (WIN_CPUInfo));
 	if (pfnCPUID)
@@ -2879,5 +2943,5 @@ const CPUInfoFlags *I_CPUInfo(void)
 }
 
 // note CPUAFFINITY code used to reside here
-FUNCMATH void I_RegisterSysCommands(void) {}
+void I_RegisterSysCommands(void) {}
 #endif
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 365d70729f23c650d7fce7e65e6874c6de55573f..5a4fd7a02247c0ef985868c8e0fa3ee5619b0c39 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1,8 +1,10 @@
 // Emacs style mode select   -*- C++ -*-
+// SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 //
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Portions Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 2014-2018 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -39,6 +41,10 @@
 
 #ifdef HAVE_IMAGE
 #include "SDL_image.h"
+#elif 1
+#define LOAD_XPM //I want XPM!
+#include "IMG_xpm.c" //Alam: I don't want to add SDL_Image.dll/so
+#define HAVE_IMAGE //I have SDL_Image, sortof
 #endif
 
 #ifdef HAVE_IMAGE
@@ -58,7 +64,6 @@
 #include "../m_menu.h"
 #include "../d_main.h"
 #include "../s_sound.h"
-#include "../i_sound.h"  // midi pause/unpause
 #include "../i_joy.h"
 #include "../st_stuff.h"
 #include "../g_game.h"
@@ -194,7 +199,10 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen)
 			}
 			// Reposition window only in windowed mode
 			SDL_SetWindowSize(window, width, height);
-			SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
+			SDL_SetWindowPosition(window,
+				SDL_WINDOWPOS_CENTERED_DISPLAY(SDL_GetWindowDisplayIndex(window)),
+				SDL_WINDOWPOS_CENTERED_DISPLAY(SDL_GetWindowDisplayIndex(window))
+			);
 		}
 	}
 	else
@@ -351,6 +359,7 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code)
 
 static void SDLdoUngrabMouse(void)
 {
+	SDL_ShowCursor(SDL_ENABLE);
 	SDL_SetWindowGrab(window, SDL_FALSE);
 	wrapmouseok = SDL_FALSE;
 	SDL_SetRelativeMouseMode(SDL_FALSE);
@@ -360,6 +369,7 @@ void SDLforceUngrabMouse(void)
 {
 	if (SDL_WasInit(SDL_INIT_VIDEO)==SDL_INIT_VIDEO && window != NULL)
 	{
+		SDL_ShowCursor(SDL_ENABLE);
 		SDL_SetWindowGrab(window, SDL_FALSE);
 		wrapmouseok = SDL_FALSE;
 		SDL_SetRelativeMouseMode(SDL_FALSE);
@@ -562,7 +572,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 		// Tell game we got focus back, resume music if necessary
 		window_notinfocus = false;
 		if (!paused)
-			I_ResumeSong(0); //resume it
+			S_ResumeAudio(); //resume it
 
 		if (!firsttimeonmouse)
 		{
@@ -574,7 +584,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 	{
 		// Tell game we lost focus, pause music
 		window_notinfocus = true;
-		I_PauseSong(0);
+		S_PauseAudio();
 
 		if (!disable_mouse)
 		{
@@ -658,6 +668,14 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 
 	SDL_memset(&event, 0, sizeof(event_t));
 
+	// Ignore the event if the mouse is not actually focused on the window.
+	// This can happen if you used the mouse to restore keyboard focus;
+	// this apparently makes a mouse button down event but not a mouse button up event,
+	// resulting in whatever key was pressed down getting "stuck" if we don't ignore it.
+	// -- Monster Iestyn (28/05/18)
+	if (SDL_GetMouseFocus() != window)
+		return;
+
 	/// \todo inputEvent.button.which
 	if (USE_MOUSEINPUT)
 	{
@@ -753,6 +771,33 @@ static void Impl_HandleJoystickAxisEvent(SDL_JoyAxisEvent evt)
 	D_PostEvent(&event);
 }
 
+#if 0
+static void Impl_HandleJoystickHatEvent(SDL_JoyHatEvent evt)
+{
+	event_t event;
+	SDL_JoystickID joyid[2];
+
+	// Determine the Joystick IDs for each current open joystick
+	joyid[0] = SDL_JoystickInstanceID(JoyInfo.dev);
+	joyid[1] = SDL_JoystickInstanceID(JoyInfo2.dev);
+
+	if (evt.hat >= JOYHATS)
+		return; // ignore hats with too high an index
+
+	if (evt.which == joyid[0])
+	{
+		event.data1 = KEY_HAT1 + (evt.hat*4);
+	}
+	else if (evt.which == joyid[1])
+	{
+		event.data1 = KEY_2HAT1 + (evt.hat*4);
+	}
+	else return;
+
+	// NOTE: UNFINISHED
+}
+#endif
+
 static void Impl_HandleJoystickButtonEvent(SDL_JoyButtonEvent evt, Uint32 type)
 {
 	event_t event;
@@ -790,6 +835,8 @@ static void Impl_HandleJoystickButtonEvent(SDL_JoyButtonEvent evt, Uint32 type)
 	if (event.type != ev_console) D_PostEvent(&event);
 }
 
+
+
 void I_GetEvent(void)
 {
 	SDL_Event evt;
@@ -830,6 +877,11 @@ void I_GetEvent(void)
 			case SDL_JOYAXISMOTION:
 				Impl_HandleJoystickAxisEvent(evt.jaxis);
 				break;
+#if 0
+			case SDL_JOYHATMOTION:
+				Impl_HandleJoystickHatEvent(evt.jhat)
+				break;
+#endif
 			case SDL_JOYBUTTONUP:
 			case SDL_JOYBUTTONDOWN:
 				Impl_HandleJoystickButtonEvent(evt.jbutton, evt.type);
@@ -884,6 +936,8 @@ void I_StartupMouse(void)
 //
 void I_OsPolling(void)
 {
+	SDL_Keymod mod;
+
 	if (consolevent)
 		I_GetConsoleEvents();
 	if (SDL_WasInit(SDL_INIT_JOYSTICK) == SDL_INIT_JOYSTICK)
@@ -896,6 +950,18 @@ void I_OsPolling(void)
 	I_GetMouseEvents();
 
 	I_GetEvent();
+
+	mod = SDL_GetModState();
+	/* Handle here so that our state is always synched with the system. */
+	shiftdown = ctrldown = altdown = 0;
+	capslock = false;
+	if (mod & KMOD_LSHIFT) shiftdown |= 1;
+	if (mod & KMOD_RSHIFT) shiftdown |= 2;
+	if (mod & KMOD_LCTRL)   ctrldown |= 1;
+	if (mod & KMOD_RCTRL)   ctrldown |= 2;
+	if (mod & KMOD_LALT)     altdown |= 1;
+	if (mod & KMOD_RALT)     altdown |= 2;
+	if (mod & KMOD_CAPS) capslock = true;
 }
 
 //
@@ -929,6 +995,7 @@ void I_UpdateNoBlit(void)
 // from PrBoom's src/SDL/i_video.c
 static inline boolean I_SkipFrame(void)
 {
+#if 0
 	static boolean skip = false;
 
 	if (rendermode != render_soft)
@@ -948,6 +1015,8 @@ static inline boolean I_SkipFrame(void)
 		default:
 			return false;
 	}
+#endif
+	return false;
 }
 
 //
@@ -1045,7 +1114,7 @@ void I_SetPalette(RGBA_t *palette)
 }
 
 // return number of fullscreen + X11 modes
-FUNCMATH INT32 VID_NumModes(void)
+INT32 VID_NumModes(void)
 {
 	if (USE_FULLSCREEN && numVidModes != -1)
 		return numVidModes - firstEntry;
@@ -1053,7 +1122,7 @@ FUNCMATH INT32 VID_NumModes(void)
 		return MAXWINMODES;
 }
 
-FUNCMATH const char *VID_GetModeName(INT32 modeNum)
+const char *VID_GetModeName(INT32 modeNum)
 {
 #if 0
 	if (USE_FULLSCREEN && numVidModes != -1) // fullscreen modes
@@ -1083,7 +1152,7 @@ FUNCMATH const char *VID_GetModeName(INT32 modeNum)
 	return &vidModeName[modeNum][0];
 }
 
-FUNCMATH INT32 VID_GetModeForSize(INT32 w, INT32 h)
+INT32 VID_GetModeForSize(INT32 w, INT32 h)
 {
 	int i;
 	for (i = 0; i < MAXWINMODES; i++)
@@ -1446,6 +1515,7 @@ void I_StartupGraphics(void)
 #ifdef SHUFFLE
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
 #endif
+		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
 		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
 		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
 		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
@@ -1505,9 +1575,18 @@ void I_StartupGraphics(void)
 	realheight = (Uint16)vid.height;
 
 	VID_Command_Info_f();
-	if (!disable_mouse) SDL_ShowCursor(SDL_DISABLE);
 	SDLdoUngrabMouse();
 
+	SDL_RaiseWindow(window);
+
+	if (mousegrabok && !disable_mouse)
+	{
+		SDL_ShowCursor(SDL_DISABLE);
+		SDL_SetRelativeMouseMode(SDL_TRUE);
+		wrapmouseok = SDL_TRUE;
+		SDL_SetWindowGrab(window, SDL_TRUE);
+	}
+
 	graphics_started = true;
 }
 
diff --git a/src/sdl/macosx/Srb2mac.icns b/src/sdl/macosx/Srb2mac.icns
index 4baedc1c5a091a3917d2a33bc3780da5b1b5e996..96cb8a36d991818eb03b7d80343518ab168bda7d 100644
Binary files a/src/sdl/macosx/Srb2mac.icns and b/src/sdl/macosx/Srb2mac.icns differ
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index eaac87debe38e36f549942a7d2e00775b99414e1..878db3d8de37b4a8cfb19a8a95dcfaa99559ceda 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -43,6 +43,7 @@
 		1E44AF0D0B67CDE900BAD059 /* m_fixed.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AEFE0B67CDE900BAD059 /* m_fixed.c */; };
 		1E44AF0F0B67CDE900BAD059 /* m_menu.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF000B67CDE900BAD059 /* m_menu.c */; };
 		1E44AF110B67CDE900BAD059 /* m_misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF020B67CDE900BAD059 /* m_misc.c */; };
+		1E44AF110B67CDE900BAD059 /* apng.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF020B67CDE900BAD059 /* apng.c */; };
 		1E44AF130B67CDE900BAD059 /* m_random.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF040B67CDE900BAD059 /* m_random.c */; };
 		1E44AF1A0B67CE2A00BAD059 /* info.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF180B67CE2A00BAD059 /* info.c */; };
 		1E44AF1E0B67CE3600BAD059 /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = 1E44AF1C0B67CE3600BAD059 /* md5.c */; };
@@ -253,7 +254,9 @@
 		1E44AF000B67CDE900BAD059 /* m_menu.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = m_menu.c; path = ../../m_menu.c; sourceTree = SOURCE_ROOT; };
 		1E44AF010B67CDE900BAD059 /* m_menu.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = m_menu.h; path = ../../m_menu.h; sourceTree = SOURCE_ROOT; };
 		1E44AF020B67CDE900BAD059 /* m_misc.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = m_misc.c; path = ../../m_misc.c; sourceTree = SOURCE_ROOT; };
+		1E44AF020B67CDE900BAD059 /* apng.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = apng.c; path = ../../m_misc.c; sourceTree = SOURCE_ROOT; };
 		1E44AF030B67CDE900BAD059 /* m_misc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = m_misc.h; path = ../../m_misc.h; sourceTree = SOURCE_ROOT; };
+		1E44AF020B67CDE900BAD059 /* apng.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = apng.h; path = ../../m_misc.c; sourceTree = SOURCE_ROOT; };
 		1E44AF040B67CDE900BAD059 /* m_random.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = m_random.c; path = ../../m_random.c; sourceTree = SOURCE_ROOT; };
 		1E44AF050B67CDE900BAD059 /* m_random.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = m_random.h; path = ../../m_random.h; sourceTree = SOURCE_ROOT; };
 		1E44AF060B67CDE900BAD059 /* m_swap.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = m_swap.h; path = ../../m_swap.h; sourceTree = SOURCE_ROOT; };
@@ -679,6 +682,8 @@
 				1E44AEFF0B67CDE900BAD059 /* m_fixed.h */,
 				1E44AF020B67CDE900BAD059 /* m_misc.c */,
 				1E44AF030B67CDE900BAD059 /* m_misc.h */,
+				1E44AF020B67CDE900BAD059 /* apng.c */,
+				1E44AF030B67CDE900BAD059 /* apng.h */,
 				676BB51C0E0DE06100C95963 /* m_queue.c */,
 				676BB51D0E0DE06100C95963 /* m_queue.h */,
 				1E44AF040B67CDE900BAD059 /* m_random.c */,
@@ -1214,7 +1219,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.20;
+				CURRENT_PROJECT_VERSION = 2.1.24;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1231,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.20;
+				CURRENT_PROJECT_VERSION = 2.1.24;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 71832459182305a2bed1cf1e9e94446508fb8dd0..d94010d9add02dcc47693b9149f5b7fc177b148d 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -1,7 +1,16 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2014-2018 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
 /// \brief SDL Mixer interface for sound
 
 #include "../doomdef.h"
+#include "../doomstat.h" // menuactive
 
 #if defined(HAVE_SDL) && defined(HAVE_MIXER) && SOUND==SOUND_MIXER
 
@@ -20,7 +29,11 @@
 #pragma warning(default : 4214 4244)
 #endif
 
+#ifdef HAVE_MIXERX
+#include "SDL_mixer_ext.h"
+#else
 #include "SDL_mixer.h"
+#endif
 
 /* This is the version number macro for the current SDL_mixer version: */
 #ifndef SDL_MIXER_COMPILEDVERSION
@@ -34,14 +47,18 @@
 	(SDL_MIXER_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z))
 #endif
 
+// thanks alam for making the buildbots happy!
+#if SDL_MIXER_VERSION_ATLEAST(2,0,2)
+#define MUS_MP3_MAD MUS_MP3_MAD_UNUSED
+#define MUS_MODPLUG MUS_MODPLUG_UNUSED
+#endif
+
 #ifdef HAVE_LIBGME
 #include "gme/gme.h"
-#define GME_TREBLE 5.0
-#define GME_BASS 1.0
-#ifdef HAVE_PNG /// TODO: compile with zlib support without libpng
-
-#define HAVE_ZLIB
+#define GME_TREBLE 5.0f
+#define GME_BASS 1.0f
 
+#ifdef HAVE_ZLIB
 #ifndef _MSC_VER
 #ifndef _LARGEFILE64_SOURCE
 #define _LARGEFILE64_SOURCE
@@ -57,28 +74,171 @@
 #endif
 
 #include "zlib.h"
-#endif
+#endif // HAVE_ZLIB
+#endif // HAVE_LIBGME
+
+static UINT16 BUFFERSIZE = 2048;
+static UINT16 SAMPLERATE = 44100;
+
+#ifdef HAVE_OPENMPT
+#include "libopenmpt/libopenmpt.h"
 #endif
 
+/// ------------------------
+/// Audio Declarations
+/// ------------------------
+
 UINT8 sound_started = false;
 
-static boolean midimode;
 static Mix_Music *music;
-static UINT8 music_volume, midi_volume, sfx_volume;
+static UINT8 music_volume, sfx_volume, internal_volume;
 static float loop_point;
+static float song_length; // length in seconds
+static boolean songpaused;
+static UINT32 music_bytes;
+static boolean is_looping;
+
+// fading
+static boolean is_fading;
+static UINT8 fading_source;
+static UINT8 fading_target;
+static UINT32 fading_timer;
+static UINT32 fading_duration;
+static INT32 fading_id;
+static void (*fading_callback)(void);
+static boolean fading_nocleanup;
 
 #ifdef HAVE_LIBGME
 static Music_Emu *gme;
-static INT32 current_track;
+static UINT16 current_track;
+#endif
+
+#ifdef HAVE_OPENMPT
+static int mod_err = OPENMPT_ERROR_OK;
+static const char *mod_err_str;
+static UINT16 current_subsong;
+static size_t probesize;
+static int result;
+#endif
+
+#ifdef HAVE_MIXERX
+static void Midiplayer_Onchange(void)
+{
+	boolean restart = false;
+
+	if (I_SongType() != MU_NONE && I_SongType() != MU_MID_EX && I_SongType() != MU_MID)
+		return;
+
+	if (Mix_GetMidiPlayer() != cv_midiplayer.value)
+	{
+		if (Mix_SetMidiPlayer(cv_midiplayer.value)) // <> 0 means error
+			CONS_Alert(CONS_ERROR, "Midi player error: %s", Mix_GetError());
+		else
+			restart = true;
+	}
+
+	if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
+	{
+		if (!Mix_SetSoundFonts(cv_midisoundfontpath.string)) // == 0 means error
+			CONS_Alert(CONS_ERROR, "Sound font error: %s", Mix_GetError());
+		else
+			restart = true;
+	}
+
+	Mix_Timidity_addToPathList(cv_miditimiditypath.string);
+
+	if (restart)
+		S_StartEx(true);
+}
+
+static void MidiSoundfontPath_Onchange(void)
+{
+	if (Mix_GetMidiPlayer() != MIDI_Fluidsynth || (I_SongType() != MU_NONE && I_SongType() != MU_MID_EX))
+		return;
+
+	if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
+	{
+		char *miditoken;
+		char *source = strdup(cv_midisoundfontpath.string);
+		boolean proceed = true;
+		// check if file exists; menu calls this method at every keystroke
+
+		// get first token
+		miditoken = strtok(source, ";");
+
+		while (miditoken != NULL)
+		{
+			SDL_RWops *rw = SDL_RWFromFile(miditoken, "r");
+			if (rw != NULL)
+				SDL_RWclose(rw);
+			else
+			{
+				proceed = false;
+				break;
+			}
+			miditoken = strtok(NULL, ";");
+		}
+
+		free(source);
+
+		if (proceed)
+		{
+			if (!Mix_SetSoundFonts(cv_midisoundfontpath.string))
+				CONS_Alert(CONS_ERROR, "Sound font error: %s", Mix_GetError());
+			else
+				S_StartEx(true);
+		}
+	}
+}
+
+// make sure that s_sound.c does not already verify these
+// which happens when: defined(HAVE_MIXERX) && !defined(HAVE_MIXER)
+static CV_PossibleValue_t midiplayer_cons_t[] = {{MIDI_OPNMIDI, "OPNMIDI"}, {MIDI_Fluidsynth, "Fluidsynth"}, {MIDI_Timidity, "Timidity"}, {MIDI_Native, "Native"}, {0, NULL}};
+consvar_t cv_midiplayer = {"midiplayer", "OPNMIDI" /*MIDI_OPNMIDI*/, CV_CALL|CV_NOINIT|CV_SAVE, midiplayer_cons_t, Midiplayer_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_midisoundfontpath = {"midisoundfont", "sf2/8bitsf.SF2", CV_CALL|CV_NOINIT|CV_SAVE, NULL, MidiSoundfontPath_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_miditimiditypath = {"midisoundbank", "./timidity", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
+static void var_cleanup(void)
+{
+	song_length = loop_point = 0.0f;
+	music_bytes = fading_source = fading_target =\
+	 fading_timer = fading_duration = 0;
+
+	songpaused = is_looping =\
+	 is_fading = false;
+
+	// 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;
+	else
+		fading_nocleanup = false; // use it once, set it back immediately
+
+	internal_volume = 100;
+}
+/// ------------------------
+/// Audio System
+/// ------------------------
+
 void I_StartupSound(void)
 {
-	I_Assert(!sound_started);
+	//I_Assert(!sound_started);
+	if (sound_started)
+		return;
+
+#ifdef _WIN32
+	// Force DirectSound instead of WASAPI
+	// SDL 2.0.6+ defaults to the latter and it screws up our sound effects
+	SDL_setenv("SDL_AUDIODRIVER", "directsound", 1);
+#endif
 
 	// EE inits audio first so we're following along.
 	if (SDL_WasInit(SDL_INIT_AUDIO) == SDL_INIT_AUDIO)
-		CONS_Printf("SDL Audio already started\n");
+	{
+		CONS_Debug(DBG_DETAILED, "SDL Audio already started\n");
+		return;
+	}
 	else if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
 	{
 		CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
@@ -86,22 +246,36 @@ void I_StartupSound(void)
 		return;
 	}
 
-	midimode = false;
+	fading_nocleanup = false;
+
+	var_cleanup();
+
 	music = NULL;
-	music_volume = midi_volume = sfx_volume = 0;
+	music_volume = sfx_volume = 0;
 
+#ifdef HAVE_MIXERX
+	Mix_SetMidiPlayer(cv_midiplayer.value);
+	Mix_SetSoundFonts(cv_midisoundfontpath.string);
+	Mix_Timidity_addToPathList(cv_miditimiditypath.string);
+#endif
 #if SDL_MIXER_VERSION_ATLEAST(1,2,11)
-	Mix_Init(MIX_INIT_FLAC|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG);
+	Mix_Init(MIX_INIT_FLAC|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_MOD);
 #endif
 
-	if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 2048) < 0)
+	if (Mix_OpenAudio(SAMPLERATE, AUDIO_S16SYS, 2, BUFFERSIZE) < 0)
 	{
 		CONS_Alert(CONS_ERROR, "Error starting SDL_Mixer: %s\n", Mix_GetError());
 		// call to start audio failed -- we do not have it
 		return;
 	}
 
+#ifdef HAVE_OPENMPT
+	CONS_Printf("libopenmpt version: %s\n", openmpt_get_string("library_version"));
+	CONS_Printf("libopenmpt build date: %s\n", openmpt_get_string("build"));
+#endif
+
 	sound_started = true;
+	songpaused = false;
 	Mix_AllocateChannels(256);
 }
 
@@ -122,12 +296,20 @@ void I_ShutdownSound(void)
 	if (gme)
 		gme_delete(gme);
 #endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+		openmpt_module_destroy(openmpt_mhandle);
+#endif
 }
 
-FUNCMATH void I_UpdateSound(void)
+void I_UpdateSound(void)
 {
 }
 
+/// ------------------------
+/// SFX
+/// ------------------------
+
 // this is as fast as I can possibly make it.
 // sorry. more asm needed.
 static Mix_Chunk *ds2chunk(void *stream)
@@ -244,6 +426,7 @@ void *I_GetSfx(sfxinfo_t *sfx)
 {
 	void *lump;
 	Mix_Chunk *chunk;
+	SDL_RWops *rw;
 #ifdef HAVE_LIBGME
 	Music_Emu *emu;
 	gme_info_t *info;
@@ -304,8 +487,9 @@ void *I_GetSfx(sfxinfo_t *sfx)
 					gme_track_info(emu, &info, 0);
 
 					len = (info->play_length * 441 / 10) << 2;
-					mem = Z_Malloc(len, PU_SOUND, NULL);
+					mem = Z_Malloc(len, PU_SOUND, 0);
 					gme_play(emu, len >> 1, mem);
+					gme_free_info(info);
 					gme_delete(emu);
 
 					return Mix_QuickLoad_RAW((Uint8 *)mem, len);
@@ -359,7 +543,7 @@ void *I_GetSfx(sfxinfo_t *sfx)
 		}
 		Z_Free(inflatedData); // GME didn't open jack, but don't let that stop us from freeing this up
 #else
-		//CONS_Alert(CONS_ERROR,"Cannot decompress VGZ; no zlib support\n");
+		return NULL; // No zlib support
 #endif
 	}
 	// Try to read it as a GME sound
@@ -376,8 +560,9 @@ void *I_GetSfx(sfxinfo_t *sfx)
 		gme_track_info(emu, &info, 0);
 
 		len = (info->play_length * 441 / 10) << 2;
-		mem = Z_Malloc(len, PU_SOUND, NULL);
+		mem = Z_Malloc(len, PU_SOUND, 0);
 		gme_play(emu, len >> 1, mem);
+		gme_free_info(info);
 		gme_delete(emu);
 
 		return Mix_QuickLoad_RAW((Uint8 *)mem, len);
@@ -385,21 +570,43 @@ void *I_GetSfx(sfxinfo_t *sfx)
 #endif
 
 	// Try to load it as a WAVE or OGG using Mixer.
-	return Mix_LoadWAV_RW(SDL_RWFromMem(lump, sfx->length), 1);
+	rw = SDL_RWFromMem(lump, sfx->length);
+	if (rw != NULL)
+	{
+		chunk = Mix_LoadWAV_RW(rw, 1);
+		return chunk;
+	}
+
+	return NULL; // haven't been able to get anything
 }
 
 void I_FreeSfx(sfxinfo_t *sfx)
 {
 	if (sfx->data)
+	{
+		Mix_Chunk *chunk = (Mix_Chunk*)sfx->data;
+		UINT8 *abufdata = NULL;
+		if (chunk->allocated == 0)
+		{
+			// We allocated the data in this chunk, so get the abuf from mixer, then let it free the chunk, THEN we free the data
+			// I believe this should ensure the sound is not playing when we free it
+			abufdata = chunk->abuf;
+		}
 		Mix_FreeChunk(sfx->data);
+		if (abufdata)
+		{
+			// I'm going to assume we used Z_Malloc to allocate this data.
+			Z_Free(abufdata);
+		}
+	}
 	sfx->data = NULL;
 	sfx->lumpnum = LUMPERROR;
 }
 
-INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority)
+INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
 {
 	UINT8 volume = (((UINT16)vol + 1) * (UINT16)sfx_volume) / 62; // (256 * 31) / 62 == 127
-	INT32 handle = Mix_PlayChannel(-1, S_sfx[id].data, 0);
+	INT32 handle = Mix_PlayChannel(channel, S_sfx[id].data, 0);
 	Mix_Volume(handle, volume);
 	Mix_SetPanning(handle, min((UINT16)(0xff-sep)<<1, 0xff), min((UINT16)(sep)<<1, 0xff));
 	(void)pitch; // Mixer can't handle pitch
@@ -430,15 +637,110 @@ void I_SetSfxVolume(UINT8 volume)
 	sfx_volume = volume;
 }
 
-//
-// Music
-//
+/// ------------------------
+/// Music Utilities
+/// ------------------------
+
+static UINT32 get_real_volume(UINT8 volume)
+{
+#ifdef _WIN32
+	if (I_SongType() == MU_MID)
+		// HACK: Until we stop using native MIDI,
+		// disable volume changes
+		return ((UINT32)31*128/31); // volume = 31
+	else
+#endif
+		// convert volume to mixer's 128 scale
+		// then apply internal_volume as a percentage
+		return ((UINT32)volume*128/31) * (UINT32)internal_volume / 100;
+}
+
+static UINT32 get_adjusted_position(UINT32 position)
+{
+	// all in milliseconds
+	UINT32 length = I_GetSongLength();
+	UINT32 looppoint = I_GetSongLoopPoint();
+	if (length)
+		return position >= length ? (position % (length-looppoint)) : position;
+	else
+		return position;
+}
+
+static void do_fading_callback(void)
+{
+	if (fading_callback)
+		(*fading_callback)();
+	fading_callback = NULL;
+}
+
+/// ------------------------
+/// Music Hooks
+/// ------------------------
+
+static void count_music_bytes(int chan, void *stream, int len, void *udata)
+{
+	(void)chan;
+	(void)stream;
+	(void)udata;
+
+	if (!music || I_SongType() == MU_GME || I_SongType() == MU_MOD || I_SongType() == MU_MID)
+		return;
+	music_bytes += len;
+}
 
-// Music hooks
 static void music_loop(void)
 {
-	Mix_PlayMusic(music, 0);
-	Mix_SetMusicPosition(loop_point);
+	if (is_looping)
+	{
+		Mix_PlayMusic(music, 0);
+		Mix_SetMusicPosition(loop_point);
+		music_bytes = (UINT32)(loop_point*44100.0L*4); //assume 44.1khz, 4-byte length (see I_GetSongPosition)
+	}
+	else
+	{
+		// HACK: Let fade timing proceed beyond the end of a
+		// non-looping song. This is a specific case where the timing
+		// should persist after stopping a song, so I don't believe
+		// this should apply every time the user stops a song.
+		// This is auto-unset in var_cleanup, called by I_StopSong
+		fading_nocleanup = true;
+		I_StopSong();
+	}
+}
+
+static UINT32 music_fade(UINT32 interval, void *param)
+{
+	(void)param;
+
+	if (!is_fading ||
+		internal_volume == fading_target ||
+		fading_duration == 0)
+	{
+		I_StopFadingSong();
+		do_fading_callback();
+		return 0;
+	}
+	else if (songpaused) // don't decrement timer
+		return interval;
+	else if ((fading_timer -= 10) <= 0)
+	{
+		internal_volume = fading_target;
+		Mix_VolumeMusic(get_real_volume(music_volume));
+		I_StopFadingSong();
+		do_fading_callback();
+		return 0;
+	}
+	else
+	{
+		UINT8 delta = abs(fading_target - fading_source);
+		fixed_t factor = FixedDiv(fading_duration - fading_timer, fading_duration);
+		if (fading_target < fading_source)
+			internal_volume = max(min(internal_volume, fading_source - FixedMul(delta, factor)), fading_target);
+		else if (fading_target > fading_source)
+			internal_volume = min(max(internal_volume, fading_source + FixedMul(delta, factor)), fading_target);
+		Mix_VolumeMusic(get_real_volume(music_volume));
+		return interval;
+	}
 }
 
 #ifdef HAVE_LIBGME
@@ -450,88 +752,394 @@ static void mix_gme(void *udata, Uint8 *stream, int len)
 	(void)udata;
 
 	// no gme? no music.
-	if (!gme || gme_track_ended(gme))
+	if (!gme || gme_track_ended(gme) || songpaused)
 		return;
 
 	// play gme into stream
 	gme_play(gme, len/2, (short *)stream);
 
+	// Limiter to prevent music from being disorted with some formats
+	if (music_volume >= 18)
+		music_volume = 18;
+
+	// apply volume to stream
+	for (i = 0, p = (short *)stream; i < len/2; i++, p++)
+		*p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
+}
+#endif
+
+#ifdef HAVE_OPENMPT
+static void mix_openmpt(void *udata, Uint8 *stream, int len)
+{
+	int i;
+	short *p;
+
+	(void)udata;
+
+	if (!openmpt_mhandle || songpaused)
+		return;
+
+	// Play module into stream
+	openmpt_module_read_interleaved_stereo(openmpt_mhandle, SAMPLERATE, BUFFERSIZE, (short *)stream);
+
+	// Limiter to prevent music from being disorted with some formats
+	if (music_volume >= 18)
+		music_volume = 18;
+
 	// apply volume to stream
 	for (i = 0, p = (short *)stream; i < len/2; i++, p++)
-		*p = ((INT32)*p) * music_volume / 31;
+		*p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
 }
 #endif
 
-FUNCMATH void I_InitMusic(void)
+/// ------------------------
+/// Music System
+/// ------------------------
+
+void I_InitMusic(void)
 {
 }
 
 void I_ShutdownMusic(void)
 {
-	I_ShutdownDigMusic();
-	I_ShutdownMIDIMusic();
+	I_UnloadSong();
 }
 
-void I_PauseSong(INT32 handle)
+/// ------------------------
+/// Music Properties
+/// ------------------------
+
+musictype_t I_SongType(void)
 {
-	(void)handle;
-	Mix_PauseMusic();
+#ifdef HAVE_LIBGME
+	if (gme)
+		return MU_GME;
+	else
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+		return MU_MOD_EX;
+#endif
+	if (!music)
+		return MU_NONE;
+	else if (Mix_GetMusicType(music) == MUS_MID)
+	{
+#ifdef HAVE_MIXERX
+		if (Mix_GetMidiPlayer() != MIDI_Native)
+			return MU_MID_EX;
+		else
+#endif
+		return MU_MID;
+	}
+	else if (Mix_GetMusicType(music) == MUS_MOD || Mix_GetMusicType(music) == MUS_MODPLUG)
+		return MU_MOD;
+	else if (Mix_GetMusicType(music) == MUS_MP3 || Mix_GetMusicType(music) == MUS_MP3_MAD)
+		return MU_MP3;
+	else
+		return (musictype_t)Mix_GetMusicType(music);
 }
 
-void I_ResumeSong(INT32 handle)
+boolean I_SongPlaying(void)
 {
-	(void)handle;
-	Mix_ResumeMusic();
+	return (
+#ifdef HAVE_LIBGME
+		(I_SongType() == MU_GME && gme) ||
+#endif
+#ifdef HAVE_OPENMPT
+		(I_SongType() == MU_MOD_EX && openmpt_mhandle) ||
+#endif
+		music != NULL
+	);
 }
 
-//
-// Digital Music
-//
+boolean I_SongPaused(void)
+{
+	return songpaused;
+}
+
+/// ------------------------
+/// Music Effects
+/// ------------------------
 
-void I_InitDigMusic(void)
+boolean I_SetSongSpeed(float speed)
 {
+	if (speed > 250.0f)
+		speed = 250.0f; //limit speed up to 250x
 #ifdef HAVE_LIBGME
-	gme = NULL;
-	current_track = -1;
+	if (gme)
+	{
+		SDL_LockAudio();
+		gme_set_tempo(gme, speed);
+		SDL_UnlockAudio();
+		return true;
+	}
+	else
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		char modspd[16];
+		sprintf(modspd, "%g", speed);
+		openmpt_module_ctl_set(openmpt_mhandle, "play.tempo_factor", modspd);
+		return true;
+	}
+#else
+	(void)speed;
+	return false;
 #endif
+	return false;
 }
 
-void I_ShutdownDigMusic(void)
+/// ------------------------
+///  MUSIC SEEKING
+/// ------------------------
+
+UINT32 I_GetSongLength(void)
 {
-	if (midimode)
-		return;
+	INT32 length;
+
 #ifdef HAVE_LIBGME
 	if (gme)
 	{
-		Mix_HookMusic(NULL, NULL);
-		gme_delete(gme);
-		gme = NULL;
+		gme_info_t *info;
+		gme_err_t gme_e = gme_track_info(gme, &info, current_track);
+
+		if (gme_e != NULL)
+		{
+			CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
+			length = 0;
+		}
+		else
+		{
+			// reconstruct info->play_length, from GME source
+			// we only want intro + 1 loop, not 2
+			length = info->length;
+			if (length <= 0)
+			{
+				length = info->intro_length + info->loop_length; // intro + 1 loop
+				if (length <= 0)
+					length = 150 * 1000; // 2.5 minutes
+			}
+		}
+
+		gme_free_info(info);
+		return max(length, 0);
 	}
+	else
 #endif
-	if (!music)
-		return;
-	Mix_HookMusicFinished(NULL);
-	Mix_FreeMusic(music);
-	music = NULL;
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+		return (UINT32)(openmpt_module_get_duration_seconds(openmpt_mhandle) * 1000.);
+	else
+#endif
+	if (!music || I_SongType() == MU_MOD || I_SongType() == MU_MID)
+		return 0;
+	else
+	{
+#ifdef HAVE_MIXERX
+		double xlength = Mix_GetMusicTotalTime(music);
+		if (xlength >= 0)
+			return (UINT32)(xlength*1000);
+#endif
+		// VERY IMPORTANT to set your LENGTHMS= in your song files, folks!
+		// SDL mixer can't read music length itself.
+		length = (UINT32)(song_length*1000);
+		if (!length)
+			CONS_Debug(DBG_DETAILED, "Getting music length: music is missing LENGTHMS= tag. Needed for seeking.\n");
+		return length;
+	}
 }
 
-boolean I_StartDigSong(const char *musicname, boolean looping)
+boolean I_SetSongLoopPoint(UINT32 looppoint)
 {
-	char *data;
-	size_t len;
-	lumpnum_t lumpnum = W_CheckNumForName(va("O_%s",musicname));
+	if (!music || I_SongType() == MU_GME || I_SongType() == MU_MOD || I_SongType() == MU_MID || !is_looping)
+		return false;
+	else
+	{
+		UINT32 length = I_GetSongLength();
 
-	I_Assert(!music);
+		if (length > 0)
+			looppoint %= length;
+
+		loop_point = max((float)(looppoint / 1000.0L), 0);
+		return true;
+	}
+}
+
+UINT32 I_GetSongLoopPoint(void)
+{
 #ifdef HAVE_LIBGME
-	I_Assert(!gme);
+	if (gme)
+	{
+		INT32 looppoint;
+		gme_info_t *info;
+		gme_err_t gme_e = gme_track_info(gme, &info, current_track);
+
+		if (gme_e != NULL)
+		{
+			CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
+			looppoint = 0;
+		}
+		else
+			looppoint = info->intro_length > 0 ? info->intro_length : 0;
+
+		gme_free_info(info);
+		return max(looppoint, 0);
+	}
+	else
 #endif
+	if (!music || I_SongType() == MU_MOD || I_SongType() == MU_MID)
+		return 0;
+	else
+		return (UINT32)(loop_point * 1000);
+}
 
-	if (lumpnum == LUMPERROR)
+boolean I_SetSongPosition(UINT32 position)
+{
+	UINT32 length;
+#ifdef HAVE_LIBGME
+	if (gme)
+	{
+		// this is unstable, so fail silently
+		return true;
+		// this isn't required technically, but GME thread-locks for a second
+		// if you seek too high from the counter
+		// length = I_GetSongLength();
+		// if (length)
+		// 	position = get_adjusted_position(position);
+
+		// SDL_LockAudio();
+		// gme_err_t gme_e = gme_seek(gme, position);
+		// SDL_UnlockAudio();
+
+		// if (gme_e != NULL)
+		// {
+		// 	CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
+		// 	return false;
+		// }
+		// else
+		// 	return true;
+	}
+	else
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		// This isn't 100% correct because we don't account for loop points because we can't get them.
+		// But if you seek past end of song, OpenMPT seeks to 0. So adjust the position anyway.
+		openmpt_module_set_position_seconds(openmpt_mhandle, (double)(get_adjusted_position(position)/1000.0L)); // returns new position
+		return true;
+	}
+	else
+#endif
+	if (!music || I_SongType() == MU_MID)
 		return false;
-	midimode = false;
+	else if (I_SongType() == MU_MOD)
+		return Mix_SetMusicPosition(position); // Goes by channels
+	else
+	{
+		// Because SDL mixer can't identify song length, if you have
+		// a position input greater than the real length, then
+		// music_bytes becomes inaccurate.
+
+		length = I_GetSongLength(); // get it in MS
+		if (length)
+			position = get_adjusted_position(position);
+
+		Mix_RewindMusic(); // needed for mp3
+		if(Mix_SetMusicPosition((float)(position/1000.0L)) == 0)
+			music_bytes = (UINT32)(position/1000.0L*44100.0L*4); //assume 44.1khz, 4-byte length (see I_GetSongPosition)
+		else
+			// NOTE: This block fires on incorrect song format,
+			// NOT if position input is greater than song length.
+			music_bytes = 0;
+
+		return true;
+	}
+}
+
+UINT32 I_GetSongPosition(void)
+{
+#ifdef HAVE_LIBGME
+	if (gme)
+	{
+		INT32 position = gme_tell(gme);
+
+		gme_info_t *info;
+		gme_err_t gme_e = gme_track_info(gme, &info, current_track);
+
+		if (gme_e != NULL)
+		{
+			CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
+			return position;
+		}
+		else
+		{
+			// adjust position, since GME's counter keeps going past loop
+			if (info->length > 0)
+				position %= info->length;
+			else if (info->intro_length + info->loop_length > 0)
+				position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position;
+			else
+				position %= 150 * 1000; // 2.5 minutes
+		}
+
+		gme_free_info(info);
+		return max(position, 0);
+	}
+	else
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+		// This will be incorrect if we adjust for length because we can't get loop points.
+		// So return unadjusted. See note in SetMusicPosition: we adjust for that.
+		return (UINT32)(openmpt_module_get_position_seconds(openmpt_mhandle)*1000.);
+		//return get_adjusted_position((UINT32)(openmpt_module_get_position_seconds(openmpt_mhandle)*1000.));
+	else
+#endif
+	if (!music || I_SongType() == MU_MID)
+		return 0;
+	else
+	{
+#ifdef HAVE_MIXERX
+		double xposition = Mix_GetMusicPosition(music);
+		if (xposition >= 0)
+			return (UINT32)(xposition*1000);
+#endif
+		return (UINT32)(music_bytes/44100.0L*1000.0L/4); //assume 44.1khz
+		// 4 = byte length for 16-bit samples (AUDIO_S16SYS), stereo (2-channel)
+		// This is hardcoded in I_StartupSound. Other formats for factor:
+		// 8M: 1 | 8S: 2 | 16M: 2 | 16S: 4
+	}
+}
 
-	data = (char *)W_CacheLumpNum(lumpnum, PU_MUSIC);
-	len = W_LumpLength(lumpnum);
+/// ------------------------
+/// Music Playback
+/// ------------------------
+
+boolean I_LoadSong(char *data, size_t len)
+{
+	const char *key1 = "LOOP";
+	const char *key2 = "POINT=";
+	const char *key3 = "MS=";
+	const size_t key1len = strlen(key1);
+	const size_t key2len = strlen(key2);
+	const size_t key3len = strlen(key3);
+	char *p = data;
+	SDL_RWops *rw;
+
+	if (music
+#ifdef HAVE_LIBGME
+		|| gme
+#endif
+#ifdef HAVE_OPENMPT
+		|| openmpt_mhandle
+#endif
+	)
+		I_UnloadSong();
+
+	// always do this whether or not a music already exists
+	var_cleanup();
 
 #ifdef HAVE_LIBGME
 	if ((UINT8)data[0] == 0x1F
@@ -617,43 +1225,75 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 		}
 		Z_Free(inflatedData); // GME didn't open jack, but don't let that stop us from freeing this up
 #else
-		//CONS_Alert(CONS_ERROR,"Cannot decompress VGZ; no zlib support\n");
+		CONS_Alert(CONS_ERROR,"Cannot decompress VGZ; no zlib support\n");
+		return true;
 #endif
 	}
 	else if (!gme_open_data(data, len, &gme, 44100))
 	{
 		gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
-		gme_start_track(gme, 0);
-		current_track = 0;
 		gme_set_equalizer(gme, &eq);
-		Mix_HookMusic(mix_gme, gme);
 		return true;
 	}
 #endif
 
-	music = Mix_LoadMUS_RW(SDL_RWFromMem(data, len), SDL_FALSE);
+#ifdef HAVE_MIXERX
+	if (Mix_GetMidiPlayer() != cv_midiplayer.value)
+		Mix_SetMidiPlayer(cv_midiplayer.value);
+	if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
+		Mix_SetSoundFonts(cv_midisoundfontpath.string);
+	Mix_Timidity_addToPathList(cv_miditimiditypath.string); // this overwrites previous custom path
+#endif
+
+#ifdef HAVE_OPENMPT
+	/*
+		If the size of the data to be checked is bigger than the recommended size (> 2048)
+		Let's just set the probe size to the recommended size
+		Otherwise let's give it the full data size
+	*/
+
+	if (len > openmpt_probe_file_header_get_recommended_size())
+		probesize = openmpt_probe_file_header_get_recommended_size();
+	else
+		probesize = len;
+
+	result = openmpt_probe_file_header(OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT, data, probesize, len, NULL, NULL, NULL, NULL, NULL, NULL);
+
+	if (result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS) // We only cared if it succeeded, continue on if not.
+	{
+		openmpt_mhandle = openmpt_module_create_from_memory2(data, len, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+		if (!openmpt_mhandle) // Failed to create module handle? Show error and return!
+		{
+			mod_err = openmpt_module_error_get_last(openmpt_mhandle);
+			mod_err_str = openmpt_error_string(mod_err);
+			CONS_Alert(CONS_ERROR, "openmpt_module_create_from_memory2: %s\n", mod_err_str);
+			return false;
+		}
+		else
+			return true; // All good and we're ready for music playback!
+	}
+#endif
+
+	// Let's see if Mixer is able to load this.
+	rw = SDL_RWFromMem(data, len);
+	{
+		music = Mix_LoadMUS_RW(rw, 1);
+	}
 	if (!music)
 	{
 		CONS_Alert(CONS_ERROR, "Mix_LoadMUS_RW: %s\n", Mix_GetError());
-		return true;
+		return false;
 	}
 
 	// Find the OGG loop point.
 	loop_point = 0.0f;
-	if (looping)
-	{
-		const char *key1 = "LOOP";
-		const char *key2 = "POINT=";
-		const char *key3 = "MS=";
-		const size_t key1len = strlen(key1);
-		const size_t key2len = strlen(key2);
-		const size_t key3len = strlen(key3);
-		char *p = data;
-		while ((UINT32)(p - data) < len)
+	song_length = 0.0f;
+
+	while ((UINT32)(p - data) < len)
+	{
+		if (fpclassify(loop_point) == FP_ZERO && !strncmp(p, key1, key1len))
 		{
-			if (strncmp(p++, key1, key1len))
-				continue;
-			p += key1len-1; // skip OOP (the L was skipped in strncmp)
+			p += key1len; // skip LOOP
 			if (!strncmp(p, key2, key2len)) // is it LOOPPOINT=?
 			{
 				p += key2len; // skip POINT=
@@ -669,81 +1309,184 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 				loop_point = (float)(atoi(p) / 1000.0L); // LOOPMS works by real time, as miliseconds.
 				// Everything that uses LOOPMS will work perfectly with SDL_Mixer.
 			}
-			// Neither?! Continue searching.
 		}
+
+		if (fpclassify(loop_point) != FP_ZERO) // Got what we needed
+			break;
+		else // continue searching
+			p++;
 	}
+	return true;
+}
+
+void I_UnloadSong(void)
+{
+	I_StopSong();
 
-	if (Mix_PlayMusic(music, looping && loop_point == 0.0f ? -1 : 0) == -1)
+#ifdef HAVE_LIBGME
+	if (gme)
 	{
-		CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
+		gme_delete(gme);
+		gme = NULL;
+	}
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		openmpt_module_destroy(openmpt_mhandle);
+		openmpt_mhandle = NULL;
+	}
+#endif
+	if (music)
+	{
+		Mix_FreeMusic(music);
+		music = NULL;
+	}
+}
+
+boolean I_PlaySong(boolean looping)
+{
+#ifdef HAVE_LIBGME
+	if (gme)
+	{
+		gme_start_track(gme, 0);
+		current_track = 0;
+		Mix_HookMusic(mix_gme, gme);
+		return true;
+	}
+	else
+#endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		openmpt_module_select_subsong(openmpt_mhandle, 0);
+		openmpt_module_set_render_param(openmpt_mhandle, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, cv_modfilter.value);
+		if (looping)
+			openmpt_module_set_repeat_count(openmpt_mhandle, -1); // Always repeat
+		current_subsong = 0;
+		Mix_HookMusic(mix_openmpt, openmpt_mhandle);
 		return true;
 	}
-	Mix_VolumeMusic((UINT32)music_volume*128/31);
+	else
+#endif
+	if (!music)
+		return false;
+
+	if (fpclassify(song_length) == FP_ZERO && (I_SongType() == MU_OGG || I_SongType() == MU_MP3 || I_SongType() == MU_FLAC))
+		CONS_Debug(DBG_DETAILED, "This song is missing a LENGTHMS= tag! Required to make seeking work properly.\n");
+
+	if (I_SongType() != MU_MOD && I_SongType() != MU_MID && Mix_PlayMusic(music, 0) == -1)
+	{
+		CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
+		return false;
+	}
+	else if ((I_SongType() == MU_MOD || I_SongType() == MU_MID || I_SongType() == MU_MID_EX) && Mix_PlayMusic(music, looping ? -1 : 0) == -1) // if MOD, loop forever
+	{
+		CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
+		return false;
+	}
+
+	is_looping = looping;
+
+	I_SetMusicVolume(music_volume);
+
+	if (I_SongType() != MU_MOD && I_SongType() != MU_MID && I_SongType() != MU_MID_EX)
+		Mix_HookMusicFinished(music_loop); // don't bother counting if MOD
+
+	if(I_SongType() != MU_MOD && I_SongType() != MU_MID && I_SongType() != MU_MID_EX && !Mix_RegisterEffect(MIX_CHANNEL_POST, count_music_bytes, NULL, NULL))
+		CONS_Alert(CONS_WARNING, "Error registering SDL music position counter: %s\n", Mix_GetError());
 
-	if (loop_point != 0.0f)
-		Mix_HookMusicFinished(music_loop);
 	return true;
 }
 
-void I_StopDigSong(void)
+void I_StopSong(void)
 {
-	if (midimode)
-		return;
+	// HACK: See music_loop on why we want fade timing to proceed
+	// after end of song
+	if (!fading_nocleanup)
+		I_StopFadingSong();
+
 #ifdef HAVE_LIBGME
 	if (gme)
 	{
 		Mix_HookMusic(NULL, NULL);
-		gme_delete(gme);
-		gme = NULL;
 		current_track = -1;
-		return;
 	}
 #endif
-	if (!music)
-		return;
-	Mix_HookMusicFinished(NULL);
-	Mix_FreeMusic(music);
-	music = NULL;
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		Mix_HookMusic(NULL, NULL);
+		current_subsong = -1;
+	}
+#endif
+	if (music)
+	{
+		Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes);
+		Mix_HookMusicFinished(NULL);
+		Mix_HaltMusic();
+	}
+
+	var_cleanup();
 }
 
-void I_SetDigMusicVolume(UINT8 volume)
+void I_PauseSong(void)
 {
-	music_volume = volume;
-	if (midimode || !music)
+	if(I_SongType() == MU_MID) // really, SDL Mixer? why can't you pause MIDI???
 		return;
-	Mix_VolumeMusic((UINT32)volume*128/31);
+
+	if(I_SongType() != MU_GME && I_SongType() != MU_MOD && I_SongType() != MU_MID)
+		Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes);
+
+	Mix_PauseMusic();
+	songpaused = true;
 }
 
-boolean I_SetSongSpeed(float speed)
+void I_ResumeSong(void)
 {
-	if (speed > 250.0f)
-		speed = 250.0f; //limit speed up to 250x
-#ifdef HAVE_LIBGME
-	if (gme)
+	if (I_SongType() == MU_MID)
+		return;
+
+	if (I_SongType() != MU_GME && I_SongType() != MU_MOD && I_SongType() != MU_MID)
 	{
-		SDL_LockAudio();
-		gme_set_tempo(gme, speed);
-		SDL_UnlockAudio();
-		return true;
+		while(Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes) != 0) { }
+			// HACK: fixes issue of multiple effect callbacks being registered
+
+		if(music && I_SongType() != MU_MOD && I_SongType() != MU_MID && !Mix_RegisterEffect(MIX_CHANNEL_POST, count_music_bytes, NULL, NULL))
+			CONS_Alert(CONS_WARNING, "Error registering SDL music position counter: %s\n", Mix_GetError());
 	}
-#else
-	(void)speed;
+
+	Mix_ResumeMusic();
+	songpaused = false;
+}
+
+void I_SetMusicVolume(UINT8 volume)
+{
+	if (!I_SongPlaying())
+		return;
+
+#ifdef _WIN32
+	if (I_SongType() == MU_MID)
+		// HACK: Until we stop using native MIDI,
+		// disable volume changes
+		music_volume = 31;
+	else
 #endif
-	return false;
+		music_volume = volume;
+
+	Mix_VolumeMusic(get_real_volume(music_volume));
 }
 
 boolean I_SetSongTrack(int track)
 {
 #ifdef HAVE_LIBGME
-	if (current_track == track)
-		return false;
-
 	// If the specified track is within the number of tracks playing, then change it
 	if (gme)
 	{
+		if (current_track == track)
+			return false;
 		SDL_LockAudio();
-		if (track >= 0
-			&& track < gme_track_count(gme))
+		if (track >= 0 && track < gme_track_count(gme)-1)
 		{
 			gme_err_t gme_e = gme_start_track(gme, track);
 			if (gme_e != NULL)
@@ -758,84 +1501,120 @@ boolean I_SetSongTrack(int track)
 		SDL_UnlockAudio();
 		return false;
 	}
+	else
 #endif
+#ifdef HAVE_OPENMPT
+	if (openmpt_mhandle)
+	{
+		if (current_subsong == track)
+			return false;
+		SDL_LockAudio();
+		if (track >= 0 && track < openmpt_module_get_num_subsongs(openmpt_mhandle))
+		{
+			openmpt_module_select_subsong(openmpt_mhandle, track);
+			current_subsong = track;
+			SDL_UnlockAudio();
+			return true;
+		}
+		SDL_UnlockAudio();
+
+		return false;
+	}
+#endif
+	if (I_SongType() == MU_MOD)
+		return !Mix_SetMusicPosition(track);
 	(void)track;
 	return false;
 }
 
-//
-// MIDI Music
-//
+/// ------------------------
+/// MUSIC FADING
+/// ------------------------
 
-FUNCMATH void I_InitMIDIMusic(void)
+void I_SetInternalMusicVolume(UINT8 volume)
 {
+	internal_volume = volume;
+	if (!I_SongPlaying())
+		return;
+	Mix_VolumeMusic(get_real_volume(music_volume));
 }
 
-void I_ShutdownMIDIMusic(void)
+void I_StopFadingSong(void)
 {
-	if (!midimode || !music)
-		return;
-	Mix_FreeMusic(music);
-	music = NULL;
+	if (fading_id)
+		SDL_RemoveTimer(fading_id);
+	is_fading = false;
+	fading_source = fading_target = fading_timer = fading_duration = fading_id = 0;
+	// don't unset fading_nocleanup here just yet; fading_callback is cleaned up
+	// in var_cleanup()
 }
 
-void I_SetMIDIMusicVolume(UINT8 volume)
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
 {
-	// HACK: Until we stop using native MIDI,
-	// disable volume changes
-	(void)volume;
-	midi_volume = 31;
-	//midi_volume = volume;
+	INT16 volume_delta;
 
-	if (!midimode || !music)
-		return;
-	Mix_VolumeMusic((UINT32)midi_volume*128/31);
-}
+	source_volume = min(source_volume, 100);
+	volume_delta = (INT16)(target_volume - source_volume);
 
-INT32 I_RegisterSong(void *data, size_t len)
-{
-	music = Mix_LoadMUS_RW(SDL_RWFromMem(data, len), SDL_FALSE);
-	if (!music)
+	I_StopFadingSong();
+
+	if (!ms && volume_delta)
 	{
-		CONS_Alert(CONS_ERROR, "Mix_LoadMUS_RW: %s\n", Mix_GetError());
-		return -1;
-	}
-	return 1337;
-}
+		I_SetInternalMusicVolume(target_volume);
+		if (callback)
+			(*callback)();
+		return true;
 
-boolean I_PlaySong(INT32 handle, boolean looping)
-{
-	(void)handle;
+	}
+	else if (!volume_delta)
+	{
+		if (callback)
+			(*callback)();
+		return true;
+	}
 
-	midimode = true;
+	// Round MS to nearest 10
+	// If n - lower > higher - n, then round up
+	ms = (ms - ((ms / 10) * 10) > (((ms / 10) * 10) + 10) - ms) ?
+		(((ms / 10) * 10) + 10) // higher
+		: ((ms / 10) * 10); // lower
 
-	if (Mix_PlayMusic(music, looping ? -1 : 0) == -1)
+	if (!ms)
+		I_SetInternalMusicVolume(target_volume);
+	else if (source_volume != target_volume)
 	{
-		CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
-		return false;
+		fading_id = SDL_AddTimer(10, music_fade, NULL);
+		if (fading_id)
+		{
+			is_fading = true;
+			fading_timer = fading_duration = ms;
+			fading_source = source_volume;
+			fading_target = target_volume;
+			fading_callback = callback;
+
+			if (internal_volume != source_volume)
+				I_SetInternalMusicVolume(source_volume);
+		}
 	}
 
-	Mix_VolumeMusic((UINT32)midi_volume*128/31);
-	return true;
+	return is_fading;
 }
 
-void I_StopSong(INT32 handle)
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
 {
-	if (!midimode || !music)
-		return;
-
-	(void)handle;
-	Mix_HaltMusic();
+	return I_FadeSongFromVolume(target_volume, internal_volume, ms, callback);
 }
 
-void I_UnRegisterSong(INT32 handle)
+boolean I_FadeOutStopSong(UINT32 ms)
 {
-	if (!midimode || !music)
-		return;
-
-	(void)handle;
-	Mix_FreeMusic(music);
-	music = NULL;
+	return I_FadeSongFromVolume(0, internal_volume, ms, &I_StopSong);
 }
 
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
+{
+	if (I_PlaySong(looping))
+		return I_FadeSongFromVolume(100, 0, ms, NULL);
+	else
+		return false;
+}
 #endif
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index cd7ced7cab1eed7886af073f1c187b40ee18fd7e..4347b35b22b2e07682e5ced0e89bd76636366a04 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -214,8 +214,11 @@ void OglSdlFinishUpdate(boolean waitvbl)
 	HWR_DrawScreenFinalTexture(sdlw, sdlh);
 	SDL_GL_SwapWindow(window);
 
-	SetModelView(realwidth, realheight);
-	SetStates();
+	GClipRect(0, 0, realwidth, realheight, NZCLIP_PLANE);
+
+	// Sryder:	We need to draw the final screen texture again into the other buffer in the original position so that
+	//			effects that want to take the old screen can do so after this
+	HWR_DrawScreenFinalTexture(realwidth, realheight);
 }
 
 EXPORT void HWRAPI( OglSdlSetPalette) (RGBA_t *palette, RGBA_t *pgamma)
diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c
index 6ca11c9ef46b9c6e30c6935d1b7d633c2e7c1147..a371c4d374e5f026b611378e7e9f828e91312ddf 100644
--- a/src/sdl/sdl_sound.c
+++ b/src/sdl/sdl_sound.c
@@ -32,7 +32,7 @@
 #endif
 
 #ifdef HAVE_MIXER
-#include "SDL_mixer.h"
+#include <SDL_mixer.h>
 /* This is the version number macro for the current SDL_mixer version: */
 #ifndef SDL_MIXER_COMPILEDVERSION
 #define SDL_MIXER_COMPILEDVERSION \
@@ -194,8 +194,8 @@ static srb2audio_t localdata;
 static void Snd_LockAudio(void) //Alam: Lock audio data and uninstall audio callback
 {
 	if (Snd_Mutex) SDL_LockMutex(Snd_Mutex);
-	else if (nosound) return;
-	else if (nomidimusic && nodigimusic
+	else if (sound_disabled) return;
+	else if (midi_disabled && digital_disabled
 #ifdef HW3SOUND
 	         && hws_mode == HWS_DEFAULT_MODE
 #endif
@@ -208,8 +208,8 @@ static void Snd_LockAudio(void) //Alam: Lock audio data and uninstall audio call
 static void Snd_UnlockAudio(void) //Alam: Unlock audio data and reinstall audio callback
 {
 	if (Snd_Mutex) SDL_UnlockMutex(Snd_Mutex);
-	else if (nosound) return;
-	else if (nomidimusic && nodigimusic
+	else if (sound_disabled) return;
+	else if (midi_disabled && digital_disabled
 #ifdef HW3SOUND
 	         && hws_mode == HWS_DEFAULT_MODE
 #endif
@@ -219,7 +219,7 @@ static void Snd_UnlockAudio(void) //Alam: Unlock audio data and reinstall audio
 #endif
 }
 
-FUNCMATH static inline Uint16 Snd_LowerRate(Uint16 sr)
+static inline Uint16 Snd_LowerRate(Uint16 sr)
 {
 	if (sr <= audio.freq) // already lowered rate?
 		return sr; // good then
@@ -493,7 +493,7 @@ static inline void I_SetChannels(void)
 
 	INT32 *steptablemid = steptable + 128;
 
-	if (nosound)
+	if (sound_disabled)
 		return;
 
 	// This table provides step widths for pitch parameters.
@@ -604,12 +604,13 @@ void I_FreeSfx(sfxinfo_t * sfx)
 // Pitching (that is, increased speed of playback)
 //  is set, but currently not used by mixing.
 //
-INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority)
+INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
 {
 	(void)priority;
 	(void)pitch;
+	(void)channel;
 
-	if (nosound)
+	if (sound_disabled)
 		return 0;
 
 	if (S_sfx[id].data == NULL) return -1;
@@ -989,7 +990,7 @@ FUNCINLINE static ATTRINLINE void I_UpdateStream16M(Uint8 *stream, int len)
 	if (Snd_Mutex) SDL_UnlockMutex(Snd_Mutex);
 }
 
-#ifdef HAVE_LIBGME
+#if 0 //#ifdef HAVE_LIBGME
 static void I_UpdateSteamGME(Music_Emu *emu, INT16 *stream, int len, UINT8 looping)
 {
 	#define GME_BUFFER_LEN 44100*2048
@@ -1049,14 +1050,16 @@ static void SDLCALL I_UpdateStream(void *userdata, Uint8 *stream, int len)
 	else if (audio.channels == 2 && audio.format == AUDIO_S16SYS)
 	{
 		I_UpdateStream16S(stream, len);
-#ifdef HAVE_LIBGME
-		if (userdata)
-		{
-			srb2audio_t *sa_userdata = userdata;
-			if (!sa_userdata->gme_pause)
-				I_UpdateSteamGME(sa_userdata->gme_emu, (INT16 *)stream, len/4, sa_userdata->gme_loop);
-		}
-#endif
+
+		// Crashes! But no matter; this build doesn't play music anyway...
+// #ifdef HAVE_LIBGME
+// 		if (userdata)
+// 		{
+// 			srb2audio_t *sa_userdata = userdata;
+// 			if (!sa_userdata->gme_pause)
+// 				I_UpdateSteamGME(sa_userdata->gme_emu, (INT16 *)stream, len/4, sa_userdata->gme_loop);
+// 		}
+// #endif
 
 	}
 }
@@ -1136,7 +1139,7 @@ static INT32 Init3DSDriver(const char *soName)
 
 void I_ShutdownSound(void)
 {
-	if (nosound || !sound_started)
+	if (sound_disabled || !sound_started)
 		return;
 
 	CONS_Printf("I_ShutdownSound: ");
@@ -1150,7 +1153,7 @@ void I_ShutdownSound(void)
 	}
 #endif
 
-	if (nomidimusic && nodigimusic)
+	if (midi_disabled && digital_disabled)
 		SDL_CloseAudio();
 	CONS_Printf("%s", M_GetText("shut down\n"));
 	sound_started = false;
@@ -1170,7 +1173,7 @@ void I_StartupSound(void)
 	const char *sdrv_name = NULL;
 #endif
 #ifndef HAVE_MIXER
-	nomidimusic = nodigimusic = true;
+	midi_disabled = digital_disabled = true;
 #endif
 
 	memset(channels, 0, sizeof (channels)); //Alam: Clean it
@@ -1180,15 +1183,15 @@ void I_StartupSound(void)
 	audio.callback = I_UpdateStream;
 	audio.userdata = &localdata;
 
-	if (dedicated)
-	{
-		nosound = nomidimusic = nodigimusic = true;
-		return;
-	}
-
 	// Configure sound device
 	CONS_Printf("I_StartupSound:\n");
 
+#ifdef _WIN32
+	// Force DirectSound instead of WASAPI
+	// SDL 2.0.6+ defaults to the latter and it screws up our sound effects
+	SDL_setenv("SDL_AUDIODRIVER", "directsound", 1);
+#endif
+
 	// EE inits audio first so we're following along.
 	if (SDL_WasInit(SDL_INIT_AUDIO) == SDL_INIT_AUDIO)
 		CONS_Printf("SDL Audio already started\n");
@@ -1219,7 +1222,7 @@ void I_StartupSound(void)
 		audio.samples /= 2;
 	}
 
-	if (nosound)
+	if (sound_disabled)
 		return;
 
 #ifdef HW3SOUND
@@ -1267,7 +1270,7 @@ void I_StartupSound(void)
 		{
 			snddev_t            snddev;
 
-			//nosound = true;
+			//sound_disabled = true;
 			//I_AddExitFunc(I_ShutdownSound);
 			snddev.bps = 16;
 			snddev.sample_rate = audio.freq;
@@ -1294,7 +1297,7 @@ void I_StartupSound(void)
 	if (!musicStarted && SDL_OpenAudio(&audio, &audio) < 0)
 	{
 		CONS_Printf("%s", M_GetText(" couldn't open audio with desired format\n"));
-		nosound = true;
+		sound_disabled = true;
 		return;
 	}
 	else
@@ -1319,13 +1322,11 @@ void I_StartupSound(void)
 // MUSIC API.
 //
 
-void I_ShutdownMIDIMusic(void)
-{
-	nomidimusic = false;
-	if (nodigimusic) I_ShutdownMusic();
-}
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
 
-#ifdef HAVE_LIBGME
+#if 0 //#ifdef HAVE_LIBGME
 static void I_ShutdownGMEMusic(void)
 {
 	Snd_LockAudio();
@@ -1336,393 +1337,198 @@ static void I_ShutdownGMEMusic(void)
 }
 #endif
 
-void I_ShutdownDigMusic(void)
-{
-	nodigimusic = false;
-	if (nomidimusic) I_ShutdownMusic();
-}
-
-#ifdef HAVE_MIXER
-static boolean LoadSong(void *data, size_t lumplength, size_t selectpos)
+void I_InitMusic(void)
 {
-	FILE *midfile;
-	const char *tempname;
-#ifdef USE_RWOPS
-	if (canuseRW)
-	{
-		SDL_RWops *SDLRW;
-		void *olddata = Smidi[selectpos]; //quick shortcut to set
-
-		Z_Free(olddata); //free old memory
-		Smidi[selectpos] = NULL;
-
-		if (!data)
-			return olddata != NULL; //was there old data?
-
-		SDLRW = SDL_RWFromConstMem(data, (int)lumplength); //new RWops from Z_zone
-		if (!SDLRW) //ERROR while making RWops!
-		{
-			CONS_Printf(M_GetText("Couldn't load music lump: %s\n"), SDL_GetError());
-			Z_Free(data);
-			return false;
-		}
-
-		music[selectpos] = Mix_LoadMUS_RW(SDLRW); // new Mix_Chuck from RWops
-		if (music[selectpos])
-			Smidi[selectpos] = data; //all done
-		else //ERROR while making Mix_Chuck
-		{
-			CONS_Printf(M_GetText("Couldn't load music data: %s\n"), Mix_GetError());
-			Z_Free(data);
-			SDL_RWclose(SDLRW);
-			Smidi[selectpos] = NULL;
-		}
-		return true;
-	}
-#endif
-	tempname = va("%s/%s", MIDI_PATH, fmidi[selectpos]);
-
-	if (!data)
-	{
-		if (FIL_FileExists(tempname))
-			return unlink(tempname)+1;
-#ifdef MIDI_PATH2
-		else if (FIL_FileExists(tempname = va("%s/%s", MIDI_PATH2, fmidi[selectpos])))
-			return unlink(tempname)+1;
-#endif
-		else
-			return false;
-	}
-
-	midfile = fopen(tempname, "wb");
-
-#ifdef MIDI_PATH2
-	if (!midfile)
-	{
-		tempname = va("%s/%s", MIDI_PATH2, fmidi[selectpos]);
-		midfile = fopen(tempname, "wb");
-	}
+#if 0 //#ifdef HAVE_LIBGME
+	I_AddExitFunc(I_ShutdownGMEMusic);
 #endif
+}
 
-	if (!midfile)
-	{
-		CONS_Printf(M_GetText("Couldn't open file %s to write music in\n"), tempname);
-		Z_Free(data);
-		return false;
-	}
+void I_ShutdownMusic(void) { }
 
-	if (fwrite(data, lumplength, 1, midfile) == 0)
-	{
-		CONS_Printf(M_GetText("Couldn't write music into file %s because %s\n"), tempname, strerror(ferror(midfile)));
-		Z_Free(data);
-		fclose(midfile);
-		return false;
-	}
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
 
-	fclose(midfile);
+musictype_t I_SongType(void)
+{
+	return MU_NONE;
+}
 
-	Z_Free(data);
+boolean I_SongPlaying(void)
+{
+	return false;
+}
 
-	music[selectpos] = Mix_LoadMUS(tempname);
-	if (!music[selectpos]) //ERROR while making Mix_Chuck
-	{
-		CONS_Printf(M_GetText("Couldn't load music file %s: %s\n"), tempname, Mix_GetError());
-		return false;
-	}
-	return true;
+boolean I_SongPaused(void)
+{
+	return false;
 }
-#endif
 
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
 
-void I_ShutdownMusic(void)
+boolean I_SetSongSpeed(float speed)
 {
-#ifdef HAVE_MIXER
-	if ((nomidimusic && nodigimusic) || !musicStarted)
-		return;
+	(void)speed;
+	return false;
+}
 
-	CONS_Printf("%s", M_GetText("I_ShutdownMusic: "));
+/// ------------------------
+//  MUSIC SEEKING
+/// ------------------------
 
-	I_UnRegisterSong(0);
-	I_StopDigSong();
-	Mix_CloseAudio();
-#ifdef MIX_INIT
-	Mix_Quit();
-#endif
-	CONS_Printf("%s", M_GetText("shut down\n"));
-	musicStarted = SDL_FALSE;
-	if (Msc_Mutex)
-		SDL_DestroyMutex(Msc_Mutex);
-	Msc_Mutex = NULL;
-#endif
+UINT32 I_GetSongLength(void)
+{
+	return 0;
 }
 
-void I_InitMIDIMusic(void)
+boolean I_SetSongLoopPoint(UINT32 looppoint)
 {
-	if (nodigimusic) I_InitMusic();
+	(void)looppoint;
+	return false;
 }
 
-void I_InitDigMusic(void)
+UINT32 I_GetSongLoopPoint(void)
 {
-	if (nomidimusic) I_InitMusic();
+	return 0;
 }
 
-void I_InitMusic(void)
+boolean I_SetSongPosition(UINT32 position)
 {
-#ifdef HAVE_MIXER
-	char ad[100];
-	SDL_version MIXcompiled;
-	const SDL_version *MIXlinked;
-#ifdef MIXER_INIT
-	const int mixstart = MIX_INIT_OGG;
-	int mixflags;
-#endif
-#endif
-#ifdef HAVE_LIBGME
-	I_AddExitFunc(I_ShutdownGMEMusic);
-#endif
-
-	if ((nomidimusic && nodigimusic) || dedicated)
-		return;
-
-#ifdef HAVE_MIXER
-	MIX_VERSION(&MIXcompiled)
-	MIXlinked = Mix_Linked_Version();
-	I_OutputMsg("Compiled for SDL_mixer version: %d.%d.%d\n",
-	            MIXcompiled.major, MIXcompiled.minor, MIXcompiled.patch);
-#ifdef MIXER_POS
-	if (MIXlinked->major == 1 && MIXlinked->minor == 2 && MIXlinked->patch < 7)
-		canlooping = SDL_FALSE;
-#endif
-#ifdef USE_RWOPS
-	if (M_CheckParm("-noRW"))
-		canuseRW = SDL_FALSE;
-#endif
-	I_OutputMsg("Linked with SDL_mixer version: %d.%d.%d\n",
-	            MIXlinked->major, MIXlinked->minor, MIXlinked->patch);
-	if (audio.freq < 44100 && !M_CheckParm ("-freq")) //I want atleast 44Khz
-	{
-		audio.samples = (Uint16)(audio.samples*(INT32)(44100/audio.freq));
-		audio.freq = 44100; //Alam: to keep it around the same XX ms
-	}
-
-	if (sound_started
-#ifdef HW3SOUND
-		&& hws_mode == HWS_DEFAULT_MODE
-#endif
-		)
-	{
-		I_OutputMsg("Temp Shutdown of SDL Audio System");
-		SDL_CloseAudio();
-		I_OutputMsg(" Done\n");
-	}
-
-	CONS_Printf("%s", M_GetText("I_InitMusic:"));
+	(void)position;
+	return false;
+}
 
-#ifdef MIXER_INIT
-	mixflags = Mix_Init(mixstart);
-	if ((mixstart & MIX_INIT_FLAC) != (mixflags & MIX_INIT_FLAC))
-	{
-		CONS_Printf("%s", M_GetText(" Unable to load FLAC support\n"));
-	}
-	if ((mixstart & MIX_INIT_MOD ) != (mixflags & MIX_INIT_MOD ))
-	{
-		CONS_Printf("%s", M_GetText(" Unable to load MOD support\n"));
-	}
-	if ((mixstart & MIX_INIT_MP3 ) != (mixflags & MIX_INIT_MP3 ))
-	{
-		CONS_Printf("%s", M_GetText(" Unable to load MP3 support\n"));
-	}
-	if ((mixstart & MIX_INIT_OGG ) != (mixflags & MIX_INIT_OGG ))
-	{
-		CONS_Printf("%s", M_GetText(" Unable to load OGG support\n"));
-	}
-#endif
+UINT32 I_GetSongPosition(void)
+{
+	return 0;
+}
 
-	if (Mix_OpenAudio(audio.freq, audio.format, audio.channels, audio.samples) < 0) //open_music(&audio)
-	{
-		CONS_Printf(M_GetText(" Unable to open music: %s\n"), Mix_GetError());
-		nomidimusic = nodigimusic = true;
-		if (sound_started
-#ifdef HW3SOUND
-			&& hws_mode == HWS_DEFAULT_MODE
-#endif
-			)
-		{
-			if (SDL_OpenAudio(&audio, NULL) < 0) //retry
-			{
-				CONS_Printf("%s", M_GetText(" couldn't open audio with desired format\n"));
-				nosound = true;
-				sound_started = false;
-			}
-			else
-			{
-				CONS_Printf(M_GetText(" Starting with audio driver : %s\n"), SDL_AudioDriverName(ad, (int)sizeof ad));
-			}
-		}
-		return;
-	}
-	else
-		CONS_Printf(M_GetText(" Starting up with audio driver : %s with SDL_Mixer\n"), SDL_AudioDriverName(ad, (int)sizeof ad));
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
 
-	samplecount = audio.samples;
-	CV_SetValue(&cv_samplerate, audio.freq);
-	if (sound_started
-#ifdef HW3SOUND
-		&& hws_mode == HWS_DEFAULT_MODE
-#endif
-		)
-		I_OutputMsg(" Reconfigured SDL Audio System");
-	else I_OutputMsg(" Configured SDL_Mixer System");
-	I_OutputMsg(" with %d samples/slice at %ikhz(%dms buffer)\n", samplecount, audio.freq/1000, (INT32) ((audio.samples * 1000.0f) / audio.freq));
-	Mix_SetPostMix(audio.callback, audio.userdata);  // after mixing music, add sound effects
-	Mix_Resume(-1);
-	CONS_Printf("%s", M_GetText("Music initialized\n"));
-	musicStarted = SDL_TRUE;
-	Msc_Mutex = SDL_CreateMutex();
-#endif
+#if 0 //#ifdef HAVE_LIBGME
+static void I_StopGME(void)
+{
+	Snd_LockAudio();
+	gme_seek(localdata.gme_emu, 0);
+	Snd_UnlockAudio();
 }
 
-boolean I_PlaySong(INT32 handle, boolean looping)
+static void I_PauseGME(void)
 {
-	(void)handle;
-#ifdef HAVE_MIXER
-	if (nomidimusic || !musicStarted || !music[handle])
-		return false;
+	localdata.gme_pause = true;
+}
 
-#ifdef MIXER_POS
-	if (canlooping)
-		Mix_HookMusicFinished(NULL);
+static void I_ResumeGME(void)
+{
+	localdata.gme_pause = false;
+}
 #endif
 
-	if (Mix_FadeInMusic(music[handle], looping ? -1 : 0, MIDIfade) == -1)
-		CONS_Printf(M_GetText("Couldn't play song because %s\n"), Mix_GetError());
-	else
-	{
-		Mix_VolumeMusic(musicvol);
-		return true;
-	}
-#else
-	(void)looping;
-#endif
+boolean I_LoadSong(char *data, size_t len)
+{
 	return false;
 }
 
-static void I_PauseGME(void)
+void I_UnloadSong(void) { }
+
+boolean I_PlaySong(boolean looping)
 {
-#ifdef HAVE_LIBGME
-	localdata.gme_pause = true;
-#endif
+	(void)looping;
+	return false;
 }
 
-void I_PauseSong(INT32 handle)
+void I_StopSong(void)
 {
-	(void)handle;
-	I_PauseGME();
-#ifdef HAVE_MIXER
-	if ((nomidimusic && nodigimusic) || !musicStarted)
-		return;
-
-	Mix_PauseMusic();
-	//I_StopSong(handle);
+#if 0 //#ifdef HAVE_LIBGME
+	I_StopGME();
 #endif
 }
 
-static void I_ResumeGME(void)
+void I_PauseSong(void)
 {
-#ifdef HAVE_LIBGME
-	localdata.gme_pause = false;
+#if 0 //#ifdef HAVE_LIBGME
+	I_PauseGME();
 #endif
 }
 
-void I_ResumeSong(INT32 handle)
+void I_ResumeSong(void)
 {
-	(void)handle;
+#if 0
 	I_ResumeGME();
-#ifdef HAVE_MIXER
-	if ((nomidimusic && nodigimusic) || !musicStarted)
-		return;
-
-	Mix_VolumeMusic(musicvol);
-	Mix_ResumeMusic();
-	//I_PlaySong(handle, true);
 #endif
 }
 
-void I_StopSong(INT32 handle)
+void I_SetMusicVolume(UINT8 volume)
 {
-	(void)handle;
-#ifdef HAVE_MIXER
-	if (nomidimusic || !musicStarted)
-		return;
-	Mix_FadeOutMusic(MIDIfade);
-#endif
+	(void)volume;
 }
 
-void I_UnRegisterSong(INT32 handle)
+boolean I_SetSongTrack(int track)
 {
-#ifdef HAVE_MIXER
-
-	if (nomidimusic || !musicStarted)
-		return;
+	(void)track;
+	return false;
+}
 
-	Mix_HaltMusic();
-	while (Mix_PlayingMusic())
-		;
+/// ------------------------
+/// MUSIC FADING
+/// ------------------------
 
-	if (music[handle])
-		Mix_FreeMusic(music[handle]);
-	music[handle] = NULL;
-	LoadSong(NULL, 0, handle);
-#else
-	(void)handle;
-#endif
+void I_SetInternalMusicVolume(UINT8 volume)
+{
+	(void)volume;
 }
 
-INT32 I_RegisterSong(void *data, size_t len)
+void I_StopFadingSong(void)
 {
-#ifdef HAVE_MIXER
-	if (nomidimusic || !musicStarted)
-		return false;
-
-	if (!LoadSong(data, len, 0))
-		return false;
+}
 
-	if (music[0])
-		return true;
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
+{
+	(void)target_volume;
+	(void)source_volume;
+	(void)ms;
+	return false;
+}
 
-	CONS_Printf(M_GetText("Couldn't load MIDI: %s\n"), Mix_GetError());
-#else
-	(void)len;
-	(void)data;
-#endif
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
+{
+	(void)target_volume;
+	(void)ms;
 	return false;
 }
 
-void I_SetMIDIMusicVolume(UINT8 volume)
+boolean I_FadeOutStopSong(UINT32 ms)
 {
-#ifdef HAVE_MIXER
-	if ((nomidimusic && nodigimusic) || !musicStarted)
-		return;
+	(void)ms;
+	return false;
+}
 
-	if (Msc_Mutex) SDL_LockMutex(Msc_Mutex);
-	musicvol = volume * 2;
-	if (Msc_Mutex) SDL_UnlockMutex(Msc_Mutex);
-	Mix_VolumeMusic(musicvol);
-#else
-	(void)volume;
-#endif
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
+{
+        (void)ms;
+        (void)looping;
+        return false;
 }
 
-#ifdef HAVE_LIBGME
+/// ------------------------
+//  MUSIC LOADING AND CLEANUP
+//  \todo Split logic between loading and playing,
+//        then move to Playback section
+/// ------------------------
+
+#if 0 //#ifdef HAVE_LIBGME
 static void I_CleanupGME(void *userdata)
 {
 	Z_Free(userdata);
 }
-#endif
 
 static boolean I_StartGMESong(const char *musicname, boolean looping)
 {
-#ifdef HAVE_LIBGME
 	char filename[9];
 	void *data;
 	lumpnum_t lumpnum;
@@ -1768,240 +1574,7 @@ static boolean I_StartGMESong(const char *musicname, boolean looping)
 	Snd_UnlockAudio();
 
 	return true;
-#else
-	(void)musicname;
-	(void)looping;
-#endif
-	return false;
 }
-
-boolean I_StartDigSong(const char *musicname, boolean looping)
-{
-#ifdef HAVE_MIXER
-	char filename[9];
-	void *data;
-	lumpnum_t lumpnum;
-	size_t lumplength;
 #endif
 
-	if(I_StartGMESong(musicname, looping))
-		return true;
-
-#ifdef HAVE_MIXER
-	if (nodigimusic)
-		return false;
-
-	snprintf(filename, sizeof filename, "o_%s", musicname);
-
-	lumpnum = W_CheckNumForName(filename);
-
-	I_StopDigSong();
-
-	if (lumpnum == LUMPERROR)
-	{
-		// Alam_GBC: like in win32/win_snd.c: Graue 02-29-2004: don't worry about missing music, there might still be a MIDI
-		//I_OutputMsg("Music lump %s not found!\n", filename);
-		return false; // No music found. Oh well!
-	}
-	else
-		lumplength = W_LumpLength(lumpnum);
-
-	data = W_CacheLumpNum(lumpnum, PU_MUSIC);
-
-	if (Msc_Mutex) SDL_LockMutex(Msc_Mutex);
-
-#ifdef MIXER_POS
-	if (canlooping && (loopingDig = looping) == SDL_TRUE && strcmp(data, "OggS")  == 0)
-		looping = false; // Only on looping Ogg files, will we will do our own looping
-
-	// Scan the Ogg Vorbis file for the COMMENT= field for a custom
-	// loop point
-	if (!looping && loopingDig)
-	{
-		size_t scan;
-		const char *dataum = data;
-		char looplength[64];
-		UINT32 loopstart = 0;
-		UINT8 newcount = 0;
-
-		Mix_HookMusicFinished(I_FinishMusic);
-
-		for (scan = 0; scan < lumplength; scan++)
-		{
-			if (*dataum++ == 'C'){
-			if (*dataum++ == 'O'){
-			if (*dataum++ == 'M'){
-			if (*dataum++ == 'M'){
-			if (*dataum++ == 'E'){
-			if (*dataum++ == 'N'){
-			if (*dataum++ == 'T'){
-			if (*dataum++ == '='){
-			if (*dataum++ == 'L'){
-			if (*dataum++ == 'O'){
-			if (*dataum++ == 'O'){
-			if (*dataum++ == 'P'){
-			if (*dataum++ == 'P'){
-			if (*dataum++ == 'O'){
-			if (*dataum++ == 'I'){
-			if (*dataum++ == 'N'){
-			if (*dataum++ == 'T'){
-			if (*dataum++ == '=')
-			{
-
-				while (*dataum != 1 && newcount != 63)
-					looplength[newcount++] = *dataum++;
-
-				looplength[newcount] = '\0';
-
-				loopstart = atoi(looplength);
-
-			}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-			else
-				dataum--;}
-		}
-
-		if (loopstart > 0)
-		{
-			loopstartDig = (double)((44.1l+loopstart) / 44100.0l); //8 PCM chucks off and PCM to secs
-//#ifdef PARANOIA
-			//I_OutputMsg("I_StartDigSong: setting looping point to %ul PCMs(%f seconds)\n", loopstart, loopstartDig);
-//#endif
-		}
-		else
-		{
-			looping = true; // loopingDig true, but couldn't find start loop point
-		}
-	}
-	else
-		loopstartDig = 0.0l;
-#else
-	if (looping && strcmp(data, "OggS")  == 0)
-		I_OutputMsg("I_StartDigSong: SRB2 was not compiled with looping music support(no Mix_FadeInMusicPos)\n");
-#endif
-
-	if (!LoadSong(data, lumplength, 1))
-	{
-		if (Msc_Mutex) SDL_UnlockMutex(Msc_Mutex);
-		return false;
-	}
-
-	// Note: LoadSong() frees the data. Let's make sure
-	// we don't try to use the data again.
-	data = NULL;
-
-	if (Mix_FadeInMusic(music[1], looping ? -1 : 0, Digfade) == -1)
-	{
-		if (Msc_Mutex) SDL_UnlockMutex(Msc_Mutex);
-		I_OutputMsg("I_StartDigSong: Couldn't play song %s because %s\n", musicname, Mix_GetError());
-		return false;
-	}
-	Mix_VolumeMusic(musicvol);
-
-	if (Msc_Mutex) SDL_UnlockMutex(Msc_Mutex);
-	return true;
-#else
-	(void)looping;
-	(void)musicname;
-	return false;
-#endif
-}
-
-static void I_StopGME(void)
-{
-#ifdef HAVE_LIBGME
-	Snd_LockAudio();
-	gme_seek(localdata.gme_emu, 0);
-	Snd_UnlockAudio();
-#endif
-}
-
-void I_StopDigSong(void)
-{
-	I_StopGME();
-#ifdef HAVE_MIXER
-	if (nodigimusic)
-		return;
-
-#ifdef MIXER_POS
-	if (canlooping)
-		Mix_HookMusicFinished(NULL);
-#endif
-
-	Mix_HaltMusic();
-	while (Mix_PlayingMusic())
-		;
-
-	if (music[1])
-		Mix_FreeMusic(music[1]);
-	music[1] = NULL;
-	LoadSong(NULL, 0, 1);
-#endif
-}
-
-void I_SetDigMusicVolume(UINT8 volume)
-{
-	I_SetMIDIMusicVolume(volume);
-}
-
-boolean I_SetSongSpeed(float speed)
-{
-
-	(void)speed;
-	return false;
-}
-
-boolean I_SetSongTrack(int track)
-{
-	(void)track;
-	return false;
-}
-
-#ifdef MIXER_POS
-static void SDLCALL I_FinishMusic(void)
-{
-	if (!music[1])
-		return;
-	else if (Msc_Mutex) SDL_LockMutex(Msc_Mutex);
-//		I_OutputMsg("I_FinishMusic: Loopping song to %g seconds\n", loopstartDig);
-
-	if (Mix_FadeInMusicPos(music[1], loopstartDig ? 0 : -1, Digfade, loopstartDig) == 0)
-		Mix_VolumeMusic(musicvol);
-	else
-		I_OutputMsg("I_FinishMusic: Couldn't loop song because %s\n", Mix_GetError());
-
-	if (Msc_Mutex) SDL_UnlockMutex(Msc_Mutex);
-}
-#endif
 #endif //HAVE_SDL
diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h
index fea1e16487dd8f5c4f7acfbbcf2de94d9ea77b1b..d12daaa8a19027691cd54a2dde65272d8425d1a1 100644
--- a/src/sdl/sdlmain.h
+++ b/src/sdl/sdlmain.h
@@ -1,7 +1,7 @@
 // Emacs style mode select   -*- C++ -*-
 //-----------------------------------------------------------------------------
 //
-// Copyright (C) 2006 by Sonic Team Jr.
+// Copyright (C) 2006-2018 by Sonic Team Jr.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sdl/srb2icon.png b/src/sdl/srb2icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..cdee18a8412313410fb4f0f0a132b710c1670c54
Binary files /dev/null and b/src/sdl/srb2icon.png differ
diff --git a/src/sounds.c b/src/sounds.c
index 475d65cd17383fcb56c050d98206e8df018a0d0a..11ba1e0bcb4cb78c7d2547d0634fcb42a170f6ba 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -65,7 +65,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"buzz1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric zap"},
   {"buzz2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric zap"},
   {"buzz3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wacky worksurface"},
-  {"buzz4",  false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Buzz"},
+  {"buzz4",   true,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Buzz"},
   {"crumbl",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crumbling"}, // Platform Crumble Tails 03-16-2001
   {"fire",   false,   8, 32, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"},
   {"grind",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic grinding"},
@@ -76,6 +76,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"steam1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Steam jet"}, // Tails 06-19-2001
   {"steam2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Steam jet"}, // Tails 06-19-2001
   {"wbreak", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wood breaking"},
+  {"ambmac", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Machinery"},
+  {"spsmsh", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy impact"},
 
   {"rainin",  true,  24,  4, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rain"},
   {"litng1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning"},
@@ -85,14 +87,14 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"athun1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
   {"athun2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
 
-  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
   {"bubbl1", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
   {"bubbl2", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
   {"bubbl3", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
@@ -135,18 +137,18 @@ sfxinfo_t S_sfx[NUMSFX] =
 
   // Game objects, etc
   {"appear", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Appearing platform"},
-  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon explosion"},
+  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon pow"},
   {"bnce1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"}, // Boing!
   {"bnce2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Scatter"}, // Boing!
   {"cannon", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful shot"},
-  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Chaos Emerald"}, // Got Emerald! Tails 09-02-2001
+  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Emerald"}, // Got Emerald! Tails 09-02-2001
   {"cybdth", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
-  {"deton",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous beeping"},
+  {"deton",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Threatening beeping"},
   {"ding",   false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ding"},
   {"dmpain", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Machine damage"},
   {"drown",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drowning"},
   {"fizzle", false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric fizzle"},
-  {"gbeep",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous beeping"}, // Grenade beep
+  {"gbeep",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Threatening beeping"}, // Grenade beep
   {"wepfir", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing weapon"}, // defaults to thok
   {"ghit" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Goop splash"},
   {"gloop",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
@@ -177,20 +179,26 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"spring", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spring"},
   {"statu1",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pushing a statue"},
   {"statu2",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pushing a statue"},
-  {"strpst",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"}, // Starpost Sound Tails 07-04-2002
+  {"strpst",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
   {"supert",  true, 127,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Transformation"},
   {"telept", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Dash"},
   {"tink" ,  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tink"},
-  {"token" ,  true, 224,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Token"}, // SS token
+  {"token" ,  true, 224,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Token"},
   {"trfire",  true,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laser fired"},
   {"trpowr",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
   {"turhit", false,  40,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laser hit"},
   {"wdjump", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Whirlwind jump"},
+  {"shrpsp",  true,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spincushion"},
+  {"shrpgo", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Launch"},
   {"mswarp", false,  60, 16, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spinning out"},
   {"mspogo", false,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Breaking through"},
   {"boingf", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bouncing"},
   {"corkp",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cork fired"},
   {"corkh",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cork hit"},
+  {"bowl",   false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bowling"},
+  {"chuchu", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Train horn"},
+  {"bsnipe", false, 200,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Home-run smash"},
+  {"sprong", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power spring"},
 
   // Menu, interface
   {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
@@ -210,11 +218,12 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"xideya", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Success"}, // Xmas
   {"nbmper", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
   {"nxbump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"}, // Xmas
+  {"ncchip", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got chip"},
   {"ncitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"},
   {"nxitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"}, // Xmas
   {"ngdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"},
   {"nxdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"}, // Xmas
-  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill start"},
+  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill"},
   {"drill2", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill"},
   {"ncspec", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power-up"}, // Tails 12-15-2003
   {"nghurt", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
@@ -225,18 +234,26 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"hidden", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
   {"prloop", false, 104,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Gust of wind"},
   {"timeup",  true, 256,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous Countdown"},
+  {"ngjump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
+  {"peww",   false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pew"},
+
+  // Halloween
+  {"lntsit", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cacolantern awake"},
+  {"lntdie", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cacolantern death"},
+  {"pumpkn", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pumpkin smash"}, // idspispopd
+  {"ghosty", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laughter"},
 
   // Mario
   {"koopfr" , true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fire"},
-  {"mario1", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hitting a ceiling"},
-  {"mario2", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Koopa shell"},
+  {"mario1", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hit"},
+  {"mario2", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonk"},
   {"mario3", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power-up"},
   {"mario4",  true,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got coin"},
-  {"mario5", false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boot"},
+  {"mario5", false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boot-stomp"},
   {"mario6", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
   {"mario7", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fire"},
   {"mario8", false,  48,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
-  {"mario9",  true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Emerging"},
+  {"mario9",  true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Emerging power-up"},
   {"marioa",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "One-up"},
   {"thwomp",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thwomp"},
 
@@ -287,30 +304,30 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k3d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pop"},
   {"s3k3e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame Shield"},
   {"s3k3f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble Shield"},
-  {"s3k40",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction shot"},
-  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning Shield"},
+  {"s3k40",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction blast"},
+  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder Shield"},
   {"s3k42",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Twinspin"},
   {"s3k43",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame burst"},
   {"s3k44",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble bounce"},
   {"s3k45",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning zap"},
   {"s3k46",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Transformation"},
   {"s3k47",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising dust"},
-  {"s3k48",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic clink"},
-  {"s3k49",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rock"},
+  {"s3k48",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pulse"},
+  {"s3k49",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Impact"},
   {"s3k4a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Grab"},
-  {"s3k4b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Water splash"},
+  {"s3k4b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
   {"s3k4c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy hit"},
   {"s3k4d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing bullet"},
-  {"s3k4e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bomb explosion"},
+  {"s3k4e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Big explosion"},
   {"s3k4f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"},
   {"s3k50",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Siren"},
-  {"s3k51",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling bomb"},
+  {"s3k51",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling"},
   {"s3k52",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spike"},
   {"s3k53",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
   {"s3k54",  false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing"}, // MetalSonic shot fire
   {"s3k55",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical movement"},
   {"s3k56",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy landing"},
-  {"s3k57",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
+  {"s3k57",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
   {"s3k58",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical movement"},
   {"s3k59",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crumbling"},
   {"s3k5a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
@@ -319,18 +336,18 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k5d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy hit"},
   {"s3k5e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"},
   {"s3k5f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crusher stomp"},
-  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
+  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Accelerating"},
   {"s3k61",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drilling"},
   {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
   {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
   {"s3k64",  false,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Clatter"},
-  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got blue sphere"}, // Blue Spheres
-  {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage clear"},
+  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got sphere"}, // Blue Spheres
+  {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage end"},
   {"s3k67",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing missile"},
-  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Unknown possibilities"},
+  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
   {"s3k69",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Switch click"},
   {"s3k6a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage clear"},
-  {"s3k6b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k6b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Punch"},
   {"s3k6c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
   {"s3k6d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3k6e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical damage"},
@@ -349,7 +366,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k7b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Soft bounce"},
   {"s3k7c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Magnet"},
   {"s3k7d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s3k7e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eating dirt"},
+  {"s3k7e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Dust"},
   {"s3k7f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Freeze"},
   {"s3k80",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ice spike burst"},
   {"s3k81",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
@@ -360,20 +377,20 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k86",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Alarm"},
   {"s3k87",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"},
   {"s3k88",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic squeak"},
-  {"s3k89",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Advanced technology"},
+  {"s3k89",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Advanced tech"},
   {"s3k8a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boing"},
   {"s3k8b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful hit"},
-  {"s3k8c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Humming power"},
   {"s3k8d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s3k8e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Accelerating"},
   {"s3k8f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Opening"},
   {"s3k90",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Impact"},
   {"s3k91",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Closed"},
   {"s3k92",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ghost"},
-  {"s3k93",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rebuilding"},
+  {"s3k93",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Gas release"},
   {"s3k94",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spike"},
-  {"s3k95",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising from lava"},
-  {"s3k96",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling object"},
+  {"s3k95",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lava burst"},
+  {"s3k96",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Landing"},
   {"s3k97",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind"},
   {"s3k98",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling spike"},
   {"s3k99",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"},
@@ -415,8 +432,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kbcl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // long version of previous
   {"s3kbds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"},
   {"s3kbdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"}, // ditto
-  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
-  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"}, // ditto
+  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying"},
+  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying"}, // ditto
   {"s3kbfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
   {"s3kbfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"}, // ditto
   {"s3kc0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
@@ -429,8 +446,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kc3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Levitation"}, // ditto
   {"s3kc4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"},
   {"s3kc4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"}, // ditto
-  {"s3kc5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s3kc5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kc5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Revving up"},
+  {"s3kc5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Revving up"}, // ditto
   {"s3kc6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"},
   {"s3kc6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"}, // ditto
   {"s3kc7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
@@ -441,13 +458,13 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kcas", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Energy"},
   {"s3kcal", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Energy"}, // ditto
   {"s3kcbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
-  {"s3kcbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominuous rumbling"}, // ditto
+  {"s3kcbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"}, // ditto
   {"s3kccs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collapsing"},
   {"s3kccl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collapsing"}, // ditto
   {"s3kcds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
   {"s3kcdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"}, // ditto
   {"s3kces", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind tunnel"},
-  {"s3kcel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind tunnel"}, // ditto
+  {"s3kcel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Dust devil"}, // ditto
   {"s3kcfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3kcfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
   {"s3kd0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising"},
@@ -455,9 +472,9 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kd1s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3kd1l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
   {"s3kd2s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turning"},
-  {"s3kd2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turning"}, // ditto
-  {"s3kd3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"s3kd3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kd2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Moving chain"}, // ditto
+  {"s3kd3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Digging"},
+  {"s3kd3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Digging"}, // ditto
   {"s3kd4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Engine"},
   {"s3kd4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Engine"}, // ditto
   {"s3kd5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling lava"},
diff --git a/src/sounds.h b/src/sounds.h
index 60451b6ea8e3475ab0aac20760678c1611b648ef..20f89d9fbb241be125c057584634d618a3eda58e 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -42,7 +42,7 @@ typedef enum
 } skinsound_t;
 
 // free sfx for S_AddSoundFx()
-#define NUMSFXFREESLOTS 800 // Matches SOC Editor.
+#define NUMSFXFREESLOTS 1600 // Matches SOC Editor.
 #define NUMSKINSFXSLOTS (MAXSKINS*NUMSKINSOUNDS)
 
 //
@@ -142,6 +142,8 @@ typedef enum
 	sfx_steam1,
 	sfx_steam2,
 	sfx_wbreak,
+	sfx_ambmac,
+	sfx_spsmsh,
 
 	sfx_rainin,
 	sfx_litng1,
@@ -252,11 +254,17 @@ typedef enum
 	sfx_trpowr,
 	sfx_turhit,
 	sfx_wdjump,
+	sfx_shrpsp,
+	sfx_shrpgo,
 	sfx_mswarp,
 	sfx_mspogo,
 	sfx_boingf,
 	sfx_corkp,
 	sfx_corkh,
+	sfx_bowl,
+	sfx_chuchu,
+	sfx_bsnipe,
+	sfx_sprong,
 
 	// Menu, interface
 	sfx_chchng,
@@ -276,6 +284,7 @@ typedef enum
 	sfx_xideya, // Xmas
 	sfx_nbmper,
 	sfx_nxbump, // Xmas
+	sfx_ncchip,
 	sfx_ncitem,
 	sfx_nxitem, // Xmas
 	sfx_ngdone,
@@ -290,7 +299,15 @@ typedef enum
 	sfx_hoop3,
 	sfx_hidden,
 	sfx_prloop,
-	sfx_timeup, // Was gonna be played when less than ten seconds are on the clock; uncomment uses of this to see it in-context
+	sfx_timeup,
+	sfx_ngjump,
+	sfx_peww,
+
+	// Halloween
+	sfx_lntsit,
+	sfx_lntdie,
+	sfx_pumpkn,
+	sfx_ghosty,
 
 	// Mario
 	sfx_koopfr,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 437b6758a386381dc06276c1efb43357e3a83a4f..aefb4c53c70badbc2a9b85e178b4cdceb2b704dc 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -27,6 +27,8 @@
 #include "i_system.h"
 #include "m_menu.h"
 #include "m_cheat.h"
+#include "m_misc.h" // moviemode
+#include "m_anigif.h" // cv_gif_downscale
 #include "p_setup.h" // NiGHTS grading
 
 //random index
@@ -77,7 +79,6 @@ static patch_t *race1;
 static patch_t *race2;
 static patch_t *race3;
 static patch_t *racego;
-static patch_t *ttlnum;
 static patch_t *nightslink;
 static patch_t *curweapon;
 static patch_t *normring;
@@ -94,6 +95,7 @@ static patch_t *ringshield;
 static patch_t *watershield;
 static patch_t *bombshield;
 static patch_t *pityshield;
+static patch_t *pinkshield;
 static patch_t *flameshield;
 static patch_t *bubbleshield;
 static patch_t *thundershield;
@@ -101,14 +103,18 @@ static patch_t *invincibility;
 static patch_t *sneakers;
 static patch_t *gravboots;
 static patch_t *nonicon;
+static patch_t *nonicon2;
 static patch_t *bluestat;
 static patch_t *byelstat;
 static patch_t *orngstat;
 static patch_t *redstat;
 static patch_t *yelstat;
 static patch_t *nbracket;
+static patch_t *nring;
 static patch_t *nhud[12];
 static patch_t *nsshud;
+static patch_t *nbon[12];
+static patch_t *nssbon;
 static patch_t *narrow[9];
 static patch_t *nredar[8]; // Red arrow
 static patch_t *drillbar;
@@ -125,42 +131,32 @@ static boolean facefreed[MAXPLAYERS];
 
 hudinfo_t hudinfo[NUMHUDITEMS] =
 {
-	{  34, 176}, // HUD_LIVESNAME
-	{  16, 176}, // HUD_LIVESPIC
-	{  74, 184}, // HUD_LIVESNUM
-	{  38, 186}, // HUD_LIVESX
-
-	{  16,  42}, // HUD_RINGS
-	{ 220,  10}, // HUD_RINGSSPLIT
-	{  96,  42}, // HUD_RINGSNUM
-	{ 296,  10}, // HUD_RINGSNUMSPLIT
-	{ 120,  42}, // HUD_RINGSNUMTICS
-
-	{  16,  10}, // HUD_SCORE
-	{ 120,  10}, // HUD_SCORENUM
-
-	{  16,  26}, // HUD_TIME
-	{ 128,  10}, // HUD_TIMESPLIT
-	{  72,  26}, // HUD_MINUTES
-	{ 188,  10}, // HUD_MINUTESSPLIT
-	{  72,  26}, // HUD_TIMECOLON
-	{ 188,  10}, // HUD_TIMECOLONSPLIT
-	{  96,  26}, // HUD_SECONDS
-	{ 212,  10}, // HUD_SECONDSSPLIT
-	{  96,  26}, // HUD_TIMETICCOLON
-	{ 120,  26}, // HUD_TICS
-
-	{ 120,  56}, // HUD_SS_TOTALRINGS
-	{ 296,  40}, // HUD_SS_TOTALRINGS_SPLIT
-
-	{ 110,  93}, // HUD_GETRINGS
-	{ 160,  93}, // HUD_GETRINGSNUM
-	{ 124, 160}, // HUD_TIMELEFT
-	{ 168, 176}, // HUD_TIMELEFTNUM
-	{ 130,  93}, // HUD_TIMEUP
-	{ 152, 168}, // HUD_HUNTPICS
-	{ 152,  24}, // HUD_GRAVBOOTSICO
-	{ 240, 160}, // HUD_LAP
+	{  16, 176, V_SNAPTOLEFT|V_SNAPTOBOTTOM}, // HUD_LIVES
+
+	{  16,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGS
+	{  96,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGSNUM
+	{ 120,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGSNUMTICS
+
+	{  16,  10, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SCORE
+	{ 120,  10, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SCORENUM
+
+	{  16,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIME
+	{  72,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_MINUTES
+	{  72,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIMECOLON
+	{  96,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SECONDS
+	{  96,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIMETICCOLON
+	{ 120,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TICS
+
+	{   0,  56, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SS_TOTALRINGS
+
+	{ 110,  93, 0}, // HUD_GETRINGS
+	{ 160,  93, 0}, // HUD_GETRINGSNUM
+	{ 124, 160, 0}, // HUD_TIMELEFT
+	{ 168, 176, 0}, // HUD_TIMELEFTNUM
+	{ 130,  93, 0}, // HUD_TIMEUP
+	{ 152, 168, 0}, // HUD_HUNTPICS
+
+	{ 288, 176, V_SNAPTORIGHT|V_SNAPTOBOTTOM}, // HUD_POWERUPS
 };
 
 //
@@ -196,7 +192,7 @@ void ST_Ticker(void)
 }
 
 // 0 is default, any others are special palettes.
-static INT32 st_palette = 0;
+INT32 st_palette = 0;
 
 void ST_doPaletteStuff(void)
 {
@@ -209,17 +205,17 @@ void ST_doPaletteStuff(void)
 	else
 		palette = 0;
 
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		palette = 0; // No flashpals here in OpenGL
+#endif
+
 	palette = min(max(palette, 0), 13);
 
 	if (palette != st_palette)
 	{
 		st_palette = palette;
 
-#ifdef HWRENDER
-		if (rendermode == render_opengl)
-			HWR_SetPaletteColor(0);
-		else
-#endif
 		if (rendermode != render_none)
 		{
 			V_SetPaletteLump(GetPalette()); // Reset the palette
@@ -231,7 +227,7 @@ void ST_doPaletteStuff(void)
 
 void ST_UnloadGraphics(void)
 {
-	Z_FreeTags(PU_HUDGFX, PU_HUDGFX);
+	Z_FreeTag(PU_HUDGFX);
 }
 
 void ST_LoadGraphics(void)
@@ -285,18 +281,19 @@ void ST_LoadGraphics(void)
 	scatterring = W_CachePatchName("SCATIND", PU_HUDGFX);
 	grenadering = W_CachePatchName("GRENIND", PU_HUDGFX);
 	railring = W_CachePatchName("RAILIND", PU_HUDGFX);
-	jumpshield = W_CachePatchName("TVWWC0", PU_HUDGFX);
-	forceshield = W_CachePatchName("TVFOC0", PU_HUDGFX);
-	ringshield = W_CachePatchName("TVATC0", PU_HUDGFX);
-	watershield = W_CachePatchName("TVELC0", PU_HUDGFX);
-	bombshield = W_CachePatchName("TVARC0", PU_HUDGFX);
-	pityshield = W_CachePatchName("TVPIC0", PU_HUDGFX);
-	flameshield = W_CachePatchName("TVFLC0", PU_HUDGFX);
-	bubbleshield = W_CachePatchName("TVBBC0", PU_HUDGFX);
-	thundershield = W_CachePatchName("TVZPC0", PU_HUDGFX);
-	invincibility = W_CachePatchName("TVIVC0", PU_HUDGFX);
-	sneakers = W_CachePatchName("TVSSC0", PU_HUDGFX);
-	gravboots = W_CachePatchName("TVGVC0", PU_HUDGFX);
+	jumpshield = W_CachePatchName("TVWWICON", PU_HUDGFX);
+	forceshield = W_CachePatchName("TVFOICON", PU_HUDGFX);
+	ringshield = W_CachePatchName("TVATICON", PU_HUDGFX);
+	watershield = W_CachePatchName("TVELICON", PU_HUDGFX);
+	bombshield = W_CachePatchName("TVARICON", PU_HUDGFX);
+	pityshield = W_CachePatchName("TVPIICON", PU_HUDGFX);
+	pinkshield = W_CachePatchName("TVPPICON", PU_HUDGFX);
+	flameshield = W_CachePatchName("TVFLICON", PU_HUDGFX);
+	bubbleshield = W_CachePatchName("TVBBICON", PU_HUDGFX);
+	thundershield = W_CachePatchName("TVZPICON", PU_HUDGFX);
+	invincibility = W_CachePatchName("TVIVICON", PU_HUDGFX);
+	sneakers = W_CachePatchName("TVSSICON", PU_HUDGFX);
+	gravboots = W_CachePatchName("TVGVICON", PU_HUDGFX);
 
 	tagico = W_CachePatchName("TAGICO", PU_HUDGFX);
 	rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
@@ -306,6 +303,7 @@ void ST_LoadGraphics(void)
 	gotrflag = W_CachePatchName("GOTRFLAG", PU_HUDGFX);
 	gotbflag = W_CachePatchName("GOTBFLAG", PU_HUDGFX);
 	nonicon = W_CachePatchName("NONICON", PU_HUDGFX);
+	nonicon2 = W_CachePatchName("NONICON2", PU_HUDGFX);
 
 	// NiGHTS HUD things
 	bluestat = W_CachePatchName("BLUESTAT", PU_HUDGFX);
@@ -314,9 +312,14 @@ void ST_LoadGraphics(void)
 	redstat = W_CachePatchName("REDSTAT", PU_HUDGFX);
 	yelstat = W_CachePatchName("YELSTAT", PU_HUDGFX);
 	nbracket = W_CachePatchName("NBRACKET", PU_HUDGFX);
+	nring = W_CachePatchName("NRNG1", PU_HUDGFX);
 	for (i = 0; i < 12; ++i)
+	{
 		nhud[i] = W_CachePatchName(va("NHUD%d", i+1), PU_HUDGFX);
+		nbon[i] = W_CachePatchName(va("NBON%d", i+1), PU_HUDGFX);
+	}
 	nsshud = W_CachePatchName("NSSHUD", PU_HUDGFX);
+	nssbon = W_CachePatchName("NSSBON", PU_HUDGFX);
 	minicaps = W_CachePatchName("MINICAPS", PU_HUDGFX);
 
 	for (i = 0; i < 8; ++i)
@@ -340,10 +343,24 @@ void ST_LoadGraphics(void)
 }
 
 // made separate so that skins code can reload custom face graphics
-void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 skinnum)
+void ST_LoadFaceGraphics(INT32 skinnum)
 {
-	faceprefix[skinnum] = W_CachePatchName(facestr, PU_HUDGFX);
-	superprefix[skinnum] = W_CachePatchName(superstr, PU_HUDGFX);
+	if (skins[skinnum].sprites[SPR2_XTRA].numframes)
+	{
+		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+		spriteframe_t *sprframe = &sprdef->spriteframes[0];
+		faceprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX);
+		if (skins[skinnum].sprites[(SPR2_XTRA|FF_SPR2SUPER)].numframes)
+		{
+			sprdef = &skins[skinnum].sprites[SPR2_XTRA|FF_SPR2SUPER];
+			sprframe = &sprdef->spriteframes[0];
+			superprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX);
+		}
+		else
+			superprefix[skinnum] = faceprefix[skinnum]; // not manually freed, okay to set to same pointer
+	}
+	else
+		faceprefix[skinnum] = superprefix[skinnum] = W_CachePatchName("MISSING", PU_HUDGFX); // ditto
 	facefreed[skinnum] = false;
 }
 
@@ -352,7 +369,7 @@ void ST_ReloadSkinFaceGraphics(void)
 	INT32 i;
 
 	for (i = 0; i < numskins; i++)
-		ST_LoadFaceGraphics(skins[i].face, skins[i].superface, i);
+		ST_LoadFaceGraphics(i);
 }
 
 static inline void ST_InitData(void)
@@ -418,89 +435,13 @@ void ST_changeDemoView(void)
 
 boolean st_overlay;
 
-static INT32 SCZ(INT32 z)
-{
-	return FixedInt(FixedMul(z<<FRACBITS, vid.fdupy));
-}
-
-static INT32 SCY(INT32 y)
-{
-	//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	y = SCZ(y); // scale to resolution
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += vid.height / 2;
-	}
-	return y;
-}
-
-static INT32 STRINGY(INT32 y)
-{
-	//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += BASEVIDHEIGHT / 2;
-	}
-	return y;
-}
-
-static INT32 SPLITFLAGS(INT32 f)
-{
-	// Pass this V_SNAPTO(TOP|BOTTOM) and it'll trim them to account for splitscreen! -Red
-	if (splitscreen)
-	{
-		if (stplyr != &players[displayplayer])
-			f &= ~V_SNAPTOTOP;
-		else
-			f &= ~V_SNAPTOBOTTOM;
-	}
-	return f;
-}
-
-static INT32 SCX(INT32 x)
-{
-	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
-}
-
-#if 0
-static INT32 SCR(INT32 r)
-{
-	fixed_t y;
-		//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	y = FixedMul(r*FRACUNIT, vid.fdupy); // scale to resolution
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += vid.height / 2;
-	}
-	return FixedInt(FixedDiv(y, vid.fdupy));
-}
-#endif
-
 // =========================================================================
 //                          INTERNAL DRAWING
 // =========================================================================
-#define ST_DrawOverlayNum(x,y,n)           V_DrawTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n)
-#define ST_DrawPaddedOverlayNum(x,y,n,d)   V_DrawPaddedTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n, d)
-#define ST_DrawOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p)
-#define ST_DrawMappedOverlayPatch(x,y,p,c) V_DrawMappedScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p, c)
-#define ST_DrawNumFromHud(h,n,f)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n)
-#define ST_DrawPadNumFromHud(h,n,q,f)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n, q)
-#define ST_DrawPatchFromHud(h,p,f)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, p)
-#define ST_DrawNumFromHudWS(h,n,f)      V_DrawTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n)
-#define ST_DrawPadNumFromHudWS(h,n,q,f) V_DrawPaddedTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n, q)
-#define ST_DrawPatchFromHudWS(h,p,f)    V_DrawScaledPatch(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, p)
+#define ST_DrawTopLeftOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, p)
+#define ST_DrawNumFromHud(h,n,flags)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, n)
+#define ST_DrawPadNumFromHud(h,n,q,flags)   V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, n, q)
+#define ST_DrawPatchFromHud(h,p,flags)      V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, p)
 
 // Draw a number, scaled, over the view, maybe with set translucency
 // Always draw the number completely since it's overlay
@@ -537,100 +478,203 @@ static void ST_DrawNightsOverlayNum(fixed_t x /* right border */, fixed_t y, fix
 // Devmode information
 static void ST_drawDebugInfo(void)
 {
-	INT32 height = 192;
-	INT32 dist = 8;
+	INT32 height = 0, h = 8, w = 18, lowh;
+	void (*textfunc)(INT32, INT32, INT32, const char *);
 
 	if (!(stplyr->mo && cv_debug))
 		return;
 
-	if (cv_ticrate.value)
+#define VFLAGS V_MONOSPACE|V_SNAPTOTOP|V_SNAPTORIGHT
+
+	if ((moviemode == MM_GIF && cv_gif_downscale.value) || vid.dupx == 1)
 	{
-		height -= 12;
-		dist >>= 1;
+		textfunc = V_DrawRightAlignedString;
+		lowh = ((vid.height/vid.dupy) - 16);
+	}
+	else
+	{
+		textfunc = V_DrawRightAlignedSmallString;
+		h /= 2;
+		w /= 2;
+		lowh = 0;
 	}
 
-	if (cv_debug & DBG_BASIC)
+#define V_DrawDebugLine(str) if (lowh && (height > lowh))\
+							{\
+								V_DrawRightAlignedThinString(320,  8+lowh, VFLAGS|V_REDMAP, "SOME INFO NOT VISIBLE");\
+								return;\
+							}\
+							textfunc(320, height, VFLAGS, str);\
+							height += h;
+
+#define V_DrawDebugFlag(f, str) textfunc(width, height, VFLAGS|f, str);\
+								width -= w
+
+	if (cv_debug & DBG_MEMORY)
 	{
-		const fixed_t d = AngleFixed(stplyr->mo->angle);
-		V_DrawRightAlignedString(320, height - 24, V_MONOSPACE, va("X: %6d", stplyr->mo->x>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Y: %6d", stplyr->mo->y>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("Z: %6d", stplyr->mo->z>>FRACBITS));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("A: %6d", FixedInt(d)));
+		V_DrawDebugLine(va("Heap: %8sKB", sizeu1(Z_TotalUsage()>>10)));
 
-		height -= (32+dist);
+		height += h/2;
 	}
 
-	if (cv_debug & DBG_DETAILED)
+	if (cv_debug & DBG_RANDOMIZER) // randomizer testing
 	{
-		V_DrawRightAlignedString(320, height - 104, V_MONOSPACE, va("SHIELD: %5x", stplyr->powers[pw_shield]));
-		V_DrawRightAlignedString(320, height - 96,  V_MONOSPACE, va("SCALE: %5d%%", (stplyr->mo->scale*100)/FRACUNIT));
-		V_DrawRightAlignedString(320, height - 88,  V_MONOSPACE, va("CARRY: %5x", stplyr->powers[pw_carry]));
-		V_DrawRightAlignedString(320, height - 80,  V_MONOSPACE, va("AIR: %4d, %3d", stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]));
+		fixed_t peekres = P_RandomPeek();
+		peekres *= 10000;     // Change from fixed point
+		peekres >>= FRACBITS; // to displayable decimal
 
-		// Flags
-		V_DrawRightAlignedString(304-92, height - 72, V_MONOSPACE, "PF:");
-		V_DrawString(304-90,             height - 72, (stplyr->pflags & PF_STARTJUMP) ? V_GREENMAP : V_REDMAP, "SJ");
-		V_DrawString(304-72,             height - 72, (stplyr->pflags & PF_JUMPED) ? V_GREENMAP : V_REDMAP, "JD");
-		V_DrawString(304-54,             height - 72, (stplyr->pflags & PF_SPINNING) ? V_GREENMAP : V_REDMAP, "SP");
-		V_DrawString(304-36,             height - 72, (stplyr->pflags & PF_STARTDASH) ? V_GREENMAP : V_REDMAP, "ST");
-		V_DrawString(304-18,             height - 72, (stplyr->pflags & PF_THOKKED) ? V_GREENMAP : V_REDMAP, "TH");
-		V_DrawString(304,                height - 72, (stplyr->pflags & PF_SHIELDABILITY) ? V_GREENMAP : V_REDMAP, "SH");
+		V_DrawDebugLine(va("Init: %08x", P_GetInitSeed()));
+		V_DrawDebugLine(va("Seed: %08x", P_GetRandSeed()));
+		V_DrawDebugLine(va("==  :    .%04d", peekres));
 
-		V_DrawRightAlignedString(320, height - 64, V_MONOSPACE, va("CEILZ: %6d", stplyr->mo->ceilingz>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 56, V_MONOSPACE, va("FLOORZ: %6d", stplyr->mo->floorz>>FRACBITS));
+		height += h/2;
+	}
 
-		V_DrawRightAlignedString(320, height - 48, V_MONOSPACE, va("CNVX: %6d", stplyr->cmomx>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 40, V_MONOSPACE, va("CNVY: %6d", stplyr->cmomy>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 32, V_MONOSPACE, va("PLTZ: %6d", stplyr->mo->pmomz>>FRACBITS));
+	if (cv_debug & DBG_PLAYER)
+	{
+		INT32 width = 320;
+		const fixed_t d = AngleFixed(stplyr->drawangle);
 
-		V_DrawRightAlignedString(320, height - 24, V_MONOSPACE, va("MOMX: %6d", stplyr->rmomx>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("MOMY: %6d", stplyr->rmomy>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("MOMZ: %6d", stplyr->mo->momz>>FRACBITS));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("SPEED: %6d", stplyr->speed>>FRACBITS));
+		V_DrawDebugLine(va("SHIELD: %5x", stplyr->powers[pw_shield]));
+		V_DrawDebugLine(va("SCALE: %5d%%", (stplyr->mo->scale*100)>>FRACBITS));
+		V_DrawDebugLine(va("CARRY: %5x", stplyr->powers[pw_carry]));
+		V_DrawDebugLine(va("AIR: %4d, %3d", stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]));
+		V_DrawDebugLine(va("ABILITY: %3d, %3d", stplyr->charability, stplyr->charability2));
+		V_DrawDebugLine(va("ACTIONSPD: %5d", stplyr->actionspd>>FRACBITS));
+		V_DrawDebugLine(va("PEEL: %3d", stplyr->dashmode));
+		V_DrawDebugLine(va("SCOREADD: %3d", stplyr->scoreadd));
 
-		height -= (112+dist);
+		// Flags
+		V_DrawDebugFlag(((stplyr->pflags & PF_SHIELDABILITY)  ? V_GREENMAP : V_REDMAP), "SH");
+		V_DrawDebugFlag(((stplyr->pflags & PF_THOKKED)        ? V_GREENMAP : V_REDMAP), "TH");
+		V_DrawDebugFlag(((stplyr->pflags & PF_STARTDASH)      ? V_GREENMAP : V_REDMAP), "ST");
+		V_DrawDebugFlag(((stplyr->pflags & PF_SPINNING)       ? V_GREENMAP : V_REDMAP), "SP");
+		V_DrawDebugFlag(((stplyr->pflags & PF_NOJUMPDAMAGE)   ? V_GREENMAP : V_REDMAP), "ND");
+		V_DrawDebugFlag(((stplyr->pflags & PF_JUMPED)         ? V_GREENMAP : V_REDMAP), "JD");
+		V_DrawDebugFlag(((stplyr->pflags & PF_STARTJUMP)      ? V_GREENMAP : V_REDMAP), "SJ");
+		V_DrawDebugFlag(0, "PF/SF:");
+		height += h;
+		width = 320;
+		V_DrawDebugFlag(((stplyr->pflags & PF_INVIS)          ? V_GREENMAP : V_REDMAP), "*I");
+		V_DrawDebugFlag(((stplyr->pflags & PF_NOCLIP)         ? V_GREENMAP : V_REDMAP), "*C");
+		V_DrawDebugFlag(((stplyr->pflags & PF_GODMODE)        ? V_GREENMAP : V_REDMAP), "*G");
+		V_DrawDebugFlag(((stplyr->charflags & SF_SUPER)       ? V_GREENMAP : V_REDMAP), "SU");
+		V_DrawDebugFlag(((stplyr->pflags & PF_APPLYAUTOBRAKE) ? V_GREENMAP : V_REDMAP), "AA");
+		V_DrawDebugFlag(((stplyr->pflags & PF_SLIDING)        ? V_GREENMAP : V_REDMAP), "SL");
+		V_DrawDebugFlag(((stplyr->pflags & PF_BOUNCING)       ? V_GREENMAP : V_REDMAP), "BO");
+		V_DrawDebugFlag(((stplyr->pflags & PF_GLIDING)        ? V_GREENMAP : V_REDMAP), "GL");
+		height += h;
+
+		V_DrawDebugLine(va("DRAWANGLE: %6d", FixedInt(d)));
+
+		height += h/2;
 	}
 
-	if (cv_debug & DBG_RANDOMIZER) // randomizer testing
+	if (cv_debug & DBG_DETAILED)
 	{
-		fixed_t peekres = P_RandomPeek();
-		peekres *= 10000;     // Change from fixed point
-		peekres >>= FRACBITS; // to displayable decimal
+		INT32 width = 320;
+
+		V_DrawDebugLine(va("CEILINGZ: %6d", stplyr->mo->ceilingz>>FRACBITS));
+		V_DrawDebugLine(va("FLOORZ: %6d", stplyr->mo->floorz>>FRACBITS));
+
+		V_DrawDebugLine(va("CMOMX: %6d", stplyr->cmomx>>FRACBITS));
+		V_DrawDebugLine(va("CMOMY: %6d", stplyr->cmomy>>FRACBITS));
+		V_DrawDebugLine(va("PMOMZ: %6d", stplyr->mo->pmomz>>FRACBITS));
 
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Init: %08x", P_GetInitSeed()));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("Seed: %08x", P_GetRandSeed()));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("==  :    .%04d", peekres));
+		width = 320;
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_APPLYPMOMZ)      ? V_GREENMAP : V_REDMAP), "AP");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_SPRUNG)          ? V_GREENMAP : V_REDMAP), "SP");
+		//V_DrawDebugFlag(((stplyr->mo->eflags & MFE_PUSHED)          ? V_GREENMAP : V_REDMAP), "PU"); -- not relevant to players
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_GOOWATER)        ? V_GREENMAP : V_REDMAP), "GW");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_VERTICALFLIP)    ? V_GREENMAP : V_REDMAP), "VF");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_JUSTSTEPPEDDOWN) ? V_GREENMAP : V_REDMAP), "JS");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_UNDERWATER)      ? V_GREENMAP : V_REDMAP), "UW");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_TOUCHWATER)      ? V_GREENMAP : V_REDMAP), "TW");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_JUSTHITFLOOR)    ? V_GREENMAP : V_REDMAP), "JH");
+		V_DrawDebugFlag(((stplyr->mo->eflags & MFE_ONGROUND)        ? V_GREENMAP : V_REDMAP), "OG");
+		V_DrawDebugFlag(0, "MFE:");
+		height += h;
 
-		height -= (24+dist);
+		V_DrawDebugLine(va("MOMX: %6d", stplyr->rmomx>>FRACBITS));
+		V_DrawDebugLine(va("MOMY: %6d", stplyr->rmomy>>FRACBITS));
+		V_DrawDebugLine(va("MOMZ: %6d", stplyr->mo->momz>>FRACBITS));
+
+		V_DrawDebugLine(va("SPEED: %6d", stplyr->speed>>FRACBITS));
+
+		height += h/2;
 	}
 
-	if (cv_debug & DBG_MEMORY)
+	if (cv_debug & DBG_BASIC)
 	{
-		V_DrawRightAlignedString(320, height,     V_MONOSPACE, va("Heap: %7sKB", sizeu1(Z_TagsUsage(0, INT32_MAX)>>10)));
+		const fixed_t d = AngleFixed(stplyr->mo->angle);
+		V_DrawDebugLine(va("X: %6d", stplyr->mo->x>>FRACBITS));
+		V_DrawDebugLine(va("Y: %6d", stplyr->mo->y>>FRACBITS));
+		V_DrawDebugLine(va("Z: %6d", stplyr->mo->z>>FRACBITS));
+		V_DrawDebugLine(va("A: %6d", FixedInt(d)));
+
+		//height += h/2;
 	}
+
+#undef V_DrawDebugFlag
+#undef V_DrawDebugLine
+#undef VFLAGS
 }
 
 static void ST_drawScore(void)
 {
+	if (F_GetPromptHideHud(hudinfo[HUD_SCORE].y))
+		return;
+
 	// SCORE:
 	ST_DrawPatchFromHud(HUD_SCORE, sboscore, V_HUDTRANS);
 	if (objectplacing)
 	{
 		if (op_displayflags > UINT16_MAX)
-			ST_DrawOverlayPatch(SCX(hudinfo[HUD_SCORENUM].x-tallminus->width), SCY(hudinfo[HUD_SCORENUM].y), tallminus);
+			ST_DrawTopLeftOverlayPatch((hudinfo[HUD_SCORENUM].x-tallminus->width), hudinfo[HUD_SCORENUM].y, tallminus);
 		else
 			ST_DrawNumFromHud(HUD_SCORENUM, op_displayflags, V_HUDTRANS);
 	}
 	else
-		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score,V_HUDTRANS);
+		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score, V_HUDTRANS);
+}
+
+static void ST_drawRaceNum(INT32 time)
+{
+	INT32 height, bounce;
+	patch_t *racenum;
+
+	time += TICRATE;
+	height = ((3*BASEVIDHEIGHT)>>2) - 8;
+	bounce = TICRATE - (1 + (time % TICRATE));
+
+	switch (time/TICRATE)
+	{
+		case 3:
+			racenum = race3;
+			break;
+		case 2:
+			racenum = race2;
+			break;
+		case 1:
+			racenum = race1;
+			break;
+		default:
+			racenum = racego;
+			break;
+	}
+	if (bounce < 3)
+	{
+		height -= (2 - bounce);
+		if (!(P_AutoPause() || paused) && !bounce)
+				S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
+	}
+	V_DrawScaledPatch(((BASEVIDWIDTH - SHORT(racenum->width))/2), height, V_PERPLAYER, racenum);
 }
 
 static void ST_drawTime(void)
 {
 	INT32 seconds, minutes, tictrn, tics;
-
-	// TIME:
-	ST_DrawPatchFromHudWS(HUD_TIME, ((mapheaderinfo[gamemap-1]->countdown && countdowntimer < 11*TICRATE && leveltime/5 & 1) ? sboredtime : sbotime), V_HUDTRANS);
+	boolean downwards = false;
 
 	if (objectplacing)
 	{
@@ -641,21 +685,73 @@ static void ST_drawTime(void)
 	}
 	else
 	{
-		tics = (mapheaderinfo[gamemap-1]->countdown ? countdowntimer : stplyr->realtime);
-		seconds = G_TicsToSeconds(tics);
+		// Counting down the hidetime?
+		if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (stplyr->realtime <= (hidetime*TICRATE)))
+		{
+			tics = (hidetime*TICRATE - stplyr->realtime);
+			if (tics < 3*TICRATE)
+				ST_drawRaceNum(tics);
+			downwards = true;
+		}
+		else
+		{
+			// Hidetime finish!
+			if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (stplyr->realtime < ((hidetime+1)*TICRATE)))
+				ST_drawRaceNum(hidetime*TICRATE - stplyr->realtime);
+
+			// Time limit?
+			if (gametype != GT_COOP && gametype != GT_RACE && gametype != GT_COMPETITION && cv_timelimit.value && timelimitintics > 0)
+			{
+				if (timelimitintics >= stplyr->realtime)
+				{
+					tics = (timelimitintics - stplyr->realtime);
+					if (tics < 3*TICRATE)
+						ST_drawRaceNum(tics);
+				}
+				else // Overtime!
+					tics = 0;
+				downwards = true;
+			}
+			// Post-hidetime normal.
+			else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+				tics = stplyr->realtime - hidetime*TICRATE;
+			// "Shadow! What are you doing? Hurry and get back here
+			// right now before the island blows up with you on it!"
+			// "Blows up??" *awkward silence* "I've got to get outta
+			// here and find Amy and Tails right away!"
+			else if (mapheaderinfo[gamemap-1]->countdown)
+			{
+				tics = countdowntimer;
+				downwards = true;
+			}
+			// Normal.
+			else
+				tics = stplyr->realtime;
+		}
+
 		minutes = G_TicsToMinutes(tics, true);
+		seconds = G_TicsToSeconds(tics);
 		tictrn  = G_TicsToCentiseconds(tics);
 	}
 
-	if (cv_timetic.value == 1) // Tics only -- how simple is this?
-		ST_DrawNumFromHudWS(HUD_SECONDS, tics, V_HUDTRANS);
+	if (F_GetPromptHideHud(hudinfo[HUD_TIME].y))
+		return;
+
+	// TIME:
+	ST_DrawPatchFromHud(HUD_TIME, ((downwards && (tics < 30*TICRATE) && (leveltime/5 & 1)) ? sboredtime : sbotime), V_HUDTRANS);
+
+	if (!tics && downwards && (leveltime/5 & 1)) // overtime!
+		return;
+
+	if (cv_timetic.value == 3) // Tics only -- how simple is this?
+		ST_DrawNumFromHud(HUD_SECONDS, tics, V_HUDTRANS);
 	else
 	{
-		ST_DrawNumFromHudWS(HUD_MINUTES, minutes, V_HUDTRANS); // Minutes
-		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
-		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
+		ST_DrawNumFromHud(HUD_MINUTES, minutes, V_HUDTRANS); // Minutes
+		ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
+		ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
 
-		if (!splitscreen && (cv_timetic.value == 2 || cv_timetic.value == 3 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
+		if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
 			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period
 			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics
@@ -667,50 +763,51 @@ static inline void ST_drawRings(void)
 {
 	INT32 ringnum;
 
-	ST_DrawPatchFromHudWS(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+	if (F_GetPromptHideHud(hudinfo[HUD_RINGS].y))
+		return;
 
-	if (objectplacing)
-		ringnum = op_currentdoomednum;
-	else if (!useNightsSS && G_IsSpecialStage(gamemap))
-	{
-		INT32 i;
-		ringnum = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && players[i].mo && players[i].rings > 0)
-				ringnum += players[i].rings;
-	}
-	else
-		ringnum = max(stplyr->rings, 0);
+	ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+
+	ringnum = ((objectplacing) ? op_currentdoomednum : max(stplyr->rings, 0));
 
-	if (!splitscreen && cv_timetic.value == 3) // Yes, even in modeattacking
-		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+	if (cv_timetic.value == 2) // Yes, even in modeattacking
+		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 	else
-		ST_DrawNumFromHudWS(HUD_RINGSNUM, ringnum, ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+		ST_DrawNumFromHud(HUD_RINGSNUM, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 }
 
-static void ST_drawLives(void)
+static void ST_drawLivesArea(void)
 {
-	const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0);
-	INT32 livescount;
+	INT32 v_colmap = V_YELLOWMAP, livescount;
 	boolean notgreyedout;
 
 	if (!stplyr->skincolor)
 		return; // Just joined a server, skin isn't loaded yet!
 
+	if (F_GetPromptHideHud(hudinfo[HUD_LIVES].y))
+		return;
+
 	// face background
-	V_DrawSmallScaledPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, livesback);
+	V_DrawSmallScaledPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+		hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, livesback);
 
 	// face
-	if (stplyr->mo && stplyr->mo->color)
+	if (stplyr->spectator)
+	{
+		// spectator face
+		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, SKINCOLOR_CLOUDY, GTC_CACHE);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANSHALF, faceprefix[stplyr->skin], colormap);
+	}
+	else if (stplyr->mo && stplyr->mo->color)
 	{
 		// skincolor face/super
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->color, GTC_CACHE);
 		patch_t *face = faceprefix[stplyr->skin];
 		if (stplyr->powers[pw_super])
 			face = superprefix[stplyr->skin];
-		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,face, colormap);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, face, colormap);
 		if (cv_translucenthud.value == 10 && stplyr->powers[pw_super] == 1 && stplyr->mo->tracer)
 		{
 			INT32 v_supertrans = (stplyr->mo->tracer->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
@@ -718,8 +815,8 @@ static void ST_drawLives(void)
 			{
 				v_supertrans <<= V_ALPHASHIFT;
 				colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->tracer->color, GTC_CACHE);
-				V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-					V_SNAPTOLEFT|V_SNAPTOBOTTOM|v_supertrans|v_splitflag,face, colormap);
+				V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+					hudinfo[HUD_LIVES].f|V_PERPLAYER|v_supertrans, face, colormap);
 			}
 		}
 	}
@@ -727,94 +824,161 @@ static void ST_drawLives(void)
 	{
 		// skincolor face
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE);
-		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,faceprefix[stplyr->skin], colormap);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, faceprefix[stplyr->skin], colormap);
 	}
 
-	// name
-	if (strlen(skins[stplyr->skin].hudname) > 8)
-		V_DrawThinString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
-			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
-	else
-		V_DrawString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
-			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
-	// x
-	V_DrawScaledPatch(hudinfo[HUD_LIVESX].x, hudinfo[HUD_LIVESX].y + (v_splitflag ? -4 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, stlivex);
-
-	// lives number
-	if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
+	// Lives number
+	if (G_GametypeUsesLives() || gametype == GT_RACE)
 	{
-		INT32 i;
-		livescount = 0;
-		notgreyedout = (stplyr->lives > 0);
-		for (i = 0; i < MAXPLAYERS; i++)
+		// x
+		V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, stlivex);
+
+		// lives number
+		if (gametype == GT_RACE)
 		{
-			if (!playeringame[i])
-				continue;
+			livescount = INFLIVES;
+			notgreyedout = true;
+		}
+		else if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
+		{
+			INT32 i;
+			livescount = 0;
+			notgreyedout = (stplyr->lives > 0);
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
 
-			if (players[i].lives < 1)
-				continue;
+				if (players[i].lives < 1)
+					continue;
 
-			if (players[i].lives > 1)
-				notgreyedout = true;
+				if (players[i].lives > 1)
+					notgreyedout = true;
 
-			if (players[i].lives == 0x7f)
-			{
-				livescount = 0x7f;
-				break;
+				if (players[i].lives == INFLIVES)
+				{
+					livescount = INFLIVES;
+					break;
+				}
+				else if (livescount < 99)
+					livescount += (players[i].lives);
 			}
-			else if (livescount < 99)
-				livescount += (players[i].lives);
+		}
+		else
+		{
+			livescount = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? INFLIVES : stplyr->lives);
+			notgreyedout = true;
+		}
+
+		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 (livescount > 99)
+				livescount = 99;
+			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
+				hudinfo[HUD_LIVES].f|V_PERPLAYER|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF), va("%d",livescount));
 		}
 	}
-	else
+	// Spectator
+	else if (stplyr->spectator)
+		v_colmap = V_GRAYMAP;
+	// Tag
+	else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	{
+		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;
+		}
+	}
+	// Team name
+	else if (G_GametypeHasTeams())
 	{
-		livescount = stplyr->lives;
-		notgreyedout = true;
+		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");
+			v_colmap = V_REDMAP;
+		}
+		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");
+			v_colmap = V_BLUEMAP;
+		}
 	}
 
-	if (livescount == 0x7f)
-		V_DrawCharacter(hudinfo[HUD_LIVESNUM].x - 8, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), '\x16' | 0x80 | V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, false);
+	// name
+	v_colmap |= (V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER);
+	if (strlen(skins[stplyr->skin].hudname) <= 5)
+		V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
+	else if (V_ThinStringWidth(skins[stplyr->skin].hudname, v_colmap) <= 40)
+		V_DrawRightAlignedThinString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
 	else
+		V_DrawThinString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
+
+	// Power Stones collected
+	if (G_RingSlingerGametype()
+#ifdef HAVE_BLUA
+	&& LUA_HudEnabled(hud_powerstones)
+#endif
+	)
 	{
-		if (livescount > 99)
-			livescount = 99;
-		V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF)|v_splitflag,
-			((livescount > 99) ? "!!" : va("%d",livescount)));
+		INT32 workx = hudinfo[HUD_LIVES].x+1, j;
+		if ((leveltime & 1) && stplyr->powers[pw_invulnerability] && (stplyr->powers[pw_sneakers] == stplyr->powers[pw_invulnerability])) // hack; extremely unlikely to be activated unintentionally
+		{
+			for (j = 0; j < 7; ++j) // "super" indicator
+			{
+				V_DrawScaledPatch(workx, hudinfo[HUD_LIVES].y-9, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, emeraldpics[1][j]);
+				workx += 8;
+			}
+		}
+		else
+		{
+			for (j = 0; j < 7; ++j) // powerstones
+			{
+				if (stplyr->powers[pw_emeralds] & (1 << j))
+					V_DrawScaledPatch(workx, hudinfo[HUD_LIVES].y-9, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, emeraldpics[1][j]);
+				workx += 8;
+			}
+		}
 	}
 }
 
 static void ST_drawInput(void)
 {
-	//const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0); -- no splitscreen support - record attack only for base game
-	const UINT8 accent = (stplyr->skincolor ? Color_Index[stplyr->skincolor-1][4] : 0);
-	UINT8 col, offs;
+	const INT32 accent = V_SNAPTOLEFT|V_SNAPTOBOTTOM|(stplyr->skincolor ? Color_Index[stplyr->skincolor-1][4] : 0);
+	INT32 col;
+	UINT8 offs;
 
-	INT32 x = hudinfo[HUD_LIVESPIC].x, y = hudinfo[HUD_LIVESPIC].y;
+	INT32 x = hudinfo[HUD_LIVES].x, y = hudinfo[HUD_LIVES].y;
 
 	if (stplyr->powers[pw_carry] == CR_NIGHTSMODE)
 		y -= 16;
 
+	if (F_GetPromptHideHud(y))
+		return;
+
 	// O backing
-	V_DrawFill(x, y-1, 16, 16, 20);
-	V_DrawFill(x, y+15, 16, 1, 29);
+	V_DrawFill(x, y-1, 16, 16, hudinfo[HUD_LIVES].f|20);
+	V_DrawFill(x, y+15, 16, 1, hudinfo[HUD_LIVES].f|29);
 
 	if (cv_showinputjoy.value) // joystick render!
 	{
-		/*V_DrawFill(x   , y   , 16,  1, 16);
-		V_DrawFill(x   , y+15, 16,  1, 16);
-		V_DrawFill(x   , y+ 1,  1, 14, 16);
-		V_DrawFill(x+15, y+ 1,  1, 14, 16); -- red's outline*/
+		/*V_DrawFill(x   , y   , 16,  1, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x   , y+15, 16,  1, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x   , y+ 1,  1, 14, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x+15, y+ 1,  1, 14, hudinfo[HUD_LIVES].f|16); -- red's outline*/
 		if (stplyr->cmd.sidemove || stplyr->cmd.forwardmove)
 		{
 			// joystick hole
-			V_DrawFill(x+5, y+4, 6, 6, 29);
+			V_DrawFill(x+5, y+4, 6, 6, hudinfo[HUD_LIVES].f|29);
 			// joystick top
 			V_DrawFill(x+3+stplyr->cmd.sidemove/12,
 				y+2-stplyr->cmd.forwardmove/12,
-				10, 10, 29);
+				10, 10, hudinfo[HUD_LIVES].f|29);
 			V_DrawFill(x+3+stplyr->cmd.sidemove/9,
 				y+1-stplyr->cmd.forwardmove/9,
 				10, 10, accent);
@@ -822,10 +986,10 @@ static void ST_drawInput(void)
 		else
 		{
 			// just a limited, greyed out joystick top
-			V_DrawFill(x+3, y+11, 10, 1, 29);
+			V_DrawFill(x+3, y+11, 10, 1, hudinfo[HUD_LIVES].f|29);
 			V_DrawFill(x+3,
 				y+1,
-				10, 10, 16);
+				10, 10, hudinfo[HUD_LIVES].f|16);
 		}
 	}
 	else // arrows!
@@ -839,10 +1003,10 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x- 2, y+10,  6,  1, 29);
-			V_DrawFill(x+ 4, y+ 9,  1,  1, 29);
-			V_DrawFill(x+ 5, y+ 8,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x- 2, y+10,  6,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 4, y+ 9,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 5, y+ 8,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x- 2, y+ 5-offs,  6,  6, col);
 		V_DrawFill(x+ 4, y+ 6-offs,  1,  4, col);
@@ -857,12 +1021,12 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+ 5, y+ 3,  1,  1, 29);
-			V_DrawFill(x+ 6, y+ 4,  1,  1, 29);
-			V_DrawFill(x+ 7, y+ 5,  2,  1, 29);
-			V_DrawFill(x+ 9, y+ 4,  1,  1, 29);
-			V_DrawFill(x+10, y+ 3,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+ 5, y+ 3,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 6, y+ 4,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 7, y+ 5,  2,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 9, y+ 4,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+10, y+ 3,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+ 5, y- 2-offs,  6,  6, col);
 		V_DrawFill(x+ 6, y+ 4-offs,  4,  1, col);
@@ -877,10 +1041,10 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+12, y+10,  6,  1, 29);
-			V_DrawFill(x+11, y+ 9,  1,  1, 29);
-			V_DrawFill(x+10, y+ 8,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+12, y+10,  6,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+11, y+ 9,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+10, y+ 8,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+12, y+ 5-offs,  6,  6, col);
 		V_DrawFill(x+11, y+ 6-offs,  1,  4, col);
@@ -895,8 +1059,8 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+ 5, y+17,  6,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+ 5, y+17,  6,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+ 5, y+12-offs,  6,  6, col);
 		V_DrawFill(x+ 6, y+11-offs,  4,  1, col);
@@ -912,16 +1076,16 @@ static void ST_drawInput(void)
 	else\
 	{\
 		offs = 1;\
-		col = 16;\
-		V_DrawFill(x+16+(xoffs), y+9+(yoffs), 10, 1, 29);\
+		col = hudinfo[HUD_LIVES].f|16;\
+		V_DrawFill(x+16+(xoffs), y+9+(yoffs), 10, 1, hudinfo[HUD_LIVES].f|29);\
 	}\
 	V_DrawFill(x+16+(xoffs), y+(yoffs)-offs, 10, 10, col);\
-	V_DrawCharacter(x+16+1+(xoffs), y+1+(yoffs)-offs, symb, false)
+	V_DrawCharacter(x+16+1+(xoffs), y+1+(yoffs)-offs, hudinfo[HUD_LIVES].f|symb, false)
 
 	drawbutt( 4,-3, BT_JUMP, 'J');
 	drawbutt(15,-3, BT_USE,  'S');
 
-	V_DrawFill(x+16+4, y+8, 21, 10, 20); // sundial backing
+	V_DrawFill(x+16+4, y+8, 21, 10, hudinfo[HUD_LIVES].f|20); // sundial backing
 	if (stplyr->mo)
 	{
 		UINT8 i, precision;
@@ -941,7 +1105,7 @@ static void ST_drawInput(void)
 		{
 			V_DrawFill(x+16+14-(i*xcomp)/precision,
 				y+12-(i*ycomp)/precision,
-				1, 1, 16);
+				1, 1, hudinfo[HUD_LIVES].f|16);
 		}
 
 		if (ycomp <= 0)
@@ -958,57 +1122,82 @@ static void ST_drawInput(void)
 		if (stplyr->pflags & PF_AUTOBRAKE)
 		{
 			V_DrawThinString(x, y,
+				hudinfo[HUD_LIVES].f|
 				((!stplyr->powers[pw_carry]
 				&& (stplyr->pflags & PF_APPLYAUTOBRAKE)
 				&& !(stplyr->cmd.sidemove || stplyr->cmd.forwardmove)
-				&& (stplyr->rmomx || stplyr->rmomy))
+				&& (stplyr->rmomx || stplyr->rmomy)
+				&& (!stplyr->capsule || (stplyr->capsule->reactiontime != (stplyr-players)+1)))
 				? 0 : V_GRAYMAP),
 				"AUTOBRAKE");
 			y -= 8;
 		}
 		if (stplyr->pflags & PF_ANALOGMODE)
 		{
-			V_DrawThinString(x, y, 0, "ANALOG");
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "ANALOG");
 			y -= 8;
 		}
 	}
 	if (!demosynced) // should always be last, so it doesn't push anything else around
-		V_DrawThinString(x, y, ((leveltime & 4) ? V_YELLOWMAP : V_REDMAP), "BAD DEMO!!");
+		V_DrawThinString(x, y, hudinfo[HUD_LIVES].f|((leveltime & 4) ? V_YELLOWMAP : V_REDMAP), "BAD DEMO!!");
 }
 
-static void ST_drawLevelTitle(void)
+void ST_drawLevelTitle(tic_t titletime)
 {
 	char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
 	char *subttl = mapheaderinfo[gamemap-1]->subttl;
 	INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
-	INT32 lvlttlxpos;
+	INT32 lvlttly, zoney, lvlttlxpos, ttlnumxpos, zonexpos;
 	INT32 subttlxpos = BASEVIDWIDTH/2;
-	INT32 ttlnumxpos;
-	INT32 zonexpos;
 
-	INT32 lvlttly;
-	INT32 zoney;
-
-	if (!(timeinmap > 2 && timeinmap-3 < 110))
+	if (!(titletime > 2 && titletime-3 < 110))
 		return;
 
+	lvlttlxpos = ((BASEVIDWIDTH/2) - (V_LevelNameWidth(lvlttl)/2));
+
 	if (actnum > 0)
-	{
-		ttlnum = W_CachePatchName(va("TTL%.2d", actnum), PU_CACHE);
-		lvlttlxpos = ((BASEVIDWIDTH/2) - (V_LevelNameWidth(lvlttl)/2)) - SHORT(ttlnum->width);
-	}
-	else
-		lvlttlxpos = ((BASEVIDWIDTH/2) - (V_LevelNameWidth(lvlttl)/2));
+		lvlttlxpos -= V_LevelActNumWidth(actnum);
 
 	ttlnumxpos = lvlttlxpos + V_LevelNameWidth(lvlttl);
 	zonexpos = ttlnumxpos - V_LevelNameWidth(M_GetText("ZONE"));
+	ttlnumxpos++;
 
 	if (lvlttlxpos < 0)
 		lvlttlxpos = 0;
 
+#if 0 // toaster's experiment. srb2&toast.exe one day, maybe? Requires stuff below to be converted to fixed point.
+#define MIDTTLY 79
+#define MIDZONEY 105
+#define MIDDIFF 4
+
+	if (titletime < 10)
+	{
+		fixed_t z = ((titletime - 3)<<FRACBITS)/7;
+		INT32 ttlh = V_LevelNameHeight(lvlttl);
+		zoney = (200<<FRACBITS) - ((200 - (MIDZONEY + MIDDIFF))*z);
+		lvlttly = ((MIDTTLY + ttlh - MIDDIFF)*z) - (ttlh<<FRACBITS);
+	}
+	else if (titletime < 105)
+	{
+		fixed_t z = (((titletime - 10)*MIDDIFF)<<(FRACBITS+1))/95;
+		zoney = ((MIDZONEY + MIDDIFF)<<FRACBITS) - z;
+		lvlttly = ((MIDTTLY - MIDDIFF)<<FRACBITS) + z;
+	}
+	else
+	{
+		fixed_t z = ((titletime - 105)<<FRACBITS)/7;
+		INT32 zoneh = V_LevelNameHeight(M_GetText("ZONE"));
+		zoney = (MIDZONEY + zoneh - MIDDIFF)*(FRACUNIT - z) - (zoneh<<FRACBITS);
+		lvlttly = ((MIDTTLY + MIDDIFF)<<FRACBITS) + ((200 - (MIDTTLY + MIDDIFF))*z);
+	}
+
+#undef MIDTTLY
+#undef MIDZONEY
+#undef MIDDIFF
+#else
 	// There's no consistent algorithm that can accurately define the old positions
-	// so I just ended up resorting to a single switct statement to define them
-	switch (timeinmap-3)
+	// so I just ended up resorting to a single switch statement to define them
+	switch (titletime-3)
 	{
 		case 0:   zoney = 200; lvlttly =   0; break;
 		case 1:   zoney = 188; lvlttly =  12; break;
@@ -1024,193 +1213,268 @@ static void ST_drawLevelTitle(void)
 		case 109: zoney =   0; lvlttly = 200; break;
 		default:  zoney = 104; lvlttly =  80; break;
 	}
+#endif
 
 	if (actnum)
-		V_DrawScaledPatch(ttlnumxpos, zoney, 0, ttlnum);
+		V_DrawLevelActNum(ttlnumxpos, zoney, V_PERPLAYER, actnum);
 
-	V_DrawLevelTitle(lvlttlxpos, lvlttly, 0, lvlttl);
+	V_DrawLevelTitle(lvlttlxpos, lvlttly, V_PERPLAYER, lvlttl);
 
 	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
-		V_DrawLevelTitle(zonexpos, zoney, 0, M_GetText("ZONE"));
+		V_DrawLevelTitle(zonexpos, zoney, V_PERPLAYER, M_GetText("ZONE"));
 
 	if (lvlttly+48 < 200)
-		V_DrawCenteredString(subttlxpos, lvlttly+48, V_ALLOWLOWERCASE, subttl);
+		V_DrawCenteredString(subttlxpos, lvlttly+48, V_PERPLAYER|V_ALLOWLOWERCASE, subttl);
 }
 
-static void ST_drawFirstPersonHUD(void)
+static void ST_drawPowerupHUD(void)
 {
-	player_t *player = stplyr;
 	patch_t *p = NULL;
 	UINT16 invulntime = 0;
+	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};
+#define ICONSEP (16+4) // matches weapon rings HUD
+
+	if (F_GetPromptHideHud(hudinfo[HUD_POWERUPS].y))
+		return;
 
-	if (player->playerstate != PST_LIVE)
+	if (stplyr->spectator || stplyr->playerstate != PST_LIVE)
 		return;
 
+// -------
+// Shields
+// -------
+
 	// Graue 06-18-2004: no V_NOSCALESTART, no SCX, no SCY, snap to right
-	if ((player->powers[pw_shield] & SH_NOSTACK & ~SH_FORCEHP) == SH_FORCE)
+	if (stplyr->powers[pw_shield] & SH_NOSTACK)
 	{
-		UINT8 i, max = (player->powers[pw_shield] & SH_FORCEHP);
-		for (i = 0; i <= max; i++)
+		shieldoffs[q] = ICONSEP;
+
+		if ((stplyr->powers[pw_shield] & SH_NOSTACK & ~SH_FORCEHP) == SH_FORCE)
 		{
-			INT32 flags = (V_SNAPTORIGHT|V_SNAPTOTOP)|((i == max) ? V_HUDTRANS : V_HUDTRANSHALF);
-			if (splitscreen)
-				V_DrawSmallScaledPatch(312-(3*i), STRINGY(24)+(3*i), flags, forceshield);
-			else
-				V_DrawScaledPatch(304-(3*i), 24+(3*i), flags, forceshield);
+			UINT8 i, max = (stplyr->powers[pw_shield] & SH_FORCEHP);
+			for (i = 0; i <= max; i++)
+			{
+				V_DrawSmallScaledPatch(offs-(i<<1), hudinfo[HUD_POWERUPS].y-(i<<1), (V_PERPLAYER|hudinfo[HUD_POWERUPS].f)|((i == max) ? V_HUDTRANS : V_HUDTRANSHALF), forceshield);
+			}
 		}
-	}
-	else switch (player->powers[pw_shield] & SH_NOSTACK)
-	{
-	case SH_WHIRLWIND:   p = jumpshield;    break;
-	case SH_ELEMENTAL:   p = watershield;   break;
-	case SH_ARMAGEDDON:  p = bombshield;    break;
-	case SH_ATTRACT:     p = ringshield;    break;
-	case SH_PITY:        p = pityshield;    break;
-	case SH_FLAMEAURA:   p = flameshield;   break;
-	case SH_BUBBLEWRAP:  p = bubbleshield;  break;
-	case SH_THUNDERCOIN: p = thundershield; break;
-	default: break;
-	}
+		else
+		{
+			switch (stplyr->powers[pw_shield] & SH_NOSTACK)
+			{
+				case SH_WHIRLWIND:   p = jumpshield;    break;
+				case SH_ELEMENTAL:   p = watershield;   break;
+				case SH_ARMAGEDDON:  p = bombshield;    break;
+				case SH_ATTRACT:     p = ringshield;    break;
+				case SH_PITY:        p = pityshield;    break;
+				case SH_PINK:        p = pinkshield;    break;
+				case SH_FLAMEAURA:   p = flameshield;   break;
+				case SH_BUBBLEWRAP:  p = bubbleshield;  break;
+				case SH_THUNDERCOIN: p = thundershield; break;
+				default: break;
+			}
 
-	if (p)
+			if (p)
+				V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, p);
+		}
+	}
+	else if (shieldoffs[q])
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
+		if (shieldoffs[q] > 1)
+			shieldoffs[q] = 2*shieldoffs[q]/3;
 		else
-			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
+			shieldoffs[q] = 0;
 	}
 
-	// pw_flashing just sets the icon to flash no matter what.
-	invulntime = player->powers[pw_flashing] ? 1 : player->powers[pw_invulnerability];
-	if (invulntime > 3*TICRATE || (invulntime && leveltime & 1))
+	offs -= shieldoffs[q];
+
+// ---------
+// CTF flags
+// ---------
+
+	// YOU have a flag. Display a monitor-like icon for it.
+	if (stplyr->gotflag)
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
-		else
-			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
+		flagoffs[q] = ICONSEP;
+		p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, p);
 	}
-
-	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers] && leveltime & 1))
+	else if (flagoffs[q])
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
-		else
-			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
-	}
-
-	p = NULL;
-
-	{
-		UINT32 airtime;
-		UINT32 frame = 0;
-		spriteframe_t *sprframe;
-		// If both air timers are active, use the air timer with the least time left
-		if (player->powers[pw_underwater] && player->powers[pw_spacetime])
-			airtime = min(player->powers[pw_underwater], player->powers[pw_spacetime]);
-		else // Use whichever one is active otherwise
-			airtime = (player->powers[pw_spacetime]) ? player->powers[pw_spacetime] : player->powers[pw_underwater];
-
-		if (!airtime)
-			return; // No air timers are active, nothing would be drawn anyway
-
-		airtime--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
-
-		if (airtime > 11*TICRATE)
-			return; // Not time to draw any drown numbers yet
-		// Choose which frame to use based on time left
-		if (airtime <= 11*TICRATE && airtime >= 10*TICRATE)
-			frame = 5;
-		else if (airtime <= 9*TICRATE && airtime >= 8*TICRATE)
-			frame = 4;
-		else if (airtime <= 7*TICRATE && airtime >= 6*TICRATE)
-			frame = 3;
-		else if (airtime <= 5*TICRATE && airtime >= 4*TICRATE)
-			frame = 2;
-		else if (airtime <= 3*TICRATE && airtime >= 2*TICRATE)
-			frame = 1;
-		else if (airtime <= 1*TICRATE && airtime > 0)
-			frame = 0;
+		if (flagoffs[q] > 1)
+			flagoffs[q] = 2*flagoffs[q]/3;
 		else
-			return; // Don't draw anything between numbers
-
-		if (player->charflags & SF_MACHINE)
-			frame += 6;  // Robots use different drown numbers
-
-		// Get the front angle patch for the frame
-		sprframe = &sprites[SPR_DRWN].spriteframes[frame];
-		p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+			flagoffs[q] = 0;
 	}
 
-	// Display the countdown drown numbers!
-	if (p)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset)), SCY(60 - SHORT(p->topoffset)),
-			V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, p);
-}
-
-static void ST_drawNightsRecords(void)
-{
-	INT32 aflag = 0;
+	offs -= flagoffs[q];
 
-	if (!stplyr->texttimer)
-		return;
+// --------------------
+// Timer-based powerups
+// --------------------
 
-	if (stplyr->texttimer < TICRATE/2)
-		aflag = (9 - 9*stplyr->texttimer/(TICRATE/2)) << V_ALPHASHIFT;
+#define DRAWTIMERICON(patch, timer) \
+	V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, patch); \
+	V_DrawRightAlignedThinString(offs + 16, hudinfo[HUD_POWERUPS].y + 8, V_PERPLAYER|hudinfo[HUD_POWERUPS].f, va("%d", timer/TICRATE));
 
-	// A "Bonus Time Start" by any other name...
-	if (stplyr->textvar == 1)
+	// Invincibility, both from monitor and after being hit
+	invulntime = stplyr->powers[pw_flashing] ? stplyr->powers[pw_flashing] : stplyr->powers[pw_invulnerability];
+	// Note: pw_flashing always makes the icon flicker regardless of time, unlike pw_invulnerability
+	if (stplyr->powers[pw_invulnerability] > 3*TICRATE || (invulntime && leveltime & 1))
 	{
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(52), V_GREENMAP|aflag, M_GetText("GET TO THE GOAL!"));
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60),            aflag, M_GetText("SCORE MULTIPLIER START!"));
+		DRAWTIMERICON(invincibility, invulntime)
+	}
 
-		if (stplyr->finishedtime)
-		{
-			V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(140), aflag, "TIME:");
-			V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
-			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", (stplyr->startedtime - stplyr->finishedtime)/TICRATE));
-			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", (stplyr->finishedtime/TICRATE) * 100));
-		}
+	if (invulntime > 7)
+		offs -= ICONSEP;
+	else
+	{
+		UINT8 a = ICONSEP, b = 7-invulntime;
+		while (b--)
+			a = 2*a/3;
+		offs -= a;
 	}
 
-	// Get n [more] Spheres
-	else if (stplyr->textvar <= 3 && stplyr->textvar >= 2)
+	// Super Sneakers
+	if (stplyr->powers[pw_sneakers] > 3*TICRATE || (stplyr->powers[pw_sneakers] && leveltime & 1))
 	{
-		if (!stplyr->capsule)
-			return;
+		DRAWTIMERICON(sneakers, stplyr->powers[pw_sneakers])
+	}
 
-		// Yes, this string is an abomination.
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), aflag,
-		                     va(M_GetText("\x80GET\x82 %d\x80 %s%s%s!"), stplyr->capsule->health,
-		                        (stplyr->textvar == 3) ? M_GetText("MORE ") : "",
-		                        (G_IsSpecialStage(gamemap)) ? "SPHERE" : "RING",
-		                        (stplyr->capsule->health > 1) ? "S" : ""));
+	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;
 	}
 
-	// End Bonus
-	else if (stplyr->textvar == 4)
+	// Gravity Boots
+	if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
 	{
-		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(140), aflag, (G_IsSpecialStage(gamemap)) ? "ORBS:" : "RINGS:");
-		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
-		ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 48)<<FRACBITS, STRINGY(160)<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
+		DRAWTIMERICON(gravboots, stplyr->powers[pw_gravityboots])
+	}
 
-		// If new record, say so!
-		if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
-		{
-			if (stplyr->texttimer & 16)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), V_YELLOWMAP|aflag, "* NEW RECORD *");
-		}
+#undef DRAWTIMERICON
+#undef ICONSEP
+}
 
-		if (P_HasGrades(gamemap, stplyr->lastmare + 1))
-		{
-			if (aflag)
-				V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), aflag,
-				                       ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
-			else
-				V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), 0,
-				                  ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
+static void ST_drawFirstPersonHUD(void)
+{
+	patch_t *p = NULL;
+	UINT32 airtime;
+	spriteframe_t *sprframe;
+	// If both air timers are active, use the air timer with the least time left
+	if (stplyr->powers[pw_underwater] && stplyr->powers[pw_spacetime])
+		airtime = min(stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]);
+	else // Use whichever one is active otherwise
+		airtime = (stplyr->powers[pw_spacetime]) ? stplyr->powers[pw_spacetime] : stplyr->powers[pw_underwater];
+
+	if (airtime < 1)
+		return; // No air timers are active, nothing would be drawn anyway
+
+	airtime--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
+
+	if (airtime > 11*TICRATE)
+		return; // Not time to draw any drown numbers yet
+
+	if (!((airtime > 10*TICRATE - 5)
+	|| (airtime <= 9*TICRATE && airtime > 8*TICRATE - 5)
+	|| (airtime <= 7*TICRATE && airtime > 6*TICRATE - 5)
+	|| (airtime <= 5*TICRATE && airtime > 4*TICRATE - 5)
+	|| (airtime <= 3*TICRATE && airtime > 2*TICRATE - 5)
+	|| (airtime <= 1*TICRATE)))
+		return; // Don't draw anything between numbers
+
+	if (!((airtime % 10) < 5))
+		return; // Keep in line with the flashing thing from third person.
+
+	airtime /= (2*TICRATE); // To be strictly accurate it'd need to be ((airtime/TICRATE) - 1)/2, but integer division rounds down for us
+
+	if (stplyr->charflags & SF_MACHINE)
+		airtime += 6;  // Robots use different drown numbers
+
+	// Get the front angle patch for the frame
+	sprframe = &sprites[SPR_DRWN].spriteframes[airtime];
+	p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+
+	// Display the countdown drown numbers!
+	if (p && !F_GetPromptHideHud(60 - SHORT(p->topoffset)))
+		V_DrawScaledPatch((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset), 60 - SHORT(p->topoffset),
+			V_PERPLAYER|V_PERPLAYER|V_TRANSLUCENT, p);
+}
+
+static void ST_drawNightsRecords(void)
+{
+	INT32 aflag = V_PERPLAYER;
+
+	if (!stplyr->texttimer)
+		return;
+
+	if (stplyr->texttimer < TICRATE/2)
+		aflag |= (9 - 9*stplyr->texttimer/(TICRATE/2)) << V_ALPHASHIFT;
+
+	switch (stplyr->textvar)
+	{
+		case 1: // A "Bonus Time Start" by any other name...
+		{
+			V_DrawCenteredString(BASEVIDWIDTH/2, 52, V_GREENMAP|aflag, M_GetText("GET TO THE GOAL!"));
+			V_DrawCenteredString(BASEVIDWIDTH/2, 60,            aflag, M_GetText("SCORE MULTIPLIER START!"));
+
+			if (stplyr->finishedtime)
+			{
+				V_DrawString(BASEVIDWIDTH/2 - 48, 140, aflag, "TIME:");
+				V_DrawString(BASEVIDWIDTH/2 - 48, 148, aflag, "BONUS:");
+				V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, 140, V_ORANGEMAP|aflag, va("%d", (stplyr->startedtime - stplyr->finishedtime)/TICRATE));
+				V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, 148, V_ORANGEMAP|aflag, va("%d", (stplyr->finishedtime/TICRATE) * 100));
+			}
+			break;
+		}
+		case 2: // Get n Spheres
+		case 3: // Get n more Spheres
+		{
+			if (!stplyr->capsule)
+				return;
+
+			// Yes, this string is an abomination.
+			V_DrawCenteredString(BASEVIDWIDTH/2, 60, aflag,
+								 va(M_GetText("\x80GET\x82 %d\x80 %s%s%s!"), stplyr->capsule->health,
+									(stplyr->textvar == 3) ? M_GetText("MORE ") : "",
+									(G_IsSpecialStage(gamemap)) ? "SPHERE" : "CHIP",
+									(stplyr->capsule->health > 1) ? "S" : ""));
+			break;
+		}
+		case 4: // End Bonus
+		{
+			V_DrawString(BASEVIDWIDTH/2 - 56, 140, aflag, (G_IsSpecialStage(gamemap)) ? "SPHERES:" : "CHIPS:");
+			V_DrawString(BASEVIDWIDTH/2 - 56, 148, aflag, "BONUS:");
+			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedspheres));
+			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 148, V_ORANGEMAP|aflag, va("%d", stplyr->finishedspheres * 50));
+			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 (stplyr->texttimer & 16)
+					V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_YELLOWMAP|aflag, "* NEW RECORD *");
+			}
+
+			if (P_HasGrades(gamemap, stplyr->lastmare + 1))
+			{
+				if (aflag)
+					V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, 160, aflag,
+										   ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
+				else
+					V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, 160, 0,
+									  ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
+			}
+			break;
 		}
+		default:
+			break;
 	}
 }
 
@@ -1246,29 +1510,59 @@ static skincolors_t linkColor[2][NUMLINKCOLORS] = {
 {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_drawNiGHTSHUD(void)
+static void ST_drawNiGHTSLink(void)
 {
-	INT32 origamount;
-	INT32 minlink = 1;
-	INT32 total_ringcount;
-	boolean nosshack = false;
+	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);
+	skincolors_t colornum;
+	fixed_t x, y, scale;
 
-	// When debugging, show "0 Link".
-	if (cv_debug & DBG_NIGHTSBASIC)
-		minlink = 0;
+	if (sel != prevsel[q])
+	{
+		prevsel[q] = sel;
+		prevtime[q] = 2 + mag;
+	}
 
-	// Cheap hack: don't display when the score is showing (it popping up for a split second when exiting a map is intentional)
-	if (stplyr->texttimer && stplyr->textvar == 4)
-		minlink = INT32_MAX;
+	if (stplyr->powers[pw_nights_linkfreeze] && (!(stplyr->powers[pw_nights_linkfreeze] & 2) || (stplyr->powers[pw_nights_linkfreeze] > flashingtics)))
+		colornum = SKINCOLOR_ICY;
+	else
+		colornum = linkColor[mag][sel];
 
-	if (G_IsSpecialStage(gamemap))
-	{ // Since special stages share score, time, rings, etc.
-		// disable splitscreen mode for its HUD.
-		if (stplyr != &players[displayplayer])
-			return;
-		nosshack = splitscreen;
-		splitscreen = false;
+	aflag |= ((stplyr->linktimer < (UINT32)nightslinktics/3)
+	? (9 - 9*stplyr->linktimer/(nightslinktics/3)) << V_ALPHASHIFT
+	: 0);
+
+	y = (160+11)<<FRACBITS;
+	aflag |= V_SNAPTOBOTTOM;
+
+	x = (160+4)<<FRACBITS;
+
+	if (prevtime[q])
+	{
+		scale = ((32 + prevtime[q])<<FRACBITS)/32;
+		prevtime[q]--;
 	}
+	else
+		scale = FRACUNIT;
+
+	y -= (11*scale);
+
+	ST_DrawNightsOverlayNum(x-(4*scale), y, scale, aflag, (stplyr->linkcount-1), nightsnum, colornum);
+	V_DrawFixedPatch(x+(4*scale), y, scale, aflag, nightslink,
+		colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
+
+	// Show remaining link time left in debug
+	if (cv_debug & DBG_NIGHTSBASIC)
+		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_SNAPTOBOTTOM, va("End in %d.%02d", stplyr->linktimer/TICRATE, G_TicsToCentiseconds(stplyr->linktimer)));
+}
+
+
+static void ST_drawNiGHTSHUD(void)
+{
+	INT32 origamount;
+	INT32 total_spherecount, total_ringcount;
+	const boolean oldspecialstage = (G_IsSpecialStage(gamemap) && !(maptol & TOL_NIGHTS));
 
 	// Drill meter
 	if (
@@ -1277,122 +1571,52 @@ static void ST_drawNiGHTSHUD(void)
 #endif
 	stplyr->powers[pw_carry] == CR_NIGHTSMODE)
 	{
-		INT32 locx, locy;
+		INT32 locx = 16, locy = 180;
 		INT32 dfill;
 		UINT8 fillpatch;
 
-		if (splitscreen || nosshack)
-		{
-			locx = 110;
-			locy = 188;
-		}
-		else
-		{
-			locx = 16;
-			locy = 180;
-		}
-
 		// Use which patch?
 		if (stplyr->pflags & PF_DRILLING)
 			fillpatch = (stplyr->drillmeter & 1) + 1;
 		else
 			fillpatch = 0;
 
-		if (splitscreen)
-		{ // 11-5-14 Replaced the old hack with a slightly better hack. -Red
-			V_DrawScaledPatch(locx, STRINGY(locy)-3, SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillfill[fillpatch]);
-		}
-		else if (nosshack)
-		{ // Even dirtier hack-of-a-hack to draw seperate drill meters in splitscreen special stages but nothing else.
-			splitscreen = true;
-			V_DrawScaledPatch(locx, STRINGY(locy)-3, V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_HUDTRANS, drillfill[fillpatch]);
-			stplyr = &players[secondarydisplayplayer];
-			if (stplyr->pflags & PF_DRILLING)
-				fillpatch = (stplyr->drillmeter & 1) + 1;
-			else
-				fillpatch = 0;
-			V_DrawScaledPatch(locx, STRINGY(locy-3), V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
-			stplyr = &players[displayplayer];
-			splitscreen = false;
-		}
-		else
-		{ // Draw normally. <:3
-			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
-		}
+		V_DrawScaledPatch(locx, locy, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
+		for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
+			V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
 
 		// Display actual drill amount and bumper time
-		if (cv_debug & DBG_NIGHTSBASIC)
+		if (!splitscreen && (cv_debug & DBG_NIGHTSBASIC))
 		{
 			if (stplyr->bumpertime)
-				V_DrawString(SCX(locx), SCY(locy - 8), V_NOSCALESTART|V_REDMAP|V_MONOSPACE, va("BUMPER: 0.%02d", G_TicsToCentiseconds(stplyr->bumpertime)));
+				V_DrawString(locx, locy - 8, V_REDMAP|V_MONOSPACE, va("BUMPER: 0.%02d", G_TicsToCentiseconds(stplyr->bumpertime)));
 			else
-				V_DrawString(SCX(locx), SCY(locy - 8), V_NOSCALESTART|V_MONOSPACE, va("Drill: %3d%%", (stplyr->drillmeter*100)/(96*20)));
+				V_DrawString(locx, locy - 8, V_MONOSPACE, va("Drill: %3d%%", (stplyr->drillmeter*100)/(96*20)));
 		}
 	}
 
+	/*if (G_IsSpecialStage(gamemap))
+	{ // Since special stages share score, time, rings, etc.
+		// disable splitscreen mode for its HUD.
+		// --------------------------------------
+		// NOPE! Consistency between different splitscreen stuffs
+		// now we've got the screen squashing instead. ~toast
+		if (stplyr != &players[displayplayer])
+			return;
+		nosshack = splitscreen;
+		splitscreen = false;
+	}*/
+
 	// Link drawing
-	if (
+	if (!oldspecialstage
+	// Don't display when the score is showing (it popping up for a split second when exiting a map is intentional)
+	&& !(stplyr->texttimer && stplyr->textvar == 4)
 #ifdef HAVE_BLUA
-	LUA_HudEnabled(hud_nightslink) &&
+	&& LUA_HudEnabled(hud_nightslink)
 #endif
-	stplyr->linkcount > minlink)
+	&& ((cv_debug & DBG_NIGHTSBASIC) || stplyr->linkcount > 1)) // When debugging, show "0 Link".
 	{
-		skincolors_t colornum;
-		INT32 aflag;
-		fixed_t x, y, scale;
-
-		if (stplyr->powers[pw_nights_linkfreeze] && (!(stplyr->powers[pw_nights_linkfreeze] & 2) || (stplyr->powers[pw_nights_linkfreeze] > flashingtics)))
-			colornum = SKINCOLOR_ICY;
-		else
-			colornum = linkColor[((stplyr->linkcount-1 >= 300) ? 1 : 0)][((stplyr->linkcount-1) / 5) % NUMLINKCOLORS];
-
-		aflag = ((stplyr->linktimer < 2*TICRATE/3)
-		? (9 - 9*stplyr->linktimer/(2*TICRATE/3)) << V_ALPHASHIFT
-		: 0);
-
-		if (splitscreen)
-		{
-			x = (256+4)<<FRACBITS;
-			y = (STRINGY(152)+11)<<FRACBITS;
-			aflag |= SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT;
-		}
-		else
-		{
-			x = (160+4)<<FRACBITS;
-			y = (160+11)<<FRACBITS;
-			aflag |= V_SNAPTOBOTTOM;
-		}
-
-		switch (stplyr->linktimer)
-		{
-			case (2*TICRATE):
-				scale = (36*FRACUNIT)/32;
-				break;
-			case (2*TICRATE - 1):
-				scale = (34*FRACUNIT)/32;
-				break;
-			default:
-				scale = FRACUNIT;
-				break;
-		}
-
-		y -= (11*scale);
-
-		ST_DrawNightsOverlayNum(x-(4*scale), y, scale, aflag, (stplyr->linkcount-1), nightsnum, colornum);
-		V_DrawFixedPatch(x+(4*scale), y, scale, aflag, nightslink,
-			colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
-
-		// Show remaining link time left in debug
-		if (cv_debug & DBG_NIGHTSBASIC)
-			V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_SNAPTOBOTTOM, va("End in %d.%02d", stplyr->linktimer/TICRATE, G_TicsToCentiseconds(stplyr->linktimer)));
+		ST_drawNiGHTSLink();
 	}
 
 	if (gametype == GT_RACE || gametype == GT_COMPETITION)
@@ -1404,25 +1628,33 @@ static void ST_drawNiGHTSHUD(void)
 
 	// Begin drawing brackets/chip display
 #ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_nightsrings))
+	if (LUA_HudEnabled(hud_nightsspheres))
 	{
 #endif
-	ST_DrawOverlayPatch(SCX(16), SCY(8), nbracket);
+	ST_DrawTopLeftOverlayPatch(16, 8, nbracket);
 	if (G_IsSpecialStage(gamemap))
-		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nsshud);
+		ST_DrawTopLeftOverlayPatch(24, 16, (
+			(stplyr->bonustime && (leveltime & 4) && (states[S_BLUESPHEREBONUS].frame & FF_ANIMATE)) ? nssbon : nsshud));
 	else
-		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nhud[(leveltime/2)%12]);
+		ST_DrawTopLeftOverlayPatch(24, 16, *(((stplyr->bonustime) ? nbon : nhud)+((leveltime/2)%12)));
 
 	if (G_IsSpecialStage(gamemap))
 	{
 		INT32 i;
-		total_ringcount = 0;
+		total_spherecount = total_ringcount = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/ && players[i].rings)
-				total_ringcount += players[i].rings;
+		{
+			if (!playeringame[i])
+				continue;
+			total_spherecount += players[i].spheres;
+			total_ringcount += players[i].rings;
+		}
 	}
 	else
-		total_ringcount = stplyr->rings;
+	{
+		total_spherecount = stplyr->spheres;
+		total_ringcount = stplyr->spheres;
+	}
 
 	if (stplyr->capsule)
 	{
@@ -1432,8 +1664,8 @@ static void ST_drawNiGHTSHUD(void)
 		origamount = stplyr->capsule->spawnpoint->angle;
 		I_Assert(origamount > 0); // should not happen now
 
-		ST_DrawOverlayPatch(SCX(72), SCY(8), nbracket);
-		ST_DrawOverlayPatch(SCX(74), SCY(8) + SCZ(4), minicaps);
+		ST_DrawTopLeftOverlayPatch(72, 8, nbracket);
+		ST_DrawTopLeftOverlayPatch(74, 8 + 4, minicaps);
 
 		if (stplyr->capsule->reactiontime != 0)
 		{
@@ -1442,10 +1674,10 @@ static void ST_drawNiGHTSHUD(void)
 
 			for (r = 0; r < 5; r++)
 			{
-				ST_DrawOverlayPatch(SCX(230 - (7*r)), SCY(144), redstat);
-				ST_DrawOverlayPatch(SCX(188 - (7*r)), SCY(144), orngstat);
-				ST_DrawOverlayPatch(SCX(146 - (7*r)), SCY(144), yelstat);
-				ST_DrawOverlayPatch(SCX(104 - (7*r)), SCY(144), byelstat);
+				V_DrawScaledPatch(230 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, redstat);
+				V_DrawScaledPatch(188 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, orngstat);
+				V_DrawScaledPatch(146 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, yelstat);
+				V_DrawScaledPatch(104 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, byelstat);
 			}
 
 			amount = (origamount - stplyr->capsule->health);
@@ -1464,7 +1696,7 @@ static void ST_drawNiGHTSHUD(void)
 					if (r > 10) ++t;
 					if (r > 5)  ++t;
 
-					ST_DrawOverlayPatch(SCX(69 + (7*t)), SCY(144), bluestat);
+					V_DrawScaledPatch(69 + (7*t), 144, V_PERPLAYER|V_HUDTRANS, bluestat);
 				}
 			}
 		}
@@ -1473,40 +1705,79 @@ static void ST_drawNiGHTSHUD(void)
 			INT32 cfill;
 
 			// Lil' white box!
-			V_DrawScaledPatch(15, STRINGY(8) + 34, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
+			V_DrawScaledPatch(15, 8 + 34, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
 
 			amount = (origamount - stplyr->capsule->health);
 			amount = (amount * length)/origamount;
 
-			for (cfill = 0; cfill < amount && cfill < 88; ++cfill)
-				V_DrawScaledPatch(15 + cfill + 1, STRINGY(8) + 35, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
+			for (cfill = 0; cfill < amount && cfill < length; ++cfill)
+				V_DrawScaledPatch(15 + cfill + 1, 8 + 35, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 		}
 
-		if (total_ringcount >= stplyr->capsule->health)
-			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), nredar[leveltime%8]);
+		if (total_spherecount >= stplyr->capsule->health)
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, nredar[leveltime&7]);
 		else
-			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[(leveltime/2)%8]);
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)&7]);
+	}
+	else if (oldspecialstage && total_spherecount < (INT32)ssspheres)
+	{
+		INT32 cfill, amount;
+		const INT32 length = 88;
+		UINT8 em = P_GetNextEmerald();
+		ST_DrawTopLeftOverlayPatch(72, 8, nbracket);
+
+		if (em <= 7)
+			ST_DrawTopLeftOverlayPatch(80, 8 + 8, emeraldpics[0][em]);
+
+		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)&7]);
+
+		// Lil' white box!
+		V_DrawScaledPatch(15, 8 + 34, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
+
+		amount = (total_spherecount * length)/ssspheres;
+
+		for (cfill = 0; cfill < amount && cfill < length; ++cfill)
+			V_DrawScaledPatch(15 + cfill + 1, 8 + 35, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 	}
 	else
-		ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[8]);
+		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[8]);
+
+	if (oldspecialstage)
+	{
+		// invert for s3k style junk
+		total_spherecount = ssspheres - total_spherecount;
+		if (total_spherecount < 0)
+			total_spherecount = 0;
 
-	if (total_ringcount >= 100)
-		ST_DrawOverlayNum((total_ringcount >= 1000) ? SCX(76) : SCX(72), SCY(8) + SCZ(11), total_ringcount);
+		if (nummaprings > 0) // don't count down if there ISN'T a valid maximum number of rings, like sonic 3
+		{
+			total_ringcount = nummaprings - total_ringcount;
+			if (total_ringcount < 0)
+				total_ringcount = 0;
+		}
+
+		// now rings! you know, for that perfect bonus.
+		V_DrawScaledPatch(272, 8, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, nbracket);
+		V_DrawScaledPatch(280, 16+1, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, nring);
+		V_DrawScaledPatch(280, 8+5, V_FLIP|V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, narrow[8]);
+		V_DrawTallNum(272, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, total_ringcount);
+	}
+
+	if (total_spherecount >= 100)
+		V_DrawTallNum((total_spherecount >= 1000) ? 76 : 72, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_spherecount);
 	else
-		ST_DrawOverlayNum(SCX(68), SCY(8) + SCZ(11), total_ringcount);
+		V_DrawTallNum(68, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_spherecount);
 #ifdef HAVE_BLUA
 	}
 #endif
 
 	// Score
-	if (!stplyr->exiting
+	if (!stplyr->exiting && !oldspecialstage
 #ifdef HAVE_BLUA
 	&& LUA_HudEnabled(hud_nightsscore)
 #endif
 	)
-	{
-		ST_DrawNightsOverlayNum(304<<FRACBITS, STRINGY(16)<<FRACBITS, FRACUNIT, SPLITFLAGS(V_SNAPTOTOP)|V_SNAPTORIGHT, stplyr->marescore, nightsnum, SKINCOLOR_AZURE);
-	}
+		ST_DrawNightsOverlayNum(304<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT, stplyr->marescore, nightsnum, SKINCOLOR_AZURE);
 
 	if (!stplyr->exiting
 #ifdef HAVE_BLUA
@@ -1518,22 +1789,21 @@ static void ST_drawNiGHTSHUD(void)
 		if (modeattacking == ATTACKING_NIGHTS)
 		{
 			INT32 maretime = max(stplyr->realtime - stplyr->marebegunat, 0);
-			fixed_t cornerx = vid.width, cornery = vid.height-SCZ(20);
 
-#define ASSISHHUDFIX(n) (n*vid.dupx)
-			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(22), cornery, W_CachePatchName("NGRTIMER", PU_HUDGFX));
-			ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(22), cornery, G_TicsToCentiseconds(maretime), 2);
-			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(46), cornery, sboperiod);
+#define VFLAGS V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_PERPLAYER|V_HUDTRANS
+			V_DrawScaledPatch(BASEVIDWIDTH-22, BASEVIDHEIGHT-20, VFLAGS, W_CachePatchName("NGRTIMER", PU_HUDGFX));
+			V_DrawPaddedTallNum(BASEVIDWIDTH-22, BASEVIDHEIGHT-20, VFLAGS, G_TicsToCentiseconds(maretime), 2);
+			V_DrawScaledPatch(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, sboperiod);
 			if (maretime < 60*TICRATE)
-				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime));
+				V_DrawTallNum(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, G_TicsToSeconds(maretime));
 			else
 			{
-				ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime), 2);
-				ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(70), cornery, sbocolon);
-				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(70), cornery, G_TicsToMinutes(maretime, true));
+				V_DrawPaddedTallNum(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, G_TicsToSeconds(maretime), 2);
+				V_DrawScaledPatch(BASEVIDWIDTH-70, BASEVIDHEIGHT-20, VFLAGS, sbocolon);
+				V_DrawTallNum(BASEVIDWIDTH-70, BASEVIDHEIGHT-20, VFLAGS, G_TicsToMinutes(maretime, true));
 			}
+#undef VFLAGS
 		}
-#undef ASSISHHUDFIX
 	}
 
 	// Ideya time remaining
@@ -1545,6 +1815,7 @@ static void ST_drawNiGHTSHUD(void)
 	{
 		INT32 realnightstime = stplyr->nightstime/TICRATE;
 		INT32 numbersize;
+		UINT8 col = ((realnightstime < 10) ? SKINCOLOR_RED : SKINCOLOR_SUPERGOLD4);
 
 		if (G_IsSpecialStage(gamemap))
 		{
@@ -1562,10 +1833,10 @@ static void ST_drawNiGHTSHUD(void)
 			if (flashingLeft < TICRATE/2) // Start fading out
 			{
 				UINT32 fadingFlag = (9 - 9*flashingLeft/(TICRATE/2)) << V_ALPHASHIFT;
-				V_DrawTranslucentPatch(SCX(160 - (minus5sec->width/2)), SCY(28), V_NOSCALESTART|fadingFlag, minus5sec);
+				V_DrawTranslucentPatch(160 - (minus5sec->width/2), 28, V_PERPLAYER|fadingFlag, minus5sec);
 			}
 			else
-				V_DrawScaledPatch(SCX(160 - (minus5sec->width/2)), SCY(28), V_NOSCALESTART, minus5sec);
+				V_DrawScaledPatch(160 - (minus5sec->width/2), 28, V_PERPLAYER, minus5sec);
 		}
 
 		if (realnightstime < 10)
@@ -1575,52 +1846,91 @@ static void ST_drawNiGHTSHUD(void)
 		else
 			numbersize = 48/2;
 
-		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, STRINGY(12)<<FRACBITS, FRACUNIT, SPLITFLAGS(V_SNAPTOTOP), realnightstime, nightsnum,
-			((realnightstime < 10) ? SKINCOLOR_RED : SKINCOLOR_SUPERGOLD4));
+		if ((oldspecialstage && leveltime & 2)
+			&& (stplyr->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)))
+			col = SKINCOLOR_ORANGE;
+
+		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP, realnightstime, nightsnum, col);
 
 		// Show exact time in debug
 		if (cv_debug & DBG_NIGHTSBASIC)
 			V_DrawString(160 + numbersize + 8, 24, V_SNAPTOTOP|((realnightstime < 10) ? V_REDMAP : V_YELLOWMAP), va("%02d", G_TicsToCentiseconds(stplyr->nightstime)));
 	}
 
-	// Show pickup durations
-	if (cv_debug & DBG_NIGHTSBASIC)
+	if (oldspecialstage)
 	{
-		UINT16 pwr;
-
-		if (stplyr->powers[pw_nights_superloop])
+		if (leveltime < 5*TICRATE)
 		{
-			pwr = stplyr->powers[pw_nights_superloop];
-			V_DrawSmallScaledPatch(SCX(110), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUA0",PU_CACHE));
-			V_DrawThinString(SCX(106), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			INT32 aflag = V_PERPLAYER;
+			tic_t drawtime = (5*TICRATE) - leveltime;
+			if (drawtime < TICRATE/2)
+				aflag |= (9 - 9*drawtime/(TICRATE/2)) << V_ALPHASHIFT;
+			// This one, not quite as much so.
+			V_DrawCenteredString(BASEVIDWIDTH/2, 60, aflag,
+		                     va(M_GetText("\x80GET\x82 %d\x80 SPHERE%s!"), ssspheres,
+		                        (ssspheres > 1) ? "S" : ""));
 		}
-
-		if (stplyr->powers[pw_nights_helper])
+	}
+	else
+	{
+		// Show pickup durations
+		if (cv_debug & DBG_NIGHTSBASIC)
 		{
-			pwr = stplyr->powers[pw_nights_helper];
-			V_DrawSmallScaledPatch(SCX(150), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUC0",PU_CACHE));
-			V_DrawThinString(SCX(146), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
-		}
+			UINT16 pwr;
 
-		if (stplyr->powers[pw_nights_linkfreeze])
-		{
-			pwr = stplyr->powers[pw_nights_linkfreeze];
-			V_DrawSmallScaledPatch(SCX(190), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUE0",PU_CACHE));
-			V_DrawThinString(SCX(186), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			if (stplyr->powers[pw_nights_superloop])
+			{
+				pwr = stplyr->powers[pw_nights_superloop];
+				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
+				V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
+
+			if (stplyr->powers[pw_nights_helper])
+			{
+				pwr = stplyr->powers[pw_nights_helper];
+				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
+				V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
+
+			if (stplyr->powers[pw_nights_linkfreeze])
+			{
+				pwr = stplyr->powers[pw_nights_linkfreeze];
+				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
+				V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			}
 		}
-	}
 
-	// Records/extra text
+		// Records/extra text
 #ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_nightsrecords))
+		if (LUA_HudEnabled(hud_nightsrecords))
 #endif
-	ST_drawNightsRecords();
+		ST_drawNightsRecords();
+	}
+}
 
-	if (nosshack)
-		splitscreen = true;
+static void ST_drawWeaponSelect(INT32 xoffs, INT32 y)
+{
+	INT32 q = stplyr->weapondelay, del = 0, p = 16;
+	while (q)
+	{
+		if (q > p)
+		{
+			del += p;
+			q -= p;
+			q /= 2;
+			if (p > 1)
+				p /= 2;
+		}
+		else
+		{
+			del += q;
+			break;
+		}
+	}
+	V_DrawScaledPatch(6 + xoffs, y-2 - del/2, V_PERPLAYER|V_SNAPTOBOTTOM, curweapon);
 }
 
-static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, INT32 xoffs, patch_t *pat)
+static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, INT32 xoffs, INT32 y, patch_t *pat)
 {
 	INT32 txtflags = 0, patflags = 0;
 
@@ -1631,30 +1941,30 @@ static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, I
 
 		if (weapon == pw_infinityring
 		|| (stplyr->ringweapons & rwflag))
-			txtflags |= V_20TRANS;
+			; //txtflags |= V_20TRANS;
 		else
 		{
 			txtflags |= V_TRANSLUCENT;
 			patflags =  V_80TRANS;
 		}
 
-		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|patflags, pat);
-
-		if (stplyr->powers[weapon] > 99)
-			V_DrawThinString(8 + xoffs + 1, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
-		else
-			V_DrawString(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
+		V_DrawScaledPatch(8 + xoffs, y, V_PERPLAYER|V_SNAPTOBOTTOM|patflags, pat);
+		V_DrawRightAlignedThinString(24 + xoffs, y + 8, V_PERPLAYER|V_SNAPTOBOTTOM|txtflags, va("%d", stplyr->powers[weapon]));
 
 		if (stplyr->currentweapon == wepflag)
-			V_DrawScaledPatch(6 + xoffs, STRINGY(162 - (splitscreen ? 4 : 2)), V_SNAPTOLEFT, curweapon);
+			ST_drawWeaponSelect(xoffs, y);
 	}
 	else if (stplyr->ringweapons & rwflag)
-		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|V_TRANSLUCENT, pat);
+		V_DrawScaledPatch(8 + xoffs, y, V_PERPLAYER|V_SNAPTOBOTTOM|V_TRANSLUCENT, pat);
 }
 
 static void ST_drawMatchHUD(void)
 {
-	INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10);
+	const INT32 y = 176; // HUD_LIVES
+	INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10) - 6;
+
+	if (F_GetPromptHideHud(y))
+		return;
 
 	if (!G_RingSlingerGametype())
 		return;
@@ -1662,254 +1972,275 @@ static void ST_drawMatchHUD(void)
 	if (G_TagGametype() && !(stplyr->pflags & PF_TAGIT))
 		return;
 
-#ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_weaponrings)) {
-#endif
+	{
+		if (stplyr->powers[pw_infinityring])
+			ST_drawWeaponRing(pw_infinityring, 0, 0, offset, y, infinityring);
+		else
+		{
+			if (stplyr->rings > 0)
+				V_DrawScaledPatch(8 + offset, y, V_PERPLAYER|V_SNAPTOBOTTOM, normring);
+			else
+				V_DrawTranslucentPatch(8 + offset, y, V_PERPLAYER|V_SNAPTOBOTTOM|V_80TRANS, normring);
 
-	if (stplyr->powers[pw_infinityring])
-		ST_drawWeaponRing(pw_infinityring, 0, 0, offset, infinityring);
-	else if (stplyr->rings > 0)
-		V_DrawScaledPatch(8 + offset, STRINGY(162), V_SNAPTOLEFT, normring);
-	else
-		V_DrawTranslucentPatch(8 + offset, STRINGY(162), V_SNAPTOLEFT|V_80TRANS, normring);
-
-	if (!stplyr->currentweapon)
-		V_DrawScaledPatch(6 + offset, STRINGY(162 - (splitscreen ? 4 : 2)), V_SNAPTOLEFT, curweapon);
-
-	offset += 20;
-	ST_drawWeaponRing(pw_automaticring, RW_AUTO, WEP_AUTO, offset, autoring);
-	offset += 20;
-	ST_drawWeaponRing(pw_bouncering, RW_BOUNCE, WEP_BOUNCE, offset, bouncering);
-	offset += 20;
-	ST_drawWeaponRing(pw_scatterring, RW_SCATTER, WEP_SCATTER, offset, scatterring);
-	offset += 20;
-	ST_drawWeaponRing(pw_grenadering, RW_GRENADE, WEP_GRENADE, offset, grenadering);
-	offset += 20;
-	ST_drawWeaponRing(pw_explosionring, RW_EXPLODE, WEP_EXPLODE, offset, explosionring);
-	offset += 20;
-	ST_drawWeaponRing(pw_railring, RW_RAIL, WEP_RAIL, offset, railring);
+			if (!stplyr->currentweapon)
+				ST_drawWeaponSelect(offset, y);
+		}
 
-#ifdef HAVE_BLUA
+		offset += 20;
+		ST_drawWeaponRing(pw_automaticring, RW_AUTO, WEP_AUTO, offset, y, autoring);
+		offset += 20;
+		ST_drawWeaponRing(pw_bouncering, RW_BOUNCE, WEP_BOUNCE, offset, y, bouncering);
+		offset += 20;
+		ST_drawWeaponRing(pw_scatterring, RW_SCATTER, WEP_SCATTER, offset, y, scatterring);
+		offset += 20;
+		ST_drawWeaponRing(pw_grenadering, RW_GRENADE, WEP_GRENADE, offset, y, grenadering);
+		offset += 20;
+		ST_drawWeaponRing(pw_explosionring, RW_EXPLODE, WEP_EXPLODE, offset, y, explosionring);
+		offset += 20;
+		ST_drawWeaponRing(pw_railring, RW_RAIL, WEP_RAIL, offset, y, railring);
 	}
+}
 
-	if (LUA_HudEnabled(hud_powerstones)) {
-#endif
-
-	// Power Stones collected
-	offset = 136; // Used for Y now
-
-	if (stplyr->powers[pw_emeralds] & EMERALD1)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[0]);
-
-	offset += 8;
-
-	if (stplyr->powers[pw_emeralds] & EMERALD2)
-		V_DrawScaledPatch(40, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[1]);
-
-	if (stplyr->powers[pw_emeralds] & EMERALD6)
-		V_DrawScaledPatch(16, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[5]);
+static void ST_drawTextHUD(void)
+{
+	INT32 y = 42 + 16; // HUD_RINGS
+	boolean donef12 = false;
 
-	offset += 16;
+#define textHUDdraw(str) \
+{\
+	V_DrawThinString(16, y, V_PERPLAYER|V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM, str);\
+	y += 8;\
+}
 
-	if (stplyr->powers[pw_emeralds] & EMERALD3)
-		V_DrawScaledPatch(40, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[2]);
+	if (F_GetPromptHideHud(y))
+		return;
 
-	if (stplyr->powers[pw_emeralds] & EMERALD5)
-		V_DrawScaledPatch(16, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[4]);
+	if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE))
+		textHUDdraw(M_GetText("\x86""Spectator mode:"))
 
-	offset += 8;
+	if (circuitmap)
+	{
+		if (stplyr->exiting)
+			textHUDdraw(M_GetText("\x82""FINISHED!"))
+		else
+			textHUDdraw(va("Lap:""\x82 %u/%d", stplyr->laps+1, cv_numlaps.value))
+	}
 
-	if (stplyr->powers[pw_emeralds] & EMERALD4)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[3]);
+	if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
+	{
+		INT32 i, total = 0, exiting = 0;
 
-	offset -= 16;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i] || players[i].spectator)
+				continue;
+			if (players[i].lives <= 0)
+				continue;
 
-	if (stplyr->powers[pw_emeralds] & EMERALD7)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[6]);
+			total++;
+			if (players[i].exiting)
+				exiting++;
+		}
 
-#ifdef HAVE_BLUA
-	}
-#endif
-}
+		if (cv_playersforexit.value != 4)
+		{
+			total *= cv_playersforexit.value;
+			if (total & 3)
+				total += 4; // round up
+			total /= 4;
+		}
 
-static inline void ST_drawRaceHUD(void)
-{
-	if (leveltime >= TICRATE && leveltime < 5*TICRATE)
-	{
-		INT32 height = ((3*BASEVIDHEIGHT)>>2) - 8;
-		INT32 bounce = (leveltime % TICRATE);
-		patch_t *racenum;
-		switch (leveltime/TICRATE)
+		if (exiting < total)
 		{
-			case 1:
-				racenum = race3;
-				break;
-			case 2:
-				racenum = race2;
-				break;
-			case 3:
-				racenum = race1;
-				break;
-			default:
-				racenum = racego;
-				break;
+			if (!splitscreen && !donef12)
+			{
+				textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
+				donef12 = true;
+			}
+			total -= exiting;
+			textHUDdraw(va(M_GetText("%d player%s remaining"), total, ((total == 1) ? "" : "s")))
 		}
-		if (bounce < 3)
+	}
+	else if (gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
+	{
+		if (!splitscreen && !donef12)
 		{
-			height -= (2 - bounce);
-			if (!bounce)
-					S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
+			textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
+			donef12 = true;
 		}
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(racenum->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, racenum);
 	}
-
-	if (circuitmap)
+	else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
 	{
-		if (stplyr->exiting)
-			V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), V_YELLOWMAP, "FINISHED!");
+		INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
+
+		if (respawntime > 0 && !stplyr->spectator)
+			textHUDdraw(va(M_GetText("Respawn in %d..."), respawntime))
 		else
-			V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps+1, cv_numlaps.value));
+			textHUDdraw(M_GetText("\x82""JUMP:""\x80 Respawn"))
 	}
-}
-
-static void ST_drawTagHUD(void)
-{
-	char pstime[33] = "";
-	char pstext[33] = "";
-
-	// Figure out what we're going to print.
-	if (leveltime < hidetime * TICRATE) //during the hide time, the seeker and hiders have different messages on their HUD.
+	else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE))
 	{
-		if (hidetime)
-			sprintf(pstime, "%d", (hidetime - leveltime/TICRATE)); //hide time is in seconds, not tics.
+		if (!splitscreen && !donef12)
+		{
+			textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
+			donef12 = true;
+		}
 
-		if (stplyr->pflags & PF_TAGIT && !stplyr->spectator)
-			sprintf(pstext, "%s", M_GetText("WAITING FOR PLAYERS TO HIDE..."));
-		else
+		textHUDdraw(M_GetText("\x82""JUMP:""\x80 Rise"))
+		textHUDdraw(M_GetText("\x82""SPIN:""\x80 Lower"))
+
+		if (G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS))
+			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
+		else if (gametype == GT_COOP)
 		{
-			if (!stplyr->spectator) //spectators get a generic HUD message rather than a gametype specific one.
+			if (stplyr->lives <= 0
+			&& cv_cooplives.value == 2
+			&& (netgame || multiplayer))
 			{
-				if (gametype == GT_HIDEANDSEEK) //hide and seek.
-					sprintf(pstext, "%s", M_GetText("HIDE BEFORE TIME RUNS OUT!"));
-				else //default
-					sprintf(pstext, "%s", M_GetText("FLEE BEFORE YOU ARE HUNTED!"));
+				INT32 i;
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (!playeringame[i])
+						continue;
+
+					if (&players[i] == stplyr)
+						continue;
+
+					if (players[i].lives > 1)
+						break;
+					}
+
+				if (i != MAXPLAYERS)
+					textHUDdraw(M_GetText("You'll steal a life on respawn..."))
+				else
+					textHUDdraw(M_GetText("Wait to respawn..."))
 			}
 			else
-				sprintf(pstext, "%s", M_GetText("HIDE TIME REMAINING:"));
+				textHUDdraw(M_GetText("Wait to respawn..."))
 		}
+		else
+			textHUDdraw(M_GetText("\x82""FIRE:""\x80 Enter game"))
 	}
-	else
-	{
-		if (cv_timelimit.value && timelimitintics >= leveltime)
-			sprintf(pstime, "%d", (timelimitintics-leveltime)/TICRATE);
 
-		if (stplyr->pflags & PF_TAGIT)
-			sprintf(pstext, "%s", M_GetText("YOU'RE IT!"));
-		else
+	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (!stplyr->spectator))
+	{
+		if (leveltime < hidetime * TICRATE)
+		{
+			if (stplyr->pflags & PF_TAGIT)
+			{
+				textHUDdraw(M_GetText("\x82""You are blindfolded!"))
+				textHUDdraw(M_GetText("Waiting for players to hide..."))
+			}
+			else if (gametype == GT_HIDEANDSEEK)
+				textHUDdraw(M_GetText("Hide before time runs out!"))
+			else
+				textHUDdraw(M_GetText("Flee before you are hunted!"))
+		}
+		else if (gametype == GT_HIDEANDSEEK && !(stplyr->pflags & PF_TAGIT))
 		{
-			if (cv_timelimit.value)
-				sprintf(pstext, "%s", M_GetText("TIME REMAINING:"));
-			else //Since having no hud message in tag is not characteristic:
-				sprintf(pstext, "%s", M_GetText("NO TIME LIMIT"));
+			if (!splitscreen && !donef12)
+			{
+				textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
+				donef12 = true;
+			}
+			textHUDdraw(M_GetText("You cannot move while hiding."))
 		}
 	}
 
-	// Print the stuff.
-	if (pstext[0])
-	{
-		if (splitscreen)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(168), 0, pstext);
-		else
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, pstext);
-	}
-	if (pstime[0])
-	{
-		if (splitscreen)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, pstime);
-		else
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(192), 0, pstime);
-	}
+#undef textHUDdraw
+
 }
 
-static void ST_drawCTFHUD(void)
+static inline void ST_drawRaceHUD(void)
 {
-	INT32 i;
-	UINT16 whichflag = 0;
+	if (leveltime > TICRATE && leveltime <= 5*TICRATE)
+		ST_drawRaceNum(4*TICRATE - leveltime);
+}
 
-	// Draw the flags
-	V_DrawSmallScaledPatch(256, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, rflagico);
-	V_DrawSmallScaledPatch(288, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, bflagico);
+static void ST_drawTeamHUD(void)
+{
+	patch_t *p;
+#define SEP 20
 
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (players[i].gotflag & GF_REDFLAG) // Red flag isn't at base
-			V_DrawScaledPatch(256, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
-		else if (players[i].gotflag & GF_BLUEFLAG) // Blue flag isn't at base
-			V_DrawScaledPatch(288, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
+	if (F_GetPromptHideHud(0)) // y base is 0
+		return;
 
-		whichflag |= players[i].gotflag;
-		if ((whichflag & (GF_REDFLAG|GF_BLUEFLAG)) == (GF_REDFLAG|GF_BLUEFLAG))
-			break; // both flags were found, let's stop early
-	}
+	if (gametype == GT_CTF)
+		p = bflagico;
+	else
+		p = bmatcico;
 
-	// YOU have a flag. Display a monitor-like icon for it.
-	if (stplyr->gotflag)
-	{
-		patch_t *p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
+	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 42, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
-		else
-			V_DrawScaledPatch(304, 24 + 84, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
-	}
+	if (gametype == GT_CTF)
+		p = rflagico;
+	else
+		p = rmatcico;
 
-	// Display a countdown timer showing how much time left until the flag your team dropped returns to base.
+	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
+
+	if (gametype != GT_CTF)
+		goto num;
 	{
-		char timeleft[33];
-		if (redflag && redflag->fuse > 1)
+		INT32 i;
+		UINT16 whichflag = 0;
+
+		// Show which flags aren't at base.
+		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			sprintf(timeleft, "%u", (redflag->fuse / TICRATE));
-			V_DrawCenteredString(268, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
+			if (players[i].gotflag & GF_BLUEFLAG) // Blue flag isn't at base
+				V_DrawScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(nonicon->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon);
+			if (players[i].gotflag & GF_REDFLAG) // Red flag isn't at base
+				V_DrawScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(nonicon2->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon2);
+
+			whichflag |= players[i].gotflag;
+			if ((whichflag & (GF_REDFLAG|GF_BLUEFLAG)) == (GF_REDFLAG|GF_BLUEFLAG))
+				break; // both flags were found, let's stop early
 		}
 
-		if (blueflag && blueflag->fuse > 1)
+		// Display a countdown timer showing how much time left until the flag returns to base.
 		{
-			sprintf(timeleft, "%u", (blueflag->fuse / TICRATE));
-			V_DrawCenteredString(300, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
+			if (blueflag && blueflag->fuse > 1)
+				V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (blueflag->fuse / TICRATE)));
+
+			if (redflag && redflag->fuse > 1)
+				V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (redflag->fuse / TICRATE)));
 		}
 	}
-}
 
-// Draws "Red Team", "Blue Team", or "Spectator" for team gametypes.
-static inline void ST_drawTeamName(void)
-{
-	if (stplyr->ctfteam == 1)
-		V_DrawString(256, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "RED TEAM");
-	else if (stplyr->ctfteam == 2)
-		V_DrawString(248, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "BLUE TEAM");
-	else
-		V_DrawString(244, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "SPECTATOR");
+num:
+	V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", bluescore));
+	V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", redscore));
+
+#undef SEP
 }
 
-static void ST_drawSpecialStageHUD(void)
+/*static void ST_drawSpecialStageHUD(void)
 {
-	if (totalrings > 0)
-		ST_DrawNumFromHudWS(HUD_SS_TOTALRINGS, totalrings, V_HUDTRANS);
+	if (ssspheres > 0)
+	{
+		if (hudinfo[HUD_SS_TOTALRINGS].x)
+			ST_DrawNumFromHud(HUD_SS_TOTALRINGS, ssspheres, V_HUDTRANS);
+		else if (cv_timetic.value == 2)
+			V_DrawTallNum(hudinfo[HUD_RINGSNUMTICS].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUMTICS].f|V_PERPLAYER|V_HUDTRANS, ssspheres);
+		else
+			V_DrawTallNum(hudinfo[HUD_RINGSNUM].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUM].f|V_PERPLAYER|V_HUDTRANS, ssspheres);
+	}
 
-	if (leveltime < 5*TICRATE && totalrings > 0)
+	if (leveltime < 5*TICRATE && ssspheres > 0)
 	{
 		ST_DrawPatchFromHud(HUD_GETRINGS, getall, V_HUDTRANS);
-		ST_DrawNumFromHud(HUD_GETRINGSNUM, totalrings, V_HUDTRANS);
+		ST_DrawNumFromHud(HUD_GETRINGSNUM, ssspheres, V_HUDTRANS);
 	}
 
 	if (sstimer)
 	{
-		V_DrawString(hudinfo[HUD_TIMELEFT].x, STRINGY(hudinfo[HUD_TIMELEFT].y), V_HUDTRANS, M_GetText("TIME LEFT"));
+		V_DrawString(hudinfo[HUD_TIMELEFT].x, hudinfo[HUD_TIMELEFT].y, hudinfo[HUD_TIMELEFT].f|V_PERPLAYER|V_HUDTRANS, M_GetText("TIME LEFT"));
 		ST_DrawNumFromHud(HUD_TIMELEFTNUM, sstimer/TICRATE, V_HUDTRANS);
 	}
 	else
 		ST_DrawPatchFromHud(HUD_TIMEUP, timeup, V_HUDTRANS);
-}
+}*/
 
 static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset)
 {
@@ -1947,7 +2278,8 @@ static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offse
 		interval = 0;
 	}
 
-	V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, STRINGY(hudinfo[HUD_HUNTPICS].y), V_HUDTRANS, patches[i]);
+	if (!F_GetPromptHideHud(hudinfo[HUD_HUNTPICS].y))
+		V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, hudinfo[HUD_HUNTPICS].y, hudinfo[HUD_HUNTPICS].f|V_PERPLAYER|V_HUDTRANS, patches[i]);
 	return interval;
 }
 
@@ -2006,31 +2338,33 @@ static void ST_doItemFinderIconsAndSound(void)
 		return;
 
 	// Scan thinkers to find emblem mobj with these ids
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
+
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_EMBLEM)
-		{
-			if (!(mo2->flags & MF_SPECIAL))
-				continue;
+		if (mo2->type != MT_EMBLEM)
+			continue;
 
-			for (i = 0; i < stemblems; ++i)
+		if (!(mo2->flags & MF_SPECIAL))
+			continue;
+
+		for (i = 0; i < stemblems; ++i)
+		{
+			if (mo2->health == emblems[i] + 1)
 			{
-				if (mo2->health == emblems[i]+1)
-				{
-					soffset = (i * 20) - ((stemblems-1) * 10);
+				soffset = (i * 20) - ((stemblems - 1) * 10);
 
-					newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
-					if (newinterval && (!interval || newinterval < interval))
-						interval = newinterval;
+				newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
+				if (newinterval && (!interval || newinterval < interval))
+					interval = newinterval;
 
-					break;
-				}
+				break;
 			}
 		}
+
 	}
 
 	if (!(P_AutoPause() || paused) && interval > 0 && leveltime && leveltime % interval == 0)
@@ -2045,7 +2379,8 @@ static void ST_overlayDrawer(void)
 	//hu_showscores = auto hide score/time/rings when tab rankings are shown
 	if (!(hu_showscores && (netgame || multiplayer)))
 	{
-		if (maptol & TOL_NIGHTS)
+		if ((maptol & TOL_NIGHTS || G_IsSpecialStage(gamemap)) &&
+			!F_GetPromptHideHudAll())
 			ST_drawNiGHTSHUD();
 		else
 		{
@@ -2061,12 +2396,13 @@ static void ST_overlayDrawer(void)
 			if (LUA_HudEnabled(hud_rings))
 #endif
 			ST_drawRings();
-			if (G_GametypeUsesLives()
+
+			if (!modeattacking
 #ifdef HAVE_BLUA
 			&& LUA_HudEnabled(hud_lives)
 #endif
 			)
-				ST_drawLives();
+				ST_drawLivesArea();
 		}
 	}
 
@@ -2106,49 +2442,52 @@ static void ST_overlayDrawer(void)
 		}
 
 		if (p)
-			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, STRINGY(BASEVIDHEIGHT/2 - (SHORT(p->height)/2)) - (splitscreen ? 4 : 0), (stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
+			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, BASEVIDHEIGHT/2 - (SHORT(p->height)/2), V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
 	}
 
+	if (G_GametypeHasTeams())
+		ST_drawTeamHUD();
 
 	if (!hu_showscores) // hide the following if TAB is held
 	{
 		// Countdown timer for Race Mode
-		if (countdown)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(176), 0, va("%d", countdown/TICRATE));
-
-		// If you are in overtime, put a big honkin' flashin' message on the screen.
-		if (G_RingSlingerGametype() && cv_overtime.value
-			&& (leveltime > (timelimitintics + TICRATE/2)) && cv_timelimit.value && (leveltime/TICRATE % 2 == 0))
+		if (countdown > 1)
 		{
-			if (splitscreen)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(168), 0, M_GetText("OVERTIME!"));
+			tic_t time = countdown/TICRATE + 1;
+			if (time < 4)
+				ST_drawRaceNum(countdown);
 			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, M_GetText("OVERTIME!"));
+			{
+				tic_t num = time;
+				INT32 sz = SHORT(tallnum[0]->width)/2, width = 0;
+				do
+				{
+					width += sz;
+					num /= 10;
+				} while (num);
+				V_DrawTallNum((BASEVIDWIDTH/2) + width, ((3*BASEVIDHEIGHT)>>2) - 7, V_PERPLAYER, time);
+				//V_DrawCenteredString(BASEVIDWIDTH/2, 176, V_PERPLAYER, va("%d", countdown/TICRATE + 1));
+			}
 		}
 
+		// If you are in overtime, put a big honkin' flashin' message on the screen.
+		if (G_RingSlingerGametype() && cv_overtime.value
+		&& (leveltime > (timelimitintics + TICRATE/2)) && cv_timelimit.value && (leveltime/TICRATE % 2 == 0))
+			V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_PERPLAYER, M_GetText("OVERTIME!"));
+
 		// Draw Match-related stuff
 		//\note Match HUD is drawn no matter what gametype.
 		// ... just not if you're a spectator.
-		if (!stplyr->spectator)
+		if (!stplyr->spectator
+#ifdef HAVE_BLUA
+		&& (LUA_HudEnabled(hud_weaponrings))
+#endif
+		)
 			ST_drawMatchHUD();
 
 		// Race HUD Stuff
 		if (gametype == GT_RACE || gametype == GT_COMPETITION)
 			ST_drawRaceHUD();
-		// Tag HUD Stuff
-		else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
-			ST_drawTagHUD();
-		// CTF HUD Stuff
-		else if (gametype == GT_CTF)
-			ST_drawCTFHUD();
-
-		// Team names for team gametypes
-		if (G_GametypeHasTeams())
-			ST_drawTeamName();
-
-		// Special Stage HUD
-		if (!useNightsSS && G_IsSpecialStage(gamemap) && stplyr == &players[displayplayer])
-			ST_drawSpecialStageHUD();
 
 		// Emerald Hunt Indicators
 		if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER))
@@ -2156,9 +2495,6 @@ static void ST_overlayDrawer(void)
 		else
 			ST_doHuntIconsAndSound();
 
-		if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
-			V_DrawScaledPatch(hudinfo[HUD_GRAVBOOTSICO].x, STRINGY(hudinfo[HUD_GRAVBOOTSICO].y), V_SNAPTORIGHT, gravboots);
-
 		if(!P_IsLocalPlayer(stplyr))
 		{
 			char name[MAXPLAYERNAME+1];
@@ -2171,12 +2507,21 @@ static void ST_overlayDrawer(void)
 		}
 
 		// This is where we draw all the fun cheese if you have the chasecam off!
-		if ((stplyr == &players[displayplayer] && !camera.chase)
-			|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
+		if (!(maptol & TOL_NIGHTS))
 		{
-			ST_drawFirstPersonHUD();
+			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_drawPowerupHUD();  // same as it ever was...
 		}
 	}
+	else if (!(netgame || multiplayer) && cv_powerupdisplay.value == 2)
+		ST_drawPowerupHUD(); // same as it ever was...
 
 #ifdef HAVE_BLUA
 	if (!(netgame || multiplayer) || !hu_showscores)
@@ -2189,106 +2534,16 @@ static void ST_overlayDrawer(void)
 	&& LUA_HudEnabled(hud_stagetitle)
 #endif
 	)
-		ST_drawLevelTitle();
-
-	if (!hu_showscores && (netgame || multiplayer) && displayplayer == consoleplayer)
-	{
-		if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
-		{
-			INT32 i, total = 0, exiting = 0;
-
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-				if (players[i].lives <= 0)
-					continue;
-
-				total++;
-				if (players[i].exiting)
-					exiting++;
-			}
-
-			if (cv_playersforexit.value != 4)
-			{
-				total *= cv_playersforexit.value;
-				if (total % 4) total += 4; // round up
-				total /= 4;
-			}
+		ST_drawLevelTitle(timeinmap+70);
 
-			if (exiting >= total)
-				;
-			else
-			{
-				total -= exiting;
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(124), 0, va(M_GetText("%d more player%s required to exit."), total, ((total == 1) ? "" : "s")));
-				if (!splitscreen)
-					V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-			}
-		}
-		else if (!splitscreen && gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
-			V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-		else if (gametype == GT_HIDEANDSEEK &&
-		 (!stplyr->spectator && !(stplyr->pflags & PF_TAGIT)) && (leveltime > hidetime * TICRATE))
-		{
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(116), 0, M_GetText("You cannot move while hiding."));
-			if (!splitscreen)
-				V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-		}
-		else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
-		{
-			INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
-			if (respawntime > 0 && !stplyr->spectator)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, va(M_GetText("Respawn in: %d second%s."), respawntime, respawntime == 1 ? "" : "s"));
-			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Jump to respawn."));
-		}
-		else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE)
+	if (!hu_showscores && (netgame || multiplayer)
 #ifdef HAVE_BLUA
 		&& LUA_HudEnabled(hud_textspectator)
 #endif
-		)
-		{
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), V_HUDTRANSHALF, M_GetText("You are a spectator."));
-			if (G_GametypeHasTeams())
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to be assigned to a team."));
-			else if (G_IsSpecialStage(gamemap) && useNightsSS)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("You cannot play until the stage has ended."));
-			else if (gametype == GT_COOP && stplyr->lives <= 0)
-			{
-				if (cv_cooplives.value == 1
-				&& (netgame || multiplayer))
-				{
-					INT32 i;
-					for (i = 0; i < MAXPLAYERS; i++)
-					{
-						if (!playeringame[i])
-							continue;
-
-						if (&players[i] == stplyr)
-							continue;
-
-						if (players[i].lives > 1)
-							break;
-					}
-
-					if (i != MAXPLAYERS)
-						V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132)-(splitscreen ? 12 : 0), V_HUDTRANSHALF, M_GetText("You'll steal a life on respawn."));
-				}
-			}
-			else if (gametype != GT_COOP)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to enter the game."));
-			if (!splitscreen)
-			{
-				V_DrawCenteredString(BASEVIDWIDTH/2, 148, V_HUDTRANSHALF, M_GetText("Press F12 to watch another player."));
-				V_DrawCenteredString(BASEVIDWIDTH/2, 164, V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
-			}
-			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(136), V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
-		}
-	}
+	)
+		ST_drawTextHUD();
 
-	if (modeattacking)
+	if (modeattacking && !(demoplayback && hu_showscores))
 		ST_drawInput();
 
 	ST_drawDebugInfo();
@@ -2299,15 +2554,26 @@ void ST_Drawer(void)
 #ifdef SEENAMES
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
-		if (cv_seenames.value == 1)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF, player_names[seenplayer-players]);
-		else if (cv_seenames.value == 2)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF,
-			va("%s%s", G_GametypeHasTeams() ? ((seenplayer->ctfteam == 1) ? "\x85" : "\x84") : "", player_names[seenplayer-players]));
-		else //if (cv_seenames.value == 3)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF,
-			va("%s%s", !G_RingSlingerGametype() || (G_GametypeHasTeams() && players[consoleplayer].ctfteam == seenplayer->ctfteam)
-			 ? "\x83" : "\x85", player_names[seenplayer-players]));
+		INT32 c = 0;
+		switch (cv_seenames.value)
+		{
+			case 1: // Colorless
+				break;
+			case 2: // Team
+				if (G_GametypeHasTeams())
+					c = (seenplayer->ctfteam == 1) ? V_REDMAP : V_BLUEMAP;
+				break;
+			case 3: // Ally/Foe
+			default:
+				// Green = Ally, Red = Foe
+				if (G_GametypeHasTeams())
+					c = (players[consoleplayer].ctfteam == seenplayer->ctfteam) ? V_GREENMAP : V_REDMAP;
+				else // Everyone is an ally, or everyone is a foe!
+					c = (G_RingSlingerGametype()) ? V_REDMAP : V_GREENMAP;
+				break;
+		}
+
+		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF|c, player_names[seenplayer-players]);
 	}
 #endif
 
@@ -2328,6 +2594,22 @@ void ST_Drawer(void)
 #endif
 		if (rendermode != render_none) ST_doPaletteStuff();
 
+	// Blindfold!
+	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	&& (leveltime < hidetime * TICRATE))
+	{
+		if (players[displayplayer].pflags & PF_TAGIT)
+		{
+			stplyr = &players[displayplayer];
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31|V_PERPLAYER);
+		}
+		else if (splitscreen && players[secondarydisplayplayer].pflags & PF_TAGIT)
+		{
+			stplyr = &players[secondarydisplayplayer];
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31|V_PERPLAYER);
+		}
+	}
+
 	if (st_overlay)
 	{
 		// No deadview!
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 9be37b50287b2e9d001ac28a394c97b7a13439b6..40574f46c1d07249318ee713fec74e1291778a4a 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -24,7 +24,7 @@
 //
 
 // Called by main loop.
-FUNCMATH void ST_Ticker(void);
+void ST_Ticker(void);
 
 // Called by main loop.
 void ST_Drawer(void);
@@ -42,11 +42,14 @@ void ST_UnloadGraphics(void);
 void ST_LoadGraphics(void);
 
 // face load graphics, called when skin changes
-void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 playernum);
+void ST_LoadFaceGraphics(INT32 playernum);
 void ST_ReloadSkinFaceGraphics(void);
 
 void ST_doPaletteStuff(void);
 
+// level title draw
+void ST_drawLevelTitle(tic_t titletime);
+
 // return if player a is in the same team as player b
 boolean ST_SameTeam(player_t *a, player_t *b);
 
@@ -55,6 +58,7 @@ boolean ST_SameTeam(player_t *a, player_t *b);
 //--------------------
 
 extern boolean st_overlay; // sb overlay on or off when fullscreen
+extern INT32 st_palette; // 0 is default, any others are special palettes.
 
 extern lumpnum_t st_borderpatchnum;
 // patches, also used in intermission
@@ -72,38 +76,28 @@ extern patch_t *ngradeletters[7];
   */
 typedef struct
 {
-	INT32 x, y;
+	INT32 x, y, f;
 } hudinfo_t;
 
 typedef enum
 {
-	HUD_LIVESNAME,
-	HUD_LIVESPIC,
-	HUD_LIVESNUM,
-	HUD_LIVESX,
+	HUD_LIVES,
 
 	HUD_RINGS,
-	HUD_RINGSSPLIT,
 	HUD_RINGSNUM,
-	HUD_RINGSNUMSPLIT,
 	HUD_RINGSNUMTICS,
 
 	HUD_SCORE,
 	HUD_SCORENUM,
 
 	HUD_TIME,
-	HUD_TIMESPLIT,
 	HUD_MINUTES,
-	HUD_MINUTESSPLIT,
 	HUD_TIMECOLON,
-	HUD_TIMECOLONSPLIT,
 	HUD_SECONDS,
-	HUD_SECONDSSPLIT,
 	HUD_TIMETICCOLON,
 	HUD_TICS,
 
 	HUD_SS_TOTALRINGS,
-	HUD_SS_TOTALRINGS_SPLIT,
 
 	HUD_GETRINGS,
 	HUD_GETRINGSNUM,
@@ -111,8 +105,7 @@ typedef enum
 	HUD_TIMELEFTNUM,
 	HUD_TIMEUP,
 	HUD_HUNTPICS,
-	HUD_GRAVBOOTSICO,
-	HUD_LAP,
+	HUD_POWERUPS,
 
 	NUMHUDITEMS
 } hudnum_t;
diff --git a/src/string.c b/src/string.c
index d7f8b36794549cb45ad257b3b27c04733f8347b4..2a03e87296711f437b9bf4e61a0217b3e37bcf81 100644
--- a/src/string.c
+++ b/src/string.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by Graue.
-// Copyright (C) 2006-2016 by Sonic Team Junior.
+// Copyright (C) 2006-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tables.c b/src/tables.c
index e181b9aa8853a1fe4bc216c56c763d139d98b69a..a31572d706183cdf56c0e6e7caa9a143a03c5369 100644
--- a/src/tables.c
+++ b/src/tables.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -37,6 +37,15 @@ unsigned SlopeDiv(unsigned num, unsigned den)
 	return ans <= SLOPERANGE ? ans : SLOPERANGE;
 }
 
+UINT64 SlopeDivEx(unsigned int num, unsigned int den)
+{
+	UINT64 ans;
+	if (den < 512)
+		return SLOPERANGE;
+	ans = ((UINT64)num<<3)/(den>>8);
+	return ans <= SLOPERANGE ? ans : SLOPERANGE;
+}
+
 fixed_t AngleFixed(angle_t af)
 {
 	angle_t wa = ANGLE_180;
diff --git a/src/tables.h b/src/tables.h
index 03e80788abbe2febdd206d2486c82353a0043bde..60db2b5a6750a0314f128a14698ed79aba00f073 100644
--- a/src/tables.h
+++ b/src/tables.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -81,6 +81,8 @@ extern angle_t tantoangle[SLOPERANGE+1];
 
 // Utility function, called by R_PointToAngle.
 FUNCMATH unsigned SlopeDiv(unsigned num, unsigned den);
+// Only called by R_PointToAngleEx
+UINT64 SlopeDivEx(unsigned int num, unsigned int den);
 
 // 360 - angle_t(ANGLE_45) = ANGLE_315
 FUNCMATH FUNCINLINE static ATTRINLINE angle_t InvAngle(angle_t a)
diff --git a/src/tmap.nas b/src/tmap.nas
index bb4a8c67272439e358749866e32bef04ad3b8898..c72c1890aab39ed44d2739ab2a26f8877cdcdba5 100644
--- a/src/tmap.nas
+++ b/src/tmap.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2016 by Sonic Team Junior.
+;; Copyright (C) 1999-2018 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/tmap.s b/src/tmap.s
index ea1f1c26c7e43fd8d06bfbb0355d01c39d829eec..05c644798f445e2377f6ef92db323913180a6f37 100644
--- a/src/tmap.s
+++ b/src/tmap.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -13,7 +13,7 @@
 // structures, must match the C structures!
 #include "asm_defs.inc"
 
-// Rappel: seuls EAX, ECX, EDX peuvent ˆtre ‚cras‚s librement.
+// Rappel: seuls EAX, ECX, EDX peuvent �tre �cras�s librement.
 //         il faut sauver esi,edi, cd...gs
 
 /* Attention aux comparaisons!                                              */
@@ -28,7 +28,7 @@
 /*              cmp     A,B                     // B-A , set flags          */
 /*              jg      B_greater_than_A                                    */
 /*                                                                          */
-/*        (soustrait l'op‚rande source DE l'op‚rande destination,           */
+/*        (soustrait l'op�rande source DE l'op�rande destination,           */
 /*         comme sur Motorola! )                                            */
 
 // RAPPEL: Intel
@@ -66,7 +66,7 @@ C(vidwidth):    .long   0       //use this one out of the inner loops
 .globl C(ASM_PatchRowBytes)
 C(ASM_PatchRowBytes):
     pushl   %ebp
-    movl    %esp, %ebp      // assure l'"adressabilit‚ du stack"
+    movl    %esp, %ebp      // assure l'"adressabilit� du stack"
 
     movl    ARG1, %edx         // read first arg
     movl    %edx, C(vidwidth)
diff --git a/src/tmap_asm.s b/src/tmap_asm.s
index e8b7af1cd82b915835adf93da08380be15c31cc9..99cb0b627abe6703ef7ebdc785f93d101ef6140c 100644
--- a/src/tmap_asm.s
+++ b/src/tmap_asm.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tmap_mmx.nas b/src/tmap_mmx.nas
index 011b2a0627afb2e547dd2facdd1104fceb7e3bd0..c512de8e9d6b62411157e24941a49b990a3bf1d7 100644
--- a/src/tmap_mmx.nas
+++ b/src/tmap_mmx.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DOSDOOM.
-;; Copyright (C) 2010-2016 by Sonic Team Junior.
+;; Copyright (C) 2010-2018 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/tmap_vc.nas b/src/tmap_vc.nas
index 8fe39ec8971d8be0755b1c6158bb4724eff64ade..e943d48d860209200bda8434925e3b45333f1b10 100644
--- a/src/tmap_vc.nas
+++ b/src/tmap_vc.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2016 by Sonic Team Junior.
+;; Copyright (C) 1999-2018 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/v_video.c b/src/v_video.c
index 20fe9229e8171910d741a1ac493eca764ace095f..2ec06a787cf5d4759b5f69c7ec3a62efda1a5269 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,6 +15,8 @@
 
 #include "doomdef.h"
 #include "r_local.h"
+#include "p_local.h" // stplyr
+#include "g_game.h" // players
 #include "v_video.h"
 #include "hu_stuff.h"
 #include "r_draw.h"
@@ -72,6 +74,8 @@ consvar_t cv_csaturation = {"csaturation", "10", CV_SAVE|CV_CALL, saturation_con
 consvar_t cv_bsaturation = {"bsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_msaturation = {"msaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_allcaps = {"allcaps", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 static CV_PossibleValue_t constextsize_cons_t[] = {
 	{V_NOSCALEPATCH, "Small"}, {V_SMALLSCALEPATCH, "Medium"}, {V_MEDSCALEPATCH, "Large"}, {0, "Huge"},
 	{0, NULL}};
@@ -107,6 +111,7 @@ consvar_t cv_grcoronasize = {"gr_coronasize", "1", CV_SAVE| CV_FLOAT, 0, NULL, 0
 static CV_PossibleValue_t CV_MD2[] = {{0, "Off"}, {1, "On"}, {2, "Old"}, {0, NULL}};
 // console variables in development
 consvar_t cv_grmd2 = {"gr_md2", "Off", CV_SAVE, CV_MD2, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grspritebillboarding = {"gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
 // local copy of the palette for V_GetColor()
@@ -304,12 +309,14 @@ static boolean InitCube(void)
 	return true;
 }
 
+#ifdef BACKWARDSCOMPATCORRECTION
 /*
 So it turns out that the way gamma was implemented previously, the default
 colour profile of the game was messed up. Since this bad decision has been
 around for a long time, and the intent is to keep the base game looking the
 same, I'm not gonna be the one to remove this base modification.
 toast 20/04/17
+... welp yes i am (27/07/19, see the ifdef around it)
 */
 const UINT8 correctiontable[256] =
 	{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
@@ -328,6 +335,7 @@ const UINT8 correctiontable[256] =
 	208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
 	224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
 	240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255};
+#endif
 
 // keep a copy of the palette so that we can get the RGB value for a color index at any time.
 static void LoadPalette(const char *lumpname)
@@ -346,12 +354,18 @@ static void LoadPalette(const char *lumpname)
 	pal = W_CacheLumpNum(lumpnum, PU_CACHE);
 	for (i = 0; i < palsize; i++)
 	{
+#ifdef BACKWARDSCOMPATCORRECTION
 		pMasterPalette[i].s.red = pLocalPalette[i].s.red = correctiontable[*pal++];
 		pMasterPalette[i].s.green = pLocalPalette[i].s.green = correctiontable[*pal++];
 		pMasterPalette[i].s.blue = pLocalPalette[i].s.blue = correctiontable[*pal++];
+#else
+		pMasterPalette[i].s.red = pLocalPalette[i].s.red = *pal++;
+		pMasterPalette[i].s.green = pLocalPalette[i].s.green = *pal++;
+		pMasterPalette[i].s.blue = pLocalPalette[i].s.blue = *pal++;
+#endif
 		pMasterPalette[i].s.alpha = pLocalPalette[i].s.alpha = 0xFF;
 
-		// lerp of colour cubing!
+		// lerp of colour cubing! if you want, make it smoother yourself
 		if (cube)
 		{
 			float working[4][3];
@@ -530,7 +544,6 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 {
 	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
 	UINT32 alphalevel = 0;
-	boolean flip = false;
 
 	fixed_t col, ofs, colfrac, rowfrac, fdup;
 	INT32 dupx, dupy;
@@ -540,12 +553,14 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	fixed_t pwidth; // patch width
 	fixed_t offx = 0; // x offset
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
 #ifdef HWRENDER
-	// oh please
-	if (rendermode != render_soft && !con_startup)
+	//if (rendermode != render_soft && !con_startup)		// Why?
+	if (rendermode != render_soft)
 	{
 		HWR_DrawFixedPatch((GLPatch_t *)patch, x, y, pscale, scrn, colormap);
 		return;
@@ -606,26 +621,102 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	colfrac = FixedDiv(FRACUNIT, fdup);
 	rowfrac = FixedDiv(FRACUNIT, fdup);
 
-	if (scrn & V_OFFSET) // Crosshair shit
+	// So it turns out offsets aren't scaled in V_NOSCALESTART unless V_OFFSET is applied ...poo, that's terrible
+	// For now let's just at least give V_OFFSET the ability to support V_FLIP
+	// I'll probably make a better fix for 2.2 where I don't have to worry about breaking existing support for stuff
+	// -- Monster Iestyn 29/10/18
 	{
-		y -= FixedMul((SHORT(patch->topoffset)*dupy)<<FRACBITS,  pscale);
-		x -= FixedMul((SHORT(patch->leftoffset)*dupx)<<FRACBITS, pscale);
-	}
-	else
-	{
-		y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
+		fixed_t offsetx = 0, offsety = 0;
 
+		// left offset
 		if (scrn & V_FLIP)
+			offsetx = FixedMul((SHORT(patch->width) - SHORT(patch->leftoffset))<<FRACBITS, pscale) + 1;
+		else
+			offsetx = FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
+
+		// top offset
+		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
+		offsety = FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
+
+		if ((scrn & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
 		{
-			flip = true;
-			x -= FixedMul((SHORT(patch->width) - SHORT(patch->leftoffset))<<FRACBITS, pscale);
+			offsetx = FixedMul(offsetx, dupx<<FRACBITS);
+			offsety = FixedMul(offsety, dupy<<FRACBITS);
 		}
-		else
-			x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
+
+		// Subtract the offsets from x/y positions
+		x -= offsetx;
+		y -= offsety;
 	}
 
-	if (scrn & V_SPLITSCREEN)
-		y>>=1;
+	if (splitscreen && (scrn & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
+		fdup >>= 1;
+		rowfrac <<= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			colfrac <<= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				scrn &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP;
+			}
+		}
+	}
 
 	desttop = screens[scrn&V_PARAMMASK];
 
@@ -650,12 +741,15 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
-			// if it's meant to cover the whole screen, black out the rest
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
 			{
 				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				if (!column->topdelta)
+				{
+					source = (const UINT8 *)(column) + 3;
+					V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, source[0]);
+				}
 			}
 
 			if (vid.width != BASEVIDWIDTH * dupx)
@@ -666,16 +760,22 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
 					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+				if (perplayershuffle & 4)
+					x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+				else if (perplayershuffle & 8)
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
-					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
-				else if (scrn & V_SNAPTOBOTTOM)
+				if (scrn & V_SNAPTOBOTTOM)
 					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
 					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+				if (perplayershuffle & 1)
+					y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+				else if (perplayershuffle & 2)
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 			}
 		}
 
@@ -698,7 +798,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, ++offx, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
-		if (flip) // offx is measured from right edge instead of left
+		if (scrn & V_FLIP) // offx is measured from right edge instead of left
 		{
 			if (x+pwidth-offx < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 				break;
@@ -722,7 +822,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 			prevdelta = topdelta;
 			source = (const UINT8 *)(column) + 3;
 			dest = desttop;
-			if (flip)
+			if (scrn & V_FLIP)
 				dest = deststart + (destend - desttop);
 			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
 
@@ -750,6 +850,8 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	UINT8 *desttop, *dest;
 	const UINT8 *source, *deststop;
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
@@ -793,6 +895,84 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
 	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
 
+	if (splitscreen && (scrn & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
+		fdup >>= 1;
+		rowfrac <<= 1;
+		y >>= 1;
+		sy >>= 1;
+		h >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			colfrac <<= 1;
+			x >>= 1;
+			sx >>= 1;
+			w >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				sx += adjustx;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				sx += adjustx;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				scrn &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
 	desttop = screens[scrn&V_PARAMMASK];
 
 	if (!desttop)
@@ -816,12 +996,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
 			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
+			// no the patch is cropped do not do this ever
 
 			if (vid.width != BASEVIDWIDTH * dupx)
 			{
@@ -831,16 +1006,22 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
 					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+				if (perplayershuffle & 4)
+					x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+				else if (perplayershuffle & 8)
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
-					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
-				else if (scrn & V_SNAPTOBOTTOM)
+				if (scrn & V_SNAPTOBOTTOM)
 					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
 					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+				if (perplayershuffle & 1)
+					y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+				else if (perplayershuffle & 2)
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 			}
 		}
 
@@ -883,7 +1064,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 //
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skincolor)
 {
-	if (skins[skinnum].flags & SF_HIRES)
+	if (skinnum < 0 || skinnum >= numskins || (skins[skinnum].flags & SF_HIRES))
 		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
 	else
 	{
@@ -987,6 +1168,8 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	UINT8 *dest;
 	const UINT8 *deststop;
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
@@ -998,13 +1181,81 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	}
 #endif
 
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
 	if (!(c & V_NOSCALESTART))
 	{
 		INT32 dupx = vid.dupx, dupy = vid.dupy;
 
 		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
 		{ // Clear the entire screen, from dest to deststop. Yes, this really works.
-			memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
+			memset(screens[0], (c&255), vid.width * vid.height * vid.bpp);
 			return;
 		}
 
@@ -1022,6 +1273,10 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				x += (vid.width - (BASEVIDWIDTH * dupx));
 			else if (!(c & V_SNAPTOLEFT))
 				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 		}
 		if (vid.height != BASEVIDHEIGHT * dupy)
 		{
@@ -1030,6 +1285,10 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				y += (vid.height - (BASEVIDHEIGHT * dupy));
 			else if (!(c & V_SNAPTOTOP))
 				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 		}
 	}
 
@@ -1059,7 +1318,404 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	c &= 255;
 
 	for (;(--h >= 0) && dest < deststop; dest += vid.width)
-		memset(dest, (UINT8)(c&255), w * vid.bpp);
+		memset(dest, c, w * vid.bpp);
+}
+
+#ifdef HWRENDER
+// This is now a function since it's otherwise repeated 2 times and honestly looks retarded:
+static UINT32 V_GetHWConsBackColor(void)
+{
+	UINT32 hwcolor;
+	switch (cons_backcolor.value)
+	{
+		case 0:		hwcolor = 0xffffff00;	break; 	// White
+		case 1:		hwcolor = 0x80808000;	break; 	// Black
+		case 2:		hwcolor = 0xdeb88700;	break;	// Sepia
+		case 3:		hwcolor = 0x40201000;	break; 	// Brown
+		case 4:		hwcolor = 0xfa807200;	break; 	// Pink
+		case 5:		hwcolor = 0xff69b400;	break; 	// Raspberry
+		case 6:		hwcolor = 0xff000000;	break; 	// Red
+		case 7:		hwcolor = 0xffd68300;	break;	// Creamsicle
+		case 8:		hwcolor = 0xff800000;	break; 	// Orange
+		case 9:		hwcolor = 0xdaa52000;	break; 	// Gold
+		case 10:	hwcolor = 0x80800000;	break; 	// Yellow
+		case 11:	hwcolor = 0x00ff0000;	break; 	// Emerald
+		case 12:	hwcolor = 0x00800000;	break; 	// Green
+		case 13:	hwcolor = 0x4080ff00;	break; 	// Cyan
+		case 14:	hwcolor = 0x4682b400;	break; 	// Steel
+		case 15:	hwcolor = 0x1e90ff00;	break;	// Periwinkle
+		case 16:	hwcolor = 0x0000ff00;	break; 	// Blue
+		case 17:	hwcolor = 0xff00ff00;	break; 	// Purple
+		case 18:	hwcolor = 0xee82ee00;	break; 	// Lavender
+		// Default green
+		default:	hwcolor = 0x00800000;	break;
+	}
+	return hwcolor;
+}
+#endif
+
+
+// THANK YOU MPC!!!
+// and thanks toaster for cleaning it up.
+
+void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
+{
+	UINT8 *dest;
+	const UINT8 *deststop;
+	INT32 u;
+	UINT8 *fadetable;
+	UINT32 alphalevel = 0;
+	UINT8 perplayershuffle = 0;
+
+	if (rendermode == render_none)
+		return;
+
+#ifdef HWRENDER
+	if (rendermode != render_soft && rendermode != render_none)
+	{
+		UINT32 hwcolor = V_GetHWConsBackColor();
+		HWR_DrawConsoleFill(x, y, w, h, c, hwcolor);	// we still use the regular color stuff but only for flags. actual draw color is "hwcolor" for this.
+		return;
+	}
+#endif
+
+	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	{
+		if (alphalevel == 13)
+			alphalevel = hudminusalpha[cv_translucenthud.value];
+		else if (alphalevel == 14)
+			alphalevel = 10 - cv_translucenthud.value;
+		else if (alphalevel == 15)
+			alphalevel = hudplusalpha[cv_translucenthud.value];
+
+		if (alphalevel >= 10)
+			return; // invis
+	}
+
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(c & V_NOSCALESTART))
+	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
+
+		x *= dupx;
+		y *= dupy;
+		w *= dupx;
+		h *= dupy;
+
+		// Center it if necessary
+		if (vid.width != BASEVIDWIDTH * dupx)
+		{
+			// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+			// so center this imaginary screen
+			if (c & V_SNAPTORIGHT)
+				x += (vid.width - (BASEVIDWIDTH * dupx));
+			else if (!(c & V_SNAPTOLEFT))
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (c & V_SNAPTOBOTTOM)
+				y += (vid.height - (BASEVIDHEIGHT * dupy));
+			else if (!(c & V_SNAPTOTOP))
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (x >= vid.width || y >= vid.height)
+		return; // off the screen
+	if (x < 0) {
+		w += x;
+		x = 0;
+	}
+	if (y < 0) {
+		h += y;
+		y = 0;
+	}
+
+	if (w <= 0 || h <= 0)
+		return; // zero width/height wouldn't draw anything
+	if (x + w > vid.width)
+		w = vid.width-x;
+	if (y + h > vid.height)
+		h = vid.height-y;
+
+	dest = screens[0] + y*vid.width + x;
+	deststop = screens[0] + vid.rowbytes * vid.height;
+
+	c &= 255;
+
+	// Jimita (12-04-2018)
+	if (alphalevel)
+	{
+		fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		{
+			u = 0;
+			while (u < w)
+			{
+				dest[u] = fadetable[consolebgmap[dest[u]]];
+				u++;
+			}
+		}
+	}
+	else
+	{
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		{
+			u = 0;
+			while (u < w)
+			{
+				dest[u] = consolebgmap[dest[u]];
+				u++;
+			}
+		}
+	}
+}
+
+//
+// If color is 0x00 to 0xFF, draw transtable (strength range 0-9).
+// Else, use COLORMAP lump (strength range 0-31).
+// c is not color, it is for flags only. transparency flags will be ignored.
+// IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH!
+// I have kept the safety checks for strength out of this function;
+// I don't trust Lua users with it, so it doesn't matter.
+//
+void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength)
+{
+	UINT8 *dest;
+	const UINT8 *deststop;
+	INT32 u;
+	UINT8 *fadetable;
+	UINT8 perplayershuffle = 0;
+
+	if (rendermode == render_none)
+		return;
+
+#ifdef HWRENDER
+	if (rendermode != render_soft && rendermode != render_none)
+	{
+		// ughhhhh please can someone else do this? thanks ~toast 25/7/19 in 38 degrees centigrade w/o AC
+		HWR_DrawFadeFill(x, y, w, h, c, color, strength); // toast two days later - left above comment in 'cause it's funny
+		return;
+	}
+#endif
+
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(c & V_NOSCALESTART))
+	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
+
+		x *= dupx;
+		y *= dupy;
+		w *= dupx;
+		h *= dupy;
+
+		// Center it if necessary
+		if (vid.width != BASEVIDWIDTH * dupx)
+		{
+			// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+			// so center this imaginary screen
+			if (c & V_SNAPTORIGHT)
+				x += (vid.width - (BASEVIDWIDTH * dupx));
+			else if (!(c & V_SNAPTOLEFT))
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (c & V_SNAPTOBOTTOM)
+				y += (vid.height - (BASEVIDHEIGHT * dupy));
+			else if (!(c & V_SNAPTOTOP))
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (x >= vid.width || y >= vid.height)
+		return; // off the screen
+	if (x < 0) {
+		w += x;
+		x = 0;
+	}
+	if (y < 0) {
+		h += y;
+		y = 0;
+	}
+
+	if (w <= 0 || h <= 0)
+		return; // zero width/height wouldn't draw anything
+	if (x + w > vid.width)
+		w = vid.width-x;
+	if (y + h > vid.height)
+		h = vid.height-y;
+
+	dest = screens[0] + y*vid.width + x;
+	deststop = screens[0] + vid.rowbytes * vid.height;
+
+	c &= 255;
+
+	fadetable = ((color & 0xFF00) // Color is not palette index?
+		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
+		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+	for (;(--h >= 0) && dest < deststop; dest += vid.width)
+	{
+		u = 0;
+		while (u < w)
+		{
+			dest[u] = fadetable[dest[u]];
+			u++;
+		}
+	}
 }
 
 //
@@ -1165,14 +1821,6 @@ void V_DrawPatchFill(patch_t *pat)
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
 	INT32 x, y, pw = SHORT(pat->width) * dupz, ph = SHORT(pat->height) * dupz;
 
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-	{
-		pw = FixedMul(SHORT(pat->width)*FRACUNIT, vid.fdupx)>>FRACBITS;
-		ph = FixedMul(SHORT(pat->height)*FRACUNIT, vid.fdupy)>>FRACBITS;
-	}
-#endif
-
 	for (x = 0; x < vid.width; x += pw)
 	{
 		for (y = 0; y < vid.height; y += ph)
@@ -1183,25 +1831,34 @@ void V_DrawPatchFill(patch_t *pat)
 //
 // Fade all the screen buffer, so that the menu is more readable,
 // especially now that we use the small hufont in the menus...
+// If color is 0x00 to 0xFF, draw transtable (strength range 0-9).
+// Else, use COLORMAP lump (strength range 0-31).
+// IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH!
+// I have kept the safety checks out of this function;
+// the v.fadeScreen Lua interface handles those.
 //
-void V_DrawFadeScreen(void)
+void V_DrawFadeScreen(UINT16 color, UINT8 strength)
 {
-	const UINT8 *fadetable = (UINT8 *)colormaps + 16*256;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
-	UINT8 *buf = screens[0];
-
 #ifdef HWRENDER
 	if (rendermode != render_soft && rendermode != render_none)
 	{
-		HWR_FadeScreenMenuBack(0x01010160, 0); // hack, 0 means full height
+		HWR_FadeScreenMenuBack(color, strength);
 		return;
 	}
 #endif
 
-	// heavily simplified -- we don't need to know x or y
-	// position when we're doing a full screen fade
-	for (; buf < deststop; ++buf)
-		*buf = fadetable[*buf];
+	{
+		const UINT8 *fadetable = ((color & 0xFF00) // Color is not palette index?
+		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
+		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+		UINT8 *buf = screens[0];
+
+		// heavily simplified -- we don't need to know x or y
+		// position when we're doing a full screen fade
+		for (; buf < deststop; ++buf)
+			*buf = fadetable[*buf];
+	}
 }
 
 // Simple translucency with one color, over a set number of lines starting from the top.
@@ -1212,21 +1869,7 @@ void V_DrawFadeConsBack(INT32 plines)
 #ifdef HWRENDER // not win32 only 19990829 by Kin
 	if (rendermode != render_soft && rendermode != render_none)
 	{
-		UINT32 hwcolor;
-		switch (cons_backcolor.value)
-		{
-			case 0:		hwcolor = 0xffffff00;	break; // White
-			case 1:		hwcolor = 0x80808000;	break; // Gray
-			case 2:		hwcolor = 0x40201000;	break; // Brown
-			case 3:		hwcolor = 0xff000000;	break; // Red
-			case 4:		hwcolor = 0xff800000;	break; // Orange
-			case 5:		hwcolor = 0x80800000;	break; // Yellow
-			case 6:		hwcolor = 0x00800000;	break; // Green
-			case 7:		hwcolor = 0x0000ff00;	break; // Blue
-			case 8:		hwcolor = 0x4080ff00;	break; // Cyan
-			// Default green
-			default:	hwcolor = 0x00800000;	break;
-		}
+		UINT32 hwcolor = V_GetHWConsBackColor();
 		HWR_DrawConsoleBack(hwcolor, plines);
 		return;
 	}
@@ -1239,26 +1882,95 @@ void V_DrawFadeConsBack(INT32 plines)
 		*buf = consolebgmap[*buf];
 }
 
+// Very similar to F_DrawFadeConsBack, except we draw from the middle(-ish) of the screen to the bottom.
+void V_DrawPromptBack(INT32 boxheight, INT32 color)
+{
+	UINT8 *deststop, *buf;
+
+	boxheight *= vid.dupy;
+
+	if (color == INT32_MAX)
+		color = cons_backcolor.value;
+
+#ifdef HWRENDER
+	if (rendermode != render_soft && rendermode != render_none)
+	{
+		UINT32 hwcolor;
+		switch (color)
+		{
+			case 0:		hwcolor = 0xffffff00;	break; 	// White
+			case 1:		hwcolor = 0x00000000;	break; 	// Black // Note this is different from V_DrawFadeConsBack
+			case 2:		hwcolor = 0xdeb88700;	break;	// Sepia
+			case 3:		hwcolor = 0x40201000;	break; 	// Brown
+			case 4:		hwcolor = 0xfa807200;	break; 	// Pink
+			case 5:		hwcolor = 0xff69b400;	break; 	// Raspberry
+			case 6:		hwcolor = 0xff000000;	break; 	// Red
+			case 7:		hwcolor = 0xffd68300;	break;	// Creamsicle
+			case 8:		hwcolor = 0xff800000;	break; 	// Orange
+			case 9:		hwcolor = 0xdaa52000;	break; 	// Gold
+			case 10:	hwcolor = 0x80800000;	break; 	// Yellow
+			case 11:	hwcolor = 0x00ff0000;	break; 	// Emerald
+			case 12:	hwcolor = 0x00800000;	break; 	// Green
+			case 13:	hwcolor = 0x4080ff00;	break; 	// Cyan
+			case 14:	hwcolor = 0x4682b400;	break; 	// Steel
+			case 15:	hwcolor = 0x1e90ff00;	break;	// Periwinkle
+			case 16:	hwcolor = 0x0000ff00;	break; 	// Blue
+			case 17:	hwcolor = 0xff00ff00;	break; 	// Purple
+			case 18:	hwcolor = 0xee82ee00;	break; 	// Lavender
+			// Default green
+			default:	hwcolor = 0x00800000;	break;
+		}
+		HWR_DrawTutorialBack(hwcolor, boxheight);
+		return;
+	}
+#endif
+
+	CON_SetupBackColormapEx(color, true);
+
+	// heavily simplified -- we don't need to know x or y position,
+	// just the start and stop positions
+	deststop = screens[0] + vid.rowbytes * vid.height;
+	buf = deststop - vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5); // 4 lines of space plus gaps between and some leeway
+	for (; buf < deststop; ++buf)
+		*buf = promptbgmap[*buf];
+}
+
 // Gets string colormap, used for 0x80 color codes
 //
-static const UINT8 *V_GetStringColormap(INT32 colorflags)
+UINT8 *V_GetStringColormap(INT32 colorflags)
 {
 	switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT)
 	{
-	case 1: // 0x81, purple
-		return purplemap;
-	case 2: // 0x82, yellow
+	case  1: // 0x81, magenta
+		return magentamap;
+	case  2: // 0x82, yellow
 		return yellowmap;
-	case 3: // 0x83, lgreen
+	case  3: // 0x83, lgreen
 		return lgreenmap;
-	case 4: // 0x84, blue
+	case  4: // 0x84, blue
 		return bluemap;
-	case 5: // 0x85, red
+	case  5: // 0x85, red
 		return redmap;
-	case 6: // 0x86, gray
+	case  6: // 0x86, gray
 		return graymap;
-	case 7: // 0x87, orange
+	case  7: // 0x87, orange
 		return orangemap;
+	case  8: // 0x88, sky
+		return skymap;
+	case  9: // 0x89, purple
+		return purplemap;
+	case 10: // 0x8A, aqua
+		return aquamap;
+	case 11: // 0x8B, peridot
+		return peridotmap;
+	case 12: // 0x8C, azure
+		return azuremap;
+	case 13: // 0x8D, brown
+		return brownmap;
+	case 14: // 0x8E, rosy
+		return rosymap;
+	case 15: // 0x8F, invert
+		return invertmap;
 	default: // reset
 		return NULL;
 	}
@@ -1290,6 +2002,32 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed)
 		V_DrawScaledPatch(x, y, flags, hu_font[c]);
 }
 
+// Writes a single character for the chat. (draw WHITE if bit 7 set)
+// Essentially the same as the above but it's small or big depending on what resolution you've chosen to huge..
+//
+void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap)
+{
+	INT32 w, flags;
+	//const UINT8 *colormap = V_GetStringColormap(c);
+
+	flags = c & ~(V_CHARCOLORMASK | V_PARAMMASK);
+	c &= 0x7f;
+	if (lowercaseallowed)
+		c -= HU_FONTSTART;
+	else
+		c = toupper(c) - HU_FONTSTART;
+	if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
+		return;
+
+	w = (vid.width < 640 ) ? (SHORT(hu_font[c]->width)/2) : (SHORT(hu_font[c]->width));	// use normal sized characters if we're using a terribly low resolution.
+	if (x + w > vid.width)
+		return;
+
+	V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, (vid.width < 640) ? (FRACUNIT) : (FRACUNIT/2), flags, hu_font[c], colormap);
+
+
+}
+
 // Precompile a wordwrapped string to any given width.
 // This is a muuuch better method than V_WORDWRAP.
 char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
@@ -1367,7 +2105,7 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 {
 	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0;
 	const char *ch = string;
-	INT32 charflags = 0;
+	INT32 charflags = (option & V_CHARCOLORMASK);
 	const UINT8 *colormap = NULL;
 	INT32 spacewidth = 4, charwidth = 0;
 
@@ -1385,10 +2123,9 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		dupx = dupy = 1;
 		scrwidth = vid.width/vid.dupx;
 		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
 	}
 
-	charflags = (option & V_CHARCOLORMASK);
-
 	switch (option & V_SPACINGMASK)
 	{
 		case V_MONOSPACE:
@@ -1446,7 +2183,7 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		else
 			w = SHORT(hu_font[c]->width) * dupx;
 
-		if (cx+left > scrwidth)
+		if (cx > scrwidth)
 			break;
 		if (cx+left + w < 0) //left boundary check
 		{
@@ -1499,6 +2236,7 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		dupx = dupy = 1;
 		scrwidth = vid.width/vid.dupx;
 		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
 	}
 
 	charflags = (option & V_CHARCOLORMASK);
@@ -1558,7 +2296,8 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 		else
 			w = SHORT(hu_font[c]->width) * dupx / 2;
-		if (cx+left > scrwidth)
+
+		if (cx > scrwidth)
 			break;
 		if (cx+left + w < 0) //left boundary check
 		{
@@ -1605,6 +2344,7 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		dupx = dupy = 1;
 		scrwidth = vid.width/vid.dupx;
 		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
 	}
 
 	charflags = (option & V_CHARCOLORMASK);
@@ -1662,7 +2402,7 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		else
 			w = (SHORT(tny_font[c]->width) * dupx);
 
-		if (cx+left > scrwidth)
+		if (cx > scrwidth)
 			break;
 		if (cx+left + w < 0) //left boundary check
 		{
@@ -1705,6 +2445,7 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		dupx = dupy = 1;
 		scrwidth = vid.width/vid.dupx;
 		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
 	}
 
 	switch (option & V_SPACINGMASK)
@@ -1759,9 +2500,9 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		else
 			w = SHORT(hu_font[c]->width) * dupx;
 
-		if ((cx>>FRACBITS)+left > scrwidth)
+		if ((cx>>FRACBITS) > scrwidth)
 			break;
-		if (cx+left + w < 0) //left boundary check
+		if ((cx>>FRACBITS)+left + w < 0) //left boundary check
 		{
 			cx += w<<FRACBITS;
 			continue;
@@ -1819,6 +2560,16 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits)
 	} while (--digits);
 }
 
+// Draw an act number for a level title
+// Todo: actually draw two-digit numbers as two act num patches
+void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, INT32 num)
+{
+	if (num < 0 || num > 19)
+		return; // not supported
+
+	V_DrawScaledPatch(x, y, flags, ttlnum[num]);
+}
+
 // Write a string using the credit font
 // NOTE: the text is centered for screens larger than the base width
 //
@@ -1861,7 +2612,7 @@ void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string)
 		}
 
 		w = SHORT(cred_font[c]->width) * dupx;
-		if ((cx>>FRACBITS) + w > scrwidth)
+		if ((cx>>FRACBITS) > scrwidth)
 			break;
 
 		V_DrawSciencePatch(cx, cy, option, cred_font[c], FRACUNIT);
@@ -1897,8 +2648,10 @@ INT32 V_CreditStringWidth(const char *string)
 //
 void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 {
-	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth = BASEVIDWIDTH;
+	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, left = 0;
 	const char *ch = string;
+	INT32 charflags = (option & V_CHARCOLORMASK);
+	const UINT8 *colormap = NULL;
 
 	if (option & V_NOSCALESTART)
 	{
@@ -1907,21 +2660,32 @@ void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth = vid.width;
 	}
 	else
+	{
 		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
+	}
 
-	for (;;)
+	for (;;ch++)
 	{
-		c = *ch++;
-		if (!c)
+		if (!*ch)
 			break;
-		if (c == '\n')
+		if (*ch & 0x80) //color parsing -x 2.16.09
+		{
+			// manually set flags override color codes
+			if (!(option & V_CHARCOLORMASK))
+				charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
+			continue;
+		}
+		if (*ch == '\n')
 		{
 			cx = x;
 			cy += 12*dupy;
 			continue;
 		}
 
-		c = toupper(c) - LT_FONTSTART;
+		c = toupper(*ch) - LT_FONTSTART;
 		if (c < 0 || c >= LT_FONTSIZE || !lt_font[c])
 		{
 			cx += 16*dupx;
@@ -1929,17 +2693,18 @@ void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 
 		w = SHORT(lt_font[c]->width) * dupx;
-		if (cx + w > scrwidth)
-			break;
 
-		//left boundary check
-		if (cx < 0)
+		if (cx > scrwidth)
+			break;
+		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
 			continue;
 		}
 
-		V_DrawScaledPatch(cx, cy, option, lt_font[c]);
+		colormap = V_GetStringColormap(charflags);
+		V_DrawFixedPatch(cx<<FRACBITS, cy<<FRACBITS, FRACUNIT, option, lt_font[c], colormap);
+
 		cx += w;
 	}
 }
@@ -1953,6 +2718,8 @@ INT32 V_LevelNameWidth(const char *string)
 
 	for (i = 0; i < strlen(string); i++)
 	{
+		if (string[i] & 0x80)
+			continue;
 		c = toupper(string[i]) - LT_FONTSTART;
 		if (c < 0 || c >= LT_FONTSIZE || !lt_font[c])
 			w += 16;
@@ -1983,6 +2750,16 @@ INT32 V_LevelNameHeight(const char *string)
 	return w;
 }
 
+// For ST_drawLevelTitle
+// Returns the width of the act num patch
+INT32 V_LevelActNumWidth(INT32 num)
+{
+	if (num < 0 || num > 19)
+		return 0; // not a valid number
+
+	return SHORT(ttlnum[num]->width);
+}
+
 //
 // Find string width from hu_font chars
 //
@@ -2008,11 +2785,9 @@ INT32 V_StringWidth(const char *string, INT32 option)
 
 	for (i = 0; i < strlen(string); i++)
 	{
-		c = string[i];
-		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
+		if (string[i] & 0x80)
 			continue;
-
-		c = toupper(c) - HU_FONTSTART;
+		c = toupper(string[i]) - HU_FONTSTART;
 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 			w += spacewidth;
 		else
@@ -2050,11 +2825,9 @@ INT32 V_SmallStringWidth(const char *string, INT32 option)
 
 	for (i = 0; i < strlen(string); i++)
 	{
-		c = string[i];
-		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
+		if (string[i] & 0x80)
 			continue;
-
-		c = toupper(c) - HU_FONTSTART;
+		c = toupper(string[i]) - HU_FONTSTART;
 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 			w += spacewidth;
 		else
@@ -2089,11 +2862,9 @@ INT32 V_ThinStringWidth(const char *string, INT32 option)
 
 	for (i = 0; i < strlen(string); i++)
 	{
-		c = string[i];
-		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
+		if (string[i] & 0x80)
 			continue;
-
-		c = toupper(c) - HU_FONTSTART;
+		c = toupper(string[i]) - HU_FONTSTART;
 		if (c < 0 || c >= HU_FONTSIZE || !tny_font[c])
 			w += spacewidth;
 		else
diff --git a/src/v_video.h b/src/v_video.h
index f9fd475f463014caad938c45a9d64d4d36233f61..7eb990295de7c23d35eef3ebf29a243b74081eae 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -31,7 +31,8 @@ extern consvar_t cv_ticrate, cv_constextsize,\
 cv_globalgamma, cv_globalsaturation, \
 cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue,\
 cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma, \
-cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation;
+cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation,\
+cv_allcaps;
 
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
@@ -73,13 +74,21 @@ extern RGBA_t *pMasterPalette;
 #define V_CHARCOLORSHIFT     12
 #define V_CHARCOLORMASK      0x0000F000
 // for simplicity's sake, shortcuts to specific colors
-#define V_PURPLEMAP          0x00001000
+#define V_MAGENTAMAP         0x00001000
 #define V_YELLOWMAP          0x00002000
 #define V_GREENMAP           0x00003000
 #define V_BLUEMAP            0x00004000
 #define V_REDMAP             0x00005000
 #define V_GRAYMAP            0x00006000
 #define V_ORANGEMAP          0x00007000
+#define V_SKYMAP             0x00008000
+#define V_PURPLEMAP          0x00009000
+#define V_AQUAMAP            0x0000A000
+#define V_PERIDOTMAP         0x0000B000
+#define V_AZUREMAP           0x0000C000
+#define V_BROWNMAP           0x0000D000
+#define V_ROSYMAP            0x0000E000
+#define V_INVERTMAP          0x0000F000
 
 // use bits 17-20 for alpha transparency
 #define V_ALPHASHIFT         16
@@ -112,8 +121,8 @@ extern RGBA_t *pMasterPalette;
 #define V_WRAPX              0x10000000 // Don't clamp texture on X (for HW mode)
 #define V_WRAPY              0x20000000 // Don't clamp texture on Y (for HW mode)
 
-#define V_NOSCALESTART       0x40000000  // don't scale x, y, start coords
-#define V_SPLITSCREEN        0x80000000
+#define V_NOSCALESTART       0x40000000 // don't scale x, y, start coords
+#define V_PERPLAYER          0x80000000 // automatically adjust coordinates/scaling for splitscreen mode
 
 // defines for old functions
 #define V_DrawPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s|V_NOSCALESTART|V_NOSCALEPATCH, p, NULL)
@@ -143,21 +152,30 @@ void V_DrawScaledPic (INT32 px1, INT32 py1, INT32 scrn, INT32 lumpnum);
 
 // fill a box with a single color
 void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c);
+void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c);
 // fill a box with a flat as a pattern
 void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum);
 
 // fade down the screen buffer before drawing the menu over
-void V_DrawFadeScreen(void);
+void V_DrawFadeScreen(UINT16 color, UINT8 strength);
+// available to lua over my dead body, which will probably happen in this heat
+void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength);
 
 void V_DrawFadeConsBack(INT32 plines);
+void V_DrawPromptBack(INT32 boxheight, INT32 color);
 
 // draw a single character
 void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed);
+// draw a single character, but for the chat
+void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap);
+
+UINT8 *V_GetStringColormap(INT32 colorflags);
 
 void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string);
 
 // wordwrap a string using the hu_font
 char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string);
+UINT8 *V_GetStringColormap(INT32 colorflags);
 
 // draw a string using the hu_font
 void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string);
@@ -177,10 +195,12 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 // Draw tall nums, used for menu, HUD, intermission
 void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num);
 void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits);
+void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, INT32 num);
 
 // Find string width from lt_font chars
 INT32 V_LevelNameWidth(const char *string);
 INT32 V_LevelNameHeight(const char *string);
+INT32 V_LevelActNumWidth(INT32 num); // act number width
 
 void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string);
 INT32 V_CreditStringWidth(const char *string);
diff --git a/src/vid_copy.s b/src/vid_copy.s
index 9d8e7d4e09ad17d35d78636577f8279e426209ae..050a80999c86a29408affa4e017301e2d42fc537 100644
--- a/src/vid_copy.s
+++ b/src/vid_copy.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/w_wad.c b/src/w_wad.c
index 75bf40983831d655ad0317aa908b72b2bc23f06c..d02ce9ce6061e43f3fa229e549c4821680d8090c 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -77,14 +77,25 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #define O_BINARY 0
 #endif
 
-#if defined(_MSC_VER)
-#pragma pack(1)
+#ifdef HAVE_ZLIB
+#ifndef _MSC_VER
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
 #endif
 
-#if defined(_MSC_VER)
-#pragma pack()
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
 #endif
 
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 0
+#endif
+
+#include "zlib.h"
+#endif
+
+
 typedef struct
 {
 	const char *name;
@@ -119,10 +130,11 @@ void W_Shutdown(void)
 	while (numwadfiles--)
 	{
 		fclose(wadfiles[numwadfiles]->handle);
-		Z_Free(wadfiles[numwadfiles]->lumpinfo);
 		Z_Free(wadfiles[numwadfiles]->filename);
 		while (wadfiles[numwadfiles]->numlumps--)
 			Z_Free(wadfiles[numwadfiles]->lumpinfo[wadfiles[numwadfiles]->numlumps].name2);
+
+		Z_Free(wadfiles[numwadfiles]->lumpinfo);
 		Z_Free(wadfiles[numwadfiles]);
 	}
 }
@@ -150,9 +162,15 @@ FILE *W_OpenWadFile(const char **filename, boolean useerrors)
 {
 	FILE *handle;
 
-	strncpy(filenamebuf, *filename, MAX_WADPATH);
-	filenamebuf[MAX_WADPATH - 1] = '\0';
-	*filename = filenamebuf;
+	// Officially, strncpy should not have overlapping buffers, since W_VerifyNMUSlumps is called after this, and it
+	// changes filename to point at filenamebuf, it would technically be doing that. I doubt any issue will occur since
+	// they point to the same location, but it's better to be safe and this is a simple change.
+	if (filenamebuf != *filename)
+	{
+		strncpy(filenamebuf, *filename, MAX_WADPATH);
+		filenamebuf[MAX_WADPATH - 1] = '\0';
+		*filename = filenamebuf;
+	}
 
 	// open wad file
 	if ((handle = fopen(*filename, "rb")) == NULL)
@@ -183,27 +201,41 @@ 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)
+static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 {
 	UINT16 posStart, posEnd;
+#ifdef HAVE_BLUA
 	posStart = W_CheckNumForFolderStartPK3("Lua/", wadnum, 0);
 	if (posStart != INT16_MAX)
 	{
 		posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart);
+		posStart++; // first "lump" will be "Lua/" folder itself, so ignore it
 		for (; posStart < posEnd; posStart++)
 			LUA_LoadLump(wadnum, posStart);
 	}
-	posStart = W_CheckNumForFolderStartPK3("SOCs/", wadnum, 0);
+#endif
+	posStart = W_CheckNumForFolderStartPK3("SOC/", wadnum, 0);
 	if (posStart != INT16_MAX)
 	{
-		posEnd = W_CheckNumForFolderEndPK3("SOCs/", wadnum, posStart);
+		posEnd = W_CheckNumForFolderEndPK3("SOC/", wadnum, posStart);
+
+		posStart++; // first "lump" will be "SOC/" folder itself, so ignore it
 		for(; posStart < posEnd; posStart++)
-			DEH_LoadDehackedLumpPwad(wadnum, posStart);
+		{
+			lumpinfo_t *lump_p = &wadfiles[wadnum]->lumpinfo[posStart];
+			size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+			char *name = malloc(length + 1);
+			sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
+			name[length] = '\0';
+			CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
+			DEH_LoadDehackedLumpPwad(wadnum, posStart, mainfile);
+			free(name);
+		}
 	}
 }
 
 // search for all DEHACKED lump in all wads and load it
-static inline void W_LoadDehackedLumps(UINT16 wadnum)
+static inline void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
 {
 	UINT16 lump;
 
@@ -222,26 +254,24 @@ static inline void W_LoadDehackedLumps(UINT16 wadnum)
 		for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
 			if (memcmp(lump_p->name,"SOC_",4)==0) // Check for generic SOC lump
 			{	// shameless copy+paste of code from LUA_LoadLump
-				char *name = malloc(strlen(wadfiles[wadnum]->filename)+10);
-				strcpy(name, wadfiles[wadnum]->filename);
-				if (!fasticmp(&name[strlen(name) - 4], ".soc")) {
-					// If it's not a .soc file, copy the lump name in too.
-					name[strlen(wadfiles[wadnum]->filename)] = '|';
-					M_Memcpy(name+strlen(wadfiles[wadnum]->filename)+1, lump_p->name, 8);
-					name[strlen(wadfiles[wadnum]->filename)+9] = '\0';
-				}
+				size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+				char *name = malloc(length + 1);
+				sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
+				name[length] = '\0';
+
 				CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
-				DEH_LoadDehackedLumpPwad(wadnum, lump);
+				DEH_LoadDehackedLumpPwad(wadnum, lump, mainfile);
+				free(name);
 			}
 			else if (memcmp(lump_p->name,"MAINCFG",8)==0) // Check for MAINCFG
 			{
 				CONS_Printf(M_GetText("Loading main config from %s\n"), wadfiles[wadnum]->filename);
-				DEH_LoadDehackedLumpPwad(wadnum, lump);
+				DEH_LoadDehackedLumpPwad(wadnum, lump, mainfile);
 			}
 			else if (memcmp(lump_p->name,"OBJCTCFG",8)==0) // Check for OBJCTCFG
 			{
 				CONS_Printf(M_GetText("Loading object config from %s\n"), wadfiles[wadnum]->filename);
-				DEH_LoadDehackedLumpPwad(wadnum, lump);
+				DEH_LoadDehackedLumpPwad(wadnum, lump, mainfile);
 			}
 	}
 
@@ -303,6 +333,325 @@ static void W_InvalidateLumpnumCache(void)
 	memset(lumpnumcache, 0, sizeof (lumpnumcache));
 }
 
+/** Detect a file type.
+ * \todo Actually detect the wad/pkzip headers and whatnot, instead of just checking the extensions.
+ */
+static restype_t ResourceFileDetect (const char* filename)
+{
+	if (!stricmp(&filename[strlen(filename) - 4], ".pk3"))
+		return RET_PK3;
+	if (!stricmp(&filename[strlen(filename) - 4], ".soc"))
+		return RET_SOC;
+	if (!stricmp(&filename[strlen(filename) - 4], ".lua"))
+		return RET_LUA;
+
+	return RET_WAD;
+}
+
+/** Create a 1-lump lumpinfo_t for standalone files.
+ */
+static lumpinfo_t* ResGetLumpsStandalone (FILE* handle, UINT16* numlumps, const char* lumpname)
+{
+	lumpinfo_t* lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL);
+	lumpinfo->position = 0;
+	fseek(handle, 0, SEEK_END);
+	lumpinfo->size = ftell(handle);
+	fseek(handle, 0, SEEK_SET);
+	strcpy(lumpinfo->name, lumpname);
+	// Allocate the lump's full name.
+	lumpinfo->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+	strcpy(lumpinfo->name2, lumpname);
+	lumpinfo->name2[8] = '\0';
+	*numlumps = 1;
+	return lumpinfo;
+}
+
+/** Create a lumpinfo_t array for a WAD file.
+ */
+static lumpinfo_t* ResGetLumpsWad (FILE* handle, UINT16* nlmp, const char* filename)
+{
+	UINT16 numlumps = *nlmp;
+	lumpinfo_t* lumpinfo;
+	size_t i;
+	INT32 compressed = 0;
+
+	wadinfo_t header;
+	lumpinfo_t *lump_p;
+	filelump_t *fileinfo;
+	void *fileinfov;
+
+	// read the header
+	if (fread(&header, 1, sizeof header, handle) < sizeof header)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Can't read wad header because %s\n"), strerror(ferror(handle)));
+		return NULL;
+	}
+
+	if (memcmp(header.identification, "ZWAD", 4) == 0)
+		compressed = 1;
+	else if (memcmp(header.identification, "IWAD", 4) != 0
+		&& memcmp(header.identification, "PWAD", 4) != 0
+		&& memcmp(header.identification, "SDLL", 4) != 0)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Invalid WAD header\n"));
+		return NULL;
+	}
+
+	header.numlumps = LONG(header.numlumps);
+	header.infotableofs = LONG(header.infotableofs);
+
+	// read wad file directory
+	i = header.numlumps * sizeof (*fileinfo);
+	fileinfov = fileinfo = malloc(i);
+	if (fseek(handle, header.infotableofs, SEEK_SET) == -1
+		|| fread(fileinfo, 1, i, handle) < i)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Corrupt wadfile directory (%s)\n"), strerror(ferror(handle)));
+		free(fileinfov);
+		return NULL;
+	}
+
+	numlumps = header.numlumps;
+
+	// fill in lumpinfo for this wad
+	lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
+	for (i = 0; i < numlumps; i++, lump_p++, fileinfo++)
+	{
+		lump_p->position = LONG(fileinfo->filepos);
+		lump_p->size = lump_p->disksize = LONG(fileinfo->size);
+		if (compressed) // wad is compressed, lump might be
+		{
+			UINT32 realsize = 0;
+			if (fseek(handle, lump_p->position, SEEK_SET)
+				== -1 || fread(&realsize, 1, sizeof realsize,
+				handle) < sizeof realsize)
+			{
+				I_Error("corrupt compressed file: %s; maybe %s", /// \todo Avoid the bailout?
+					filename, strerror(ferror(handle)));
+			}
+			realsize = LONG(realsize);
+			if (realsize != 0)
+			{
+				lump_p->size = realsize;
+				lump_p->compression = CM_LZF;
+			}
+			else
+			{
+				lump_p->size -= 4;
+				lump_p->compression = CM_NOCOMPRESSION;
+			}
+
+			lump_p->position += 4;
+			lump_p->disksize -= 4;
+		}
+		else
+			lump_p->compression = CM_NOCOMPRESSION;
+		memset(lump_p->name, 0x00, 9);
+		strncpy(lump_p->name, fileinfo->name, 8);
+		// Allocate the lump's full name.
+		lump_p->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+		strncpy(lump_p->name2, fileinfo->name, 8);
+		lump_p->name2[8] = '\0';
+	}
+	free(fileinfov);
+	*nlmp = numlumps;
+	return lumpinfo;
+}
+
+/** Optimized pattern search in a file.
+ */
+static boolean ResFindSignature (FILE* handle, char endPat[], UINT32 startpos)
+{
+	char *s;
+	int c;
+
+	fseek(handle, startpos, SEEK_SET);
+	s = endPat;
+	while((c = fgetc(handle)) != EOF)
+	{
+		if (*s != c && s > endPat) // No match?
+			s = endPat; // We "reset" the counter by sending the s pointer back to the start of the array.
+		if (*s == c)
+		{
+			s++;
+			if (*s == 0x00) // The array pointer has reached the key char which marks the end. It means we have matched the signature.
+			{
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+#if defined(_MSC_VER)
+#pragma pack(1)
+#endif
+typedef struct zend_s
+{
+	char signature[4];
+	UINT16 diskpos;
+	UINT16 cdirdisk;
+	UINT16 diskentries;
+	UINT16 entries;
+	UINT32 cdirsize;
+	UINT32 cdiroffset;
+	UINT16 commentlen;
+} ATTRPACK zend_t;
+
+typedef struct zentry_s
+{
+	char signature[4];
+	UINT16 version;
+	UINT16 versionneeded;
+	UINT16 flags;
+	UINT16 compression;
+	UINT16 modtime;
+	UINT16 moddate;
+	UINT32 CRC32;
+	UINT32 compsize;
+	UINT32 size;
+	UINT16 namelen;
+	UINT16 xtralen;
+	UINT16 commlen;
+	UINT16 diskstart;
+	UINT16 attrint;
+	UINT32 attrext;
+	UINT32 offset;
+} ATTRPACK zentry_t;
+
+typedef struct zlentry_s
+{
+	char signature[4];
+	UINT16 versionneeded;
+	UINT16 flags;
+	UINT16 compression;
+	UINT16 modtime;
+	UINT16 moddate;
+	UINT32 CRC32;
+	UINT32 compsize;
+	UINT32 size;
+	UINT16 namelen;
+	UINT16 xtralen;
+} ATTRPACK zlentry_t;
+#if defined(_MSC_VER)
+#pragma pack()
+#endif
+
+/** Create a lumpinfo_t array for a PKZip file.
+ */
+static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
+{
+    zend_t zend;
+    zentry_t* zentries;
+    zentry_t* zentry;
+
+	UINT16 numlumps = *nlmp;
+	lumpinfo_t* lumpinfo;
+	lumpinfo_t *lump_p;
+	size_t i;
+
+	char pat_central[] = {0x50, 0x4b, 0x01, 0x02, 0x00};
+	char pat_end[] = {0x50, 0x4b, 0x05, 0x06, 0x00};
+
+	// Look for central directory end signature near end of file.
+	// Contains entry number (number of lumps), and central directory start offset.
+	fseek(handle, 0, SEEK_END);
+	if (!ResFindSignature(handle, pat_end, max(0, ftell(handle) - (22 + 65536))))
+	{
+		CONS_Alert(CONS_ERROR, "Missing central directory\n");
+		return NULL;
+	}
+
+	fseek(handle, -4, SEEK_CUR);
+	if (fread(&zend, 1, sizeof zend, handle) < sizeof zend)
+	{
+		CONS_Alert(CONS_ERROR, "Corrupt central directory (%s)\n", strerror(ferror(handle)));
+		return NULL;
+	}
+	numlumps = zend.entries;
+
+	lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
+	zentry = zentries = malloc(numlumps * sizeof (*zentries));
+
+	fseek(handle, zend.cdiroffset, SEEK_SET);
+	for (i = 0; i < numlumps; i++, zentry++, lump_p++)
+	{
+		char* fullname;
+		char* trimname;
+		char* dotpos;
+
+		if (fread(zentry, 1, sizeof(zentry_t), handle) < sizeof(zentry_t))
+		{
+			CONS_Alert(CONS_ERROR, "Failed to read central directory (%s)\n", strerror(ferror(handle)));
+			Z_Free(lumpinfo);
+			free(zentries);
+			return NULL;
+		}
+		if (memcmp(zentry->signature, pat_central, 4))
+		{
+			CONS_Alert(CONS_ERROR, "Central directory is corrupt\n");
+			Z_Free(lumpinfo);
+			free(zentries);
+			return NULL;
+		}
+
+		lump_p->position = zentry->offset + zentry->namelen + zentry->xtralen + sizeof(zlentry_t);
+		lump_p->disksize = zentry->compsize;
+		lump_p->size = zentry->size;
+
+		fullname = malloc(zentry->namelen + 1);
+		if (fgets(fullname, zentry->namelen + 1, handle) != fullname)
+		{
+			CONS_Alert(CONS_ERROR, "Unable to read lumpname (%s)\n", strerror(ferror(handle)));
+			Z_Free(lumpinfo);
+			free(zentries);
+			free(fullname);
+			return NULL;
+		}
+
+		// Strip away file address and extension for the 8char name.
+		if ((trimname = strrchr(fullname, '/')) != 0)
+			trimname++;
+		else
+			trimname = fullname; // Care taken for root files.
+
+		if ((dotpos = strrchr(trimname, '.')) == 0)
+			dotpos = fullname + strlen(fullname); // Watch for files without extension.
+
+		memset(lump_p->name, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
+		strncpy(lump_p->name, trimname, min(8, dotpos - trimname));
+
+		lump_p->name2 = Z_Calloc(zentry->namelen + 1, PU_STATIC, NULL);
+		strncpy(lump_p->name2, fullname, zentry->namelen);
+
+		free(fullname);
+
+		switch(zentry->compression)
+		{
+		case 0:
+			lump_p->compression = CM_NOCOMPRESSION;
+			break;
+#ifdef HAVE_ZLIB
+		case 8:
+			lump_p->compression = CM_DEFLATE;
+			break;
+#endif
+		case 14:
+			lump_p->compression = CM_LZF;
+			break;
+		default:
+			CONS_Alert(CONS_WARNING, "%s: Unsupported compression method\n", fullname);
+			lump_p->compression = CM_UNSUPPORTED;
+			break;
+		}
+	}
+
+	free(zentries);
+
+	*nlmp = numlumps;
+	return lumpinfo;
+}
+
 //  Allocate a wadfile, setup the lumpinfo (directory) and
 //  lumpcache, add the wadfile to the current active wadfiles
 //
@@ -314,15 +663,14 @@ static void W_InvalidateLumpnumCache(void)
 //
 // Can now load dehacked files (.soc)
 //
-UINT16 W_InitFile(const char *filename)
+UINT16 W_InitFile(const char *filename, boolean mainfile)
 {
 	FILE *handle;
-	lumpinfo_t *lumpinfo;
+	lumpinfo_t *lumpinfo = NULL;
 	wadfile_t *wadfile;
-	enum restype type;
-	UINT16 numlumps;
+	restype_t type;
+	UINT16 numlumps = 0;
 	size_t i;
-	INT32 compressed = 0;
 	size_t packetsize;
 	UINT8 md5sum[16];
 	boolean important;
@@ -330,6 +678,16 @@ UINT16 W_InitFile(const char *filename)
 	if (!(refreshdirmenu & REFRESHDIR_ADDFILE))
 		refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier
 
+	if (refreshdirname)
+		Z_Free(refreshdirname);
+	if (dirmenu)
+	{
+		refreshdirname = Z_StrDup(filename);
+		nameonly(refreshdirname);
+	}
+	else
+		refreshdirname = NULL;
+
 	//CONS_Debug(DBG_SETUP, "Loading %s\n", filename);
 	//
 	// check if limit of active wadfiles
@@ -350,9 +708,7 @@ UINT16 W_InitFile(const char *filename)
 	// see PutFileNeeded in d_netfil.c
 	if ((important = !W_VerifyNMUSlumps(filename)))
 	{
-		packetsize = packetsizetally;
-
-		packetsize += nameonlylength(filename) + 22;
+		packetsize = packetsizetally + nameonlylength(filename) + 22;
 
 		if (packetsize > MAXFILENEEDED*sizeof(UINT8))
 		{
@@ -379,324 +735,37 @@ UINT16 W_InitFile(const char *filename)
 		if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
 		{
 			CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), filename);
+			if (handle)
+				fclose(handle);
 			return INT16_MAX;
 		}
 	}
 #endif
 
-	// detect dehacked file with the "soc" extension
-	if (!stricmp(&filename[strlen(filename) - 4], ".soc"))
+	switch(type = ResourceFileDetect(filename))
 	{
-		// This code emulates a wadfile with one lump name "OBJCTCFG"
-		// at position 0 and size of the whole file.
-		// This allows soc files to be like all wads, copied by network and loaded at the console.
-		type = RET_WAD;
-
-		numlumps = 1;
-		lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL);
-		lumpinfo->position = 0;
-		fseek(handle, 0, SEEK_END);
-		lumpinfo->size = ftell(handle);
-		fseek(handle, 0, SEEK_SET);
-		strcpy(lumpinfo->name, "OBJCTCFG");
-		// Allocate the lump's full name.
-		lumpinfo->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
-		strcpy(lumpinfo->name2, "OBJCTCFG");
-		lumpinfo->name2[8] = '\0';
-	}
+	case RET_SOC:
+		lumpinfo = ResGetLumpsStandalone(handle, &numlumps, "OBJCTCFG");
+		break;
 #ifdef HAVE_BLUA
-	// detect lua script with the "lua" extension
-	else if (!stricmp(&filename[strlen(filename) - 4], ".lua"))
-	{
-		// This code emulates a wadfile with one lump name "LUA_INIT"
-		// at position 0 and size of the whole file.
-		// This allows soc files to be like all wads, copied by network and loaded at the console.
-		type = RET_WAD;
-
-		numlumps = 1;
-		lumpinfo = Z_Calloc(sizeof (*lumpinfo), PU_STATIC, NULL);
-		lumpinfo->position = 0;
-		fseek(handle, 0, SEEK_END);
-		lumpinfo->size = ftell(handle);
-		fseek(handle, 0, SEEK_SET);
-		strcpy(lumpinfo->name, "LUA_INIT");
-		// Allocate the lump's full name.
-		lumpinfo->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
-		strcpy(lumpinfo->name2, "LUA_INIT");
-		lumpinfo->name2[8] = '\0';
-	}
+	case RET_LUA:
+		lumpinfo = ResGetLumpsStandalone(handle, &numlumps, "LUA_INIT");
+		break;
 #endif
-	else if (!stricmp(&filename[strlen(filename) - 4], ".pk3"))
-	{
-		char curHeader[4];
-		unsigned long size;
-		char seekPat[] = {0x50, 0x4b, 0x01, 0x02, 0x00};
-		char endPat[] = {0x50, 0x4b, 0x05, 0x06, 0x00};
-		char *s;
-		int c;
-		UINT32 position;
-		boolean matched = false;
-		lumpinfo_t *lump_p;
-
-		type = RET_PK3;
-
-		// Obtain the file's size.
-		fseek(handle, 0, SEEK_END);
-		size = ftell(handle);
-		CONS_Debug(DBG_SETUP, "PK3 size is: %ld\n", size);
-
-		// We must look for the central directory through the file. (Thanks to JTE for this algorithm.)
-		// All of the central directory entry headers have a signature of 0x50 0x4b 0x01 0x02.
-		// The first entry found means the beginning of the central directory.
-		fseek(handle, -min(size, (22 + 65536)), SEEK_CUR);
-		s = endPat;
-		while((c = fgetc(handle)) != EOF)
-		{
-			if (*s != c && s > endPat) // No match?
-				s = endPat; // We "reset" the counter by sending the s pointer back to the start of the array.
-			if (*s == c)
-			{
-				s++;
-				if (*s == 0x00) // The array pointer has reached the key char which marks the end. It means we have matched the signature.
-				{
-					matched = true;
-					CONS_Debug(DBG_SETUP, "Found PK3 central directory at position %ld.\n", ftell(handle));
-					break;
-				}
-			}
-		}
-
-		// Error if we couldn't find the central directory at all. It likely means this is not a ZIP/PK3 file.
-		if (matched == false)
-		{
-			CONS_Alert(CONS_ERROR, "No central directory inside PK3! File may be corrupted or incomplete.\n");
-			return INT16_MAX;
-		}
-
-		fseek(handle, 4, SEEK_CUR);
-		fread(&numlumps, 1, 2, handle);
-		fseek(handle, 6, SEEK_CUR);
-		fread(&position, 1, 4, handle);
-		lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
-		fseek(handle, position, SEEK_SET);
-
-		// Since we found the central directory, now we can map our lumpinfo table.
-		// We will look for file headers inside it, until we reach the central directory end signature.
-		// We exactly know what data to expect this time, so now we don't need to do a byte-by-byte search.
-		CONS_Debug(DBG_SETUP, "Now finding central directory file headers...\n");
-		for (i = 0; i < numlumps; i++, lump_p++)
-		{
-			fread(curHeader, 1, 4, handle);
-
-			// We found a central directory entry signature?
-			if (!strncmp(curHeader, seekPat, 3))
-			{
-				// Let's fill in the fields that we actually need.
-				// (Declaring all those vars might not be the optimal way to do this, sorry.)
-				char *eName;
-				int namePos;
-				int nameEnd;
-				unsigned short int eNameLen = 8;
-				unsigned short int eXFieldLen = 0;
-				unsigned short int lNameLen = 0;
-				unsigned short int lXFieldLen = 0;
-				unsigned short int eCommentLen = 0;
-				unsigned short int eCompression = 0;
-				unsigned int eSize = 0;
-				unsigned int eCompSize = 0;
-				unsigned int eLocalHeaderOffset = 0;
-				unsigned long int rememberPos = 0;
-
-				// We get the compression type indicator value.
-				fseek(handle, 6, SEEK_CUR);
-				fread(&eCompression, 1, 2, handle);
-				// Get the size
-				fseek(handle, 8, SEEK_CUR);
-				fread(&eCompSize, 1, 4, handle);
-				fread(&eSize, 1, 4, handle);
-				// We get the variable length fields.
-				fread(&eNameLen, 1, 2, handle);
-				fread(&eXFieldLen, 1, 2, handle);
-				fread(&eCommentLen, 1, 2, handle);
-				fseek(handle, 8, SEEK_CUR);
-				fread(&eLocalHeaderOffset, 1, 4, handle); // Get the offset.
-
-				eName = malloc(sizeof(char)*(eNameLen + 1));
-				fgets(eName, eNameLen + 1, handle);
-
-				// Don't load lump if folder.
-//				if (*(eName + eNameLen - 1) == '/')
-//					continue;
-
-				// We must calculate the position for the actual data.
-				// Why not eLocalHeaderOffset + 30 + eNameLen + eXFieldLen? That's because the extra field and name lengths MAY be different in the local headers.
-				rememberPos = ftell(handle);
-				fseek(handle, eLocalHeaderOffset + 26, SEEK_SET);
-				fread(&lNameLen, 1, 2, handle);
-				fread(&lXFieldLen, 1, 2, handle);
-				lump_p->position = ftell(handle) + lNameLen + lXFieldLen;
-
-				fseek(handle, rememberPos, SEEK_SET); // Let's go back to the central dir.
-				lump_p->disksize = eCompSize;
-				lump_p->size = eSize;
-
-				// We will trim the file's full name so that only the filename is left.
-				namePos = eNameLen - 1;
-				while(namePos--)
-					if(eName[namePos] == '/')
-						break;
-				namePos++;
-				// We will remove the file extension too.
-				nameEnd = 0;
-				while(nameEnd++ < 8)
-					if(eName[namePos + nameEnd] == '.')
-						break;
-				memset(lump_p->name, '\0', 9);
-				strncpy(lump_p->name, eName + namePos, nameEnd);
-
-				lump_p->name2 = Z_Malloc((eNameLen+1)*sizeof(char), PU_STATIC, NULL);
-				strncpy(lump_p->name2, eName, eNameLen);
-				lump_p->name2[eNameLen] = '\0';
-
-				// We set the compression type from what we're supporting so far.
-				switch(eCompression)
-				{
-				case 0:
-					lump_p->compression = CM_NOCOMPRESSION;
-					break;
-				case 8:
-					lump_p->compression = CM_DEFLATE;
-					break;
-				case 14:
-					lump_p->compression = CM_LZF;
-					break;
-				default:
-					CONS_Alert(CONS_WARNING, "Lump has an unsupported compression type!\n");
-					lump_p->compression = CM_UNSUPPORTED;
-					break;
-				}
-				CONS_Debug(DBG_SETUP, "File %s, data begins at: %ld\n", eName, lump_p->position);
-				fseek(handle, eXFieldLen + eCommentLen, SEEK_CUR); // We skip to where we expect the next central directory entry or end marker to be.
-				free(eName);
-			}
-			// We found the central directory end signature?
-			else if (!strncmp(curHeader, endPat, 4))
-			{
-				CONS_Debug(DBG_SETUP, "Central directory end signature found at: %ld\n", ftell(handle));
-
-				/*// We will create a "virtual" marker lump at the very end of lumpinfo for convenience.
-				// This marker will be used by the different lump-seeking (eg. textures, sprites, etc.) in PK3-specific cases in an auxiliary way.
-				lumpinfo = (lumpinfo_t*) Z_Realloc(lumpinfo, (numlumps + 1)*sizeof(*lumpinfo), PU_STATIC, NULL);
-				strcpy(lumpinfo[numlumps].name, "PK3_ENDM\0");
-				lumpinfo[numlumps].name2 = Z_Malloc(14 * sizeof(char), PU_STATIC, NULL);
-				strcpy(lumpinfo[numlumps].name2, "PK3_ENDMARKER\0");
-				lumpinfo[numlumps].position = 0;
-				lumpinfo[numlumps].size = 0;
-				lumpinfo[numlumps].disksize = 0;
-				lumpinfo[numlumps].compression = CM_NOCOMPRESSION;
-				numlumps++;*/
-				break;
-			}
-			// ... None of them? We're only expecting either a central directory signature entry or the central directory end signature.
-			// The file may be broken or incomplete...
-			else
-			{
-				CONS_Alert(CONS_WARNING, "Expected central directory header signature, got something else!");
-				return INT16_MAX;
-			}
-		}
-		// If we've reached this far, then it means our dynamically stored lumpinfo has to be ready.
-		// Now we finally build our... incorrectly called wadfile.
-		// TODO: Maybe we should give them more generalized names, like resourcefile or resfile or something.
-		// Mostly for clarity and better understanding when reading the code.
+	case RET_PK3:
+		lumpinfo = ResGetLumpsZip(handle, &numlumps);
+		break;
+	case RET_WAD:
+		lumpinfo = ResGetLumpsWad(handle, &numlumps, filename);
+		break;
+	default:
+		CONS_Alert(CONS_ERROR, "Unsupported file format\n");
 	}
-	// assume wad file
-	else
-	{
-		wadinfo_t header;
-		lumpinfo_t *lump_p;
-		filelump_t *fileinfo;
-		void *fileinfov;
-
-		type = RET_WAD;
-
-		// read the header
-		if (fread(&header, 1, sizeof header, handle) < sizeof header)
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("Can't read wad header from %s because %s\n"), filename, strerror(ferror(handle)));
-			return INT16_MAX;
-		}
-
-		if (memcmp(header.identification, "ZWAD", 4) == 0)
-			compressed = 1;
-		else if (memcmp(header.identification, "IWAD", 4) != 0
-			&& memcmp(header.identification, "PWAD", 4) != 0
-			&& memcmp(header.identification, "SDLL", 4) != 0)
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("%s does not have a valid WAD header\n"), filename);
-			return INT16_MAX;
-		}
-
-		header.numlumps = LONG(header.numlumps);
-		header.infotableofs = LONG(header.infotableofs);
-
-		// read wad file directory
-		i = header.numlumps * sizeof (*fileinfo);
-		fileinfov = fileinfo = malloc(i);
-		if (fseek(handle, header.infotableofs, SEEK_SET) == -1
-			|| fread(fileinfo, 1, i, handle) < i)
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("Wadfile directory in %s is corrupted (%s)\n"), filename, strerror(ferror(handle)));
-			free(fileinfov);
-			return INT16_MAX;
-		}
-
-		numlumps = header.numlumps;
-
-		// fill in lumpinfo for this wad
-		lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
-		for (i = 0; i < numlumps; i++, lump_p++, fileinfo++)
-		{
-			lump_p->position = LONG(fileinfo->filepos);
-			lump_p->size = lump_p->disksize = LONG(fileinfo->size);
-			if (compressed) // wad is compressed, lump might be
-			{
-				UINT32 realsize = 0;
 
-				if (fseek(handle, lump_p->position, SEEK_SET)
-					== -1 || fread(&realsize, 1, sizeof realsize,
-					handle) < sizeof realsize)
-				{
-					I_Error("corrupt compressed file: %s; maybe %s",
-						filename, strerror(ferror(handle)));
-				}
-				realsize = LONG(realsize);
-				if (realsize != 0)
-				{
-					lump_p->size = realsize;
-					lump_p->compression = CM_LZF;
-				}
-				else
-				{
-					lump_p->size -= 4;
-					lump_p->compression = CM_NOCOMPRESSION;
-				}
-
-				lump_p->position += 4;
-				lump_p->disksize -= 4;
-			}
-			else
-			{
-				lump_p->compression = CM_NOCOMPRESSION;
-			}
-			memset(lump_p->name, 0x00, 9);
-			strncpy(lump_p->name, fileinfo->name, 8);
-			// Allocate the lump's full name.
-			lump_p->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
-			strncpy(lump_p->name2, fileinfo->name, 8);
-			lump_p->name2[8] = '\0';
-		}
-		free(fileinfov);
+	if (lumpinfo == NULL)
+	{
+		fclose(handle);
+		return INT16_MAX;
 	}
 
 	//
@@ -711,6 +780,7 @@ UINT16 W_InitFile(const char *filename)
 	wadfile->important = important;
 	fseek(handle, 0, SEEK_END);
 	wadfile->filesize = (unsigned)ftell(handle);
+	wadfile->type = type;
 
 	// already generated, just copy it over
 	M_Memcpy(&wadfile->md5sum, &md5sum, 16);
@@ -733,14 +803,28 @@ UINT16 W_InitFile(const char *filename)
 	numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded
 
 	// TODO: HACK ALERT - Load Lua & SOC stuff right here. I feel like this should be out of this place, but... Let's stick with this for now.
-	if (wadfile->type == RET_WAD)
-		W_LoadDehackedLumps(numwadfiles - 1);
-	else if (wadfile->type == RET_PK3)
-		W_LoadDehackedLumpsPK3(numwadfiles - 1);
-
+	switch (wadfile->type)
+	{
+	case RET_WAD:
+		W_LoadDehackedLumps(numwadfiles - 1, mainfile);
+		break;
+	case RET_PK3:
+		W_LoadDehackedLumpsPK3(numwadfiles - 1, mainfile);
+		break;
+	case RET_SOC:
+		CONS_Printf(M_GetText("Loading SOC from %s\n"), wadfile->filename);
+		DEH_LoadDehackedLumpPwad(numwadfiles - 1, 0, mainfile);
+		break;
+#ifdef HAVE_BLUA
+	case RET_LUA:
+		LUA_LoadLump(numwadfiles - 1, 0);
+		break;
+#endif
+	default:
+		break;
+	}
 
 	W_InvalidateLumpnumCache();
-
 	return wadfile->numlumps;
 }
 
@@ -755,7 +839,7 @@ UINT16 W_InitFile(const char *filename)
   * \return 1 if all files were loaded, 0 if at least one was missing or
   *           invalid.
   */
-INT32 W_InitMultipleFiles(char **filenames)
+INT32 W_InitMultipleFiles(char **filenames, UINT16 mainfiles)
 {
 	INT32 rc = 1;
 
@@ -766,7 +850,7 @@ INT32 W_InitMultipleFiles(char **filenames)
 	for (; *filenames; filenames++)
 	{
 		//CONS_Debug(DBG_SETUP, "Loading %s\n", *filenames);
-		rc &= (W_InitFile(*filenames) != INT16_MAX) ? 1 : 0;
+		rc &= (W_InitFile(*filenames, numwadfiles < mainfiles) != INT16_MAX) ? 1 : 0;
 	}
 
 	if (!numwadfiles)
@@ -1001,7 +1085,6 @@ lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, con
 			if (check < beid)
 				return (i<<16)+check; // found it, in our constraints
 		}
-
 	}
 	return LUMPERROR;
 }
@@ -1038,6 +1121,41 @@ size_t W_LumpLength(lumpnum_t lumpnum)
 	return W_LumpLengthPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
 
+//
+// W_IsLumpWad
+// Is the lump a WAD? (presumably in a PK3)
+//
+boolean W_IsLumpWad(lumpnum_t lumpnum)
+{
+	if (wadfiles[WADFILENUM(lumpnum)]->type == RET_PK3)
+	{
+		const char *lumpfullName = (wadfiles[WADFILENUM(lumpnum)]->lumpinfo + LUMPNUM(lumpnum))->name2;
+
+		if (strlen(lumpfullName) < 4)
+			return false; // can't possibly be a WAD can it?
+		return !strnicmp(lumpfullName + strlen(lumpfullName) - 4, ".wad", 4);
+	}
+
+	return false; // WADs should never be inside non-PK3s as far as SRB2 is concerned
+}
+
+//
+// W_IsLumpFolder
+// Is the lump a folder? (in a PK3 obviously)
+//
+boolean W_IsLumpFolder(UINT16 wad, UINT16 lump)
+{
+	if (wadfiles[wad]->type == RET_PK3)
+	{
+		const char *name = wadfiles[wad]->lumpinfo[lump].name2;
+
+		return (name[strlen(name)-1] == '/'); // folders end in '/'
+	}
+
+	return false; // non-PK3s don't have folders
+}
+
+#ifdef HAVE_ZLIB
 /* report a zlib or i/o error */
 void zerr(int ret)
 {
@@ -1062,6 +1180,24 @@ void zerr(int ret)
         CONS_Printf("zlib version mismatch!\n");
     }
 }
+#endif
+
+#define NO_PNG_LUMPS
+
+#ifdef NO_PNG_LUMPS
+static void ErrorIfPNG(UINT8 *d, size_t s, char *f, char *l)
+{
+    if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
+        return;
+    // Check for PNG file signature using memcmp
+    // As it may be faster on CPUs with slow unaligned memory access
+    // Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
+    if (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0)
+    {
+        I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .PNG - please convert to either Doom or Flat (raw) image format.", l, f);
+    }
+}
+#endif
 
 /** Reads bytes from the head of a lump.
   * Note: If the lump is compressed, the whole thing has to be read anyway.
@@ -1102,7 +1238,15 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 	switch(wadfiles[wad]->lumpinfo[lump].compression)
 	{
 	case CM_NOCOMPRESSION:		// If it's uncompressed, we directly write the data into our destination, and return the bytes read.
+#ifdef NO_PNG_LUMPS
+		{
+			size_t bytesread = fread(dest, 1, size, handle);
+			ErrorIfPNG(dest, bytesread, wadfiles[wad]->filename, l->name2);
+			return bytesread;
+		}
+#else
 		return fread(dest, 1, size, handle);
+#endif
 	case CM_LZF:		// Is it LZF compressed? Used by ZWADs.
 		{
 #ifdef ZWAD
@@ -1117,35 +1261,41 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 				I_Error("wad %d, lump %d: cannot read compressed data", wad, lump);
 			retval = lzf_decompress(rawData, l->disksize, decData, l->size);
 #ifndef AVOID_ERRNO
-			if (retval == 0 && errno == E2BIG) // errno is a global var set by the lzf functions when something goes wrong.
+			if (retval == 0) // If this was returned, check if errno was set
 			{
-				I_Error("wad %d, lump %d: compressed data too big (bigger than %s)", wad, lump, sizeu1(l->size));
+				// errno is a global var set by the lzf functions when something goes wrong.
+				if (errno == E2BIG)
+					I_Error("wad %d, lump %d: compressed data too big (bigger than %s)", wad, lump, sizeu1(l->size));
+				else if (errno == EINVAL)
+					I_Error("wad %d, lump %d: invalid compressed data", wad, lump);
 			}
-			else if (retval == 0 && errno == EINVAL)
-				I_Error("wad %d, lump %d: invalid compressed data", wad, lump);
-			else
+			// Otherwise, fall back on below error (if zero was actually the correct size then ???)
 #endif
 			if (retval != l->size)
 			{
 				I_Error("wad %d, lump %d: decompressed to wrong number of bytes (expected %s, got %s)", wad, lump, sizeu1(l->size), sizeu2(retval));
 			}
-#else
-			(void)wad;
-			(void)lump;
-			//I_Error("ZWAD files not supported on this platform.");
-			return NULL;
-#endif
+
 			if (!decData) // Did we get no data at all?
 				return 0;
 			M_Memcpy(dest, decData + offset, size);
 			Z_Free(rawData);
 			Z_Free(decData);
+#ifdef NO_PNG_LUMPS
+			ErrorIfPNG(dest, size, wadfiles[wad]->filename, l->name2);
+#endif
 			return size;
+#else
+			//I_Error("ZWAD files not supported on this platform.");
+			return 0;
+#endif
+
 		}
+#ifdef HAVE_ZLIB
 	case CM_DEFLATE: // Is it compressed via DEFLATE? Very common in ZIPs/PK3s, also what most doom-related editors support.
 		{
-			z_const Bytef *rawData; // The lump's raw data.
-			Bytef *decData; // Lump's decompressed real data.
+			UINT8 *rawData; // The lump's raw data.
+			UINT8 *decData; // Lump's decompressed real data.
 
 			int zErr; // Helper var.
 			z_stream strm;
@@ -1180,12 +1330,12 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 				{
 					size = 0;
 					zerr(zErr);
-					(void)inflateEnd(&strm);
 				}
+
+				(void)inflateEnd(&strm);
 			}
 			else
 			{
-				CONS_Printf("whopet\n");
 				size = 0;
 				zerr(zErr);
 			}
@@ -1193,11 +1343,16 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(rawData);
 			Z_Free(decData);
 
+#ifdef NO_PNG_LUMPS
+			ErrorIfPNG(dest, size, wadfiles[wad]->filename, l->name2);
+#endif
 			return size;
 		}
+#endif
 	default:
 		I_Error("wad %d, lump %d: unsupported compression type!", wad, lump);
 	}
+	return 0;
 }
 
 size_t W_ReadLumpHeader(lumpnum_t lumpnum, void *dest, size_t size, size_t offset)
diff --git a/src/w_wad.h b/src/w_wad.h
index f067258849de2234076483753f33860116d0db61..6517388504d39b66945f284b9bd294aa1bcaae18 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -22,6 +22,22 @@
 #pragma interface
 #endif
 
+// a raw entry of the wad directory
+// NOTE: This sits here and not in w_wad.c because p_setup.c makes use of it to load map WADs inside PK3s.
+#if defined(_MSC_VER)
+#pragma pack(1)
+#endif
+typedef struct
+{
+	UINT32 filepos; // file offset of the resource
+	UINT32 size; // size of the resource
+	char name[8]; // name of the resource
+} ATTRPACK filelump_t;
+#if defined(_MSC_VER)
+#pragma pack()
+#endif
+
+
 // ==============================================================
 //               WAD FILE STRUCTURE DEFINITIONS
 // ==============================================================
@@ -34,19 +50,13 @@ typedef struct
 	UINT32 infotableofs; // the 'directory' of resources
 } wadinfo_t;
 
-// a raw entry of the wad directory
-typedef struct
-{
-	UINT32 filepos; // file offset of the resource
-	UINT32 size; // size of the resource
-	char name[8]; // name of the resource
-} ATTRPACK filelump_t;
-
 // Available compression methods for lumps.
 typedef enum
 {
 	CM_NOCOMPRESSION,
+#ifdef HAVE_ZLIB
 	CM_DEFLATE,
+#endif
 	CM_LZF,
 	CM_UNSUPPORTED
 } compmethod;
@@ -59,7 +69,6 @@ typedef struct
 	char name[9]; // filelump_t name[]
 	char *name2; // Used by PK3s. Dynamically allocated name.
 	size_t size; // real (uncompressed) size
-	INT32 compressed; // i
 	compmethod compression; // lump compression method
 } lumpinfo_t;
 
@@ -67,7 +76,7 @@ typedef struct
 //                         DYNAMIC WAD LOADING
 // =========================================================================
 
-#define MAX_WADPATH 128
+#define MAX_WADPATH 512
 #define MAX_WADFILES 48 // maximum of wad files used at the same time
 // (there is a max of simultaneous open files anyway, and this should be plenty)
 
@@ -78,12 +87,19 @@ typedef struct
 #endif
 
 // Resource type of the WAD. Yeah, I know this sounds dumb, but I'll leave it like this until I clean up the code further.
-enum restype {RET_WAD, RET_PK3};
+typedef enum restype
+{
+	RET_WAD,
+	RET_SOC,
+	RET_LUA,
+	RET_PK3,
+	RET_UNKNOWN,
+} restype_t;
 
 typedef struct wadfile_s
 {
 	char *filename;
-	enum restype type;
+	restype_t type;
 	lumpinfo_t *lumpinfo;
 	lumpcache_t *lumpcache;
 #ifdef HWRENDER
@@ -93,6 +109,7 @@ typedef struct wadfile_s
 	FILE *handle;
 	UINT32 filesize; // for network
 	UINT8 md5sum[16];
+
 	boolean important; // also network - !W_VerifyNMUSlumps
 } wadfile_t;
 
@@ -109,11 +126,11 @@ void W_Shutdown(void);
 // Opens a WAD file. Returns the FILE * handle for the file, or NULL if not found or could not be opened
 FILE *W_OpenWadFile(const char **filename, boolean useerrors);
 // Load and add a wadfile to the active wad files, returns numbers of lumps, INT16_MAX on error
-UINT16 W_InitFile(const char *filename);
+UINT16 W_InitFile(const char *filename, boolean mainfile);
 
 // W_InitMultipleFiles returns 1 if all is okay, 0 otherwise,
 // so that it stops with a message if a file was not found, but not if all is okay.
-INT32 W_InitMultipleFiles(char **filenames);
+INT32 W_InitMultipleFiles(char **filenames, UINT16 mainfiles);
 
 const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump);
 const char *W_CheckNameForNum(lumpnum_t lumpnum);
@@ -133,7 +150,12 @@ UINT8 W_LumpExists(const char *name); // Lua uses this.
 size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump);
 size_t W_LumpLength(lumpnum_t lumpnum);
 
+boolean W_IsLumpWad(lumpnum_t lumpnum); // for loading maps from WADs in PK3s
+boolean W_IsLumpFolder(UINT16 wad, UINT16 lump); // for detecting folder "lumps"
+
+#ifdef HAVE_ZLIB
 void zerr(int ret); // zlib error checking
+#endif
 
 size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, size_t offset);
 size_t W_ReadLumpHeader(lumpnum_t lump, void *dest, size_t size, size_t offest); // read all or a part of a lump
diff --git a/src/win32/Makefile.cfg b/src/win32/Makefile.cfg
index 749734a727512fe87d3856d74e94a2902b31cb3a..27926b2085cdbd282fe9113d3a0f3346281295df 100644
--- a/src/win32/Makefile.cfg
+++ b/src/win32/Makefile.cfg
@@ -7,23 +7,48 @@
 #
 
 ifdef MINGW64
-	NOASM=1
-	NONX86=1
+	HAVE_LIBGME=1
+	LIBGME_CFLAGS=-I../libs/gme/include
+	LIBGME_LDFLAGS=-L../libs/gme/win64 -lgme
+ifdef HAVE_OPENMPT
+	LIBOPENMPT_CFLAGS?=-I../libs/libopenmpt/inc
+	LIBOPENMPT_LDFLAGS?=-L../libs/libopenmpt/lib/x86_64/mingw -lopenmpt
+endif
+ifndef NOMIXERX
+	HAVE_MIXERX=1
+	SDL_CFLAGS?=-I../libs/SDL2/x86_64-w64-mingw32/include/SDL2 -I../libs/SDLMixerX/x86_64-w64-mingw32/include/SDL2 -Dmain=SDL_main
+	SDL_LDFLAGS?=-L../libs/SDL2/x86_64-w64-mingw32/lib -L../libs/SDLMixerX/x86_64-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
+else
 	SDL_CFLAGS?=-I../libs/SDL2/x86_64-w64-mingw32/include/SDL2 -I../libs/SDL2_mixer/x86_64-w64-mingw32/include/SDL2 -Dmain=SDL_main
 	SDL_LDFLAGS?=-L../libs/SDL2/x86_64-w64-mingw32/lib -L../libs/SDL2_mixer/x86_64-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
+endif
 else
 	HAVE_LIBGME=1
 	LIBGME_CFLAGS=-I../libs/gme/include
 	LIBGME_LDFLAGS=-L../libs/gme/win32 -lgme
+ifdef HAVE_OPENMPT
+	LIBOPENMPT_CFLAGS?=-I../libs/libopenmpt/inc
+	LIBOPENMPT_LDFLAGS?=-L../libs/libopenmpt/lib/x86/mingw -lopenmpt
+endif
+ifndef NOMIXERX
+	HAVE_MIXERX=1
+	SDL_CFLAGS?=-I../libs/SDL2/i686-w64-mingw32/include/SDL2 -I../libs/SDLMixerX/i686-w64-mingw32/include/SDL2 -Dmain=SDL_main
+	SDL_LDFLAGS?=-L../libs/SDL2/i686-w64-mingw32/lib -L../libs/SDLMixerX/i686-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
+else
 	SDL_CFLAGS?=-I../libs/SDL2/i686-w64-mingw32/include/SDL2 -I../libs/SDL2_mixer/i686-w64-mingw32/include/SDL2 -Dmain=SDL_main
 	SDL_LDFLAGS?=-L../libs/SDL2/i686-w64-mingw32/lib -L../libs/SDL2_mixer/i686-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
 endif
+endif
 
 ifndef NOASM
 	USEASM=1
 endif
 
+ifndef NONET
+ifndef MINGW64 #miniupnc is broken with MINGW64
 	HAVE_MINIUPNPC=1
+endif
+endif
 
 	OPTS=-DSTDC_HEADERS
 
@@ -31,10 +56,6 @@ ifndef GCC44
 	#OPTS+=-mms-bitfields
 endif
 
-ifndef MINGW64
-	OPTS+=-gdwarf-2
-endif
-
 ifndef SDL
 	OPTS+=-D_WINDOWS
 endif
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 95f9c85f94aeb1e6b62b2ac9447402a01c811840..c0fe8eda9ad5f2d73f7a0c2cd7f1a2023a02dd0e 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -1,10 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Debug|Win32">
       <Configuration>Debug</Configuration>
       <Platform>Win32</Platform>
     </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
     <ProjectConfiguration Include="Release|Win32">
       <Configuration>Release</Configuration>
       <Platform>Win32</Platform>
@@ -23,26 +39,49 @@
     <ProjectGuid>{0F554F1D-ED49-4D65-A9A7-F63C57F277BE}</ProjectGuid>
     <Keyword>Win32Proj</Keyword>
     <RootNamespace>Srb2win</RootNamespace>
-    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+    <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
-  <PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <PlatformToolset>v140</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
     <UseDebugLibraries>true</UseDebugLibraries>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+    <PlatformToolset>v141</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
     <UseDebugLibraries>false</UseDebugLibraries>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
+    <UseDebugLibraries>true</UseDebugLibraries>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
     <UseDebugLibraries>true</UseDebugLibraries>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+    <PlatformToolset>v141</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <PlatformToolset>v140</PlatformToolset>
     <UseDebugLibraries>false</UseDebugLibraries>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>
+    <PlatformToolset>v141</PlatformToolset>
+  </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
   </ImportGroup>
@@ -52,31 +91,99 @@
     <Import Project="..\..\libs\FMOD.props" />
     <Import Project="..\..\libs\zlib.props" />
     <Import Project="..\..\libs\libpng.props" />
+    <Import Project="..\..\libs\libgme.props" />
     <Import Project="SRB2Win.props" />
   </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Debug.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Debug.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Release.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Release.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Debug.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Debug.props" />
+  </ImportGroup>
   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
     <Import Project="..\..\SRB2_Release.props" />
   </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\SRB2_Release.props" />
+  </ImportGroup>
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup>
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
     <RunCodeAnalysis>false</RunCodeAnalysis>
   </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
+    <Link>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <ClCompile>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
+    <Link>
+      <RandomizedBaseAddress>true</RandomizedBaseAddress>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
+    <ClCompile>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Link>
+      <AdditionalDependencies>gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="..\am_map.c" />
+    <ClCompile Include="..\apng.c" />
     <ClCompile Include="..\blua\lapi.c" />
     <ClCompile Include="..\blua\lauxlib.c" />
     <ClCompile Include="..\blua\lbaselib.c" />
@@ -119,6 +226,7 @@
     <ClCompile Include="..\hardware\hw3sound.c" />
     <ClCompile Include="..\hardware\hw_bsp.c" />
     <ClCompile Include="..\hardware\hw_cache.c" />
+    <ClCompile Include="..\hardware\hw_clip.c" />
     <ClCompile Include="..\hardware\hw_draw.c" />
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
@@ -185,6 +293,7 @@
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
     <ClCompile Include="..\r_plane.c" />
+    <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
@@ -224,6 +333,7 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\am_map.h" />
+    <ClInclude Include="..\apng.h" />
     <ClInclude Include="..\blua\lapi.h" />
     <ClInclude Include="..\blua\lauxlib.h" />
     <ClInclude Include="..\blua\lcode.h" />
@@ -276,6 +386,7 @@
     <ClInclude Include="..\hardware\hw3dsdrv.h" />
     <ClInclude Include="..\hardware\hw3sound.h" />
     <ClInclude Include="..\hardware\hws_data.h" />
+    <ClInclude Include="..\hardware\hw_clip.h" />
     <ClInclude Include="..\hardware\hw_data.h" />
     <ClInclude Include="..\hardware\hw_defs.h" />
     <ClInclude Include="..\hardware\hw_dll.h" />
@@ -333,6 +444,7 @@
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
     <ClInclude Include="..\r_plane.h" />
+    <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index cfd46f1f81d260f7f9645759f7601626c6a64dad..93806e3951066fff4172cdc9f60a0315171e74f4 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -93,6 +93,9 @@
     <ClCompile Include="..\hardware\hw_cache.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_clip.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_draw.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
@@ -450,6 +453,13 @@
     <ClCompile Include="..\string.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_clip.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\apng.c" />
+    <ClCompile Include="..\r_portal.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="afxres.h">
@@ -473,6 +483,9 @@
     <ClInclude Include="win_main.h">
       <Filter>Win32app</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_clip.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\hardware\hw_data.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -845,6 +858,13 @@
     <ClInclude Include="..\fastcmp.h">
       <Filter>O_Other</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_clip.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\apng.h" />
+    <ClInclude Include="..\r_portal.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Image Include="Srb2win.ico">
diff --git a/src/win32/Srb2win-vc9.vcproj b/src/win32/Srb2win-vc9.vcproj
index 24235bc7b158cb8ec8d6dea0ecb38454c7dd2488..a64b8638cde6986a2377440a62bfe060293cb93f 100644
--- a/src/win32/Srb2win-vc9.vcproj
+++ b/src/win32/Srb2win-vc9.vcproj
@@ -2851,6 +2851,50 @@
 				RelativePath="..\m_misc.h"
 				>
 			</File>
+			<File
+				RelativePath="..\apng.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\apng.h"
+				>
+			</File>
 			<File
 				RelativePath="..\m_queue.c"
 				>
diff --git a/src/win32/Srb2win.props b/src/win32/Srb2win.props
index 44a30d50d4f71b3d8942a5730fc0300baa0a9f7c..fa152f0c97aa6894c289eadb388cb7b33fb153d2 100644
--- a/src/win32/Srb2win.props
+++ b/src/win32/Srb2win.props
@@ -5,7 +5,10 @@
   <PropertyGroup />
   <ItemDefinitionGroup>
     <ClCompile>
-      <PreprocessorDefinitions>_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <!-- x86/x64 defines: has specific libraries that ARM does not -->
+      <PreprocessorDefinitions Condition="'$(Platform)' == 'Win32' OR '$(Platform)' == 'x64'">HAVE_ZLIB;HAVE_LIBGME;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <!-- ARM defines -->
+      <PreprocessorDefinitions Condition="'$(Platform)' != 'Win32' AND '$(Platform)' != 'x64'">_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link />
     <Link>
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index 426a5e01301a1cf0c7f21bcdf3b784a32f2066f3..b60ba750d06cbc8e3413a02b9aebc5f335852fd7 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -36,18 +36,18 @@ IDI_ICON1               ICON    DISCARDABLE     "Srb2win.ico"
 // TEXTINCLUDE
 //
 
-1 TEXTINCLUDE DISCARDABLE 
+1 TEXTINCLUDE DISCARDABLE
 BEGIN
     "resource.h\0"
 END
 
-2 TEXTINCLUDE DISCARDABLE 
+2 TEXTINCLUDE DISCARDABLE
 BEGIN
     "#include ""afxres.h""\r\n"
     "\0"
 END
 
-3 TEXTINCLUDE DISCARDABLE 
+3 TEXTINCLUDE DISCARDABLE
 BEGIN
     "\r\n"
     "\0"
@@ -84,7 +84,7 @@ BEGIN
             VALUE "FileDescription", "Sonic Robo Blast 2\0"
             VALUE "FileVersion", "1, 09\0"
             VALUE "InternalName", "srb2\0"
-            VALUE "LegalCopyright", "Copyright © 1998-2005 Sonic Team Junior\0"
+            VALUE "LegalCopyright", "Copyright � 1998-2018 by Sonic Team Junior\0"
             VALUE "LegalTrademarks", "Sonic the Hedgehog and related characters are trademarks of Sega.\0"
             VALUE "OriginalFilename", "srb2win.exe\0"
             VALUE "PrivateBuild", "\0"
diff --git a/src/win32/fabdxlib.c b/src/win32/fabdxlib.c
index c19b036a0863cf1394c0548218e83d518afbadd2..45ec5d0d321914916d8137204ac26e3a0377bba4 100644
--- a/src/win32/fabdxlib.c
+++ b/src/win32/fabdxlib.c
@@ -147,7 +147,7 @@ static inline BOOL LoadDirectDraw(VOID)
 	DDrawDLL = LoadLibraryA("DDRAW.DLL");
 	if (DDrawDLL == NULL)
 		return false;
-	pfnDirectDrawCreate = (DDCreate)GetProcAddress(DDrawDLL, "DirectDrawCreate");
+	pfnDirectDrawCreate = (DDCreate)(LPVOID)GetProcAddress(DDrawDLL, "DirectDrawCreate");
 	if (pfnDirectDrawCreate == NULL)
 		return false;
 	return true;
diff --git a/src/win32/win_cd.c b/src/win32/win_cd.c
index 4ac1506e5993ac139455a57c0a162d7a2e393fc7..2586b84405649d431e673d1f7c8e4bb3a4d27a9c 100644
--- a/src/win32/win_cd.c
+++ b/src/win32/win_cd.c
@@ -161,7 +161,7 @@ static BOOL wasPlaying;
 //static INT     cdVolume = 0;          // current cd volume (0-31)
 
 // 0-31 like Music & Sfx, though CD hardware volume is 0-255.
-consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // allow Update for next/loop track
 // some crap cd drivers take up to
@@ -471,7 +471,7 @@ void I_PlayCD(UINT8 nTrack, UINT8 bLooping)
 	//faB: stop MIDI music, MIDI music will restart if volume is upped later
 	cv_digmusicvolume.value = 0;
 	cv_midimusicvolume.value = 0;
-	I_StopSong (0);
+	I_StopSong();
 
 	//faB: I don't use the notify message, I'm trying to minimize the delay
 	mciPlay.dwCallback = (DWORD)((size_t)hWndMain);
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 8fa4d17f73c8857f49b4a3ab0ca9a8ae69ea9381..71eda04371d4223dedcd5e193d8c27ed8cf0f70d 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -117,6 +117,7 @@ static loadfunc_t hwdFuncTable[] = {
 #ifdef SHUFFLE
 	{"PostImgRedraw@4",     &hwdriver.pfnPostImgRedraw},
 #endif
+	{"FlushScreenTextures@0",&hwdriver.pfnFlushScreenTextures},
 	{"StartScreenWipe@0",   &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe@0",     &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe@4",      &hwdriver.pfnDoScreenWipe},
@@ -147,6 +148,7 @@ static loadfunc_t hwdFuncTable[] = {
 #ifdef SHUFFLE
 	{"PostImgRedraw",       &hwdriver.pfnPostImgRedraw},
 #endif
+	{"FlushScreenTextures", &hwdriver.pfnFlushScreenTextures},
 	{"StartScreenWipe",     &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe",       &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe",        &hwdriver.pfnDoScreenWipe},
diff --git a/src/win32/win_main.c b/src/win32/win_main.c
index 6c774f5576dad667e64f990b2082df815049b563..8a29f7e188593becc0c480b77008ae1841e8b737 100644
--- a/src/win32/win_main.c
+++ b/src/win32/win_main.c
@@ -42,7 +42,7 @@
 #include "fabdxlib.h"
 #include "win_main.h"
 #include "win_dbg.h"
-#include "../i_sound.h" // midi pause/unpause
+#include "../s_sound.h" // pause sound with handling
 #include "../g_input.h" // KEY_MOUSEWHEELxxx
 #include "../screen.h" // for BASEVID*
 
@@ -110,9 +110,9 @@ static LRESULT CALLBACK MainWndproc(HWND hWnd, UINT message, WPARAM wParam, LPAR
 
 			// pause music when alt-tab
 			if (appActive  && !paused)
-				I_ResumeSong(0);
+				S_ResumeAudio();
 			else if (!paused)
-				I_PauseSong(0);
+				S_PauseAudio();
 			{
 				HANDLE ci = GetStdHandle(STD_INPUT_HANDLE);
 				DWORD mode;
diff --git a/src/win32/win_snd.c b/src/win32/win_snd.c
index 88f34abf8a86ef2d2f1c009e8ddceb8cc47d1f2f..13f766728fa295dc03c852c72978b62ccc531c22 100644
--- a/src/win32/win_snd.c
+++ b/src/win32/win_snd.c
@@ -17,10 +17,8 @@
 #include "gme/gme.h"
 #define GME_TREBLE 5.0
 #define GME_BASS 1.0
-#ifdef HAVE_PNG /// TODO: compile with zlib support without libpng
-
-#define HAVE_ZLIB
 
+#ifdef HAVE_ZLIB
 #ifndef _MSC_VER
 #ifndef _LARGEFILE64_SOURCE
 #define _LARGEFILE64_SOURCE
@@ -36,13 +34,12 @@
 #endif
 
 #include "zlib.h"
-#endif
-#endif
+#endif // HAVE_ZLIB
+#endif // HAVE_LIBGME
 
 static FMOD_SYSTEM *fsys;
 static FMOD_SOUND *music_stream;
 static FMOD_CHANNEL *music_channel;
-static boolean midimode;
 
 static UINT8 music_volume, midi_volume, sfx_volume;
 static INT32 current_track;
@@ -355,12 +352,13 @@ void I_FreeSfx(sfxinfo_t *sfx)
 	sfx->data = NULL;
 }
 
-INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority)
+INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
 {
 	FMOD_SOUND *sound;
 	FMOD_CHANNEL *chan;
 	INT32 i;
 	float frequency;
+	(void)channel;
 
 	sound = (FMOD_SOUND *)S_sfx[id].data;
 	I_Assert(sound != NULL);
@@ -440,9 +438,9 @@ void I_SetSfxVolume(UINT8 volume)
 	sfx_volume = volume;
 }
 
-//
-// MUSIC
-//
+/// ------------------------
+//  MUSIC SYSTEM
+/// ------------------------
 
 void I_InitMusic(void)
 {
@@ -450,53 +448,117 @@ void I_InitMusic(void)
 
 void I_ShutdownMusic(void)
 {
-	I_ShutdownDigMusic();
-	I_ShutdownMIDIMusic();
+	I_StopSong();
 }
 
-void I_PauseSong(INT32 handle)
-{
-	UNREFERENCED_PARAMETER(handle);
-	if (music_stream)
-		FMR_MUSIC(FMOD_Channel_SetPaused(music_channel, true));
-}
+/// ------------------------
+//  MUSIC PROPERTIES
+/// ------------------------
 
-void I_ResumeSong(INT32 handle)
+musictype_t I_SongType(void)
 {
-	UNREFERENCED_PARAMETER(handle);
-	if (music_stream)
-		FMR_MUSIC(FMOD_Channel_SetPaused(music_channel, false));
+	FMOD_SOUND_TYPE type;
+
+#ifdef HAVE_LIBGME
+	if (gme)
+		return MU_GME;
+#endif
+
+	if (!music_stream)
+		return MU_NONE;
+
+	if (FMOD_Sound_GetFormat(music_stream, &type, NULL, NULL, NULL) == FMOD_OK)
+	{
+		switch(type)
+		{
+			case FMOD_SOUND_TYPE_WAV:
+				return MU_WAV;
+			case FMOD_SOUND_TYPE_MOD:
+				return MU_MOD;
+			case FMOD_SOUND_TYPE_MIDI:
+				return MU_MID;
+			case FMOD_SOUND_TYPE_OGGVORBIS:
+				return MU_OGG;
+			case FMOD_SOUND_TYPE_MPEG:
+				return MU_MP3;
+			case FMOD_SOUND_TYPE_FLAC:
+				return MU_FLAC;
+			default:
+				return MU_NONE;
+		}
+	}
+	else
+		return MU_NONE;
 }
 
-void I_InitDigMusic(void)
+boolean I_SongPlaying(void)
 {
+	return (music_stream != NULL);
 }
 
-void I_ShutdownDigMusic(void)
+boolean I_SongPaused(void)
 {
-	if (!midimode)
-		I_StopDigSong();
+	FMOD_BOOL fmpaused = false;
+	if (music_stream)
+		FMOD_Channel_GetPaused(music_channel, &fmpaused);
+	return (boolean)fmpaused;
 }
 
-boolean I_StartDigSong(const char *musicname, boolean looping)
+/// ------------------------
+//  MUSIC EFFECTS
+/// ------------------------
+
+boolean I_SetSongSpeed(float speed)
 {
-	char *data;
-	size_t len;
-	FMOD_CREATESOUNDEXINFO fmt;
-	lumpnum_t lumpnum = W_CheckNumForName(va("O_%s",musicname));
+	FMOD_RESULT e;
+	float frequency;
+	if (!music_stream)
+		return false;
+	if (speed > 250.0f)
+		speed = 250.0f; //limit speed up to 250x
 
-	if (lumpnum == LUMPERROR)
+#ifdef HAVE_LIBGME
+	// Try to set GME speed
+	if (gme)
 	{
-		lumpnum = W_CheckNumForName(va("D_%s",musicname));
-		if (lumpnum == LUMPERROR)
-			return false;
-		midimode = true;
+		gme_set_tempo(gme, speed);
+		return true;
+	}
+#endif
+
+	// Try to set Mod/Midi speed
+	e = FMOD_Sound_SetMusicSpeed(music_stream, speed);
+
+	if (e == FMOD_ERR_FORMAT)
+	{
+		// Just change pitch instead for Ogg/etc.
+		FMR(FMOD_Sound_GetDefaults(music_stream, &frequency, NULL, NULL, NULL));
+		FMR_MUSIC(FMOD_Channel_SetFrequency(music_channel, speed*frequency));
 	}
 	else
-		midimode = false;
+		FMR_MUSIC(e);
+
+	return true;
+}
+
+/// ------------------------
+//  MUSIC PLAYBACK
+/// ------------------------
 
-	data = (char *)W_CacheLumpNum(lumpnum, PU_MUSIC);
-	len = W_LumpLength(lumpnum);
+boolean I_LoadSong(char *data, size_t len)
+{
+	FMOD_CREATESOUNDEXINFO fmt;
+	FMOD_RESULT e;
+	FMOD_TAG tag;
+	unsigned int loopstart, loopend;
+
+	if (
+#ifdef HAVE_LIBGME
+		gme ||
+#endif
+		music_stream
+	)
+		I_UnloadSong();
 
 	memset(&fmt, 0, sizeof(FMOD_CREATESOUNDEXINFO));
 	fmt.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
@@ -531,8 +593,6 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 					gme_equalizer_t gmeq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
 					Z_Free(inflatedData); // GME supposedly makes a copy for itself, so we don't need this lying around
 					Z_Free(data); // We don't need this, either.
-					gme_start_track(gme, 0);
-					current_track = 0;
 					gme_set_equalizer(gme,&gmeq);
 					fmt.format = FMOD_SOUND_FORMAT_PCM16;
 					fmt.defaultfrequency = 44100;
@@ -541,10 +601,7 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 					fmt.decodebuffersize = (44100 * 2) / 35;
 					fmt.pcmreadcallback = GMEReadCallback;
 					fmt.userdata = gme;
-					FMR(FMOD_System_CreateStream(fsys, NULL, FMOD_OPENUSER | (looping ? FMOD_LOOP_NORMAL : 0), &fmt, &music_stream));
-					FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
-					FMR(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
-					FMR(FMOD_Channel_SetPriority(music_channel, 0));
+					FMR(FMOD_System_CreateStream(fsys, NULL, FMOD_OPENUSER, &fmt, &music_stream));
 					return true;
 				}
 			}
@@ -603,8 +660,6 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 	{
 		gme_equalizer_t gmeq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
 		Z_Free(data); // We don't need this anymore.
-		gme_start_track(gme, 0);
-		current_track = 0;
 		gme_set_equalizer(gme,&gmeq);
 		fmt.format = FMOD_SOUND_FORMAT_PCM16;
 		fmt.defaultfrequency = 44100;
@@ -613,147 +668,206 @@ boolean I_StartDigSong(const char *musicname, boolean looping)
 		fmt.decodebuffersize = (44100 * 2) / 35;
 		fmt.pcmreadcallback = GMEReadCallback;
 		fmt.userdata = gme;
-		FMR(FMOD_System_CreateStream(fsys, NULL, FMOD_OPENUSER | (looping ? FMOD_LOOP_NORMAL : 0), &fmt, &music_stream));
-		FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
-		FMR(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
-		FMR(FMOD_Channel_SetPriority(music_channel, 0));
+		FMR(FMOD_System_CreateStream(fsys, NULL, FMOD_OPENUSER, &fmt, &music_stream));
 		return true;
 	}
 #endif
 
 	fmt.length = len;
+
+	e = FMOD_System_CreateStream(fsys, data, FMOD_OPENMEMORY_POINT, &fmt, &music_stream);
+	if (e != FMOD_OK)
 	{
-		FMOD_RESULT e = FMOD_System_CreateStream(fsys, data, FMOD_OPENMEMORY_POINT|(looping ? FMOD_LOOP_NORMAL : 0), &fmt, &music_stream);
-		if (e != FMOD_OK)
-		{
-			if (e == FMOD_ERR_FORMAT)
-				CONS_Alert(CONS_WARNING, "Failed to play music lump %s due to invalid format.\n", W_CheckNameForNum(lumpnum));
-			else
-				FMR(e);
-			return false;
-		}
+		if (e == FMOD_ERR_FORMAT)
+			CONS_Alert(CONS_WARNING, "Failed to play music lump due to invalid format.\n");
+		else
+			FMR(e);
+		return false;
 	}
-	FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
-	if (midimode)
-		FMR(FMOD_Channel_SetVolume(music_channel, midi_volume / 31.0));
-	else
-		FMR(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
-	FMR(FMOD_Channel_SetPriority(music_channel, 0));
-	current_track = 0;
 
 	// Try to find a loop point in streaming music formats (ogg, mp3)
-	if (looping)
+
+	// A proper LOOPPOINT is its own tag, stupid.
+	e = FMOD_Sound_GetTag(music_stream, "LOOPPOINT", 0, &tag);
+	if (e != FMOD_ERR_TAGNOTFOUND)
 	{
-		FMOD_RESULT e;
-		FMOD_TAG tag;
-		unsigned int loopstart, loopend;
+		FMR(e);
+		loopstart = atoi((char *)tag.data); // assumed to be a string data tag.
+		FMR(FMOD_Sound_GetLoopPoints(music_stream, NULL, FMOD_TIMEUNIT_PCM, &loopend, FMOD_TIMEUNIT_PCM));
+		if (loopstart > 0)
+			FMR(FMOD_Sound_SetLoopPoints(music_stream, loopstart, FMOD_TIMEUNIT_PCM, loopend, FMOD_TIMEUNIT_PCM));
+		return true;
+	}
+
+	// Use LOOPMS for time in miliseconds.
+	e = FMOD_Sound_GetTag(music_stream, "LOOPMS", 0, &tag);
+	if (e != FMOD_ERR_TAGNOTFOUND)
+	{
+		FMR(e);
+		loopstart = atoi((char *)tag.data); // assumed to be a string data tag.
+		FMR(FMOD_Sound_GetLoopPoints(music_stream, NULL, FMOD_TIMEUNIT_MS, &loopend, FMOD_TIMEUNIT_PCM));
+		if (loopstart > 0)
+			FMR(FMOD_Sound_SetLoopPoints(music_stream, loopstart, FMOD_TIMEUNIT_MS, loopend, FMOD_TIMEUNIT_PCM));
+		return true;
+	}
 
-		// A proper LOOPPOINT is its own tag, stupid.
-		e = FMOD_Sound_GetTag(music_stream, "LOOPPOINT", 0, &tag);
-		if (e != FMOD_ERR_TAGNOTFOUND)
+	// Try to fetch it from the COMMENT tag, like A.J. Freda
+	e = FMOD_Sound_GetTag(music_stream, "COMMENT", 0, &tag);
+	if (e != FMOD_ERR_TAGNOTFOUND)
+	{
+		char *loopText;
+		// Handle any errors that arose, first
+		FMR(e);
+		// Figure out where the number starts
+		loopText = strstr((char *)tag.data,"LOOPPOINT=");
+		if (loopText != NULL)
 		{
-			FMR(e);
-			loopstart = atoi((char *)tag.data); // assumed to be a string data tag.
+			// Skip the "LOOPPOINT=" part.
+			loopText += 10;
+			// Convert it to our looppoint
+			// FMOD seems to ensure the tag is properly NULL-terminated.
+			// atoi will stop when it reaches anything that's not a number.
+			loopstart = atoi(loopText);
+			// Now do the rest like above
 			FMR(FMOD_Sound_GetLoopPoints(music_stream, NULL, FMOD_TIMEUNIT_PCM, &loopend, FMOD_TIMEUNIT_PCM));
 			if (loopstart > 0)
 				FMR(FMOD_Sound_SetLoopPoints(music_stream, loopstart, FMOD_TIMEUNIT_PCM, loopend, FMOD_TIMEUNIT_PCM));
-			return true;
-		}
-
-		// Use LOOPMS for time in miliseconds.
-		e = FMOD_Sound_GetTag(music_stream, "LOOPMS", 0, &tag);
-		if (e != FMOD_ERR_TAGNOTFOUND)
-		{
-			FMR(e);
-			loopstart = atoi((char *)tag.data); // assumed to be a string data tag.
-			FMR(FMOD_Sound_GetLoopPoints(music_stream, NULL, FMOD_TIMEUNIT_MS, &loopend, FMOD_TIMEUNIT_PCM));
-			if (loopstart > 0)
-				FMR(FMOD_Sound_SetLoopPoints(music_stream, loopstart, FMOD_TIMEUNIT_MS, loopend, FMOD_TIMEUNIT_PCM));
-			return true;
-		}
-
-		// Try to fetch it from the COMMENT tag, like A.J. Freda
-		e = FMOD_Sound_GetTag(music_stream, "COMMENT", 0, &tag);
-		if (e != FMOD_ERR_TAGNOTFOUND)
-		{
-			char *loopText;
-			// Handle any errors that arose, first
-			FMR(e);
-			// Figure out where the number starts
-			loopText = strstr((char *)tag.data,"LOOPPOINT=");
-			if (loopText != NULL)
-			{
-				// Skip the "LOOPPOINT=" part.
-				loopText += 10;
-				// Convert it to our looppoint
-				// FMOD seems to ensure the tag is properly NULL-terminated.
-				// atoi will stop when it reaches anything that's not a number.
-				loopstart = atoi(loopText);
-				// Now do the rest like above
-				FMR(FMOD_Sound_GetLoopPoints(music_stream, NULL, FMOD_TIMEUNIT_PCM, &loopend, FMOD_TIMEUNIT_PCM));
-				if (loopstart > 0)
-					FMR(FMOD_Sound_SetLoopPoints(music_stream, loopstart, FMOD_TIMEUNIT_PCM, loopend, FMOD_TIMEUNIT_PCM));
-			}
-			return true;
 		}
+		return true;
 	}
 
-	// No special loop point, but we're playing so it's all good.
+	// No special loop point
 	return true;
 }
 
-void I_StopDigSong(void)
+void I_UnloadSong(void)
 {
+	I_StopSong();
+#ifdef HAVE_LIBGME
+	if (gme)
+	{
+		gme_delete(gme);
+		gme = NULL;
+	}
+#endif
 	if (music_stream)
+	{
 		FMR(FMOD_Sound_Release(music_stream));
-	music_stream = NULL;
+		music_stream = NULL;
+	}
+}
+
+boolean I_PlaySong(boolean looping)
+{
 #ifdef HAVE_LIBGME
 	if (gme)
-		gme_delete(gme);
-	gme = NULL;
+	{
+		gme_start_track(gme, 0);
+		current_track = 0;
+		FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
+		FMR(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
+		FMR(FMOD_Channel_SetPriority(music_channel, 0));
+		return true;
+	}
 #endif
-	current_track = -1;
+
+	FMR(FMOD_Sound_SetMode(music_stream, (looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF)));
+	FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
+	if (I_SongType() != MU_MID)
+		FMR(FMOD_Channel_SetVolume(music_channel, midi_volume / 31.0));
+	else
+		FMR(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
+	FMR(FMOD_Channel_SetPriority(music_channel, 0));
+	current_track = 0;
+
+	return true;
+}
+
+void I_StopSong(void)
+{
+	if (music_channel)
+		FMR_MUSIC(FMOD_Channel_Stop(music_channel));
+}
+
+void I_PauseSong(void)
+{
+	if (music_channel)
+		FMR_MUSIC(FMOD_Channel_SetPaused(music_channel, true));
 }
 
-void I_SetDigMusicVolume(UINT8 volume)
+void I_ResumeSong(void)
 {
+	if (music_channel)
+		FMR_MUSIC(FMOD_Channel_SetPaused(music_channel, false));
+}
+
+void I_SetMusicVolume(UINT8 volume)
+{
+	if (!music_channel)
+		return;
+
 	// volume is 0 to 31.
-	music_volume = volume;
-	if (!midimode && music_stream)
-		FMR_MUSIC(FMOD_Channel_SetVolume(music_channel, volume / 31.0));
+	if (I_SongType() == MU_MID)
+		music_volume = 31; // windows bug hack
+	else
+		music_volume = volume;
+
+	FMR_MUSIC(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
 }
 
-boolean I_SetSongSpeed(float speed)
+UINT32 I_GetSongLength(void)
+{
+	UINT32 length;
+	if (I_SongType() == MU_MID)
+		return 0;
+	FMR_MUSIC(FMOD_Sound_GetLength(music_stream, &length, FMOD_TIMEUNIT_MS));
+	return length;
+}
+
+boolean I_SetSongLoopPoint(UINT32 looppoint)
+{
+        (void)looppoint;
+        return false;
+}
+
+UINT32 I_GetSongLoopPoint(void)
+{
+	return 0;
+}
+
+boolean I_SetSongPosition(UINT32 position)
 {
 	FMOD_RESULT e;
-	float frequency;
-	if (!music_stream)
+	if(I_SongType() == MU_MID)
+		// Dummy out; this works for some MIDI, but not others.
+		// SDL does not support this for any MIDI.
 		return false;
-	if (speed > 250.0f)
-		speed = 250.0f; //limit speed up to 250x
 
-#ifdef HAVE_LIBGME
-	// Try to set GME speed
-	if (gme)
-	{
-		gme_set_tempo(gme, speed);
+	e = FMOD_Channel_SetPosition(music_channel, position, FMOD_TIMEUNIT_MS);
+	if (e == FMOD_OK)
 		return true;
-	}
-#endif
-
-	// Try to set Mod/Midi speed
-	e = FMOD_Sound_SetMusicSpeed(music_stream, speed);
-
-	if (e == FMOD_ERR_FORMAT)
+	else if (e == FMOD_ERR_UNSUPPORTED // Only music modules, numbnuts!
+			|| e == FMOD_ERR_INVALID_POSITION) // Out-of-bounds!
+		return false;
+	else // Congrats, you horribly broke it somehow
 	{
-		// Just change pitch instead for Ogg/etc.
-		FMR(FMOD_Sound_GetDefaults(music_stream, &frequency, NULL, NULL, NULL));
-		FMR_MUSIC(FMOD_Channel_SetFrequency(music_channel, speed*frequency));
-	}
-	else
 		FMR_MUSIC(e);
+		return false;
+	}
+}
 
-	return true;
+UINT32 I_GetSongPosition(void)
+{
+	FMOD_RESULT e;
+	unsigned int fmposition = 0;
+	if(I_SongType() == MU_MID)
+		// Dummy out because unsupported, even though FMOD does this correctly.
+		return 0;
+	e = FMOD_Channel_GetPosition(music_channel, &fmposition, FMOD_TIMEUNIT_MS);
+	if (e == FMOD_OK)
+		return (UINT32)fmposition;
+	else
+		return 0;
 }
 
 boolean I_SetSongTrack(INT32 track)
@@ -801,61 +915,45 @@ boolean I_SetSongTrack(INT32 track)
 	return false;
 }
 
-//
-// Fuck MIDI. ... Okay fine, you can have your silly D_-only mode.
-//
+/// ------------------------
+/// MUSIC FADING
+/// ------------------------
 
-void I_InitMIDIMusic(void)
+void I_SetInternalMusicVolume(UINT8 volume)
 {
+	(void)volume;
 }
 
-void I_ShutdownMIDIMusic(void)
+void I_StopFadingSong(void)
 {
-	if (midimode)
-		I_StopSong(0);
 }
 
-void I_SetMIDIMusicVolume(UINT8 volume)
+boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
 {
-	// volume is 0 to 31.
-	midi_volume = volume;
-	if (midimode && music_stream)
-		FMR_MUSIC(FMOD_Channel_SetVolume(music_channel, volume / 31.0));
-}
-
-INT32 I_RegisterSong(void *data, size_t len)
-{
-	FMOD_CREATESOUNDEXINFO fmt;
-	memset(&fmt, 0, sizeof(FMOD_CREATESOUNDEXINFO));
-	fmt.cbsize = sizeof(FMOD_CREATESOUNDEXINFO);
-	fmt.length = len;
-	FMR(FMOD_System_CreateStream(fsys, (char *)data, FMOD_OPENMEMORY_POINT, &fmt, &music_stream));
-	return 1337;
+	(void)target_volume;
+	(void)source_volume;
+	(void)ms;
+	(void)callback;
+	return false;
 }
 
-boolean I_PlaySong(INT32 handle, boolean looping)
+boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
 {
-	if (1337 == handle)
-	{
-		midimode = true;
-		if (looping)
-			FMR(FMOD_Sound_SetMode(music_stream, FMOD_LOOP_NORMAL));
-		FMR(FMOD_System_PlaySound(fsys, FMOD_CHANNEL_FREE, music_stream, false, &music_channel));
-		FMR_MUSIC(FMOD_Channel_SetVolume(music_channel, midi_volume / 31.0));
-		FMR_MUSIC(FMOD_Channel_SetPriority(music_channel, 0));
-	}
-	return true;
+	(void)target_volume;
+	(void)ms;
+	(void)callback;
+	return false;
 }
 
-void I_StopSong(INT32 handle)
+boolean I_FadeOutStopSong(UINT32 ms)
 {
-	I_UnRegisterSong(handle);
+	(void)ms;
+	return false;
 }
 
-void I_UnRegisterSong(INT32 handle)
+boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
 {
-	UNREFERENCED_PARAMETER(handle);
-	if (music_stream)
-		FMR(FMOD_Sound_Release(music_stream));
-	music_stream = NULL;
+        (void)ms;
+        (void)looping;
+        return false;
 }
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 316da61d4923d07c934915f88570d75bd98d31ef..d10f73b587e521ca1dfbd2ced7b46e381d78600a 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -639,9 +639,6 @@ void I_Error(const char *error, ...)
 	if (!errorcount)
 	{
 		M_SaveConfig(NULL); // save game config, cvars..
-#ifndef NONET
-		D_SaveBan(); // save the ban list
-#endif
 		G_SaveGameData();
 	}
 
@@ -771,6 +768,8 @@ void I_Quit(void)
 		ShowEndTxt(co);
 	}
 	fflush(stderr);
+	if (myargmalloc)
+		free(myargv); // Deallocate allocated memory
 	W_Shutdown();
 	exit(0);
 }
@@ -3396,7 +3395,7 @@ BOOL LoadDirectInput(VOID)
 	DInputDLL = LoadLibraryA("DINPUT.DLL");
 	if (DInputDLL == NULL)
 		return false;
-	pfnDirectInputCreateA = (DICreateA)GetProcAddress(DInputDLL, "DirectInputCreateA");
+	pfnDirectInputCreateA = (DICreateA)(LPVOID)GetProcAddress(DInputDLL, "DirectInputCreateA");
 	if (pfnDirectInputCreateA == NULL)
 		return false;
 	return true;
@@ -3530,7 +3529,7 @@ void I_GetDiskFreeSpace(INT64* freespace)
 
 	if (!testwin95)
 	{
-		pfnGetDiskFreeSpaceEx = (p_GetDiskFreeSpaceExA)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetDiskFreeSpaceExA");
+		pfnGetDiskFreeSpaceEx = (p_GetDiskFreeSpaceExA)(LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetDiskFreeSpaceExA");
 		testwin95 = true;
 	}
 	if (pfnGetDiskFreeSpaceEx)
@@ -3616,7 +3615,7 @@ const CPUInfoFlags *I_CPUInfo(void)
 {
 	static CPUInfoFlags WIN_CPUInfo;
 	SYSTEM_INFO SI;
-	p_IsProcessorFeaturePresent pfnCPUID = (p_IsProcessorFeaturePresent)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsProcessorFeaturePresent");
+	p_IsProcessorFeaturePresent pfnCPUID = (p_IsProcessorFeaturePresent)(LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsProcessorFeaturePresent");
 
 	ZeroMemory(&WIN_CPUInfo,sizeof (WIN_CPUInfo));
 	if (pfnCPUID)
@@ -3659,9 +3658,9 @@ static p_SetProcessAffinityMask pfnSetProcessAffinityMask = NULL;
 static inline VOID GetAffinityFuncs(VOID)
 {
 	HMODULE h = GetModuleHandleA("kernel32.dll");
-	pfnGetCurrentProcess = (p_GetCurrentProcess)GetProcAddress(h, "GetCurrentProcess");
-	pfnGetProcessAffinityMask = (p_GetProcessAffinityMask)GetProcAddress(h, "GetProcessAffinityMask");
-	pfnSetProcessAffinityMask = (p_SetProcessAffinityMask)GetProcAddress(h, "SetProcessAffinityMask");
+	pfnGetCurrentProcess = (p_GetCurrentProcess)(LPVOID)GetProcAddress(h, "GetCurrentProcess");
+	pfnGetProcessAffinityMask = (p_GetProcessAffinityMask)(LPVOID)GetProcAddress(h, "GetProcessAffinityMask");
+	pfnSetProcessAffinityMask = (p_SetProcessAffinityMask)(LPVOID)GetProcAddress(h, "SetProcessAffinityMask");
 }
 
 static void CPUAffinity_OnChange(void)
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index e9b5d7e9f95a29b45b538fc20de5fa23f09501dd..e2f32fa618d2ff646f1b81a2f156e798cc88f154 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -1000,7 +1000,7 @@ static INT32 WINAPI VID_SetDirectDrawMode(viddef_t *lvid, vmode_t *currentmode)
 	// but rather render to memory bitmap buffer
 	lvid->direct = NULL;
 
-	if (!cv_stretch.value && (float)vid.width/vid.height != ((float)BASEVIDWIDTH/BASEVIDHEIGHT))
+	if (!cv_stretch.value && fabsf((float)vid.width/vid.height - ((float)BASEVIDWIDTH/BASEVIDHEIGHT)) > 1.0E-36f)
 		vid.height = (int)(vid.width * ((float)BASEVIDHEIGHT/BASEVIDWIDTH));// Adjust the height to match
 
 	return 1;
diff --git a/src/y_inter.c b/src/y_inter.c
index 8a93b3eabcecd93b31d39fa967e1236a5c6f7bb1..975902ab089b2593f738bb14540cb2e812df1df8 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2004-2016 by Sonic Team Junior.
+// Copyright (C) 2004-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -70,15 +70,15 @@ typedef union
 		UINT32 score, total; // fake score, total
 		UINT32 tics; // time
 
-		patch_t *ttlnum; // act number being displayed
+		INT32 actnum; // act number being displayed
 		patch_t *ptotal; // TOTAL
 		UINT8 gotlife; // Number of extra lives obtained
 	} coop;
 
 	struct
 	{
-		char passed1[29]; // KNUCKLES GOT    / CRAWLA HONCHO
-		char passed2[17];             // A CHAOS EMERALD / GOT THEM ALL!
+		char passed1[29];             // KNUCKLES GOT     / CRAWLA HONCHO
+		char passed2[17];             // A CHAOS EMERALD? / GOT THEM ALL!
 		char passed3[15];             //                   CAN NOW BECOME
 		char passed4[SKINNAMESIZE+7]; //                   SUPER CRAWLA HONCHO
 		INT32 passedx1;
@@ -86,8 +86,8 @@ typedef union
 		INT32 passedx3;
 		INT32 passedx4;
 
-		y_bonus_t bonus;
-		patch_t *bonuspatch;
+		y_bonus_t bonuses[2];
+		patch_t *bonuspatches[2];
 
 		patch_t *pscore; // SCORE
 		UINT32 score; // fake score
@@ -161,27 +161,25 @@ static void Y_FollowIntermission(void);
 static void Y_UnloadData(void);
 
 // Stuff copy+pasted from st_stuff.c
-static INT32 SCX(INT32 x)
-{
-	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
-}
-static INT32 SCY(INT32 z)
-{
-	return FixedInt(FixedMul(z<<FRACBITS, vid.fdupy));
-}
-
-#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n)
-#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n, q)
-#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, p)
+#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
+#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n, q)
+#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, p)
 
 static void Y_IntermissionTokenDrawer(void)
 {
-	INT32 y;
-	INT32 offs = 0;
+	INT32 y, offs, lowy, calc;
 	UINT32 tokencount;
-	INT32 lowy = BASEVIDHEIGHT - 32;
-	INT16 temp = SHORT(tokenicon->height)/2;
-	INT32 calc;
+	INT16 temp;
+	UINT8 em;
+
+	offs = 0;
+	lowy = BASEVIDHEIGHT - 32 - 8;
+	temp = SHORT(tokenicon->height)/2;
+
+	em = 0;
+	while (emeralds & (1 << em))
+		if (++em == 7)
+			return;
 
 	if (tallydonetic != -1)
 	{
@@ -190,7 +188,7 @@ static void Y_IntermissionTokenDrawer(void)
 			offs = 8;
 	}
 
-	V_DrawFill(32, lowy-1, 16, 1, 31); // slot
+	V_DrawSmallScaledPatch(32, lowy-1, 0, emeraldpics[2][em]); // coinbox
 
 	y = (lowy + offs + 1) - (temp + (token + 1)*8);
 
@@ -255,31 +253,34 @@ void Y_IntermissionDrawer(void)
 		if (gottoken) // first to be behind everything else
 			Y_IntermissionTokenDrawer();
 
-		// draw score
-		ST_DrawPatchFromHud(HUD_SCORE, sboscore);
-		ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
-
-		// draw time
-		ST_DrawPatchFromHud(HUD_TIME, sbotime);
-		if (cv_timetic.value == 1)
-			ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
-		else
+		if (!splitscreen)
 		{
-			INT32 seconds, minutes, tictrn;
+			// draw score
+			ST_DrawPatchFromHud(HUD_SCORE, sboscore);
+			ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
+
+			// draw time
+			ST_DrawPatchFromHud(HUD_TIME, sbotime);
+			if (cv_timetic.value == 1)
+				ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
+			else
+			{
+				INT32 seconds, minutes, tictrn;
 
-			seconds = G_TicsToSeconds(data.coop.tics);
-			minutes = G_TicsToMinutes(data.coop.tics, true);
-			tictrn  = G_TicsToCentiseconds(data.coop.tics);
+				seconds = G_TicsToSeconds(data.coop.tics);
+				minutes = G_TicsToMinutes(data.coop.tics, true);
+				tictrn  = G_TicsToCentiseconds(data.coop.tics);
 
-			ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
-			ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
-			ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
+				ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
+				ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
+				ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
 
-			// we should show centiseconds on the intermission screen too, if the conditions are right.
-			if (modeattacking || cv_timetic.value == 2)
-			{
-				ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
-				ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
+				// we should show centiseconds on the intermission screen too, if the conditions are right.
+				if (modeattacking || cv_timetic.value == 2)
+				{
+					ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
+					ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
+				}
 			}
 		}
 
@@ -287,8 +288,8 @@ void Y_IntermissionDrawer(void)
 		V_DrawLevelTitle(data.coop.passedx1, 49, 0, data.coop.passed1);
 		V_DrawLevelTitle(data.coop.passedx2, 49+V_LevelNameHeight(data.coop.passed2)+2, 0, data.coop.passed2);
 
-		if (mapheaderinfo[gamemap-1]->actnum)
-			V_DrawScaledPatch(244, 57, 0, data.coop.ttlnum);
+		if (data.coop.actnum)
+			V_DrawLevelActNum(244, 57, 0, data.coop.actnum);
 
 		bonusy = 150;
 		// Total
@@ -314,89 +315,195 @@ void Y_IntermissionDrawer(void)
 		INT32 xoffset1 = 0; // Line 1 x offset
 		INT32 xoffset2 = 0; // Line 2 x offset
 		INT32 xoffset3 = 0; // Line 3 x offset
+		INT32 xoffset4 = 0; // Line 4 x offset
+		INT32 xoffset5 = 0; // Line 5 x offset
+		INT32 xoffset6 = 0; // Line 6 x offset
 		UINT8 drawsection = 0;
 
 		if (gottoken) // first to be behind everything else
 			Y_IntermissionTokenDrawer();
 
 		// draw the header
-		if (intertic <= TICRATE)
+		if (intertic <= 2*TICRATE)
 			animatetic = 0;
-		else if (!animatetic && data.spec.bonus.points == 0 && data.spec.passed3[0] != '\0')
-			animatetic = intertic;
+		else if (!animatetic && data.spec.bonuses[0].points == 0 && data.spec.bonuses[1].points == 0 && data.spec.passed3[0] != '\0')
+			animatetic = intertic + TICRATE;
 
-		if (animatetic)
+		if (animatetic && (tic_t)intertic >= animatetic)
 		{
+			const INT32 scradjust = (vid.width/vid.dupx)>>3; // 40 for BASEVIDWIDTH
 			INT32 animatetimer = (intertic - animatetic);
-			if (animatetimer <= 8)
+			if (animatetimer <= 16)
 			{
-				xoffset1 = -(animatetimer     * 40);
-				xoffset2 = -((animatetimer-2) * 40);
+				xoffset1 = -(animatetimer      * scradjust);
+				xoffset2 = -((animatetimer- 2) * scradjust);
+				xoffset3 = -((animatetimer- 4) * scradjust);
+				xoffset4 = -((animatetimer- 6) * scradjust);
+				xoffset5 = -((animatetimer- 8) * scradjust);
+				xoffset6 = -((animatetimer-10) * scradjust);
 				if (xoffset2 > 0) xoffset2 = 0;
+				if (xoffset3 > 0) xoffset3 = 0;
+				if (xoffset4 > 0) xoffset4 = 0;
+				if (xoffset5 > 0) xoffset5 = 0;
+				if (xoffset6 > 0) xoffset6 = 0;
 			}
-			else if (animatetimer <= 19)
+			else if (animatetimer < 34)
 			{
 				drawsection = 1;
-				xoffset1 = (16-animatetimer) * 40;
-				xoffset2 = (18-animatetimer) * 40;
-				xoffset3 = (20-animatetimer) * 40;
+				xoffset1 = (24-animatetimer) * scradjust;
+				xoffset2 = (26-animatetimer) * scradjust;
+				xoffset3 = (28-animatetimer) * scradjust;
+				xoffset4 = (30-animatetimer) * scradjust;
+				xoffset5 = (32-animatetimer) * scradjust;
+				xoffset6 = (34-animatetimer) * scradjust;
 				if (xoffset1 < 0) xoffset1 = 0;
 				if (xoffset2 < 0) xoffset2 = 0;
+				if (xoffset3 < 0) xoffset3 = 0;
+				if (xoffset4 < 0) xoffset4 = 0;
+				if (xoffset5 < 0) xoffset5 = 0;
 			}
 			else
+			{
 				drawsection = 1;
+				if (animatetimer == 32)
+					S_StartSound(NULL, sfx_s3k68);
+			}
 		}
 
 		if (drawsection == 1)
 		{
+			const char *ringtext = "\x82" "50 RINGS, NO SHIELD";
+			const char *tut1text = "\x82" "PRESS " "\x80" "SPIN";
+			const char *tut2text = "\x82" "MID-" "\x80" "JUMP";
 			ttheight = 16;
 			V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
 			ttheight += V_LevelNameHeight(data.spec.passed3) + 2;
 			V_DrawLevelTitle(data.spec.passedx3 + xoffset2, ttheight, 0, data.spec.passed3);
 			ttheight += V_LevelNameHeight(data.spec.passed4) + 2;
 			V_DrawLevelTitle(data.spec.passedx4 + xoffset3, ttheight, 0, data.spec.passed4);
-		}
-		else if (data.spec.passed1[0] != '\0')
-		{
-			ttheight = 24;
-			V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
-			ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
-			V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
+
+			ttheight = 108;
+			V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset4 - (V_LevelNameWidth(ringtext)/2), ttheight, 0, ringtext);
+			ttheight += V_LevelNameHeight(ringtext) + 2;
+			V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset5 - (V_LevelNameWidth(tut1text)/2), ttheight, 0, tut1text);
+			ttheight += V_LevelNameHeight(tut1text) + 2;
+			V_DrawLevelTitle(BASEVIDWIDTH/2 + xoffset6 - (V_LevelNameWidth(tut2text)/2), ttheight, 0, tut2text);
 		}
 		else
 		{
-			ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
-			V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
+			INT32 yoffset = 0;
+			if (data.spec.passed1[0] != '\0')
+			{
+				ttheight = 24;
+				V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
+				ttheight += V_LevelNameHeight(data.spec.passed2) + 2;
+				V_DrawLevelTitle(data.spec.passedx2 + xoffset2, ttheight, 0, data.spec.passed2);
+			}
+			else
+			{
+				ttheight = 24 + (V_LevelNameHeight(data.spec.passed2)/2) + 2;
+				V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
+			}
+
+			V_DrawScaledPatch(152 + xoffset3, 108, 0, data.spec.bonuspatches[0]);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset3 - 68, 109, 0, data.spec.bonuses[0].points);
+			if (data.spec.bonuses[1].display)
+			{
+				V_DrawScaledPatch(152 + xoffset4, 124, 0, data.spec.bonuspatches[1]);
+				V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125, 0, data.spec.bonuses[1].points);
+				yoffset = 16;
+				// hack; pass the buck along...
+				xoffset4 = xoffset5;
+				xoffset5 = xoffset6;
+			}
+			V_DrawScaledPatch(152 + xoffset4, 124+yoffset, 0, data.spec.pscore);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125+yoffset, 0, data.spec.score);
+
+			// Draw continues!
+			if (!multiplayer /* && (data.spec.continues & 0x80) */) // Always draw outside of netplay
+			{
+				UINT8 continues = data.spec.continues & 0x7F;
+
+				V_DrawScaledPatch(152 + xoffset5, 150+yoffset, 0, data.spec.pcontinues);
+				for (i = 0; i < continues; ++i)
+				{
+					if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
+						break;
+					V_DrawContinueIcon(246 + xoffset5 - (i*12), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
+				}
+			}
 		}
 
 		// draw the emeralds
-		if (intertic & 1)
+		//if (intertic & 1)
 		{
-			INT32 emeraldx = 80;
-			for (i = 0; i < 7; ++i)
+			boolean drawthistic = !(ALL7EMERALDS(emeralds) && (intertic & 1));
+			INT32 emeraldx = 152 - 3*28;
+			INT32 em = P_GetNextEmerald();
+
+			if (em == 7)
 			{
-				if (emeralds & (1 << i))
-					V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[i]);
-				emeraldx += 24;
+				if (!stagefailed)
+				{
+					fixed_t adjust = 2*(FINESINE(FixedAngle((intertic + 1)<<(FRACBITS-4)) & FINEMASK));
+					V_DrawFixedPatch(152<<FRACBITS, (74<<FRACBITS) - adjust, FRACUNIT, 0, emeraldpics[0][em], NULL);
+				}
 			}
-		}
+			else if (em < 7)
+			{
+				static UINT8 emeraldbounces = 0;
+				static INT32 emeraldmomy = 20;
+				static INT32 emeraldy = -40;
 
-		V_DrawScaledPatch(152, 108, 0, data.spec.bonuspatch);
-		V_DrawTallNum(BASEVIDWIDTH - 68, 109, 0, data.spec.bonus.points);
-		V_DrawScaledPatch(152, 124, 0, data.spec.pscore);
-		V_DrawTallNum(BASEVIDWIDTH - 68, 125, 0, data.spec.score);
+				if (drawthistic)
+					for (i = 0; i < 7; ++i)
+					{
+						if ((i != em) && (emeralds & (1 << i)))
+							V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
+						emeraldx += 28;
+					}
 
-		// Draw continues!
-		if (!multiplayer /* && (data.spec.continues & 0x80) */) // Always draw outside of netplay
-		{
-			UINT8 continues = data.spec.continues & 0x7F;
+				emeraldx = 152 + (em-3)*28;
 
-			V_DrawScaledPatch(152, 150, 0, data.spec.pcontinues);
-			for (i = 0; i < continues; ++i)
-			{
-				if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
-					break;
-				V_DrawContinueIcon(246 - (i*12), 162, 0, *data.spec.playerchar, *data.spec.playercolor);
+				if (intertic <= 1)
+				{
+					emeraldbounces = 0;
+					emeraldmomy = 20;
+					emeraldy = -40;
+				}
+				else
+				{
+					if (!stagefailed)
+					{
+						if (emeraldbounces < 3)
+						{
+							emeraldmomy += 1;
+							emeraldy += emeraldmomy;
+							if (emeraldy > 74)
+							{
+								S_StartSound(NULL, sfx_tink); // tink
+								emeraldbounces++;
+								emeraldmomy = -(emeraldmomy/2);
+								emeraldy = 74;
+							}
+						}
+					}
+					else
+					{
+						emeraldmomy += 1;
+						emeraldy += emeraldmomy;
+						emeraldx += intertic - 6;
+						if (emeraldbounces < 1 && emeraldy > 74)
+						{
+							S_StartSound(NULL, sfx_shldls); // nope
+							emeraldbounces++;
+							emeraldmomy = -(emeraldmomy/2);
+							emeraldy = 74;
+						}
+					}
+					if (drawthistic)
+						V_DrawScaledPatch(emeraldx, emeraldy, 0, emeraldpics[0][em]);
+				}
 			}
 		}
 	}
@@ -738,7 +845,17 @@ void Y_Ticker(void)
 
 		if (!intertic) // first time only
 		{
-			S_ChangeMusicInternal("_clear", false); // don't loop it
+			if (mapheaderinfo[gamemap-1]->musinterfadeout
+#ifdef _WIN32
+				// can't fade midi due to win32 volume hack
+				&& S_MusicType() != MU_MID
+#endif
+			)
+				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
+			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+			else
+				S_ChangeMusicInternal("_clear", false); // don't loop it
 			tallydonetic = -1;
 		}
 
@@ -774,13 +891,13 @@ void Y_Ticker(void)
 		{
 			tallydonetic = intertic;
 			endtic = intertic + 3*TICRATE; // 3 second pause after end of tally
-			S_StartSound(NULL, sfx_chchng); // cha-ching!
+			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
 			{
 				if (M_UpdateUnlockablesAndExtraEmblems())
-					S_StartSound(NULL, sfx_ncitem);
+					S_StartSound(NULL, sfx_s3k68);
 
 				G_SaveGameData();
 			}
@@ -799,53 +916,80 @@ void Y_Ticker(void)
 	{
 		INT32 i;
 		UINT32 oldscore = data.spec.score;
-		boolean skip = false;
+		boolean skip = false, super = false, anybonuses = false;
 
 		if (!intertic) // first time only
 		{
-			S_ChangeMusicInternal("_clear", false); // don't loop it
+			if (mapheaderinfo[gamemap-1]->musinterfadeout
+#ifdef _WIN32
+				// can't fade midi due to win32 volume hack
+				&& S_MusicType() != MU_MID
+#endif
+			)
+				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
+			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+			else
+				S_ChangeMusicInternal("_clear", false); // don't loop it
 			tallydonetic = -1;
 		}
 
-		if (intertic < TICRATE) // one second pause before tally begins
+		if (intertic < 2*TICRATE) // TWO second pause before tally begins, thank you mazmazz
 			return;
 
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && (players[i].cmd.buttons & BT_USE))
-				skip = true;
+			if (playeringame[i])
+			{
+				if (players[i].cmd.buttons & BT_USE)
+					skip = true;
+				if (players[i].charflags & SF_SUPER)
+					super = true;
+			}
 
-		if ((data.spec.continues & 0x80) && tallydonetic != -1)
+		if (tallydonetic != -1 && ((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds))))
 		{
 			if ((intertic - tallydonetic) > (3*TICRATE)/2)
 			{
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
-				S_StartSound(NULL, sfx_s3kac); // cha-ching!
+				if (data.spec.continues & 0x80)
+					S_StartSound(NULL, sfx_s3kac); // bingly-bingly-bing!
+
 			}
 			return;
 		}
 
-		// ring bonus counts down by 222 each tic
-		data.spec.bonus.points -= 222;
-		data.spec.score += 222;
-		if (data.spec.bonus.points < 0 || skip == true) // went too far
+		// bonuses count down by 222 each tic
+		for (i = 0; i < 2; ++i)
 		{
-			data.spec.score += data.spec.bonus.points;
-			data.spec.bonus.points = 0;
+			if (!data.spec.bonuses[i].points)
+				continue;
+
+			data.spec.bonuses[i].points -= 222;
+			data.spec.score += 222;
+			if (data.spec.bonuses[i].points < 0 || skip == true) // too far?
+			{
+				data.spec.score += data.spec.bonuses[i].points;
+				data.spec.bonuses[i].points = 0;
+			}
+			if (data.spec.score > MAXSCORE)
+				data.spec.score = MAXSCORE;
+			if (data.spec.bonuses[i].points > 0)
+				anybonuses = true;
 		}
 
-		if (!data.spec.bonus.points)
+		if (!anybonuses)
 		{
 			tallydonetic = intertic;
-			if (!(data.spec.continues & 0x80)) // don't set endtic yet!
+			if (!((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds)))) // don't set endtic yet!
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
 
-			S_StartSound(NULL, sfx_chchng); // cha-ching!
+			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
 			{
 				if (M_UpdateUnlockablesAndExtraEmblems())
-					S_StartSound(NULL, sfx_ncitem);
+					S_StartSound(NULL, sfx_s3k68);
 
 				G_SaveGameData();
 			}
@@ -1081,11 +1225,7 @@ void Y_StartIntermission(void)
 			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_STATIC);
 
 			// get act number
-			if (mapheaderinfo[prevmap]->actnum)
-				data.coop.ttlnum = W_CachePatchName(va("TTL%.2d", mapheaderinfo[prevmap]->actnum),
-					PU_STATIC);
-			else
-				data.coop.ttlnum = W_CachePatchName("TTL01", PU_STATIC);
+			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
 
 			// get background patches
 			widebgpatch = W_CachePatchName("INTERSCW", PU_STATIC);
@@ -1183,7 +1323,9 @@ void Y_StartIntermission(void)
 			// give out ring bonuses
 			Y_AwardSpecialStageBonus();
 
-			data.spec.bonuspatch = W_CachePatchName(data.spec.bonus.patch, PU_STATIC);
+			for (i = 0; i < 2; ++i)
+				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_STATIC);
+
 			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_STATIC);
 			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_STATIC);
 
@@ -1235,7 +1377,7 @@ void Y_StartIntermission(void)
 				data.spec.passed1[sizeof data.spec.passed1 - 1] = '\0';
 				strcpy(data.spec.passed2, "GOT THEM ALL!");
 
-				if (skins[players[consoleplayer].skin].flags & SF_SUPER)
+				if (players[consoleplayer].charflags & SF_SUPER)
 				{
 					strcpy(data.spec.passed3, "CAN NOW BECOME");
 					snprintf(data.spec.passed4,
@@ -1256,6 +1398,11 @@ void Y_StartIntermission(void)
 				else
 					strcpy(data.spec.passed1, "YOU GOT");
 				strcpy(data.spec.passed2, "A CHAOS EMERALD");
+				if (P_GetNextEmerald() > 6)
+				{
+					data.spec.passed2[15] = '?';
+					data.spec.passed2[16] = '\0';
+				}
 			}
 			data.spec.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed1))/2;
 			data.spec.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.spec.passed2))/2;
@@ -1492,6 +1639,7 @@ static void Y_CalculateCompetitionWinners(void)
 	UINT32 maxrings[MAXPLAYERS];
 	UINT32 monitors[MAXPLAYERS];
 	UINT32 scores[MAXPLAYERS];
+	char tempname[9];
 
 	memset(data.competition.points, 0, sizeof (data.competition.points));
 	memset(points, 0, sizeof (points));
@@ -1583,8 +1731,9 @@ static void Y_CalculateCompetitionWinners(void)
 		data.competition.monitors[data.competition.numplayers] = monitors[winner];
 		data.competition.scores[data.competition.numplayers] = scores[winner];
 
-		snprintf(data.competition.name[data.competition.numplayers], 9, "%s", player_names[winner]);
-		data.competition.name[data.competition.numplayers][8] = '\0';
+		strncpy(tempname, player_names[winner], 8);
+		tempname[8] = '\0';
+		strncpy(data.competition.name[data.competition.numplayers], tempname, 9);
 
 		data.competition.color[data.competition.numplayers] = &players[winner].skincolor;
 		data.competition.character[data.competition.numplayers] = &players[winner].skin;
@@ -1647,6 +1796,26 @@ static void Y_SetRingBonus(player_t *player, y_bonus_t *bstruct)
 	bstruct->points = max(0, (player->rings) * 100);
 }
 
+//
+// Y_SetNightsBonus
+//
+static void Y_SetNightsBonus(player_t *player, y_bonus_t *bstruct)
+{
+	strncpy(bstruct->patch, "YB_NIGHT", sizeof(bstruct->patch));
+	bstruct->display = true;
+	bstruct->points = player->totalmarescore;
+}
+
+//
+// Y_SetLapBonus
+//
+static void Y_SetLapBonus(player_t *player, y_bonus_t *bstruct)
+{
+	strncpy(bstruct->patch, "YB_LAP", sizeof(bstruct->patch));
+	bstruct->display = true;
+	bstruct->points = max(0, player->totalmarebonuslap * 1000);
+}
+
 //
 // Y_SetLinkBonus
 //
@@ -1686,7 +1855,7 @@ static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
 	memset(bstruct, 0, sizeof(y_bonus_t));
 	strncpy(bstruct->patch, "YB_PERFE", sizeof(bstruct->patch));
 
-	if (data.coop.gotperfbonus == -1)
+	if (intertype != int_coop || data.coop.gotperfbonus == -1)
 	{
 		INT32 sharedringtotal = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -1694,21 +1863,39 @@ static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
 			if (!playeringame[i]) continue;
 			sharedringtotal += players[i].rings;
 		}
-		if (!sharedringtotal || sharedringtotal < nummaprings)
-			data.coop.gotperfbonus = 0;
+		if (!sharedringtotal || nummaprings == -1 || sharedringtotal < nummaprings)
+			bstruct->display = false;
 		else
-			data.coop.gotperfbonus = 1;
+		{
+			bstruct->display = true;
+			bstruct->points = 50000;
+		}
 	}
-	if (!data.coop.gotperfbonus)
+	if (intertype != int_coop)
 		return;
 
+	data.coop.gotperfbonus = (bstruct->display ? 1 : 0);
+}
+
+static void Y_SetSpecialRingBonus(player_t *player, y_bonus_t *bstruct)
+{
+	INT32 i, sharedringtotal = 0;
+
+	(void)player;
+	strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
 	bstruct->display = true;
-	bstruct->points = 50000;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i]) continue;
+		sharedringtotal += players[i].rings;
+	}
+	bstruct->points = max(0, (sharedringtotal) * 100);
 }
 
 // This list can be extended in the future with SOC/Lua, perhaps.
 typedef void (*bonus_f)(player_t *, y_bonus_t *);
-bonus_f bonuses_list[4][4] = {
+bonus_f bonuses_list[6][4] = {
 	{
 		Y_SetNullBonus,
 		Y_SetNullBonus,
@@ -1733,6 +1920,18 @@ bonus_f bonuses_list[4][4] = {
 		Y_SetRingBonus,
 		Y_SetPerfectBonus,
 	},
+	{
+		Y_SetNullBonus,
+		Y_SetNightsBonus,
+		Y_SetLapBonus,
+		Y_SetNullBonus,
+	},
+	{
+		Y_SetNullBonus,
+		Y_SetLinkBonus,
+		Y_SetLapBonus,
+		Y_SetNullBonus,
+	},
 };
 
 
@@ -1771,13 +1970,15 @@ static void Y_AwardCoopBonuses(void)
 				players[i].score = MAXSCORE;
 		}
 
-		ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
+		ptlives = min(
+			(INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
+			(INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
 		if (ptlives)
 			P_GivePlayerLives(&players[i], ptlives);
 
 		if (i == consoleplayer)
 		{
-			data.coop.gotlife = ptlives;
+			data.coop.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
 			M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses));
 		}
 	}
@@ -1794,36 +1995,46 @@ static void Y_AwardCoopBonuses(void)
 static void Y_AwardSpecialStageBonus(void)
 {
 	INT32 i, oldscore, ptlives;
-	y_bonus_t localbonus;
+	y_bonus_t localbonuses[2];
 
 	data.spec.score = players[consoleplayer].score;
-	memset(&data.spec.bonus, 0, sizeof(data.spec.bonus));
-	data.spec.bonuspatch = NULL;
+	memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
+	memset(data.spec.bonuspatches, 0, sizeof(data.coop.bonuspatches));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		oldscore = players[i].score;
 
 		if (!playeringame[i] || players[i].lives < 1) // not active or game over
-			Y_SetNullBonus(&players[i], &localbonus);
-		else if (useNightsSS) // Link instead of Score
-			Y_SetLinkBonus(&players[i], &localbonus);
+		{
+			Y_SetNullBonus(&players[i], &localbonuses[0]);
+			Y_SetNullBonus(&players[i], &localbonuses[1]);
+		}
+		else if (maptol & TOL_NIGHTS) // NiGHTS bonus score instead of Rings
+		{
+			Y_SetNightsBonus(&players[i], &localbonuses[0]);
+			Y_SetNullBonus(&players[i], &localbonuses[1]);
+		}
 		else
-			Y_SetRingBonus(&players[i], &localbonus);
-		players[i].score += localbonus.points;
+		{
+			Y_SetSpecialRingBonus(&players[i], &localbonuses[0]);
+			Y_SetPerfectBonus(&players[i], &localbonuses[1]);
+		}
+		players[i].score += localbonuses[0].points;
+		players[i].score += localbonuses[1].points;
 		if (players[i].score > MAXSCORE)
 			players[i].score = MAXSCORE;
 
 		// grant extra lives right away since tally is faked
-		ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
-		if (ptlives)
-			P_GivePlayerLives(&players[i], ptlives);
+		ptlives = min(
+			(INT32)((!ultimatemode && !modeattacking && players[i].lives != INFLIVES) ? max((INT32)((players[i].score/50000) - (oldscore/50000)), (INT32)0) : 0),
+			(INT32)(mapheaderinfo[prevmap]->maxbonuslives < 0 ? INT32_MAX : mapheaderinfo[prevmap]->maxbonuslives));
+		P_GivePlayerLives(&players[i], ptlives);
 
 		if (i == consoleplayer)
 		{
-			M_Memcpy(&data.spec.bonus, &localbonus, sizeof(data.spec.bonus));
-
-			data.spec.gotlife = ptlives;
+			data.spec.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
+			M_Memcpy(&data.spec.bonuses, &localbonuses, sizeof(data.spec.bonuses));
 
 			// Continues related
 			data.spec.continues = min(players[i].continues, 8);
@@ -1888,7 +2099,6 @@ static void Y_UnloadData(void)
 	{
 		case int_coop:
 			// unload the coop and single player patches
-			UNLOAD(data.coop.ttlnum);
 			UNLOAD(data.coop.bonuspatches[3]);
 			UNLOAD(data.coop.bonuspatches[2]);
 			UNLOAD(data.coop.bonuspatches[1]);
@@ -1899,7 +2109,8 @@ static void Y_UnloadData(void)
 			// unload the special stage patches
 			//UNLOAD(data.spec.cemerald);
 			//UNLOAD(data.spec.nowsuper);
-			UNLOAD(data.spec.bonuspatch);
+			UNLOAD(data.spec.bonuspatches[1]);
+			UNLOAD(data.spec.bonuspatches[0]);
 			UNLOAD(data.spec.pscore);
 			UNLOAD(data.spec.pcontinues);
 			break;
@@ -1917,4 +2128,5 @@ static void Y_UnloadData(void)
 			//are not handled
 			break;
 	}
+
 }
diff --git a/src/y_inter.h b/src/y_inter.h
index 26f7dc390fc183de68873b435c6add42dc2cbd69..4c6ad2bdfcfeaff454fffb1ecfc0075f421705dc 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2004-2016 by Sonic Team Junior.
+// Copyright (C) 2004-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/z_zone.c b/src/z_zone.c
index a28ea87b035d3e0e7e5c7ca3ae7005aea541524e..001c69bb3c9f3adc1a689537a2fc155d042bcb43 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by Graue.
-// Copyright (C) 2006-2016 by Sonic Team Junior.
+// Copyright (C) 2006-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -82,6 +82,59 @@ typedef struct memblock_s
 	struct memblock_s *next, *prev;
 } ATTRPACK memblock_t;
 
+// both the head and tail of the zone memory block list
+static memblock_t head;
+
+//
+// Function prototypes
+//
+static void Command_Memfree_f(void);
+#ifdef ZDEBUG
+static void Command_Memdump_f(void);
+#endif
+
+// --------------------------
+// Zone memory initialisation
+// --------------------------
+
+/** Initialises zone memory.
+  * Used at game startup.
+  *
+  * \sa I_GetFreeMem, Command_Memfree_f, Command_Memdump_f
+  */
+void Z_Init(void)
+{
+	UINT32 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);
+
+	// Note: This allocates memory. Watch out.
+	COM_AddCommand("memfree", Command_Memfree_f);
+
+#ifdef ZDEBUG
+	COM_AddCommand("memdump", Command_Memdump_f);
+#endif
+}
+
+
+// ----------------------
+// 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)
@@ -131,32 +184,12 @@ static memblock_t *Ptr2Memblock(void *ptr, const char* func)
 
 }
 
-static memblock_t head;
-
-static void Command_Memfree_f(void);
-#ifdef ZDEBUG
-static void Command_Memdump_f(void);
-#endif
-
-void Z_Init(void)
-{
-	UINT32 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);
-
-	// Note: This allocates memory. Watch out.
-	COM_AddCommand("memfree", Command_Memfree_f);
-
-#ifdef ZDEBUG
-	COM_AddCommand("memdump", Command_Memdump_f);
-#endif
-}
-
+/** Frees allocated memory.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \sa Z_FreeTags
+  */
 #ifdef ZDEBUG
 void Z_Free2(void *ptr, const char *file, INT32 line)
 #else
@@ -206,7 +239,11 @@ void Z_Free(void *ptr)
 #endif
 }
 
-// malloc() that doesn't accept failure.
+/** malloc() that doesn't accept failure.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \return A pointer to the allocated memory.
+  */
 static void *xm(size_t size)
 {
 	const size_t padedsize = size+sizeof (size_t);
@@ -227,10 +264,18 @@ static void *xm(size_t size)
 	return p;
 }
 
-// Z_Malloc
-// You can pass Z_Malloc() a NULL user if the tag is less than
-// PU_PURGELEVEL.
-
+/** The Z_MallocAlign function.
+  * Allocates a block of memory, adds it to a linked list so we can keep track of it.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \param tag Purge tag.
+  * \param user The address of a pointer to the memory to be allocated.
+  *             When the memory is freed by Z_Free later,
+  *             the pointer at this address will then be automatically set to NULL.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \note You can pass Z_Malloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_CallocAlign, Z_ReallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits,
 	const char *file, INT32 line)
@@ -307,6 +352,19 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	return given;
 }
 
+/** The Z_CallocAlign function.
+  * Allocates a block of memory, adds it to a linked list so we can keep track of it.
+  * Unlike Z_MallocAlign, this also initialises the bytes to zero.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \param tag Purge tag.
+  * \param user The address of a pointer to the memory to be allocated.
+  *             When the memory is freed by Z_Free later,
+  *             the pointer at this address will then be automatically set to NULL.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \note You can pass Z_Calloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_MallocAlign, Z_ReallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Calloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line)
 #else
@@ -323,10 +381,26 @@ void *Z_CallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 }
 
+/** The Z_ReallocAlign function.
+  * Reallocates a block of memory with a new size.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  *             If NULL, this function instead acts as a wrapper for Z_CallocAlign.
+  * \param size New size of memory block, in bytes.
+  *             If zero, then the memory is freed and NULL is returned.
+  * \param tag New purge tag.
+  * \param user The address of a pointer to the memory to be reallocated.
+  *             This can be a different user to the one originally assigned to the memory block.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \return A pointer to the reallocated memory. Can be NULL if memory was freed.
+  * \note You can pass Z_Realloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_MallocAlign, Z_CallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Realloc2(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line)
 #else
-void *Z_ReallocAlign(void *ptr, size_t size,INT32 tag, void *user,  INT32 alignbits)
+void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 {
 	void *rez;
@@ -393,6 +467,11 @@ void *Z_ReallocAlign(void *ptr, size_t size,INT32 tag, void *user,  INT32 alignb
 	return rez;
 }
 
+/** Frees all memory for a given set of tags.
+  *
+  * \param lowtag The lowest tag to consider.
+  * \param hightag The highest tag to consider.
+  */
 void Z_FreeTags(INT32 lowtag, INT32 hightag)
 {
 	memblock_t *block, *next;
@@ -407,22 +486,25 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	}
 }
 
-//
-// Z_CheckMemCleanup
-//
-// TODO: Currently blocks >= PU_PURGELEVEL are freed every
-// CLEANUPCOUNT. It might be better to keep track of
-// the total size of all purgable memory and free it when the
-// size exceeds some value.
-//
-// This was in Z_Malloc, but was freeing data at
-// unsafe times. Now it is only called when it is safe
-// to cleanup memory.
+// -----------------
+// Utility functions
+// -----------------
 
+// starting value of nextcleanup
 #define CLEANUPCOUNT 2000
 
+// number of function calls left before next cleanup
 static INT32 nextcleanup = CLEANUPCOUNT;
 
+/** This was in Z_Malloc, but was freeing data at
+  * unsafe times. Now it is only called when it is safe
+  * to cleanup memory.
+  *
+  * \todo Currently blocks >= PU_PURGELEVEL are freed every
+  *       CLEANUPCOUNT. It might be better to keep track of
+  *       the total size of all purgable memory and free it when the
+  *       size exceeds some value.
+  */
 void Z_CheckMemCleanup(void)
 {
 	if (nextcleanup-- == 0)
@@ -538,10 +620,21 @@ void Z_CheckHeap(INT32 i)
 	}
 }
 
+// ------------------------
+// Zone memory modification
+// ------------------------
+
+/** Changes a memory block's purge tag.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \param tag The new tag.
+  * \sa Z_SetUser
+  */
 #ifdef PARANOIA
 void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line)
 #else
-void Z_ChangeTag2(void *ptr, INT32 tag)
+void Z_ChangeTag(void *ptr, INT32 tag)
 #endif
 {
 	memblock_t *block;
@@ -583,12 +676,59 @@ void Z_ChangeTag2(void *ptr, INT32 tag)
 	block->tag = tag;
 }
 
+/** Changes a memory block's user.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \param newuser The new user for the memory block.
+  * \sa Z_ChangeTag
+  */
+#ifdef PARANOIA
+void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line)
+#else
+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
+
+#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);
+#endif
+
+	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
+		I_Error("Internal memory management error: "
+			"tried to make block purgable but it has no owner");
+
+	block->user = (void*)newuser;
+	*newuser = ptr;
+}
+
+// -----------------
+// Zone memory usage
+// -----------------
+
 /** Calculates memory usage for a given set of tags.
+  *
   * \param lowtag The lowest tag to consider.
   * \param hightag The highest tag to consider.
   * \return Number of bytes currently allocated in the heap for the
   *         given tags.
-  * \sa Z_TagUsage
   */
 size_t Z_TagsUsage(INT32 lowtag, INT32 hightag)
 {
@@ -605,18 +745,20 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag)
 	return cnt;
 }
 
-size_t Z_TagUsage(INT32 tagnum)
-{
-	return Z_TagsUsage(tagnum, tagnum);
-}
+// -----------------------
+// Miscellaneous functions
+// -----------------------
 
-void Command_Memfree_f(void)
+/** The function called by the "memfree" console command.
+  * Prints the memory being used by each part of the game to the console.
+  */
+static void Command_Memfree_f(void)
 {
 	UINT32 freebytes, totalbytes;
 
 	Z_CheckHeap(-1);
 	CONS_Printf("\x82%s", M_GetText("Memory Info\n"));
-	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TagsUsage(0, INT32_MAX)>>10));
+	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
 	CONS_Printf(M_GetText("Static            : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
 	CONS_Printf(M_GetText("Static (sound)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
 	CONS_Printf(M_GetText("Static (music)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
@@ -644,6 +786,11 @@ void Command_Memfree_f(void)
 }
 
 #ifdef ZDEBUG
+/** The function called by the "memdump" console command.
+  * Prints zone memory debugging information (i.e. tag, size, location in code allocated).
+  * Can be all memory allocated in game, or between a set of tags (if -min/-max args used).
+  * This command is available only if ZDEBUG is enabled.
+  */
 static void Command_Memdump_f(void)
 {
 	memblock_t *block;
@@ -665,45 +812,12 @@ static void Command_Memdump_f(void)
 }
 #endif
 
-// Creates a copy of a string.
+/** Creates a copy of a string.
+  *
+  * \param s The string to be copied.
+  * \return A copy of the string, allocated in zone memory.
+  */
 char *Z_StrDup(const char *s)
 {
 	return strcpy(ZZ_Alloc(strlen(s) + 1), s);
 }
-
-
-#ifdef PARANOIA
-void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line)
-#else
-void Z_SetUser2(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
-
-#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);
-#endif
-
-	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
-		I_Error("Internal memory management error: "
-			"tried to make block purgable but it has no owner");
-
-	block->user = (void*)newuser;
-	*newuser = ptr;
-}
diff --git a/src/z_zone.h b/src/z_zone.h
index 552fd87baeb8556611cf38e8c2eec4b24f44a188..8d32e74f11a87466bab95d2e507f9cf37b03b6ac 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2016 by Sonic Team Junior.
+// Copyright (C) 1999-2018 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -30,92 +30,115 @@
 //#define ZDEBUG
 
 //
-// ZONE MEMORY
-// PU - purge tags.
-// Tags < PU_LEVEL are not purged until freed explicitly.
-#define PU_STATIC               1 // static entire execution time
-#define PU_LUA                  2 // static entire execution time -- used by lua so it doesn't get caught in loops forever
-
-#define PU_SOUND               11 // static while playing
-#define PU_MUSIC               12 // static while playing
-#define PU_HUDGFX              13 // static until WAD added
-
-#define PU_HWRPATCHINFO        21 // Hardware GLPatch_t struct for OpenGL texture cache
-#define PU_HWRPATCHCOLMIPMAP   22 // Hardware GLMipmap_t struct colromap variation of patch
-
-#define PU_HWRCACHE            48 // static until unlocked
-#define PU_CACHE               49 // static until unlocked
-
-// Tags s.t. PU_LEVEL <= tag < PU_PURGELEVEL are purged at level start
-#define PU_LEVEL               50 // static until level exited
-#define PU_LEVSPEC             51 // a special thinker in a level
-#define PU_HWRPLANE            52
-
-// Tags >= PU_PURGELEVEL are purgable whenever needed
-#define PU_PURGELEVEL         100
-#define PU_CACHE_UNLOCKED     101
-#define PU_HWRCACHE_UNLOCKED  102 // 'second-level' cache for graphics
-                                  // stored in hardware format and downloaded as needed
-#define PU_HWRPATCHINFO_UNLOCKED 103
+// Purge tags
+//
+// Now they are an enum! -- Monster Iestyn 15/02/18
+//
+enum
+{
+	// Tags < PU_LEVEL are not purged until freed explicitly.
+	PU_STATIC                = 1, // static entire execution time
+	PU_LUA                   = 2, // static entire execution time -- used by lua so it doesn't get caught in loops forever
+
+	PU_SOUND                 = 11, // static while playing
+	PU_MUSIC                 = 12, // static while playing
+	PU_HUDGFX                = 13, // static until WAD added
+
+	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
+	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
+
+	PU_HWRCACHE              = 48, // static until unlocked
+	PU_CACHE                 = 49, // static until unlocked
+
+	// Tags s.t. PU_LEVEL <= tag < PU_PURGELEVEL are purged at level start
+	PU_LEVEL                 = 50, // static until level exited
+	PU_LEVSPEC               = 51, // a special thinker in a level
+	PU_HWRPLANE              = 52, // if ZPLANALLOC is enabled in hw_bsp.c, this is used to alloc polygons for OpenGL
+
+	// Tags >= PU_PURGELEVEL are purgable whenever needed
+	PU_PURGELEVEL            = 100, // Note: this is never actually used as a tag
+	PU_CACHE_UNLOCKED        = 101, // Note: unused
+	PU_HWRCACHE_UNLOCKED     = 102, // 'unlocked' PU_HWRCACHE memory:
+									// 'second-level' cache for graphics
+                                    // stored in hardware format and downloaded as needed
+	PU_HWRPATCHINFO_UNLOCKED = 103, // 'unlocked' PU_HWRPATCHINFO memory
+};
 
+//
+// Zone memory initialisation
+//
 void Z_Init(void);
-void Z_FreeTags(INT32 lowtag, INT32 hightag);
-void Z_CheckMemCleanup(void);
-void Z_CheckHeap(INT32 i);
-#ifdef PARANOIA
-void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line);
-#else
-void Z_ChangeTag2(void *ptr, INT32 tag);
-#endif
 
-#ifdef PARANOIA
-void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line);
-#else
-void Z_SetUser2(void *ptr, void **newuser);
-#endif
+//
+// Zone memory allocation
+//
+// enable ZDEBUG to get the file + line the functions were called from
+// for ZZ_Alloc, see doomdef.h
+//
 
+// Z_Free and alloc with alignment
 #ifdef ZDEBUG
-#define Z_Free(p) Z_Free2(p, __FILE__, __LINE__)
+#define Z_Free(p)                 Z_Free2(p, __FILE__, __LINE__)
+#define Z_MallocAlign(s,t,u,a)    Z_Malloc2(s, t, u, a, __FILE__, __LINE__)
+#define Z_CallocAlign(s,t,u,a)    Z_Calloc2(s, t, u, a, __FILE__, __LINE__)
+#define Z_ReallocAlign(p,s,t,u,a) Z_Realloc2(p,s, t, u, a, __FILE__, __LINE__)
 void Z_Free2(void *ptr, const char *file, INT32 line);
-#define Z_Malloc(s,t,u) Z_Malloc2(s, t, u, 0, __FILE__, __LINE__)
-#define Z_MallocAlign(s,t,u,a) Z_Malloc2(s, t, u, a, __FILE__, __LINE__)
 void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(1);
-#define Z_Calloc(s,t,u) Z_Calloc2(s, t, u, 0, __FILE__, __LINE__)
-#define Z_CallocAlign(s,t,u,a) Z_Calloc2(s, t, u, a, __FILE__, __LINE__)
 void *Z_Calloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(1);
-#define Z_Realloc(p,s,t,u) Z_Realloc2(p, s, t, u, 0, __FILE__, __LINE__)
-#define Z_ReallocAlign(p,s,t,u,a) Z_Realloc2(p,s, t, u, a, __FILE__, __LINE__)
 void *Z_Realloc2(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(2);
 #else
 void Z_Free(void *ptr);
 void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(1);
-#define Z_Malloc(s,t,u) Z_MallocAlign(s, t, u, 0)
 void *Z_CallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(1);
-#define Z_Calloc(s,t,u) Z_CallocAlign(s, t, u, 0)
-void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(2) ;
-#define Z_Realloc(p, s,t,u) Z_ReallocAlign(p, s, t, u, 0)
+void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(2);
 #endif
 
-size_t Z_TagUsage(INT32 tagnum);
-size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
+// 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)
 
-char *Z_StrDup(const char *in);
+// Free all memory by tag
+// these don't give line numbers for ZDEBUG currently though
+// (perhaps this should be changed in future?)
+#define Z_FreeTag(tagnum) Z_FreeTags(tagnum, tagnum)
+void Z_FreeTags(INT32 lowtag, INT32 hightag);
+
+//
+// Utility functions
+//
+void Z_CheckMemCleanup(void);
+void Z_CheckHeap(INT32 i);
 
-// This is used to get the local FILE : LINE info from CPP
-// prior to really call the function in question.
+//
+// Zone memory modification
+//
+// enable PARANOIA to get the file + line the functions were called from
 //
 #ifdef PARANOIA
 #define Z_ChangeTag(p,t) Z_ChangeTag2(p, t, __FILE__, __LINE__)
+#define Z_SetUser(p,u)   Z_SetUser2(p, u, __FILE__, __LINE__)
+void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line);
+void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line);
 #else
-#define Z_ChangeTag(p,t) Z_ChangeTag2(p, t)
+void Z_ChangeTag(void *ptr, INT32 tag);
+void Z_SetUser(void *ptr, void **newuser);
 #endif
 
-#ifdef PARANOIA
-#define Z_SetUser(p,u) Z_SetUser2(p, u, __FILE__, __LINE__)
-#else
-#define Z_SetUser(p,u) Z_SetUser2(p, u)
-#endif
+//
+// Zone memory usage
+//
+// Note: These give the memory used in bytes,
+// shift down by 10 to convert to KB
+//
+#define Z_TagUsage(tagnum) Z_TagsUsage(tagnum, tagnum)
+size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
+#define Z_TotalUsage() Z_TagsUsage(0, INT32_MAX)
 
-#define Z_Unlock(p) (void)p
+//
+// Miscellaneous functions
+//
+char *Z_StrDup(const char *in);
+#define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed
 
 #endif
diff --git a/tools/masterserver/structure.sql b/tools/masterserver/structure.sql
new file mode 100644
index 0000000000000000000000000000000000000000..3cc2cb15bbcfa24f4bdd37968bc982bbeb5f87ca
--- /dev/null
+++ b/tools/masterserver/structure.sql
@@ -0,0 +1,117 @@
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+SET AUTOCOMMIT = 0;
+START TRANSACTION;
+SET time_zone = "+00:00";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+
+--
+-- Database: `srb2ms`
+--
+
+CREATE DATABASE IF NOT EXISTS `srb2ms` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+USE `srb2ms`;
+
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `ms_bans`
+--
+
+CREATE TABLE `ms_bans` (
+  `bid` int(11) DEFAULT NULL,
+  `ipstart` int(11) DEFAULT NULL,
+  `ipend` int(11) DEFAULT NULL,
+  `full_endtime` int(11) DEFAULT NULL,
+  `permanent` tinyint(1) DEFAULT NULL,
+  `hostonly` tinyint(1) DEFAULT NULL,
+  `reason` text COLLATE utf8mb4_unicode_ci
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `ms_rooms`
+--
+
+CREATE TABLE `ms_rooms` (
+  `room_id` int(11) NOT NULL,
+  `title` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `motd` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `visible` tinyint(1) NOT NULL,
+  `order` int(11) NOT NULL,
+  `private` tinyint(1) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Dumping data for table `ms_rooms`
+--
+
+INSERT INTO `ms_rooms` (`room_id`, `title`, `motd`, `visible`, `order`, `private`) VALUES
+(10, 'Example', 'Example Room', 1, 0, 0),
+(0, 'All', 'View all of the servers currently being hosted.', 1, 1, 1);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `ms_servers`
+--
+
+CREATE TABLE `ms_servers` (
+  `sid` int(11) NOT NULL,
+  `name` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `ip` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `port` int(11) NOT NULL,
+  `version` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `timestamp` int(11) NOT NULL,
+  `room` int(11) NOT NULL,
+  `key` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `room_override` int(11) NOT NULL,
+  `upnow` tinyint(1) NOT NULL,
+  `permanent` tinyint(1) NOT NULL,
+  `delisted` tinyint(1) NOT NULL,
+  `sticky` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `ms_versions`
+--
+
+CREATE TABLE `ms_versions` (
+  `mod_id` int(11) NOT NULL,
+  `mod_version` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Dumping data for table `ms_versions`
+--
+
+INSERT INTO `ms_versions` (`mod_id`, `mod_version`) VALUES
+(12, 25);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `user`
+--
+
+CREATE TABLE `user` (
+  `userid` int(11) NOT NULL,
+  `username` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `live_authkey` blob NOT NULL,
+  `live_publickey` blob NOT NULL,
+  `salt` blob NOT NULL,
+  `password` blob NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+COMMIT;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;