diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b79bd672e..4d5f46c9f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -165,7 +165,6 @@ jobs:
             --inconclusive \
             --inline-suppr \
             --std=c99 \
-            --suppress="memleak:${{ github.workspace }}/src/u_scanner.c" \
             --project="${{ github.workspace }}/build/compile_commands.json" \
             -i"${{ github.workspace }}/third-party" \
             -D__GNUC__
diff --git a/.gitignore b/.gitignore
index 818793e79..16bb0fcad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ CMakeSettings.json
 
 # clangd
 /.cache/
+CMakeFiles/
+src/CMakeFiles/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ecd93b74..edf044f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@
 
 ## Changes
 
+- **Merged changes from [Woof! post-15.0.1]**, note:
+  - Replaced `fullscreen_hud_type` with `use_nughud`, with the NUGHUD now replacing the second-to-last HUD
+  - Removed `fuzzdark_mode` in favor of Woof!'s `fuzzmode` (_Refraction_ is equivalent to _Selective Fuzz Darkening_)
+  - Removed `comp_blazing2`; its functionality has been integrated into `comp_blazing`
 - **Improved interpolation of weapon sprites**
 - **Applied weapon inertia when firing** (added `weapon_inertia_fire` CVAR to disable it)
 - **Lowered `weapon_inertia_scale_pct` limit to -200**
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b6b34a12..25f077eed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -55,6 +55,7 @@ check_library_exists(m pow "" HAVE_LIBM)
 check_include_file("dirent.h" HAVE_DIRENT_H)
 check_symbol_exists(strcasecmp "strings.h" HAVE_DECL_STRCASECMP)
 check_symbol_exists(strncasecmp "strings.h" HAVE_DECL_STRNCASECMP)
+check_symbol_exists(getpwuid "unistd.h;sys/types.h;pwd.h" HAVE_GETPWUID)
 check_c_source_compiles("
     #include <windows.h>
     int main()
@@ -76,6 +77,16 @@ check_c_source_compiles("
   "
   HAVE__DIV64
 )
+check_c_source_compiles("
+    typedef float vec __attribute__((ext_vector_type(4)));
+    int main()
+    {
+        vec a = (vec){0, 1, 2, 3};
+        return 0;
+    }
+  "
+  HAVE_EXT_VECTOR_TYPE
+)
 
 option(CMAKE_FIND_PACKAGE_PREFER_CONFIG
        "Lookup package config files before using find modules" ON)
diff --git a/README.md b/README.md
index b4b7a08ae..25c287dfd 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,6 @@ All of these are CFG-only, so their CVAR names are included.
 - Attackers face fuzzy targets straight (`comp_faceshadow`)
 - Fix lopsided Icon of Sin explosions (`comp_iosdeath`)
 - Permanent IDCHOPPERS invulnerability (`comp_choppers`)
-- Blazing doors reopen with wrong sound (`comp_blazing2`) [p.f. Crispy Doom]
 - Manually toggled moving doors are silent (`comp_manualdoor`) [p.f. Crispy Doom]
 - Corrected switch sound source (`comp_switchsource`) [p.f. Crispy Doom]
 - Chaingun makes two sounds with one bullet (`comp_cgundblsnd`)
@@ -260,7 +259,7 @@ The Nugget Doom source code is available at GitHub: <https://github.com/MrAlaux/
 
 The following build system and libraries need to be installed:
  
- * [CMake](https://cmake.org) (>= 3.9)
+ * [CMake](https://cmake.org) (>= 3.15)
  * [SDL2](https://github.com/libsdl-org/SDL/tree/SDL2) (>= 2.0.18)
  * [SDL2_net](https://github.com/libsdl-org/SDL_net)
  * [openal-soft](https://github.com/kcat/openal-soft) (>= 1.22.0 for PC Speaker emulation)
@@ -330,7 +329,6 @@ Copyright:
  © 2007-2011 Moritz "Ripper" Kroll;  
  © 2008-2019 Simon Judd;  
  © 2017 Christoph Oelckers;  
- © 2019 Fernando Carmona Varo;  
  © 2020 Alex Mayfield;  
  © 2020 JadingTsunami;  
  © 2020-2024 Fabian Greffrath;  
@@ -341,7 +339,8 @@ Copyright:
  © 2022-2024 ceski;  
  © 2023 Andrew Apted;  
  © 2023 liPillON;  
- © 2024 pvictress.   
+ © 2024 pvictress;  
+ © 2025 Guilherme Miranda.  
 License: [GPL-2.0+](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
 
 Files: `src/i_flickstick.*, src/i_gyro.*`  
@@ -356,10 +355,9 @@ Copyright:
  © 2023 Andrew Apted.  
 License: [MIT](https://opensource.org/licenses/MIT)
 
-Files: `src/u_scanner.*`  
+Files: `src/m_scanner.*`  
 Copyright:  
- © 2010 Braden "Blzut3" Obrzut;  
- © 2019 Fernando Carmona Varo.  
+ © 2015 Braden "Blzut3" Obrzut.  
 License: [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause)
 
 Files: `src/v_flextran.*`  
diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt
index 7b7662f4c..5eaebfdc5 100644
--- a/base/CMakeLists.txt
+++ b/base/CMakeLists.txt
@@ -215,18 +215,18 @@ set(BASE_SOURCES
     all-all/sprites/ngtrf0.png
     all-all/sprites/ngtrg0.png
 
-    betalevl.wad/umapdef.lmp
+    betalevl.wad/umapinfo.lmp
 
     chex.wad/brghtmps.lmp
     chex.wad/dehacked.lmp
-    chex.wad/umapdef.lmp
+    chex.wad/umapinfo.lmp
 
     chex2.wad/brghtmps.lmp
 
     doom-all/brghtmps.lmp
 
-    e1m4b.wad/umapdef.lmp
-    e1m8b.wad/umapdef.lmp
+    e1m4b.wad/umapinfo.lmp
+    e1m8b.wad/umapinfo.lmp
 
     extras.wad/sbardef.lmp
 
@@ -235,16 +235,10 @@ set(BASE_SOURCES
 
     id1.wad/sbardef.lmp
 
-    masterlevels.wad/umapdef.lmp
-
-    nerve.wad/umapdef.lmp
-
     rekkr.wad/dehacked.lmp
     rekkrsa.wad/dehacked.lmp
     rekkrsl.wad/dehacked.lmp
 
-    sigil_v1_21.wad/umapdef.lmp
-
     tnt.wad/brghtmps.lmp)
 
 add_custom_command(OUTPUT "${BASE_PK3_PATH}"
diff --git a/base/all-all/compdb.lmp b/base/all-all/compdb.lmp
index 5508c5855..8c458321d 100644
--- a/base/all-all/compdb.lmp
+++ b/base/all-all/compdb.lmp
@@ -1,10 +1,8 @@
 {
   "type": "compatibility",
   "version": "1.0.0",
-  "data":
-  {
-    "levels":
-    [
+  "data": {
+    "levels": [
       {
         "name": "eternal_doom_25",
         "md5": "6da6fcba8089161bdec6a1d3f6c8d60f",
@@ -19,181 +17,360 @@
           }
         ]
       },
+      {
+        "name": "doomsday_of_uac_e1m8",
+        "md5": "32fc3115a3162b623f0d0f4e7dee6861",
+        "complevel": "1.9"
+      },
       {
         "name": "hell_revealed_map19",
         "md5": "811a0c97777a198bc9b2bb558cb46e6a",
-        "options": [ {"name": "comp_pain", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_pain",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "roger_ritenour_phobos_map03",
         "md5": "8fa29398776146189396aa1ac6bb9e13",
-        "options": [ {"name": "comp_floors", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_floors",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "hell_revealed_map26",
         "md5": "145c4dfcf843f2b92c73036ba0e1d98a",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
-
       {
         "name": "hell_to_pay_map14",
         "md5": "5379c080299eb961792b50ad96821543",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "hell_to_pay_map22",
         "md5": "7837b5334a277f107515d649bcefb682",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "icarus_map24",
         "md5": "2eeb1e12fa9f9545de9d99990a4a78e5",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "plutonia2_map32",
         "md5": "65a53a09a09525ae42ea210bf879cd37",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "requiem_map23",
         "md5": "2499cf9a9351be9bc4e9c66fc9f291a7",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "the_waterfront_map01",
         "md5": "3ca5493feff2e27bfd4181e6c4a3c2bf",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "gather2_map05_and_darkside_map01",
         "md5": "cbdfefac579a62de8f1b48ca4a09d381",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "reverie_map18",
         "md5": "c7a2fafb0afb2632c50ad625cdb50e51",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "project_x_map14",
         "md5": "9e5724bc6135aa6f86ee54fd4d91f1e2",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "archie_map01",
         "md5": "01899825ffeae016d39c02a7da4b218f",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "seej_map01",
         "md5": "1d9f3afdc2517c2e450491ed13896712",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "sixpack2_map02",
         "md5": "0ae745a3ab86d15fb2fb74489962c421",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "squadron_417_map21",
         "md5": "2ea635c6b6aec76b6bc77448dab22f9a",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "mayhem_2013_map05",
         "md5": "1e998262ee319b7d088e01de782e6b41",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "imps_are_ghost_gods_map01",
         "md5": "a81e2734f735a82720d8e0f1442ba0c9",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "confinement_map31",
         "md5": "aad7502cb39bc050445e17b15f72356f",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "conf256b_map07",
         "md5": "5592ea1ca3b8ee0dbb2cb352aaa00911",
-        "options": [ {"name": "comp_pain", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_pain",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "conf256b_map12",
         "md5": "cecedae33b970f2bf7f8b8631da0c8dd",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "sunlust_map30",
         "md5": "41efe03223e41935849f64114c5cb471",
-        "options": [ {"name": "comp_telefrag", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_telefrag",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "tnt_map30",
         "md5": "42b68b84ff8e55f264c31e6f4cfea82d",
-        "options": [ {"name": "comp_stairs", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_stairs",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "intercep2_map03",
         "md5": "86587e4f8c8086991c8fc5c1ccfd30b9",
-        "options": [ {"name": "comp_ledgeblock", "value": 0} ]
+        "options": [
+          {
+            "name": "comp_ledgeblock",
+            "value": 0
+          }
+        ]
       },
       {
         "name": "skulltiverse_map02",
         "md5": "b3fa4a18b31bd96e724f9aab101776a1",
-        "options": [ {"name": "comp_ledgeblock", "value": 0} ]
+        "options": [
+          {
+            "name": "comp_ledgeblock",
+            "value": 0
+          }
+        ]
       },
       {
         "name": "tntr_map30",
         "md5": "1d3c6d456bfcf360ce14aeecc155a96c",
-        "options": [ {"comp_telefrag": true} ]
+        "options": [
+          {
+            "name": "comp_telefrag",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "roomblow_e1m1",
         "md5": "68ffa69f2eaa5ced3dc4da5a300d022a",
-        "options": [ {"name": "comp_stairs", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_stairs",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "esp_map21",
         "md5": "97088f2849904bc1cd5ae1d92d163b13",
-        "options": [ {"name": "comp_stairs", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_stairs",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "av_map07",
         "md5": "941e4cb56ee4184e0b1ed43486ab0bbf",
-        "options": [ {"name": "comp_model", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_model",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "sin2_9_map02",
         "md5": "9aa5aa3020434f824624eba88916ee23",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "d2reload_map09",
         "md5": "c8de798a4d658ffc94151884c6c2bf37",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "amoreupho_map02",
         "md5": "66a8310a0a7d2af99e3a0089b2d6c897",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "dbp20_dnd_map07",
         "md5": "e26c1b6f4dfd90bb6533e6381bf61be5",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "arch_map01",
         "md5": "1d37cbd32a1ecf4763437631e7b3c29a",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       },
       {
         "name": "ur_map06",
         "md5": "cfb054683af1ed187d0565942d3dbb8f",
-        "options": [ {"name": "comp_vile", "value": 1} ]
+        "options": [
+          {
+            "name": "comp_vile",
+            "value": 1
+          }
+        ]
       }
     ]
   }
diff --git a/base/all-all/sbardef.lmp b/base/all-all/sbardef.lmp
index 758aa7c20..5929a8693 100644
--- a/base/all-all/sbardef.lmp
+++ b/base/all-all/sbardef.lmp
@@ -981,7 +981,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 16,
               "alignment": 34,
               "tranmap": null,
@@ -996,7 +996,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 8,
               "alignment": 34,
               "tranmap": null,
@@ -1010,7 +1010,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 0,
               "alignment": 34,
               "tranmap": null,
@@ -1024,7 +1024,7 @@
           {
             "widget":
             {
-              "x": 0,
+              "x": 2,
               "y": 8,
               "alignment": 0,
               "tranmap": null,
@@ -1038,7 +1038,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 167,
               "alignment": 42,
               "tranmap": null,
@@ -1851,7 +1851,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 16,
                     "alignment": 34,
                     "tranmap": null,
@@ -1866,7 +1866,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 8,
                     "alignment": 34,
                     "tranmap": null,
@@ -1880,7 +1880,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 0,
                     "alignment": 34,
                     "tranmap": null,
@@ -1894,7 +1894,7 @@
                 {
                   "widget":
                   {
-                    "x": 0,
+                    "x": 2,
                     "y": 8,
                     "alignment": 0,
                     "tranmap": null,
@@ -1908,7 +1908,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 167,
                     "alignment": 42,
                     "tranmap": null,
@@ -2033,262 +2033,1331 @@
         "children":
         [
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 44,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 4,
+              "param": 0,
+              "maxlength": 3,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 4
+                  "condition": 4,
+                  "param": 0
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 104,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 20,
-                  "param": 0
-                }
-              ],
+              "type": 0,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 235,
+              "y": 171,
+              "alignment": 34,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
               "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 138,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 2,
+              "param": 0,
+              "maxlength": 2,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "condition": 14,
+                  "param": 2
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "canvas":
             {
-              "x": 2,
-              "y": 176,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
+              "x": 104,
+              "y": 168,
+              "alignment": 0,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
-                },
-                {
-                  "condition": 20,
-                  "param": 0
+                  "condition": 15,
+                  "param": 2
                 }
               ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
+              "children":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
                 },
                 {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 16,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 2,
-              "font": "Digits",
-              "vertical_layout": 0,
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 8,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 32,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 0,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 3,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 0,
-              "y": 8,
-              "alignment": 0,
-              "tranmap": null,
-              "translation": null,
-              "type": 4,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 200,
-              "alignment": 42,
-              "tranmap": null,
-              "translation": null,
-              "type": 5,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 160,
-              "y": 200,
-              "alignment": 9,
-              "tranmap": null,
-              "translation": null,
-              "type": 6,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 0,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 7,
-              "font": "Console",
-              "duration": 4,
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 160,
-              "y": 68,
-              "alignment": 1,
-              "tranmap": null,
-              "translation": null,
-              "type": 8,
-              "font": "Console",
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                }
+              ]
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS0",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 11,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS6",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS1",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 11,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS7",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS2",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 11,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS8",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 144,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 167,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 167,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 52,
+              "alignment": 1,
+              "tranmap": null,
+              "translation": null,
+              "type": 8,
+              "font": "Console",
               "duration": 2.5,
               "conditions": null,
               "children": null
             }
           },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 9,
+              "font": "Console",
+              "under_messages": true,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 10,
+              "font": "Console",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "carousel":
+            {
+              "x": 0,
+              "y": 18,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "minimap":
+            {
+              "x": 8,
+              "y": 0,
+              "alignment": 80,
+              "width": 80,
+              "height": 80,
+              "under_messages": true
+            }
+          }
+        ]
+      },
+      {
+        "height": 200,
+        "fullscreenrender": true,
+        "fillflat": null,
+        "children":
+        [
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 176,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
           {
             "widget":
             {
               "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 200,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 200,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 68,
+              "alignment": 1,
+              "tranmap": null,
+              "translation": null,
+              "type": 8,
+              "font": "Console",
+              "duration": 2.5,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
               "y": 0,
               "alignment": 16,
               "tranmap": null,
@@ -2303,7 +3372,7 @@
           {
             "widget":
             {
-              "x": 2,
+              "x": 0,
               "y": 192,
               "alignment": 16,
               "tranmap": null,
diff --git a/base/all-all/sbhuddef.lmp b/base/all-all/sbhuddef.lmp
index ca2f4a3ac..cab824204 100644
--- a/base/all-all/sbhuddef.lmp
+++ b/base/all-all/sbhuddef.lmp
@@ -157,7 +157,7 @@
       {
         "widget":
         {
-          "x": 320,
+          "x": 318,
           "y": 8,
           "alignment": 34,
           "tranmap": null,
@@ -171,7 +171,7 @@
       {
         "widget":
         {
-          "x": 320,
+          "x": 318,
           "y": 0,
           "alignment": 34,
           "tranmap": null,
@@ -185,7 +185,7 @@
       {
         "widget":
         {
-          "x": 0,
+          "x": 2,
           "y": 8,
           "alignment": 0,
           "tranmap": null,
@@ -199,7 +199,7 @@
       {
         "widget":
         {
-          "x": 320,
+          "x": 318,
           "y": 167,
           "alignment": 42,
           "tranmap": null,
diff --git a/base/betalevl.wad/umapdef.lmp b/base/betalevl.wad/umapinfo.lmp
similarity index 100%
rename from base/betalevl.wad/umapdef.lmp
rename to base/betalevl.wad/umapinfo.lmp
diff --git a/base/chex.wad/umapdef.lmp b/base/chex.wad/umapinfo.lmp
similarity index 100%
rename from base/chex.wad/umapdef.lmp
rename to base/chex.wad/umapinfo.lmp
diff --git a/base/e1m4b.wad/umapdef.lmp b/base/e1m4b.wad/umapinfo.lmp
similarity index 100%
rename from base/e1m4b.wad/umapdef.lmp
rename to base/e1m4b.wad/umapinfo.lmp
diff --git a/base/e1m8b.wad/umapdef.lmp b/base/e1m8b.wad/umapinfo.lmp
similarity index 100%
rename from base/e1m8b.wad/umapdef.lmp
rename to base/e1m8b.wad/umapinfo.lmp
diff --git a/base/extras.wad/sbardef.lmp b/base/extras.wad/sbardef.lmp
index ea77bb95c..c6f6eaa37 100644
--- a/base/extras.wad/sbardef.lmp
+++ b/base/extras.wad/sbardef.lmp
@@ -980,7 +980,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 16,
               "alignment": 34,
               "tranmap": null,
@@ -1009,7 +1009,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 0,
               "alignment": 34,
               "tranmap": null,
@@ -1023,7 +1023,7 @@
           {
             "widget":
             {
-              "x": 0,
+              "x": 2,
               "y": 8,
               "alignment": 0,
               "tranmap": null,
@@ -1037,7 +1037,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 167,
               "alignment": 42,
               "tranmap": null,
@@ -1839,7 +1839,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 16,
                     "alignment": 34,
                     "tranmap": null,
@@ -1868,7 +1868,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 0,
                     "alignment": 34,
                     "tranmap": null,
@@ -1882,7 +1882,7 @@
                 {
                   "widget":
                   {
-                    "x": 0,
+                    "x": 2,
                     "y": 8,
                     "alignment": 0,
                     "tranmap": null,
@@ -1896,7 +1896,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 167,
                     "alignment": 42,
                     "tranmap": null,
@@ -2010,243 +2010,1298 @@
         "children":
         [
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 44,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 4,
+              "param": 0,
+              "maxlength": 3,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 4
+                  "condition": 4,
+                  "param": 0
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 104,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 20,
-                  "param": 0
-                }
-              ],
+              "type": 0,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 235,
+              "y": 171,
+              "alignment": 34,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
               "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 138,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 2,
+              "param": 0,
+              "maxlength": 2,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "condition": 14,
+                  "param": 2
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "canvas":
             {
-              "x": 2,
-              "y": 176,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
+              "x": 104,
+              "y": 168,
+              "alignment": 0,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
-                },
-                {
-                  "condition": 20,
-                  "param": 0
+                  "condition": 15,
+                  "param": 2
                 }
               ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
+              "children":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
                 },
                 {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 16,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 2,
-              "font": "Digits",
-              "vertical_layout": 0,
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 8,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 32,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 0,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 3,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 0,
-              "y": 8,
-              "alignment": 0,
-              "tranmap": null,
-              "translation": null,
-              "type": 4,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 200,
-              "alignment": 42,
-              "tranmap": null,
-              "translation": null,
-              "type": 5,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 160,
-              "y": 200,
-              "alignment": 9,
-              "tranmap": null,
-              "translation": null,
-              "type": 6,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 0,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 7,
-              "font": "Console",
-              "duration": 4,
-              "conditions": null,
-              "children": null
-            }
-          },
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                }
+              ]
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS0",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 11,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS6",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS1",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 11,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS7",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS2",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 11,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS8",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 144,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 167,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 167,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 52,
+              "alignment": 1,
+              "tranmap": null,
+              "translation": null,
+              "type": 8,
+              "font": "Console",
+              "duration": 2.5,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 9,
+              "font": "Console",
+              "under_messages": true,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 10,
+              "font": "Console",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "carousel":
+            {
+              "x": 0,
+              "y": 18,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "conditions": null,
+              "children": null
+            }
+          }
+        ]
+      },
+      {
+        "height": 200,
+        "fullscreenrender": true,
+        "fillflat": null,
+        "children":
+        [
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 176,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 200,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 200,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
           {
             "widget":
             {
@@ -2265,7 +3320,7 @@
           {
             "widget":
             {
-              "x": 2,
+              "x": 0,
               "y": 0,
               "alignment": 16,
               "tranmap": null,
@@ -2280,7 +3335,7 @@
           {
             "widget":
             {
-              "x": 2,
+              "x": 0,
               "y": 192,
               "alignment": 16,
               "tranmap": null,
diff --git a/base/id1.wad/sbardef.lmp b/base/id1.wad/sbardef.lmp
index ba9c4e44e..10606af7b 100644
--- a/base/id1.wad/sbardef.lmp
+++ b/base/id1.wad/sbardef.lmp
@@ -980,7 +980,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 16,
               "alignment": 34,
               "tranmap": null,
@@ -1009,7 +1009,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 0,
               "alignment": 34,
               "tranmap": null,
@@ -1023,7 +1023,7 @@
           {
             "widget":
             {
-              "x": 0,
+              "x": 2,
               "y": 8,
               "alignment": 0,
               "tranmap": null,
@@ -1037,7 +1037,7 @@
           {
             "widget":
             {
-              "x": 320,
+              "x": 318,
               "y": 167,
               "alignment": 42,
               "tranmap": null,
@@ -1839,7 +1839,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 16,
                     "alignment": 34,
                     "tranmap": null,
@@ -1868,7 +1868,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 0,
                     "alignment": 34,
                     "tranmap": null,
@@ -1882,7 +1882,7 @@
                 {
                   "widget":
                   {
-                    "x": 0,
+                    "x": 2,
                     "y": 8,
                     "alignment": 0,
                     "tranmap": null,
@@ -1896,7 +1896,7 @@
                 {
                   "widget":
                   {
-                    "x": 320,
+                    "x": 318,
                     "y": 167,
                     "alignment": 42,
                     "tranmap": null,
@@ -2010,243 +2010,1298 @@
         "children":
         [
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 44,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 4,
+              "param": 0,
+              "maxlength": 3,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 4
+                  "condition": 4,
+                  "param": 0
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 104,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 20,
-                  "param": 0
-                }
-              ],
+              "type": 0,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "percent":
             {
-              "x": 2,
-              "y": 192,
-              "alignment": 16,
+              "x": 235,
+              "y": 171,
+              "alignment": 34,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
               "type": 1,
-              "font": "Digits",
-              "conditions":
-              [
-                {
-                  "condition": 19,
-                  "param": 4
-                },
-                {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
               "children": null
             }
           },
           {
-            "widget":
+            "number":
             {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
+              "x": 138,
+              "y": 171,
+              "alignment": 18,
+              "font": "BigRed",
               "tranmap": null,
               "translation": null,
-              "type": 0,
-              "font": "Digits",
-              "vertical_layout": 0,
+              "type": 2,
+              "param": 0,
+              "maxlength": 2,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "condition": 14,
+                  "param": 2
                 }
               ],
               "children": null
             }
           },
           {
-            "widget":
+            "canvas":
             {
-              "x": 2,
-              "y": 176,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
+              "x": 104,
+              "y": 168,
+              "alignment": 0,
               "conditions":
               [
                 {
-                  "condition": 19,
-                  "param": 3
-                },
-                {
-                  "condition": 20,
-                  "param": 0
+                  "condition": 15,
+                  "param": 2
                 }
               ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 184,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 1,
-              "font": "Digits",
-              "conditions":
+              "children":
               [
                 {
-                  "condition": 19,
-                  "param": 3
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
                 },
                 {
-                  "condition": 21,
-                  "param": 0
-                }
-              ],
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 16,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 2,
-              "font": "Digits",
-              "vertical_layout": 0,
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 8,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 32,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 0,
-              "alignment": 34,
-              "tranmap": null,
-              "translation": null,
-              "type": 3,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 0,
-              "y": 8,
-              "alignment": 0,
-              "tranmap": null,
-              "translation": null,
-              "type": 4,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 320,
-              "y": 200,
-              "alignment": 42,
-              "tranmap": null,
-              "translation": null,
-              "type": 5,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 160,
-              "y": 200,
-              "alignment": 9,
-              "tranmap": null,
-              "translation": null,
-              "type": 6,
-              "font": "Digits",
-              "conditions": null,
-              "children": null
-            }
-          },
-          {
-            "widget":
-            {
-              "x": 2,
-              "y": 0,
-              "alignment": 16,
-              "tranmap": null,
-              "translation": null,
-              "type": 7,
-              "font": "Console",
-              "duration": 4,
-              "conditions": null,
-              "children": null
-            }
-          },
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM2",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 2
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM3",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 3
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STGNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 4,
+                    "alignment": 16,
+                    "patch": "STYSNUM4",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 4
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 7,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM5",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 5
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 19,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM6",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 6
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STGNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 7,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                },
+                {
+                  "graphic":
+                  {
+                    "x": 31,
+                    "y": 14,
+                    "alignment": 16,
+                    "patch": "STYSNUM7",
+                    "tranmap": null,
+                    "translation": null,
+                    "conditions":
+                    [
+                      {
+                        "condition": 6,
+                        "param": 7
+                      }
+                    ],
+                    "children": null
+                  }
+                }
+              ]
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 173,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 0,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 179,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 1,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 185,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 3,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 288,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "number":
+            {
+              "x": 314,
+              "y": 191,
+              "alignment": 34,
+              "font": "SmallYellow",
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "param": 2,
+              "maxlength": 3,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS0",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 11,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS3",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 171,
+              "alignment": 32,
+              "patch": "STKEYS6",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 1
+                },
+                {
+                  "condition": 10,
+                  "param": 4
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS1",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 11,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS4",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 181,
+              "alignment": 32,
+              "patch": "STKEYS7",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 2
+                },
+                {
+                  "condition": 10,
+                  "param": 5
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS2",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 11,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 11,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS5",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 13,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "graphic":
+            {
+              "x": 239,
+              "y": 191,
+              "alignment": 32,
+              "patch": "STKEYS8",
+              "tranmap": null,
+              "translation": null,
+              "conditions":
+              [
+                {
+                  "condition": 10,
+                  "param": 3
+                },
+                {
+                  "condition": 10,
+                  "param": 6
+                },
+                {
+                  "condition": 12,
+                  "param": 1
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 144,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 152,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 167,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 167,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 52,
+              "alignment": 1,
+              "tranmap": null,
+              "translation": null,
+              "type": 8,
+              "font": "Console",
+              "duration": 2.5,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 9,
+              "font": "Console",
+              "under_messages": true,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 160,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": "CRGOLD",
+              "type": 10,
+              "font": "Console",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "carousel":
+            {
+              "x": 0,
+              "y": 18,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "conditions": null,
+              "children": null
+            }
+          }
+        ]
+      },
+      {
+        "height": 200,
+        "fullscreenrender": true,
+        "fillflat": null,
+        "children":
+        [
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 192,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 4
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 0,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 176,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 20,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 184,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 1,
+              "font": "Digits",
+              "conditions":
+              [
+                {
+                  "condition": 19,
+                  "param": 3
+                },
+                {
+                  "condition": 21,
+                  "param": 0
+                }
+              ],
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 16,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 2,
+              "font": "Digits",
+              "vertical_layout": 0,
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 320,
+              "y": 8,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 32,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 0,
+              "alignment": 34,
+              "tranmap": null,
+              "translation": null,
+              "type": 3,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 2,
+              "y": 8,
+              "alignment": 0,
+              "tranmap": null,
+              "translation": null,
+              "type": 4,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 318,
+              "y": 200,
+              "alignment": 42,
+              "tranmap": null,
+              "translation": null,
+              "type": 5,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 160,
+              "y": 200,
+              "alignment": 9,
+              "tranmap": null,
+              "translation": null,
+              "type": 6,
+              "font": "Digits",
+              "conditions": null,
+              "children": null
+            }
+          },
+          {
+            "widget":
+            {
+              "x": 0,
+              "y": 0,
+              "alignment": 16,
+              "tranmap": null,
+              "translation": null,
+              "type": 7,
+              "font": "Console",
+              "duration": 4,
+              "conditions": null,
+              "children": null
+            }
+          },
           {
             "widget":
             {
@@ -2265,7 +3320,7 @@
           {
             "widget":
             {
-              "x": 2,
+              "x": 0,
               "y": 0,
               "alignment": 16,
               "tranmap": null,
@@ -2280,7 +3335,7 @@
           {
             "widget":
             {
-              "x": 2,
+              "x": 0,
               "y": 192,
               "alignment": 16,
               "tranmap": null,
diff --git a/base/masterlevels.wad/umapdef.lmp b/base/masterlevels.wad/umapdef.lmp
deleted file mode 100644
index 53719af18..000000000
--- a/base/masterlevels.wad/umapdef.lmp
+++ /dev/null
@@ -1,162 +0,0 @@
-map MAP01
-{
-	levelname = "Attack"
-	levelpic = "CWILV00"
-}
-
-map MAP02
-{
-	levelname = "Canyon"
-	levelpic = "CWILV01"
-}
-
-map MAP03
-{
-	levelname = "The Catwalk"
-	levelpic = "CWILV02"
-}
-
-map MAP04
-{
-	levelname = "The Combine"
-	levelpic = "CWILV03"
-}
-
-map MAP05
-{
-	levelname = "The Fistula"
-	levelpic = "CWILV04"
-}
-
-map MAP06
-{
-	levelname = "The Garrison"
-	levelpic = "CWILV05"
-	intertext = clear
-}
-
-map MAP07
-{
-	levelname = "Titan Manor"
-	levelpic = "CWILV06"
-}
-
-map MAP08
-{
-	levelname = "Paradox"
-	levelpic = "CWILV07"
-}	
-
-map MAP09
-{
-	levelname = "Subspace"
-	levelpic = "CWILV08"
-}
-
-map MAP10
-{
-	levelname = "Subterra"
-	levelpic = "CWILV09"
-}
-
-map MAP11
-{
-	levelname = "Trapped On Titan"
-	levelpic = "CWILV10"
-	intertext = clear
-}
-
-map MAP12
-{
-	levelname = "Virgil's Lead"
-	levelpic = "CWILV11"
-}
-
-map MAP13
-{
-	levelname = "Minos' Judgement"
-	levelpic = "CWILV12"
-}
-
-map MAP14
-{
-	levelname = "Bloodsea Keep"
-	levelpic = "CWILV13"
-	bossaction = Fatso, 23, 666
-	bossaction = Arachnotron, 30, 667
-}
-
-map MAP15
-{
-	levelname = "Mephisto's Maosoleum"
-	levelpic = "CWILV14"
-	bossaction = Fatso, 23, 666
-	bossaction = Arachnotron, 30, 667
-}
-
-map MAP16
-{
-	levelname = "Nessus"
-	levelpic = "CWILV15"
-	bossaction = Fatso, 23, 666
-	bossaction = Arachnotron, 30, 667
-}
-
-map MAP17
-{
-	levelname = "Geryon"
-	levelpic = "CWILV16"
-}
-
-map MAP18
-{
-	levelname = "Vesperas"
-	levelpic = "CWILV17"
-}
-
-map MAP19
-{
-	levelname = "Black Tower"
-	levelpic = "CWILV18"
-}
-
-map MAP20
-{
-	levelname = "The Express Elevator To Hell"
-	levelpic = "CWILV19"
-	nextsecret = "MAP21"
-	intertextsecret = clear
-	endcast = true
-	intertext = "CONGRATULATIONS YOU HAVE FINISHED... ",
-"",
-"MOST OF THE MASTER LEVELS",
-"",
-"You have ventured through the most",
-"twisted levels that hell had to",
-"offer and you have survived. ",
-"",
-"But alas the demons laugh at you",
-"since you have shown cowardice and didn't",
-"reach the most hideous level",
-"they had made for you."
-}
-
-map MAP21
-{
-	levelname = "Bad Dream"
-	levelpic = "CWILV20"
-	endcast = true
-	intertext = "CONGRATULATIONS YOU HAVE FINISHED... ",
-"",
-"ALL THE MASTER LEVELS",
-"",
-"You have ventured through all the",
-"twisted levels that hell had to",
-"offer and you have survived. ",
-"",
-"The Flames of rage flow through",
-"your veins, you are ready",
-"for more - but you don't know where",
-"to find more when the demons hide",
-"like cowards when they see you."
-}
diff --git a/base/nerve.wad/umapdef.lmp b/base/nerve.wad/umapdef.lmp
deleted file mode 100644
index 3d4477498..000000000
--- a/base/nerve.wad/umapdef.lmp
+++ /dev/null
@@ -1,111 +0,0 @@
-map MAP01
-{
-	levelname = "The Earth Base"
-	label = "Level 1"
-	levelpic = "CWILV00"
-	music = "D_MESSAG"
-	skytexture = "SKY1"
-	partime = 75
-}
-
-map MAP02
-{
-	levelname = "The Pain Labs"
-	label = "Level 2"
-	levelpic = "CWILV01"
-	music = "D_DDTBLU"
-	skytexture = "SKY1"
-	partime = 105
-}
-
-map MAP03
-{
-	levelname = "Canyon of the Dead"
-	label = "Level 3"
-	levelpic = "CWILV02"
-	music = "D_DOOM"
-	skytexture = "SKY1"
-	partime = 120
-}
-
-map MAP04
-{
-	levelname = "Hell Mountain"
-	label = "Level 4"
-	levelpic = "CWILV03"
-	music = "D_SHAWN"
-	nextsecret = "map09"
-	skytexture = "SKY3"
-	partime = 105
-}
-
-map MAP05
-{
-	levelname = "Vivisection"
-	label = "Level 5"
-	levelpic = "CWILV04"
-	music = "D_IN_CIT"
-	skytexture = "SKY3"
-	partime = 210
-}
-
-map MAP06
-{
-	levelname = "Inferno of Blood"
-	label = "Level 6"
-	levelpic = "CWILV05"
-	music = "D_THE_DA"
-	skytexture = "SKY3"
-	partime = 105
-	intertext = clear
-}
-
-map MAP07
-{
-	levelname = "Baron's Banquet"
-	label = "Level 7"
-	levelpic = "CWILV06"
-	music = "D_IN_CIT"
-	skytexture = "SKY3"
-	partime = 165
-}
-
-map MAP08
-{
-	levelname = "Tomb of Malevolence"
-	label = "Level 8"
-	levelpic = "CWILV07"
-	music = "D_SHAWN"
-	skytexture = "SKY3"
-	partime = 105
-	endcast = true
-	intertext = "TROUBLE WAS BREWING AGAIN IN YOUR FAVORITE",
-"VACATION SPOT... HELL. SOME CYBERDEMON",
-"PUNK THOUGHT HE COULD TURN HELL INTO A",
-"PERSONAL AMUSEMENT PARK, AND MAKE EARTH",
-"THE TICKET BOOTH.",
-"",
-"WELL THAT HALF-ROBOT FREAK SHOW DIDN'T",
-"KNOW WHO WAS COMING TO THE FAIR. THERE'S",
-"NOTHING LIKE A SHOOTING GALLERY FULL OF",
-"HELLSPAWN TO GET THE BLOOD PUMPING...",
-"",
-"NOW THE WALLS OF THE DEMON'S LABYRINTH",
-"ECHO WITH THE SOUND OF HIS METALLIC LIMBS",
-"HITTING THE FLOOR. HIS DEATH MOAN GURGLES",
-"OUT THROUGH THE MESS YOU LEFT OF HIS FACE.",
-"",
-"THIS RIDE IS CLOSED."
-	interbackdrop = "SLIME16"
-}
-
-map MAP09
-{
-	levelname = "March of the Demons"
-	label = "Level 9"
-	levelpic = "CWILV08"
-	music = "D_DDTBLU"
-	next = "map05"
-	skytexture = "SKY1"
-	partime = 135
-}
diff --git a/base/sigil_v1_21.wad/umapdef.lmp b/base/sigil_v1_21.wad/umapdef.lmp
deleted file mode 100644
index 2f672769f..000000000
--- a/base/sigil_v1_21.wad/umapdef.lmp
+++ /dev/null
@@ -1,4 +0,0 @@
-map E5M8
-{
-	bossaction = clear
-}
diff --git a/config.h.in b/config.h.in
index 11cde77c6..abff4507f 100644
--- a/config.h.in
+++ b/config.h.in
@@ -6,6 +6,7 @@
 #cmakedefine HAVE_DIRENT_H
 #cmakedefine01 HAVE_DECL_STRCASECMP
 #cmakedefine01 HAVE_DECL_STRNCASECMP
+#cmakedefine HAVE_GETPWUID
 #cmakedefine HAVE_HIGH_RES_TIMER
 #cmakedefine HAVE__DIV64
 #cmakedefine HAVE_ALSA
diff --git a/docs/nughud.md b/docs/nughud.md
index 4f9bf6af4..f78ed509f 100644
--- a/docs/nughud.md
+++ b/docs/nughud.md
@@ -1,7 +1,7 @@
 # Nugget Doom's NUGHUD guide
 
 **The `NUGHUD` lump** is a variant of MBF's `OPTIONS` lump, **used exclusively by Nugget Doom to customize the fullscreen HUD**
-used when the `screenblocks` CVAR is set to 11 and the _Fullscreen HUD Type_ setting is set to _NUGHUD_.
+used when the `screenblocks` CVAR is set to the maximum size minus one (usually 12) and the _Use NUGHUD_ setting is enabled.
 
 From this point onwards, _NUGHUD_ refers to the customization system in general, whereas _`NUGHUD`_ refers to a NUGHUD lump.
 
diff --git a/setup/multiplayer.c b/setup/multiplayer.c
index bd332e9e1..b898b6f95 100644
--- a/setup/multiplayer.c
+++ b/setup/multiplayer.c
@@ -282,6 +282,7 @@ static void UpdateSkillButton(void)
     switch(iwad->mission)
     {
         case pack_chex:
+        case pack_chex3v:
             skillbutton->values = chex_skills;
             break;
         case pack_hacx:
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1e7dbf020..e9eec9802 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,6 +3,7 @@ include(NuggetDoomSettings)
 set(NUGGETDOOM_SOURCES
     am_map.c               am_map.h
     d_deh.c                d_deh.h
+    d_demoloop.c           d_demoloop.h
                            d_englsh.h
                            d_event.h
                            d_french.h
@@ -27,6 +28,7 @@ set(NUGGETDOOM_SOURCES
     g_game.c               g_game.h
     g_input.c              g_input.h
     g_nextweapon.c         g_nextweapon.h
+    g_umapinfo.c           g_umapinfo.h
     hu_command.c           hu_command.h
     hu_coordinates.c       hu_coordinates.h
     hu_crosshair.c         hu_crosshair.h
@@ -64,6 +66,7 @@ set(NUGGETDOOM_SOURCES
     m_input.c              m_input.h
     m_io.c                 m_io.h
     m_json.c               m_json.h
+    m_scanner.c            m_scanner.h
     mn_font.c              mn_font.h
     mn_menu.c              mn_menu.h
     mn_setup.c             mn_internal.h
@@ -137,8 +140,6 @@ set(NUGGETDOOM_SOURCES
     st_widgets.c           st_widgets.h
     statdump.c             statdump.h
     tables.c               tables.h
-    u_mapinfo.c            u_mapinfo.h
-    u_scanner.c            u_scanner.h
     v_flextran.c           v_flextran.h
     v_fmt.c                v_fmt.h
     v_trans.c              v_trans.h
diff --git a/src/am_map.c b/src/am_map.c
index b4d6a2d7d..defe3ee48 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -2843,15 +2843,7 @@ void AM_Drawer (void)
   }
 
   if (automapoverlay == AM_OVERLAY_OFF)
-  {
     AM_clearFB(mapcolor_back);       //jff 1/5/98 background default color
-
-    // [Nugget] New weapon interpolation: don't need this
-    /*
-    if (automapactive == AM_FULL) // [Nugget] Minimap
-      pspr_interp = false;
-    */
-  }
   // [Alaux] Dark automap overlay
   else if (automapoverlay == AM_OVERLAY_DARK)
     AM_shadeScreen();
diff --git a/src/d_deh.c b/src/d_deh.c
index f6041deb5..8568257ec 100644
--- a/src/d_deh.c
+++ b/src/d_deh.c
@@ -1503,6 +1503,8 @@ char *deh_weapon[] =
   // mbf21
   "Ammo per shot",  // .ammopershot
   "MBF21 Bits",     // .flags
+  // id24
+  "Carousel icon",  // .carouselicon
 };
 
 // CHEATS - Dehacked block name = "Cheat"
@@ -1558,124 +1560,124 @@ typedef struct {
 
 deh_bexptr deh_bexptrs[] =
 {
-  {{A_Light0},         "A_Light0"},
-  {{A_WeaponReady},    "A_WeaponReady"},
-  {{A_Lower},          "A_Lower"},
-  {{A_Raise},          "A_Raise"},
-  {{A_Punch},          "A_Punch"},
-  {{A_ReFire},         "A_ReFire"},
-  {{A_FirePistol},     "A_FirePistol"},
-  {{A_Light1},         "A_Light1"},
-  {{A_FireShotgun},    "A_FireShotgun"},
-  {{A_Light2},         "A_Light2"},
-  {{A_FireShotgun2},   "A_FireShotgun2"},
-  {{A_CheckReload},    "A_CheckReload"},
-  {{A_OpenShotgun2},   "A_OpenShotgun2"},
-  {{A_LoadShotgun2},   "A_LoadShotgun2"},
-  {{A_CloseShotgun2},  "A_CloseShotgun2"},
-  {{A_FireCGun},       "A_FireCGun"},
-  {{A_GunFlash},       "A_GunFlash"},
-  {{A_FireMissile},    "A_FireMissile"},
-  {{A_Saw},            "A_Saw"},
-  {{A_FirePlasma},     "A_FirePlasma"},
-  {{A_BFGsound},       "A_BFGsound"},
-  {{A_FireBFG},        "A_FireBFG"},
-  {{A_BFGSpray},       "A_BFGSpray"},
-  {{A_Explode},        "A_Explode"},
-  {{A_Pain},           "A_Pain"},
-  {{A_PlayerScream},   "A_PlayerScream"},
-  {{A_Fall},           "A_Fall"},
-  {{A_XScream},        "A_XScream"},
-  {{A_Look},           "A_Look"},
-  {{A_Chase},          "A_Chase"},
-  {{A_FaceTarget},     "A_FaceTarget"},
-  {{A_PosAttack},      "A_PosAttack"},
-  {{A_Scream},         "A_Scream"},
-  {{A_SPosAttack},     "A_SPosAttack"},
-  {{A_VileChase},      "A_VileChase"},
-  {{A_VileStart},      "A_VileStart"},
-  {{A_VileTarget},     "A_VileTarget"},
-  {{A_VileAttack},     "A_VileAttack"},
-  {{A_StartFire},      "A_StartFire"},
-  {{A_Fire},           "A_Fire"},
-  {{A_FireCrackle},    "A_FireCrackle"},
-  {{A_Tracer},         "A_Tracer"},
-  {{A_SkelWhoosh},     "A_SkelWhoosh"},
-  {{A_SkelFist},       "A_SkelFist"},
-  {{A_SkelMissile},    "A_SkelMissile"},
-  {{A_FatRaise},       "A_FatRaise"},
-  {{A_FatAttack1},     "A_FatAttack1"},
-  {{A_FatAttack2},     "A_FatAttack2"},
-  {{A_FatAttack3},     "A_FatAttack3"},
-  {{A_BossDeath},      "A_BossDeath"},
-  {{A_CPosAttack},     "A_CPosAttack"},
-  {{A_CPosRefire},     "A_CPosRefire"},
-  {{A_TroopAttack},    "A_TroopAttack"},
-  {{A_SargAttack},     "A_SargAttack"},
-  {{A_HeadAttack},     "A_HeadAttack"},
-  {{A_BruisAttack},    "A_BruisAttack"},
-  {{A_SkullAttack},    "A_SkullAttack"},
-  {{A_Metal},          "A_Metal"},
-  {{A_SpidRefire},     "A_SpidRefire"},
-  {{A_BabyMetal},      "A_BabyMetal"},
-  {{A_BspiAttack},     "A_BspiAttack"},
-  {{A_Hoof},           "A_Hoof"},
-  {{A_CyberAttack},    "A_CyberAttack"},
-  {{A_PainAttack},     "A_PainAttack"},
-  {{A_PainDie},        "A_PainDie"},
-  {{A_KeenDie},        "A_KeenDie"},
-  {{A_BrainPain},      "A_BrainPain"},
-  {{A_BrainScream},    "A_BrainScream"},
-  {{A_BrainDie},       "A_BrainDie"},
-  {{A_BrainAwake},     "A_BrainAwake"},
-  {{A_BrainSpit},      "A_BrainSpit"},
-  {{A_SpawnSound},     "A_SpawnSound"},
-  {{A_SpawnFly},       "A_SpawnFly"},
-  {{A_BrainExplode},   "A_BrainExplode"},
-  {{A_Detonate},       "A_Detonate"},       // killough 8/9/98
-  {{A_Mushroom},       "A_Mushroom"},       // killough 10/98
-  {{A_Die},            "A_Die"},            // killough 11/98
-  {{A_Spawn},          "A_Spawn"},          // killough 11/98
-  {{A_Turn},           "A_Turn"},           // killough 11/98
-  {{A_Face},           "A_Face"},           // killough 11/98
-  {{A_Scratch},        "A_Scratch"},        // killough 11/98
-  {{A_PlaySound},      "A_PlaySound"},      // killough 11/98
-  {{A_RandomJump},     "A_RandomJump"},     // killough 11/98
-  {{A_LineEffect},     "A_LineEffect"},     // killough 11/98
-
-  {{A_FireOldBFG},      "A_FireOldBFG"},      // killough 7/19/98: classic BFG firing function
-  {{A_BetaSkullAttack}, "A_BetaSkullAttack"}, // killough 10/98: beta lost souls attacked different
-  {{A_Stop},            "A_Stop"},
+  {{.p2 = A_Light0},         "A_Light0"},
+  {{.p2 = A_WeaponReady},    "A_WeaponReady"},
+  {{.p2 = A_Lower},          "A_Lower"},
+  {{.p2 = A_Raise},          "A_Raise"},
+  {{.p2 = A_Punch},          "A_Punch"},
+  {{.p2 = A_ReFire},         "A_ReFire"},
+  {{.p2 = A_FirePistol},     "A_FirePistol"},
+  {{.p2 = A_Light1},         "A_Light1"},
+  {{.p2 = A_FireShotgun},    "A_FireShotgun"},
+  {{.p2 = A_Light2},         "A_Light2"},
+  {{.p2 = A_FireShotgun2},   "A_FireShotgun2"},
+  {{.p2 = A_CheckReload},    "A_CheckReload"},
+  {{.p2 = A_OpenShotgun2},   "A_OpenShotgun2"},
+  {{.p2 = A_LoadShotgun2},   "A_LoadShotgun2"},
+  {{.p2 = A_CloseShotgun2},  "A_CloseShotgun2"},
+  {{.p2 = A_FireCGun},       "A_FireCGun"},
+  {{.p2 = A_GunFlash},       "A_GunFlash"},
+  {{.p2 = A_FireMissile},    "A_FireMissile"},
+  {{.p2 = A_Saw},            "A_Saw"},
+  {{.p2 = A_FirePlasma},     "A_FirePlasma"},
+  {{.p2 = A_BFGsound},       "A_BFGsound"},
+  {{.p2 = A_FireBFG},        "A_FireBFG"},
+  {{.p1 = A_BFGSpray},       "A_BFGSpray"},
+  {{.p1 = A_Explode},        "A_Explode"},
+  {{.p1 = A_Pain},           "A_Pain"},
+  {{.p1 = A_PlayerScream},   "A_PlayerScream"},
+  {{.p1 = A_Fall},           "A_Fall"},
+  {{.p1 = A_XScream},        "A_XScream"},
+  {{.p1 = A_Look},           "A_Look"},
+  {{.p1 = A_Chase},          "A_Chase"},
+  {{.p1 = A_FaceTarget},     "A_FaceTarget"},
+  {{.p1 = A_PosAttack},      "A_PosAttack"},
+  {{.p1 = A_Scream},         "A_Scream"},
+  {{.p1 = A_SPosAttack},     "A_SPosAttack"},
+  {{.p1 = A_VileChase},      "A_VileChase"},
+  {{.p1 = A_VileStart},      "A_VileStart"},
+  {{.p1 = A_VileTarget},     "A_VileTarget"},
+  {{.p1 = A_VileAttack},     "A_VileAttack"},
+  {{.p1 = A_StartFire},      "A_StartFire"},
+  {{.p1 = A_Fire},           "A_Fire"},
+  {{.p1 = A_FireCrackle},    "A_FireCrackle"},
+  {{.p1 = A_Tracer},         "A_Tracer"},
+  {{.p1 = A_SkelWhoosh},     "A_SkelWhoosh"},
+  {{.p1 = A_SkelFist},       "A_SkelFist"},
+  {{.p1 = A_SkelMissile},    "A_SkelMissile"},
+  {{.p1 = A_FatRaise},       "A_FatRaise"},
+  {{.p1 = A_FatAttack1},     "A_FatAttack1"},
+  {{.p1 = A_FatAttack2},     "A_FatAttack2"},
+  {{.p1 = A_FatAttack3},     "A_FatAttack3"},
+  {{.p1 = A_BossDeath},      "A_BossDeath"},
+  {{.p1 = A_CPosAttack},     "A_CPosAttack"},
+  {{.p1 = A_CPosRefire},     "A_CPosRefire"},
+  {{.p1 = A_TroopAttack},    "A_TroopAttack"},
+  {{.p1 = A_SargAttack},     "A_SargAttack"},
+  {{.p1 = A_HeadAttack},     "A_HeadAttack"},
+  {{.p1 = A_BruisAttack},    "A_BruisAttack"},
+  {{.p1 = A_SkullAttack},    "A_SkullAttack"},
+  {{.p1 = A_Metal},          "A_Metal"},
+  {{.p1 = A_SpidRefire},     "A_SpidRefire"},
+  {{.p1 = A_BabyMetal},      "A_BabyMetal"},
+  {{.p1 = A_BspiAttack},     "A_BspiAttack"},
+  {{.p1 = A_Hoof},           "A_Hoof"},
+  {{.p1 = A_CyberAttack},    "A_CyberAttack"},
+  {{.p1 = A_PainAttack},     "A_PainAttack"},
+  {{.p1 = A_PainDie},        "A_PainDie"},
+  {{.p1 = A_KeenDie},        "A_KeenDie"},
+  {{.p1 = A_BrainPain},      "A_BrainPain"},
+  {{.p1 = A_BrainScream},    "A_BrainScream"},
+  {{.p1 = A_BrainDie},       "A_BrainDie"},
+  {{.p1 = A_BrainAwake},     "A_BrainAwake"},
+  {{.p1 = A_BrainSpit},      "A_BrainSpit"},
+  {{.p1 = A_SpawnSound},     "A_SpawnSound"},
+  {{.p1 = A_SpawnFly},       "A_SpawnFly"},
+  {{.p1 = A_BrainExplode},   "A_BrainExplode"},
+  {{.p1 = A_Detonate},       "A_Detonate"},       // killough 8/9/98
+  {{.p1 = A_Mushroom},       "A_Mushroom"},       // killough 10/98
+  {{.p1 = A_Die},            "A_Die"},            // killough 11/98
+  {{.p1 = A_Spawn},          "A_Spawn"},          // killough 11/98
+  {{.p1 = A_Turn},           "A_Turn"},           // killough 11/98
+  {{.p1 = A_Face},           "A_Face"},           // killough 11/98
+  {{.p1 = A_Scratch},        "A_Scratch"},        // killough 11/98
+  {{.p1 = A_PlaySound},      "A_PlaySound"},      // killough 11/98
+  {{.p1 = A_RandomJump},     "A_RandomJump"},     // killough 11/98
+  {{.p1 = A_LineEffect},     "A_LineEffect"},     // killough 11/98
+
+  {{.p2 = A_FireOldBFG},      "A_FireOldBFG"},      // killough 7/19/98: classic BFG firing function
+  {{.p1 = A_BetaSkullAttack}, "A_BetaSkullAttack"}, // killough 10/98: beta lost souls attacked different
+  {{.p1 = A_Stop},            "A_Stop"},
 
   // [XA] New mbf21 codepointers
-  {{A_SpawnObject},         "A_SpawnObject", 8},
-  {{A_MonsterProjectile},   "A_MonsterProjectile", 5},
-  {{A_MonsterBulletAttack}, "A_MonsterBulletAttack", 5, {0, 0, 1, 3, 5}},
-  {{A_MonsterMeleeAttack},  "A_MonsterMeleeAttack", 4, {3, 8, 0, 0}},
-  {{A_RadiusDamage},        "A_RadiusDamage", 2},
-  {{A_NoiseAlert},          "A_NoiseAlert", 0},
-  {{A_HealChase},           "A_HealChase", 2},
-  {{A_SeekTracer},          "A_SeekTracer", 2},
-  {{A_FindTracer},          "A_FindTracer", 2, {0, 10}},
-  {{A_ClearTracer},         "A_ClearTracer", 0},
-  {{A_JumpIfHealthBelow},   "A_JumpIfHealthBelow", 2},
-  {{A_JumpIfTargetInSight}, "A_JumpIfTargetInSight", 2},
-  {{A_JumpIfTargetCloser},  "A_JumpIfTargetCloser", 2},
-  {{A_JumpIfTracerInSight}, "A_JumpIfTracerInSight", 2},
-  {{A_JumpIfTracerCloser},  "A_JumpIfTracerCloser", 2},
-  {{A_JumpIfFlagsSet},      "A_JumpIfFlagsSet", 3},
-  {{A_AddFlags},            "A_AddFlags", 2},
-  {{A_RemoveFlags},         "A_RemoveFlags", 2},
-  {{A_WeaponProjectile},    "A_WeaponProjectile", 5},
-  {{A_WeaponBulletAttack},  "A_WeaponBulletAttack", 5, {0, 0, 1, 5, 3}},
-  {{A_WeaponMeleeAttack},   "A_WeaponMeleeAttack", 5, {2, 10, 1 * FRACUNIT, 0, 0}},
-  {{A_WeaponSound},         "A_WeaponSound", 2},
-  {{A_WeaponAlert},         "A_WeaponAlert", 0},
-  {{A_WeaponJump},          "A_WeaponJump", 2},
-  {{A_ConsumeAmmo},         "A_ConsumeAmmo", 1},
-  {{A_CheckAmmo},           "A_CheckAmmo", 2},
-  {{A_RefireTo},            "A_RefireTo", 2},
-  {{A_GunFlashTo},          "A_GunFlashTo", 2},
+  {{.p1 = A_SpawnObject},         "A_SpawnObject", 8},
+  {{.p1 = A_MonsterProjectile},   "A_MonsterProjectile", 5},
+  {{.p1 = A_MonsterBulletAttack}, "A_MonsterBulletAttack", 5, {0, 0, 1, 3, 5}},
+  {{.p1 = A_MonsterMeleeAttack},  "A_MonsterMeleeAttack", 4, {3, 8, 0, 0}},
+  {{.p1 = A_RadiusDamage},        "A_RadiusDamage", 2},
+  {{.p1 = A_NoiseAlert},          "A_NoiseAlert", 0},
+  {{.p1 = A_HealChase},           "A_HealChase", 2},
+  {{.p1 = A_SeekTracer},          "A_SeekTracer", 2},
+  {{.p1 = A_FindTracer},          "A_FindTracer", 2, {0, 10}},
+  {{.p1 = A_ClearTracer},         "A_ClearTracer", 0},
+  {{.p1 = A_JumpIfHealthBelow},   "A_JumpIfHealthBelow", 2},
+  {{.p1 = A_JumpIfTargetInSight}, "A_JumpIfTargetInSight", 2},
+  {{.p1 = A_JumpIfTargetCloser},  "A_JumpIfTargetCloser", 2},
+  {{.p1 = A_JumpIfTracerInSight}, "A_JumpIfTracerInSight", 2},
+  {{.p1 = A_JumpIfTracerCloser},  "A_JumpIfTracerCloser", 2},
+  {{.p1 = A_JumpIfFlagsSet},      "A_JumpIfFlagsSet", 3},
+  {{.p1 = A_AddFlags},            "A_AddFlags", 2},
+  {{.p1 = A_RemoveFlags},         "A_RemoveFlags", 2},
+  {{.p2 = A_WeaponProjectile},    "A_WeaponProjectile", 5},
+  {{.p2 = A_WeaponBulletAttack},  "A_WeaponBulletAttack", 5, {0, 0, 1, 5, 3}},
+  {{.p2 = A_WeaponMeleeAttack},   "A_WeaponMeleeAttack", 5, {2, 10, 1 * FRACUNIT, 0, 0}},
+  {{.p2 = A_WeaponSound},         "A_WeaponSound", 2},
+  {{.p2 = A_WeaponAlert},         "A_WeaponAlert", 0},
+  {{.p2 = A_WeaponJump},          "A_WeaponJump", 2},
+  {{.p2 = A_ConsumeAmmo},         "A_ConsumeAmmo", 1},
+  {{.p2 = A_CheckAmmo},           "A_CheckAmmo", 2},
+  {{.p2 = A_RefireTo},            "A_RefireTo", 2},
+  {{.p2 = A_GunFlashTo},          "A_GunFlashTo", 2},
 
   // This NULL entry must be the last in the list
   {{NULL},             "A_NULL"},  // Ty 05/16/98
@@ -2566,7 +2568,20 @@ void deh_procWeapon(DEHFILE *fpin, FILE* fpout, char *line)
 
                       weaponinfo[indexnum].flags = value;
                     }
-                    else
+                   else
+                     if (!strcasecmp(key, deh_weapon[8])) // Carousel icon
+                       {
+                          char candidate[9] = {0};
+                          M_CopyLumpName(candidate, ptr_lstrip(strval));
+                          int len = strlen(candidate);
+                          if (len < 1 || len > 7)
+                            {
+                              if (fpout) fprintf(fpout,"Bad length for carousel icon name '%s'\n",candidate);
+                              continue;
+                            }
+                          weaponinfo[indexnum].carouselicon = M_StringDuplicate(candidate);
+                       }
+                     else
                       if (fpout) fprintf(fpout,"Invalid weapon string index for '%s'\n",key);
     }
   return;
@@ -3397,7 +3412,7 @@ void rstrip(char *s)  // strip trailing whitespace
 //
 char *ptr_lstrip(char *p)  // point past leading whitespace
 {
-  while (*p >= 0 && isspace(*p))
+  while (*p && isspace(*p))
     p++;
   return p;
 }
diff --git a/src/d_demoloop.c b/src/d_demoloop.c
new file mode 100644
index 000000000..66a2cd241
--- /dev/null
+++ b/src/d_demoloop.c
@@ -0,0 +1,181 @@
+//
+//  Copyright (C) 2025 Guilherme Miranda
+//
+//  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.
+//
+//  DESCRIPTION:
+//    ID24 DemoLoop Specification - User customizable title screen sequence.
+//    Though originally hardcoded on the original Doom engine this allows
+//    for Doom modders to define custom sequences, using any provided custom
+//    graphic-and-music, or DEMO lump.
+//
+
+#include "doomdef.h"
+#include "doomstat.h"
+
+#include "i_printf.h"
+#include "m_array.h"
+#include "m_json.h"
+#include "m_misc.h"
+
+#include "d_demoloop.h"
+
+// Doom
+static demoloop_entry_t demoloop_registered[] = {
+    { "TITLEPIC", "D_INTRO",  170, TYPE_ART,  WIPE_MELT },
+    { "DEMO1",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "CREDIT",   "",         200, TYPE_ART,  WIPE_MELT },
+    { "DEMO2",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "HELP2",    "",         200, TYPE_ART,  WIPE_MELT },
+    { "DEMO3",    "",         0,   TYPE_DEMO, WIPE_MELT },
+};
+
+// Ultimate Doom
+static demoloop_entry_t demoloop_retail[] = {
+    { "TITLEPIC", "D_INTRO",  170, TYPE_ART,  WIPE_MELT },
+    { "DEMO1",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "CREDIT",   "",         200, TYPE_ART,  WIPE_MELT },
+    { "DEMO2",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "CREDIT",   "",         200, TYPE_ART,  WIPE_MELT },
+    { "DEMO3",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "DEMO4",    "",         0,   TYPE_DEMO, WIPE_MELT },
+};
+
+// Doom II & Final Doom
+static demoloop_entry_t demoloop_commercial[] = {
+    { "TITLEPIC", "D_DM2TTL", 385, TYPE_ART,  WIPE_MELT },
+    { "DEMO1",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "CREDIT",   "",         200, TYPE_ART,  WIPE_MELT },
+    { "DEMO2",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "TITLEPIC", "D_DM2TTL", 385, TYPE_ART,  WIPE_MELT },
+    { "DEMO3",    "",         0,   TYPE_DEMO, WIPE_MELT },
+    { "DEMO4",    "",         0,   TYPE_DEMO, WIPE_MELT },
+};
+
+demoloop_t demoloop = NULL;
+int        demoloop_count = 0;
+
+void D_ParseDemoLoopEntry(json_t *entry)
+{
+    const char* primary_buffer   = JS_GetStringValue(entry, "primarylump");
+    const char* secondary_buffer = JS_GetStringValue(entry, "secondarylump");
+    double      seconds          = JS_GetNumberValue(entry, "duration");
+    int         type             = JS_GetIntegerValue(entry, "type");
+    int         outro_wipe       = JS_GetIntegerValue(entry, "outro_wipe");
+
+    // We don't want a malformed entry to creep in, and break the titlescreen.
+    // If one such entry does exist, skip it.
+    // TODO: modify later to check locally for lump type.
+    if (primary_buffer == NULL || secondary_buffer == NULL || type < TYPE_ART
+        || type > TYPE_DEMO)
+    {
+        return;
+    }
+
+    // Similarly, but this time it isn't game-breaking.
+    // Let it gracefully default to "closest vanilla behavior".
+    if (outro_wipe <= WIPE_NONE || outro_wipe > WIPE_MELT)
+    {
+        outro_wipe = WIPE_MELT;
+    }
+
+    demoloop_entry_t current_entry = {0};
+
+    // Remove pointer reference to in-memory JSON data.
+    M_CopyLumpName(current_entry.primary_lump, primary_buffer);
+    M_CopyLumpName(current_entry.secondary_lump, secondary_buffer);
+    // Providing the time in seconds is much more intuitive for the end users.
+    current_entry.duration   = seconds * TICRATE;
+    current_entry.type       = type;
+    current_entry.outro_wipe = outro_wipe;
+
+    array_push(demoloop, current_entry);
+}
+
+static void D_ParseDemoLoop(void)
+{
+    // Does the JSON lump even exist?
+    json_t *json = JS_Open("DEMOLOOP", "demoloop", (version_t){1, 0, 0});
+    if (json == NULL)
+    {
+        return;
+    }
+
+    // Does lump actually have any data?
+    json_t *data = JS_GetObject(json, "data");
+    if (JS_IsNull(data) || !JS_IsObject(data))
+    {
+        I_Printf(VB_WARNING, "DEMOLOOP: data object not defined");
+        JS_Close("DEMOLOOP");
+        return;
+    }
+
+    // Does is it even have the definitions we are looking for?
+    json_t *entry_list = JS_GetObject(data, "entries");
+    if (JS_IsNull(entry_list) || !JS_IsArray(entry_list))
+    {
+        I_Printf(VB_WARNING, "DEMOLOOP: no entries defined");
+        JS_Close("DEMOLOOP");
+        return;
+    }
+
+    // If so, now parse them.
+    json_t *entry;
+    JS_ArrayForEach(entry, entry_list)
+    {
+        D_ParseDemoLoopEntry(entry);
+    }
+    demoloop_count = array_size(demoloop);
+
+    // No need to keep in memory
+    JS_Close("DEMOLOOP");
+}
+
+static void D_GetDefaultDemoLoop(GameMode_t mode)
+{
+    switch(mode)
+    {
+        case shareware:
+        case registered:
+            demoloop = demoloop_registered;
+            demoloop_count = arrlen(demoloop_registered);
+            break;
+
+        case retail:
+            demoloop = demoloop_retail;
+            demoloop_count = arrlen(demoloop_retail);
+            break;
+
+        case commercial:
+            demoloop = demoloop_commercial;
+            demoloop_count = arrlen(demoloop_commercial);
+            break;
+
+        case indetermined:
+        default:
+            // How did we get here?
+            demoloop = NULL;
+            demoloop_count = 0;
+            break;
+    }
+
+    return;
+}
+
+void D_SetupDemoLoop(void)
+{
+    D_ParseDemoLoop();
+
+    if (demoloop == NULL)
+    {
+        D_GetDefaultDemoLoop(gamemode);
+    }
+}
diff --git a/src/d_demoloop.h b/src/d_demoloop.h
new file mode 100644
index 000000000..14355660b
--- /dev/null
+++ b/src/d_demoloop.h
@@ -0,0 +1,60 @@
+//
+//  Copyright (C) 2025 Guilherme Miranda
+//
+//  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.
+//
+//  DESCRIPTION:
+//    ID24 DemoLoop Specification - User customizable title screen sequence.
+//    Though originally hardcoded on the original Doom engine this allows
+//    for Doom modders to define custom sequences, using any provided custom
+//    graphic, music or DEMO lump.
+//
+
+#ifndef _D_DEMOLOOP_
+#define _D_DEMOLOOP_
+
+// Screen graphic or DEMO lump, NONE is used for fault tolerance.
+typedef enum
+{
+    TYPE_ART,
+    TYPE_DEMO,
+} dl_type_t;
+
+// Immediate switch or screen melt, NONE is used for fault tolerance.
+// TODO: reimplement more cleanly at a later, more relevant moment
+typedef enum
+{
+    WIPE_NONE = -1,
+    WIPE_IMMEDIATE,
+    WIPE_MELT,
+} dl_wipe_t;
+
+// Individual demoloop units.
+typedef struct
+{
+    char      primary_lump[9];   // Screen graphic or DEMO lump.
+    char      secondary_lump[9]; // Music lump for screen graphic.
+    int       duration;          // Game tics.
+    dl_type_t type;
+    dl_wipe_t outro_wipe;
+} demoloop_entry_t;
+
+typedef demoloop_entry_t* demoloop_t;
+
+// Actual DemoLoop data structure.
+extern demoloop_t demoloop;
+// Formerly 7 for Ultimate Doom & Final Doom, 6 otherwise.
+extern int        demoloop_count;
+
+// Perform both "D_ParseDemoLoop" and "D_GetDefaultDemoLoop".
+void D_SetupDemoLoop(void);
+
+#endif // _D_DEMOLOOP_
diff --git a/src/d_items.h b/src/d_items.h
index a120a69b9..b53624806 100644
--- a/src/d_items.h
+++ b/src/d_items.h
@@ -59,6 +59,7 @@ typedef struct
   int         flags;
   // id24
   int         slot;
+  const char  *carouselicon;
 } weaponinfo_t;
 
 extern  weaponinfo_t    weaponinfo[NUMWEAPONS+2];
diff --git a/src/d_iwad.c b/src/d_iwad.c
index 040aee3bb..781ad6672 100644
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -28,24 +28,26 @@
 #include "m_misc.h"
 
 static const iwad_t iwads[] = {
-    {"doom2.wad",     doom2,      commercial,   "DOOM II: Hell on Earth"         },
-    {"plutonia.wad",  pack_plut,  commercial,   "Final DOOM: Plutonia Experiment"},
-    {"tnt.wad",       pack_tnt,   commercial,   "Final DOOM: TNT - Evilution"    },
+    {"doom2.wad",     doom2,       commercial,   "DOOM II: Hell on Earth"         },
+    {"plutonia.wad",  pack_plut,   commercial,   "Final DOOM: Plutonia Experiment"},
+    {"tnt.wad",       pack_tnt,    commercial,   "Final DOOM: TNT - Evilution"    },
     // "doom.wad" may be retail or registered
-    {"doom.wad",      doom,       indetermined, "DOOM"                           },
-    {"doom.wad",      doom,       registered,   "DOOM Registered"                },
-    {"doom.wad",      doom,       retail,       "The Ultimate DOOM"              },
+    {"doom.wad",      doom,        indetermined, "DOOM"                           },
+    {"doom.wad",      doom,        registered,   "DOOM Registered"                },
+    {"doom.wad",      doom,        retail,       "The Ultimate DOOM"              },
     // "doomu.wad" alias to allow retail wad to coexist with registered in the same folder
-    {"doomu.wad",     doom,       retail,       "The Ultimate DOOM"              },
-    {"doom1.wad",     doom,       shareware,    "DOOM Shareware"                 },
-    {"doom2f.wad",    doom2,      commercial,   "DOOM II: L'Enfer sur Terre"     },
-    {"freedoom2.wad", doom2,      commercial,   "Freedoom: Phase 2"              },
-    {"freedoom1.wad", doom,       retail,       "Freedoom: Phase 1"              },
-    {"freedm.wad",    doom2,      commercial,   "FreeDM"                         },
-    {"chex.wad",      pack_chex,  retail,       "Chex Quest"                     },
-    {"hacx.wad",      pack_hacx,  commercial,   "HACX: Twitch n' Kill"           },
-    {"rekkrsa.wad",   pack_rekkr, retail,       "REKKR"                          },
-    {"rekkrsl.wad",   pack_rekkr, retail,       "REKKR: Sunken Land"             },
+    {"doomu.wad",     doom,        retail,       "The Ultimate DOOM"              },
+    {"doom1.wad",     doom,        shareware,    "DOOM Shareware"                 },
+    {"doom2f.wad",    doom2,       commercial,   "DOOM II: L'Enfer sur Terre"     },
+    {"freedoom2.wad", doom2,       commercial,   "Freedoom: Phase 2"              },
+    {"freedoom1.wad", doom,        retail,       "Freedoom: Phase 1"              },
+    {"freedm.wad",    doom2,       commercial,   "FreeDM"                         },
+    {"chex.wad",      pack_chex,   retail,       "Chex Quest"                     },
+    {"chex3v.wad",    pack_chex3v, retail,       "Chex Quest 3: Vanilla Edition"  },
+    {"chex3d2.wad",   pack_chex3v, commercial,   "Chex Quest 3: Modding Edition"  },
+    {"hacx.wad",      pack_hacx,   commercial,   "HACX: Twitch n' Kill"           },
+    {"rekkrsa.wad",   pack_rekkr,  retail,       "REKKR"                          },
+    {"rekkrsl.wad",   pack_rekkr,  retail,       "REKKR: Sunken Land"             },
 };
 
 static const char *const gamemode_str[] = {
@@ -56,24 +58,11 @@ static const char *const gamemode_str[] = {
     "Unknown mode"
 };
 
-// "128 IWAD search directories should be enough for anybody".
-
-#define MAX_IWAD_DIRS 128
-
 // Array of locations to search for IWAD files
+#define M_ARRAY_INIT_CAPACITY 32
+#include "m_array.h"
 
-static boolean iwad_dirs_built = false;
-char *iwad_dirs[MAX_IWAD_DIRS];
-int num_iwad_dirs = 0;
-
-static void AddIWADDir(char *dir)
-{
-    if (num_iwad_dirs < MAX_IWAD_DIRS)
-    {
-        iwad_dirs[num_iwad_dirs] = dir;
-        ++num_iwad_dirs;
-    }
-}
+static char **iwad_dirs;
 
 // Return the path where the executable lies -- Lee Killough
 
@@ -100,6 +89,38 @@ char *D_DoomExeDir(void)
     return base;
 }
 
+// [FG] get the path to the default configuration dir to use
+
+char *D_DoomPrefDir(void)
+{
+    static char *dir;
+
+    if (dir == NULL)
+    {
+#if !defined(_WIN32) || defined(_WIN32_WCE)
+        // Configuration settings are stored in an OS-appropriate path
+        // determined by SDL.  On typical Unix systems, this might be
+        // ~/.local/share/chocolate-doom.  On Windows, we behave like
+        // Vanilla Doom and save in the current directory.
+
+        char *result = SDL_GetPrefPath("", PROJECT_SHORTNAME);
+        if (result != NULL)
+        {
+            dir = M_DirName(result);
+            SDL_free(result);
+        }
+        else
+#endif /* #ifndef _WIN32 */
+        {
+            dir = D_DoomExeDir();
+        }
+
+        M_MakeDirectory(dir);
+    }
+
+    return dir;
+}
+
 // This is Windows-specific code that automatically finds the location
 // of installed IWAD files.  The registry is inspected to find special
 // keys installed by the Windows installers for various CD versions
@@ -353,7 +374,7 @@ static void CheckUninstallStrings(void)
         {
             path = unstr + strlen(UNINSTALLER_STRING);
 
-            AddIWADDir(path);
+            array_push(iwad_dirs, path);
         }
     }
 }
@@ -381,7 +402,7 @@ static void CheckInstallRootPaths(void)
         {
             subpath = M_StringJoin(install_path, DIR_SEPARATOR_S,
                                    root_path_subdirs[j]);
-            AddIWADDir(subpath);
+            array_push(iwad_dirs, subpath);
         }
 
         free(install_path);
@@ -408,7 +429,7 @@ static void CheckSteamEdition(void)
         subpath = M_StringJoin(install_path, DIR_SEPARATOR_S,
                                steam_install_subdirs[i]);
 
-        AddIWADDir(subpath);
+        array_push(iwad_dirs, subpath);
     }
 
     free(install_path);
@@ -421,26 +442,17 @@ static void CheckDOSDefaults(void)
     // These are the default install directories used by the deice
     // installer program:
 
-    AddIWADDir("\\doom2");    // Doom II
-    AddIWADDir("\\plutonia"); // Final Doom
-    AddIWADDir("\\tnt");
-    AddIWADDir("\\doom_se"); // Ultimate Doom
-    AddIWADDir("\\doom");    // Shareware / Registered Doom
-    AddIWADDir("\\dooms");   // Shareware versions
-    AddIWADDir("\\doomsw");
+    array_push(iwad_dirs, "\\doom2");    // Doom II
+    array_push(iwad_dirs, "\\plutonia"); // Final Doom
+    array_push(iwad_dirs, "\\tnt");
+    array_push(iwad_dirs, "\\doom_se"); // Ultimate Doom
+    array_push(iwad_dirs, "\\doom");    // Shareware / Registered Doom
+    array_push(iwad_dirs, "\\dooms");   // Shareware versions
+    array_push(iwad_dirs, "\\doomsw");
 }
 
 #endif
 
-// Returns true if the specified path is a path to a file
-// of the specified name.
-
-static boolean DirIsFile(const char *path, const char *filename)
-{
-    return strchr(path, DIR_SEPARATOR) != NULL
-           && !strcasecmp(M_BaseName(path), filename);
-}
-
 // Add IWAD directories parsed from splitting a path string containing
 // paths separated by PATH_SEPARATOR. 'suffix' is a string to concatenate
 // to the end of the paths before adding them.
@@ -462,7 +474,7 @@ static void AddIWADPath(const char *path, const char *suffix)
             // as another iwad dir
             *p = '\0';
 
-            AddIWADDir(M_StringJoin(left, suffix));
+            array_push(iwad_dirs, M_StringJoin(left, suffix));
             left = p + 1;
         }
         else
@@ -471,7 +483,7 @@ static void AddIWADPath(const char *path, const char *suffix)
         }
     }
 
-    AddIWADDir(M_StringJoin(left, suffix));
+    array_push(iwad_dirs, M_StringJoin(left, suffix));
 
     free(dup_path);
 }
@@ -484,33 +496,12 @@ static void AddIWADPath(const char *path, const char *suffix)
 // <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>
 static void AddXdgDirs(void)
 {
-    char *env, *tmp_env;
-
-    // Quote:
-    // > $XDG_DATA_HOME defines the base directory relative to which
-    // > user specific data files should be stored. If $XDG_DATA_HOME
-    // > is either not set or empty, a default equal to
-    // > $HOME/.local/share should be used.
-    env = M_getenv("XDG_DATA_HOME");
-    tmp_env = NULL;
-
-    if (env == NULL)
-    {
-        char *homedir = M_getenv("HOME");
-        if (homedir == NULL)
-        {
-            homedir = "/";
-        }
-
-        tmp_env = M_StringJoin(homedir, "/.local/share");
-        env = tmp_env;
-    }
+    char *env = M_DataDir();
 
     // We support $XDG_DATA_HOME/games/doom (which will usually be
     // ~/.local/share/games/doom) as a user-writeable extension to
     // the usual /usr/share/games/doom location.
-    AddIWADDir(M_StringJoin(env, "/games/doom"));
-    free(tmp_env);
+    array_push(iwad_dirs, M_StringJoin(env, "/games/doom"));
 
     // Quote:
     // > $XDG_DATA_DIRS defines the preference-ordered set of base
@@ -548,11 +539,7 @@ static void AddSteamDirs(void)
 {
     char *homedir, *steampath;
 
-    homedir = M_getenv("HOME");
-    if (homedir == NULL)
-    {
-        homedir = "/";
-    }
+    homedir = M_HomeDir();
     steampath = M_StringJoin(homedir, "/.steam/root/steamapps/common");
 
     AddIWADPath(steampath, "/Doom 2/base");
@@ -569,27 +556,31 @@ static void AddSteamDirs(void)
 // Build a list of IWAD files
 //
 
+static char **iwad_dirs_append;
+
 void BuildIWADDirList(void)
 {
     char *env;
 
-    if (iwad_dirs_built)
+    if (array_size(iwad_dirs) > 0)
     {
         return;
     }
 
     // Look in the current directory.  Doom always does this.
-    AddIWADDir(".");
+    array_push(iwad_dirs, ".");
 
     // Next check the directory where the executable is located. This might
     // be different from the current directory.
-    AddIWADDir(D_DoomExeDir());
+    // D_DoomPrefDir() returns the executable directory on Windows,
+    // and a user-writable config directory everywhere else.
+    array_push(iwad_dirs, D_DoomPrefDir());
 
     // Add DOOMWADDIR if it is in the environment
     env = M_getenv("DOOMWADDIR");
     if (env != NULL)
     {
-        AddIWADDir(env);
+        array_push(iwad_dirs, env);
     }
 
     // Add dirs from DOOMWADPATH:
@@ -599,13 +590,6 @@ void BuildIWADDirList(void)
         AddIWADPath(env, "");
     }
 
-    // [FG] Add plain HOME directory
-    env = M_getenv("HOME");
-    if (env != NULL)
-    {
-        AddIWADDir(env);
-    }
-
 #ifdef _WIN32
 
     // Search the registry and find where IWADs have been installed.
@@ -622,9 +606,11 @@ void BuildIWADDirList(void)
 #  endif
 #endif
 
-    // Don't run this function again.
-
-    iwad_dirs_built = true;
+    char **dir;
+    array_foreach(dir, iwad_dirs_append)
+    {
+        array_push(iwad_dirs, *dir);
+    }
 }
 
 //
@@ -633,9 +619,7 @@ void BuildIWADDirList(void)
 
 char *D_FindWADByName(const char *name)
 {
-    char *path;
     char *probe;
-    int i;
 
     // Absolute path?
 
@@ -649,14 +633,15 @@ char *D_FindWADByName(const char *name)
 
     // Search through all IWAD paths for a file with the given name.
 
-    for (i = 0; i < num_iwad_dirs; ++i)
+    char **dir;
+    array_foreach(dir, iwad_dirs)
     {
         // As a special case, if this is in DOOMWADDIR or DOOMWADPATH,
         // the "directory" may actually refer directly to an IWAD
         // file.
 
-        probe = M_FileCaseExists(iwad_dirs[i]);
-        if (DirIsFile(iwad_dirs[i], name) && probe != NULL)
+        probe = M_FileCaseExists(*dir);
+        if (probe != NULL)
         {
             return probe;
         }
@@ -664,7 +649,7 @@ char *D_FindWADByName(const char *name)
 
         // Construct a string for the full path
 
-        path = M_StringJoin(iwad_dirs[i], DIR_SEPARATOR_S, name);
+        char *path = M_StringJoin(*dir, DIR_SEPARATOR_S, name);
 
         probe = M_FileCaseExists(path);
         if (probe != NULL)
@@ -769,8 +754,7 @@ char *D_FindIWADFile(void)
 
         char *iwadfile = myargv[iwadparm + 1];
 
-        char *file = malloc(strlen(iwadfile) + 5);
-        AddDefaultExtension(strcpy(file, iwadfile), ".wad");
+        char *file = AddDefaultExtension(iwadfile, ".wad");
 
         result = D_FindWADByName(file);
 
@@ -778,6 +762,11 @@ char *D_FindIWADFile(void)
         {
             I_Error("IWAD file '%s' not found!", file);
         }
+        else
+        {
+            char *iwad_dir = M_DirName(result);
+            array_push(iwad_dirs_append, iwad_dir);
+        }
 
         free(file);
     }
diff --git a/src/d_iwad.h b/src/d_iwad.h
index c527df866..28ecef55b 100644
--- a/src/d_iwad.h
+++ b/src/d_iwad.h
@@ -30,6 +30,7 @@ typedef struct
 } iwad_t;
 
 char *D_DoomExeDir(void); // killough 2/16/98: path to executable's dir
+char *D_DoomPrefDir(void); // [FG] default configuration dir
 char *D_FindWADByName(const char *filename);
 char *D_TryFindWADByName(const char *filename);
 char *D_FindLMPByName(const char *filename);
diff --git a/src/d_main.c b/src/d_main.c
index 9ea4ae576..1e2864629 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -29,6 +29,7 @@
 #include "am_map.h"
 #include "config.h"
 #include "d_deh.h"  // Ty 04/08/98 - Externalizations
+#include "d_demoloop.h"
 #include "d_event.h"
 #include "d_iwad.h"
 #include "d_loop.h"
@@ -80,7 +81,7 @@
 #include "st_stuff.h"
 #include "st_widgets.h"
 #include "statdump.h"
-#include "u_mapinfo.h"
+#include "g_umapinfo.h"
 #include "v_fmt.h"
 #include "v_video.h"
 #include "w_wad.h"
@@ -268,7 +269,7 @@ void D_Display (void)
   static boolean viewactivestate = false;
   static boolean menuactivestate = false;
   static gamestate_t oldgamestate = GS_NONE;
-  static int borderdrawcount;
+  static boolean borderdrawcount;
   int wipestart;
   boolean done, wipe;
 
@@ -320,7 +321,7 @@ void D_Display (void)
     {
       R_ExecuteSetViewSize();
       oldgamestate = GS_NONE;            // force background redraw
-      borderdrawcount = 3;
+      borderdrawcount = true;
     }
 
   if (gamestate == GS_LEVEL && gametic)
@@ -364,11 +365,11 @@ void D_Display (void)
   if (gamestate == GS_LEVEL && automap_off && scaledviewwidth != video.unscaledw)
     {
       if (menuactive || menuactivestate || !viewactivestate)
-        borderdrawcount = 3;
+        borderdrawcount = true;
       if (borderdrawcount)
         {
           R_DrawViewBorder();    // erase old menu stuff
-          borderdrawcount--;
+          borderdrawcount = false;
         }
     }
 
@@ -446,7 +447,8 @@ void D_Display (void)
 
 static int demosequence;         // killough 5/2/98: made static
 static int pagetic;
-static char *pagename;
+static const char *pagename;
+static demoloop_t demoloop_point;
 
 static int no_page_ticking; // [Nugget]
 
@@ -484,15 +486,17 @@ void D_PageDrawer(void)
 // Called after each demo or intro demosequence finishes
 //
 
-void D_AdvanceDemo (void)
+void D_AdvanceDemo(void)
 {
   advancedemo = true;
 }
 
-//
 // This cycles through the demo sequences.
-// FIXME - version dependend demo numbers?
-//
+void D_AdvanceDemoLoop(void)
+{
+  demosequence = (demosequence + 1) % demoloop_count;
+  demoloop_point = &demoloop[demosequence];
+}
 
 void D_DoAdvanceDemo(void)
 {
@@ -502,94 +506,48 @@ void D_DoAdvanceDemo(void)
     paused = false;
     gameaction = ga_nothing;
 
-    // The Ultimate Doom executable changed the demo sequence to add
-    // a DEMO4 demo.  Final Doom was based on Ultimate, so also
-    // includes this change; however, the Final Doom IWADs do not
-    // include a DEMO4 lump, so the game bombs out with an error
-    // when it reaches this point in the demo sequence.
-
-    // However! There is an alternate version of Final Doom that
-    // includes a fixed executable.
-
-    if (W_CheckNumForName("DEMO4") >= 0)
+    D_AdvanceDemoLoop();
+    switch (demoloop_point->type)
     {
-        demosequence = (demosequence + 1) % 7;
-    }
-    else
-    {
-        demosequence = (demosequence + 1) % 6;
-    }
-
-    switch (demosequence)
-    {
-        case 0:
-            if (gamemode == commercial)
-            {
-                pagetic = TICRATE * 11;
-            }
-            else
-            {
-                pagetic = 170;
-            }
+        case TYPE_ART:
             gamestate = GS_DEMOSCREEN;
-            pagename = "TITLEPIC";
-            if (gamemode == commercial)
+
+            // Needed to support the Doom 3: BFG Edition variant
+            if (W_CheckNumForName(demoloop_point->primary_lump) < 0
+                && !strcasecmp(demoloop_point->primary_lump, "TITLEPIC"))
             {
-                S_StartMusic(mus_dm2ttl);
+                M_CopyLumpName(demoloop_point->primary_lump, "DMENUPIC");
             }
-            else
+
+            if (W_CheckNumForName(demoloop_point->primary_lump) >= 0)
             {
-                S_StartMusic(mus_intro);
+                pagename = demoloop_point->primary_lump;
+                pagetic = demoloop_point->duration;
+                int music = W_CheckNumForName(demoloop_point->secondary_lump);
+                if (music >= 0)
+                {
+                    S_ChangeMusInfoMusic(music, false);
+                }
+                break;
             }
-            break;
-        case 1:
-            G_DeferedPlayDemo("DEMO1");
-            break;
-        case 2:
-            pagetic = 200;
-            gamestate = GS_DEMOSCREEN;
-            pagename = "CREDIT";
-            break;
-        case 3:
-            G_DeferedPlayDemo("DEMO2");
-            break;
-        case 4:
+            // fallthrough
+
+        case TYPE_DEMO:
             gamestate = GS_DEMOSCREEN;
-            if (gamemode == commercial)
+
+            if (W_CheckNumForName(demoloop_point->primary_lump) >= 0)
             {
-                pagetic = TICRATE * 11;
-                pagename = "TITLEPIC";
-                S_StartMusic(mus_dm2ttl);
+                G_DeferedPlayDemo(demoloop_point->primary_lump);
+                break;
             }
-            else
-            {
-                pagetic = 200;
+            // fallthrough
 
-                if (gameversion >= exe_ultimate)
-                {
-                    pagename = "CREDIT";
-                }
-                else
-                {
-                    pagename = "HELP2";
-                }
-            }
-            break;
-        case 5:
-            G_DeferedPlayDemo("DEMO3");
-            break;
-        // THE DEFINITIVE DOOM Special Edition demo
-        case 6:
-            G_DeferedPlayDemo("DEMO4");
+        default:
+            I_Printf(VB_WARNING,
+                     "D_DoAdvanceDemo: Invalid demoloop[%d] entry, skipping",
+                     demosequence);
             break;
     }
-
-    // The Doom 3: BFG Edition version of doom2.wad does not have a
-    // TITLETPIC lump.
-    if (!strcasecmp(pagename, "TITLEPIC") && W_CheckNumForName("TITLEPIC") < 0)
-    {
-        pagename = "DMENUPIC";
-    }
 }
 
 //
@@ -622,54 +580,9 @@ void D_AddFile(const char *file)
 }
 
 // killough 10/98: return the name of the program the exe was invoked as
-char *D_DoomExeName(void)
+const char *D_DoomExeName(void)
 {
-  static char *name;
-
-  if (!name) // cache multiple requests
-  {
-    char *ext;
-
-    name = M_StringDuplicate(M_BaseName(myargv[0]));
-
-    ext = strrchr(name, '.');
-    if (ext)
-      *ext = '\0';
-  }
-
-  return name;
-}
-
-// [FG] get the path to the default configuration dir to use
-
-char *D_DoomPrefDir(void)
-{
-    static char *dir;
-
-    if (dir == NULL)
-    {
-#if !defined(_WIN32) || defined(_WIN32_WCE)
-        // Configuration settings are stored in an OS-appropriate path
-        // determined by SDL.  On typical Unix systems, this might be
-        // ~/.local/share/chocolate-doom.  On Windows, we behave like
-        // Vanilla Doom and save in the current directory.
-
-        char *result = SDL_GetPrefPath("", PROJECT_SHORTNAME);
-        if (result != NULL)
-        {
-            dir = M_DirName(result);
-            SDL_free(result);
-        }
-        else
-#endif /* #ifndef _WIN32 */
-        {
-            dir = D_DoomExeDir();
-        }
-
-        M_MakeDirectory(dir);
-    }
-
-    return dir;
+  return PROJECT_SHORTNAME;
 }
 
 // Calculate the path to the directory for autoloaded WADs/DEHs.
@@ -842,9 +755,9 @@ static boolean CheckMapLump(const char *lumpname, const char *filename)
 
 static boolean FileContainsMaps(const char *filename)
 {
-    for (int i = 0; i < U_mapinfo.mapcount; ++i)
+    for (int i = 0; i < array_size(umapinfo); ++i)
     {
-        if (CheckMapLump(U_mapinfo.maps[i].mapname, filename))
+        if (CheckMapLump(umapinfo[i].mapname, filename))
         {
             return true;
         }
@@ -907,57 +820,6 @@ void IdentifyVersion(void)
 
     basedefault = M_StringJoin(D_DoomPrefDir(), DIR_SEPARATOR_S,
                                D_DoomExeName(), ".cfg");
-    // set save path to -save parm or current dir
-
-    screenshotdir = M_StringDuplicate("."); // [FG] default to current dir
-
-    basesavegame = M_StringDuplicate(
-        D_DoomPrefDir()); // jff 3/27/98 default to current dir
-
-    //!
-    // @arg <directory>
-    //
-    // Specify a path from which to load and save games. If the directory
-    // does not exist then it will automatically be created.
-    //
-
-    int p = M_CheckParmWithArgs("-save", 1);
-    if (p > 0)
-    {
-        if (basesavegame)
-        {
-            free(basesavegame);
-        }
-        basesavegame = M_StringDuplicate(myargv[p + 1]);
-
-        M_MakeDirectory(basesavegame);
-
-        // [FG] fall back to -save parm
-        if (screenshotdir)
-        {
-            free(screenshotdir);
-        }
-        screenshotdir = M_StringDuplicate(basesavegame);
-    }
-
-    //!
-    // @arg <directory>
-    //
-    // Specify a path to save screenshots. If the directory does not
-    // exist then it will automatically be created.
-    //
-
-    p = M_CheckParmWithArgs("-shotdir", 1);
-    if (p > 0)
-    {
-        if (screenshotdir)
-        {
-            free(screenshotdir);
-        }
-        screenshotdir = M_StringDuplicate(myargv[p + 1]);
-
-        M_MakeDirectory(screenshotdir);
-    }
 
     // locate the IWAD and determine game mode from it
 
@@ -965,7 +827,11 @@ void IdentifyVersion(void)
 
     if (!iwadfile)
     {
-        I_Error("IWAD not found");
+        I_Error("IWAD not found!\n"
+                "\n"
+                "Place an IWAD file (e.g. doom.wad, doom2.wad)\n"
+                "in one of the IWAD search directories, e.g.\n"
+                "%s", D_DoomPrefDir());
     }
 
     D_AddFile(iwadfile);
@@ -1026,7 +892,7 @@ static void InitGameVersion(void)
         // Determine automatically
 
         if (gamemode == shareware || gamemode == registered ||
-            (gamemode == commercial && gamemission == doom2))
+            (gamemode == commercial && gamemission != pack_tnt && gamemission != pack_plut))
         {
             // original
             gameversion = exe_doom_1_9;
@@ -1083,8 +949,7 @@ void FindResponseFile (void)
         // READ THE RESPONSE FILE INTO MEMORY
 
         // killough 10/98: add default .rsp extension
-        char *filename = malloc(strlen(myargv[i])+5);
-        AddDefaultExtension(strcpy(filename,&myargv[i][1]),".rsp");
+        char *filename = AddDefaultExtension(&myargv[i][1],".rsp");
 
         handle = M_fopen(filename,"rb");
         if (!handle)
@@ -1446,14 +1311,15 @@ static void D_ProcessDehCommandLine(void)
           if (deh)
             {
               char *probe;
-              char *file = malloc(strlen(myargv[p]) + 5);      // killough
-              AddDefaultExtension(strcpy(file, myargv[p]), ".bex");
+              char *file = AddDefaultExtension(myargv[p], ".bex");
               probe = D_TryFindWADByName(file);
+              free(file);
               if (M_access(probe, F_OK))  // nope
                 {
                   free(probe);
-                  AddDefaultExtension(strcpy(file, myargv[p]), ".deh");
+                  file = AddDefaultExtension(myargv[p], ".deh");
                   probe = D_TryFindWADByName(file);
+                  free(file);
                   if (M_access(probe, F_OK))  // still nope
                   {
                     free(probe);
@@ -1465,7 +1331,6 @@ static void D_ProcessDehCommandLine(void)
               // (apparently, this was never removed after Boom beta-killough)
               ProcessDehFile(probe, D_dehout(), 0);  // killough 10/98
               free(probe);
-              free(file);
             }
     }
   // ty 03/09/98 end of do dehacked stuff
@@ -1504,6 +1369,15 @@ static void LoadIWadBase(void)
     {
         W_AddBaseDir("doom-all");
     }
+    if (local_gamemission == doom)
+    {
+        W_AddBaseDir("doom1-all");
+    }
+    else if (local_gamemission >= doom2
+             && local_gamemission <= pack_plut)
+    {
+        W_AddBaseDir("doom2-all");
+    }
     W_AddBaseDir(M_BaseName(wadfiles[0]));
 }
 
@@ -1705,15 +1579,17 @@ void D_SetBloodColor(void)
 typedef enum {
   EXIT_SEQUENCE_OFF,          // Skip sound, skip ENDOOM.
   EXIT_SEQUENCE_SOUND_ONLY,   // Play sound, skip ENDOOM.
-  EXIT_SEQUENCE_PWAD_ENDOOM,  // Play sound, show ENDOOM for PWADs only.
+  EXIT_SEQUENCE_ENDOOM_ONLY,  // Skip sound, show ENDOOM.
   EXIT_SEQUENCE_FULL          // Play sound, show ENDOOM.
 } exit_sequence_t;
 
 static exit_sequence_t exit_sequence;
+static boolean endoom_pwad_only;
 
 boolean D_AllowQuitSound(void)
 {
-  return (exit_sequence != EXIT_SEQUENCE_OFF);
+  return (exit_sequence == EXIT_SEQUENCE_FULL
+          || exit_sequence == EXIT_SEQUENCE_SOUND_ONLY);
 }
 
 static void D_ShowEndDoom(void)
@@ -1728,17 +1604,29 @@ boolean disable_endoom = false;
 
 static boolean AllowEndDoom(void)
 {
-  return  !disable_endoom && (exit_sequence == EXIT_SEQUENCE_FULL
-          || (exit_sequence == EXIT_SEQUENCE_PWAD_ENDOOM
-              && !W_IsIWADLump(W_CheckNumForName("ENDOOM"))));
+  return (!disable_endoom
+          && (exit_sequence == EXIT_SEQUENCE_FULL
+          || exit_sequence == EXIT_SEQUENCE_ENDOOM_ONLY));
 }
 
 static void D_EndDoom(void)
 {
-  if (AllowEndDoom())
+  // Do we even want to show an ENDOOM?
+  if (!AllowEndDoom())
   {
-    D_ShowEndDoom();
+    return;
   }
+
+  // If so, is it from the IWAD?
+  bool iwad_endoom = W_IsIWADLump(W_CheckNumForName("ENDOOM"));
+
+  // Does the user want to see it, in that case?
+  if (iwad_endoom && endoom_pwad_only)
+  {
+    return;
+  }
+
+  D_ShowEndDoom();
 }
 
 // [FG] fast-forward demo to the desired map
@@ -1777,8 +1665,127 @@ static boolean CheckHaveSSG (void)
   return true;
 }
 
+static int mainwadfile;
+
+#define SET_DIR(a, b) \
+    if ((a)) \
+    { \
+        free((a)); \
+    } \
+    (a) = (b);
+
+void D_SetSavegameDirectory(void)
+{
+    // set save path to -save parm or current dir
+
+    SET_DIR(screenshotdir, M_StringDuplicate("."));
+
+    SET_DIR(basesavegame, M_StringDuplicate(D_DoomPrefDir()));
+
+    //!
+    // @arg <directory>
+    //
+    // Specify a path from which to load and save games. If the directory
+    // does not exist then it will automatically be created.
+    //
+
+    int p = M_CheckParmWithArgs("-save", 1);
+    if (p > 0)
+    {
+        SET_DIR(basesavegame, M_StringDuplicate(myargv[p + 1]));
+
+        M_MakeDirectory(basesavegame);
+
+        // [FG] fall back to -save parm
+        SET_DIR(screenshotdir, M_StringDuplicate(basesavegame));
+    }
+    else
+    {
+        // [Nugget] Set savegame path as determined by config file
+        if (savegame_dir && strcmp(savegame_dir, ""))
+        {
+            SET_DIR(basesavegame, M_StringDuplicate(savegame_dir));
+
+            M_MakeDirectory(basesavegame);
+        }
+
+        if (organize_savefiles == -1)
+        {
+            // [FG] check for at least one savegame in the old location
+            glob_t *glob = I_StartMultiGlob(
+            basesavegame, GLOB_FLAG_NOCASE | GLOB_FLAG_SORTED, "*.dsg");
+
+            organize_savefiles = (I_NextGlob(glob) == NULL);
+
+            I_EndGlob(glob);
+        }
+
+        if (organize_savefiles)
+        {
+            const char *wadname = wadfiles[0];
+
+            for (int i = mainwadfile; i < array_size(wadfiles); i++)
+            {
+                if (FileContainsMaps(wadfiles[i]))
+                {
+                    wadname = wadfiles[i];
+                    break;
+                }
+            }
+
+            char *oldsavegame = basesavegame;
+
+            // [Nugget] Don't default to a "savegames" directory if path is set by config file
+            if (!savegame_dir || !strcmp(savegame_dir, ""))
+            {
+                basesavegame =
+                    M_StringJoin(oldsavegame, DIR_SEPARATOR_S, "savegames");
+                free(oldsavegame);
+            }
+
+            M_MakeDirectory(basesavegame);
+
+            oldsavegame = basesavegame;
+            basesavegame = M_StringJoin(oldsavegame, DIR_SEPARATOR_S,
+                M_BaseName(wadname));
+            free(oldsavegame);
+        }
+    }
+
+    //!
+    // @arg <directory>
+    //
+    // Specify a path to save screenshots. If the directory does not
+    // exist then it will automatically be created.
+    //
+
+    p = M_CheckParmWithArgs("-shotdir", 1);
+    if (p > 0)
+    {
+        SET_DIR(screenshotdir, M_StringDuplicate(myargv[p + 1]));
+
+        M_MakeDirectory(screenshotdir);
+    }
+    // [Nugget] Set screenshot path as determined by config file
+    else if (screenshot_dir && strcmp(screenshot_dir, ""))
+    {
+        SET_DIR(screenshotdir, M_StringDuplicate(screenshot_dir));
+
+        M_MakeDirectory(screenshotdir);
+    }
+
+    I_Printf(VB_INFO, "Savegame directory: %s", basesavegame);
+
+    // [Nugget] Print screenshot directory too
+    I_Printf(VB_INFO, "Screenshot directory: %s\n", screenshotdir);
+}
+
+#undef SET_DIR
+
 // [Nugget] /-----------------------------------------------------------------
 
+boolean fail_safe;
+
 void D_ValidateStartSkill(void)
 {
   if (startskill == sk_custom
@@ -1809,8 +1816,6 @@ void D_UpdateCasualPlay(void)
 
 // [Nugget] -----------------------------------------------------------------/
 
-boolean fail_safe;
-
 //
 // D_DoomMain
 //
@@ -1818,7 +1823,6 @@ boolean fail_safe;
 void D_DoomMain(void)
 {
   int p;
-  int mainwadfile = 0;
 
   setbuf(stdout,NULL);
 
@@ -1885,6 +1889,21 @@ void D_DoomMain(void)
 
   IdentifyVersion();
 
+  //!
+  // @category mod
+  //
+  // Disable auto-loading of extars.wad file.
+  //
+
+  if (gamemission < pack_chex && !M_ParmExists("-noextras"))
+  {
+      char *path = D_FindWADByName("extras.wad");
+      if (path)
+      {
+          D_AddFile(path);
+      }
+  }
+
   // [FG] emulate a specific version of Doom
   InitGameVersion();
 
@@ -2390,8 +2409,6 @@ void D_DoomMain(void)
             I_Error("\nThis is not the registered version.");
     }
 
-  W_ProcessInWads("UMAPDEF", U_ParseMapDefInfo, PROCESS_PWAD);
-
   //!
   // @category mod
   //
@@ -2400,82 +2417,12 @@ void D_DoomMain(void)
 
   if (!M_ParmExists("-nomapinfo"))
   {
-    W_ProcessInWads("UMAPINFO", U_ParseMapInfo, PROCESS_IWAD | PROCESS_PWAD);
+    W_ProcessInWads("UMAPINFO", G_ParseMapInfo, PROCESS_IWAD | PROCESS_PWAD);
   }
 
   G_ParseCompDatabase();
 
-  if (!M_CheckParm("-save"))
-  {
-      // [Nugget] Set savegame path as determined by config file
-      if (savegame_dir && strcmp(savegame_dir, ""))
-      {
-          if (basesavegame)
-              free(basesavegame);
-          basesavegame = M_StringDuplicate(savegame_dir);
-
-          M_MakeDirectory(basesavegame);
-      }
-
-      if (organize_savefiles == -1)
-      {
-          // [FG] check for at least one savegame in the old location
-          glob_t *glob = I_StartMultiGlob(
-              basesavegame, GLOB_FLAG_NOCASE | GLOB_FLAG_SORTED, "*.dsg");
-
-          organize_savefiles = (I_NextGlob(glob) == NULL);
-
-          I_EndGlob(glob);
-      }
-
-      if (organize_savefiles)
-      {
-          int i;
-          const char *wadname = wadfiles[0];
-          char *oldsavegame = basesavegame;
-
-          for (i = mainwadfile; i < array_size(wadfiles); i++)
-          {
-              if (FileContainsMaps(wadfiles[i]))
-              {
-                  wadname = wadfiles[i];
-                  break;
-              }
-          }
-
-          // [Nugget] Don't default to a "savegames" directory if path is set by config file
-          if (!savegame_dir || !strcmp(savegame_dir, ""))
-          {
-            basesavegame =
-                M_StringJoin(oldsavegame, DIR_SEPARATOR_S, "savegames");
-            free(oldsavegame);
-          }
-
-          M_MakeDirectory(basesavegame);
-
-          oldsavegame = basesavegame;
-          basesavegame = M_StringJoin(oldsavegame, DIR_SEPARATOR_S,
-                                      M_BaseName(wadname));
-          free(oldsavegame);
-
-          M_MakeDirectory(basesavegame);
-      }
-  }
-
-  I_Printf(VB_INFO, "Savegame directory: %s", basesavegame);
-
-  // [Nugget] Set screenshot path as determined by config file
-  if (!M_CheckParm("-shotdir") && screenshot_dir && strcmp(screenshot_dir, ""))
-  {
-    if (screenshotdir)
-      free(screenshotdir);
-    screenshotdir = M_StringDuplicate(screenshot_dir);
-
-    M_MakeDirectory(screenshotdir);
-  }
-
-  // [Nugget] Print screenshot directory too
-  I_Printf(VB_INFO, "Screenshot directory: %s\n", screenshotdir);
+  D_SetSavegameDirectory();
 
   V_InitColorTranslation(); //jff 4/24/98 load color translation lumps
 
@@ -2510,6 +2457,7 @@ void D_DoomMain(void)
   }
 
   W_ProcessInWads("TRAKINFO", S_ParseTrakInfo, PROCESS_IWAD | PROCESS_PWAD);
+  D_SetupDemoLoop();
 
   I_Printf(VB_INFO, "M_Init: Init miscellaneous info.");
   M_Init();
@@ -2812,7 +2760,8 @@ void D_DoomMain(void)
 void D_BindMiscVariables(void)
 {
   BIND_NUM_GENERAL(exit_sequence, 0, 0, EXIT_SEQUENCE_FULL,
-    "Exit sequence (0 = Off; 1 = Sound Only; 2 = PWAD ENDOOM; 3 = Full)");
+    "Exit sequence (0 = Off; 1 = Sound Only; 2 = ENDOOM Only; 3 = Full)");
+  BIND_BOOL_GENERAL(endoom_pwad_only, false, "Show only ENDOOM from PWAD");
   BIND_BOOL_GENERAL(demobar, false, "Show demo progress bar");
 
   // [Nugget] More wipes
diff --git a/src/d_main.h b/src/d_main.h
index 0974f7642..067737fbd 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -32,10 +32,10 @@ extern boolean fail_safe;
 
 void D_AddFile(const char *file);
 
-char *D_DoomExeName(void);      // killough 10/98: executable's name
+const char *D_DoomExeName(void); // killough 10/98: executable's name
 extern char *basesavegame;     // killough 2/16/98: savegame path
 extern char *screenshotdir; // [FG] screenshot path
-char *D_DoomPrefDir(void); // [FG] default configuration dir
+void D_SetSavegameDirectory(void);
 
 extern const char *gamedescription;
 
diff --git a/src/d_think.h b/src/d_think.h
index f10e34f16..d5bf40948 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -22,9 +22,11 @@
 #ifndef __D_THINK__
 #define __D_THINK__
 
-typedef void (*actionf_v)();
-typedef void (*actionf_p1)(void *);
-typedef void (*actionf_p2)(void *, void *);
+#include "p_action.h"
+
+typedef void (*actionf_v)(void);
+typedef void (*actionf_p1)(struct mobj_s *);
+typedef void (*actionf_p2)(struct player_s *, struct pspdef_s *);
 
 typedef union actionf_u
 {
diff --git a/src/doomdef.h b/src/doomdef.h
index aea50b647..cb9d504d7 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -40,6 +40,7 @@ typedef enum {
   pack_chex,    // Chex Quest
   pack_hacx,    // Hacx
   pack_rekkr,   // Rekkr
+  pack_chex3v,  // Chex Quest 3: Vanilla Edition
   none
 } GameMission_t;
 
diff --git a/src/doomtype.h b/src/doomtype.h
index 68c8c1daa..eebd266f6 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -23,6 +23,7 @@
 
 #include <stddef.h> // size_t, NULL
 #include <stdint.h> // [FG] intptr_t types
+#include <stdbool.h>
 
 #include "config.h"
 
@@ -73,11 +74,25 @@ typedef byte lighttable_t;
 
 #define arrlen(array) (sizeof(array) / sizeof(*array))
 
-#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-
-#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#if defined(__GNUC__) || defined(__clang__)
+ #define MIN(a,b)             \
+ ({                           \
+     __typeof__ (a) _a = (a); \
+     __typeof__ (b) _b = (b); \
+     _a < _b ? _a : _b;       \
+ })
+ #define MAX(a,b)             \
+ ({                           \
+     __typeof__ (a) _a = (a); \
+     __typeof__ (b) _b = (b); \
+     _a > _b ? _a : _b;       \
+ })
+#else
+ #define MIN(a, b) (((a) < (b)) ? (a) : (b))
+ #define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
 
-#define BETWEEN(l, u, x) ((l) > (x) ? (l) : (x) > (u) ? (u) : (x))
+#define BETWEEN(l, u, x) (MAX((l), (MIN((u), (x)))))
 
 #define DIV_ROUND_FLOOR(n, d) (((n) - (d) / 2) / (d))
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 82eb2a244..602774aed 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -27,6 +27,7 @@
 #include "doomstat.h"
 #include "doomtype.h"
 #include "g_game.h"
+#include "g_umapinfo.h"
 #include "info.h"
 #include "m_misc.h" // [FG] M_StringDuplicate()
 #include "m_swap.h"
@@ -36,7 +37,6 @@
 #include "sounds.h"
 #include "st_sbardef.h"
 #include "st_stuff.h"
-#include "u_mapinfo.h"
 #include "v_fmt.h"
 #include "v_video.h"
 #include "w_wad.h"
@@ -51,8 +51,17 @@
 
 // Stage of animation:
 //  0 = text, 1 = art screen, 2 = character cast
-int finalestage;
-int finalecount;
+
+typedef enum
+{
+    FINALE_STAGE_TEXT,
+    FINALE_STAGE_ART,
+    FINALE_STAGE_CAST
+} finalestage_t;
+
+static finalestage_t finalestage;
+
+static int finalecount;
 
 // defines for the end mission display text                     // phares
 
@@ -64,21 +73,196 @@ int finalecount;
 static const char *finaletext;
 static const char *finaleflat;
 
-void    F_StartCast (void);
-void    F_CastTicker (void);
-boolean F_CastResponder (event_t *ev);
-void    F_CastDrawer (void);
+static void F_StartCast(void);
+static void F_CastTicker(void);
+static boolean F_CastResponder(event_t *ev);
+static void F_CastDrawer(void);
+static void F_TextWrite(void);
+static void F_BunnyScroll(void);
+static float Get_TextSpeed(void);
 
 static int midstage;                 // whether we're in "mid-stage"
 
-boolean using_FMI;
+static boolean mapinfo_finale;
+
+static boolean MapInfo_StartFinale(void)
+{
+    mapinfo_finale = false;
+
+    if (!gamemapinfo)
+    {
+        return false;
+    }
+
+    if (secretexit)
+    {
+        if (gamemapinfo->flags & MapInfo_InterTextSecretClear)
+        {
+            finaletext = NULL;
+        }
+        else if (gamemapinfo->intertextsecret)
+        {
+            finaletext = gamemapinfo->intertextsecret;
+        }
+    }
+    else
+    {
+        if (gamemapinfo->flags & MapInfo_InterTextClear)
+        {
+            finaletext = NULL;
+        }
+        else if (gamemapinfo->intertext)
+        {
+            finaletext = gamemapinfo->intertext;
+        }
+    }
+
+    if (gamemapinfo->interbackdrop[0])
+    {
+        finaleflat = gamemapinfo->interbackdrop;
+    }
+
+    if (!finaleflat)
+    {
+        finaleflat = "FLOOR4_8"; // use a single fallback for all maps.
+    }
+
+    int lumpnum = W_CheckNumForName(gamemapinfo->intermusic);
+    if (lumpnum >= 0)
+    {
+        S_ChangeMusInfoMusic(lumpnum, true);
+    }
+
+    mapinfo_finale = true;
+
+    return lumpnum >= 0;
+}
+
+static boolean MapInfo_Ticker()
+{
+    if (!mapinfo_finale)
+    {
+        return false;
+    }
+
+    boolean next_level = false;
+
+    if (!demo_compatibility)
+    {
+        WI_checkForAccelerate();
+    }
+    else
+    {
+        for (int i = 0; i < MAXPLAYERS; ++i)
+        {
+            if (players[i].cmd.buttons)
+            {
+                next_level = true;
+            }
+        }
+    }
+
+    if (!next_level)
+    {
+        // advance animation
+        finalecount++;
+
+        if (finalestage == FINALE_STAGE_CAST)
+        {
+            F_CastTicker();
+            return true;
+        }
+
+        if (finalestage == FINALE_STAGE_TEXT)
+        {
+            int textcount = 0;
+            if (finaletext)
+            {
+                float speed = demo_compatibility ? TEXTSPEED : Get_TextSpeed();
+                textcount = strlen(finaletext) * speed
+                            + (midstage ? NEWTEXTWAIT : TEXTWAIT);
+            }
+
+            if (!textcount || finalecount > textcount
+                || (midstage && acceleratestage))
+            {
+                next_level = true;
+            }
+        }
+    }
+
+    if (next_level)
+    {
+        if (!secretexit && gamemapinfo->flags & MapInfo_EndGame)
+        {
+            if (gamemapinfo->flags & MapInfo_EndGameCast)
+            {
+                F_StartCast();
+            }
+            else
+            {
+                finalecount = 0;
+                finalestage = FINALE_STAGE_ART;
+                wipegamestate = -1; // force a wipe
+                if (gamemapinfo->flags & MapInfo_EndGameBunny)
+                {
+                    S_StartMusic(mus_bunny);
+                }
+                else if (gamemapinfo->flags & MapInfo_EndGameStandard)
+                {
+                    mapinfo_finale = false;
+                }
+            }
+        }
+        else
+        {
+            gameaction = ga_worlddone; // next level, e.g. MAP07
+        }
+    }
+
+    return true;
+}
+
+static boolean MapInfo_Drawer(void)
+{
+    if (!mapinfo_finale)
+    {
+        return false;
+    }
+
+    switch (finalestage)
+    {
+        case FINALE_STAGE_TEXT:
+            if (finaletext)
+            {
+                F_TextWrite();
+            }
+            break;
+        case FINALE_STAGE_ART:
+            if (gamemapinfo->flags & MapInfo_EndGameBunny)
+            {
+                F_BunnyScroll();
+            }
+            else if (gamemapinfo->endpic[0])
+            {
+                V_DrawPatchFullScreen(
+                    V_CachePatchName(gamemapinfo->endpic, PU_CACHE));
+            }
+            break;
+        case FINALE_STAGE_CAST:
+            F_CastDrawer();
+            break;
+    }
+
+    return true;
+}
 
 //
 // F_StartFinale
 //
 void F_StartFinale (void)
 {
-  boolean mus_changed = false;
+  musicenum_t music_id = mus_None;
 
   gameaction = ga_nothing;
   gamestate = GS_FINALE;
@@ -91,16 +275,6 @@ void F_StartFinale (void)
   finaletext = NULL;
   finaleflat = NULL;
 
-  if (gamemapinfo && gamemapinfo->intermusic[0])
-  {
-    int l = W_CheckNumForName(gamemapinfo->intermusic);
-    if (l >= 0)
-    {
-      S_ChangeMusInfoMusic(l, true);
-      mus_changed = true;
-    }
-  }
-
   // Okay - IWAD dependend stuff.
   // This has been changed severly, and
   //  some stuff might have changed in the process.
@@ -111,7 +285,7 @@ void F_StartFinale (void)
     case registered:
     case retail:
     {
-      if (!mus_changed) S_ChangeMusic(mus_victor, true);
+      music_id = mus_victor;
       
       switch (gameepisode)
       {
@@ -141,7 +315,7 @@ void F_StartFinale (void)
     // DOOM II and missions packs with E1, M34
     case commercial:
     {
-      if (!mus_changed) S_ChangeMusic(mus_read_m, true);
+      music_id = mus_read_m;
 
       // Ty 08/27/98 - added the gamemission logic
 
@@ -188,49 +362,25 @@ void F_StartFinale (void)
 
     // Indeterminate.
     default:  // Ty 03/30/98 - not externalized
-         if (!mus_changed) S_ChangeMusic(mus_read_m, true);
+         music_id = mus_read_m;
          finaleflat = "F_SKY1"; // Not used anywhere else.
          finaletext = s_C1TEXT;  // FIXME - other text, music?
          break;
   }
-
-  using_FMI = false;
   
-  if (gamemapinfo)
+  if (!MapInfo_StartFinale())
   {
-    if (U_CheckField(gamemapinfo->intertextsecret) && secretexit)
-    {
-      finaletext = gamemapinfo->intertextsecret;
-    }
-    else if (U_CheckField(gamemapinfo->intertext) && !secretexit)
-    {
-      finaletext = gamemapinfo->intertext;
-    }
-
-    if (!finaletext)
-      finaletext = "The End"; // this is to avoid a crash on a missing text in the last map.
-
-    if (gamemapinfo->interbackdrop[0])
-    {
-      finaleflat = gamemapinfo->interbackdrop;
-    }
-
-    if (!finaleflat)
-      finaleflat = "FLOOR4_8"; // use a single fallback for all maps.
-
-    using_FMI = true;
+      S_ChangeMusic(music_id, true);
   }
 
-  finalestage = 0;
+  finalestage = FINALE_STAGE_TEXT;
   finalecount = 0;
 }
 
-
-
 boolean F_Responder (event_t *event)
 {
-  if (finalestage == 2)
-    return F_CastResponder (event);
+  if (finalestage == FINALE_STAGE_CAST)
+    return F_CastResponder(event);
         
   return false;
 }
@@ -244,7 +394,6 @@ static float Get_TextSpeed(void)
     acceleratestage=0, NEWTEXTSPEED : TEXTSPEED;
 }
 
-
 //
 // F_Ticker
 //
@@ -258,41 +407,18 @@ static float Get_TextSpeed(void)
 // killough 5/10/98: add back v1.9 demo compatibility
 //
 
-static void FMI_Ticker()
+void F_Ticker(void)
 {
-  if (U_CheckField(gamemapinfo->endpic))
+  if (MapInfo_Ticker())
   {
-    if (!strcasecmp(gamemapinfo->endpic, "$CAST"))
-    {
-      F_StartCast();
-      using_FMI = false;
-    }
-    else
-    {
-      finalecount = 0;
-      finalestage = 1;
-      wipegamestate = -1;         // force a wipe
-      if (!strcasecmp(gamemapinfo->endpic, "$BUNNY"))
-      {
-        S_StartMusic(mus_bunny);
-      }
-      else if (!strcasecmp(gamemapinfo->endpic, "!"))
-      {
-        using_FMI = false;
-      }
-    }
+      return;
   }
-  else
-    gameaction = ga_worlddone;  // next level, e.g. MAP07
-}
 
-void F_Ticker(void)
-{
   int i;
   if (!demo_compatibility)
     WI_checkForAccelerate();  // killough 3/28/98: check for acceleration
   else
-    if (gamemode == commercial && (using_FMI || finalecount > 50)) // check for skipping
+    if (gamemode == commercial && finalecount > 50) // check for skipping
       for (i=0; i<MAXPLAYERS; i++)
         if (players[i].cmd.buttons)
           goto next_level;      // go on to the next level
@@ -300,24 +426,20 @@ void F_Ticker(void)
   // advance animation
   finalecount++;
  
-  if (finalestage == 2)
+  if (finalestage == FINALE_STAGE_CAST)
     F_CastTicker();
 
-  if (!finalestage)
+  if (finalestage == FINALE_STAGE_TEXT)
     {
       float speed = demo_compatibility ? TEXTSPEED : Get_TextSpeed();
       if (finalecount > strlen(finaletext)*speed +  // phares
           (midstage ? NEWTEXTWAIT : TEXTWAIT) ||  // killough 2/28/98:
           (midstage && acceleratestage))       // changed to allow acceleration
       {
-        if (using_FMI)
-          {
-            FMI_Ticker();
-          }
-        else if (gamemode != commercial)       // Doom 1 / Ultimate Doom episode end
+        if (gamemode != commercial)       // Doom 1 / Ultimate Doom episode end
           {                               // with enough time, it's automatic
             finalecount = 0;
-            finalestage = 1;
+            finalestage = FINALE_STAGE_ART;
             wipegamestate = -1;         // force a wipe
             if (gameepisode == 3)
               S_StartMusic(mus_bunny);
@@ -326,11 +448,7 @@ void F_Ticker(void)
           if (!demo_compatibility && midstage)
             {
             next_level:
-              if (using_FMI)
-                {
-                  FMI_Ticker();
-                }
-              else if (gamemap == 30)
+              if (gamemap == 30)
                 F_StartCast();              // cast of Doom 2 characters
               else
                 gameaction = ga_worlddone;  // next level, e.g. MAP07
@@ -350,7 +468,7 @@ void F_Ticker(void)
 // text can be increased, and there's still time to read what's     //   |
 // written.                                                         // phares
 
-void F_TextWrite (void)
+static void F_TextWrite(void)
 {
   int         w;         // killough 8/9/98: move variables below
   int         count;
@@ -470,14 +588,6 @@ static int F_RandomizeSound (int sound)
   }
 }
 
-extern void A_BruisAttack(); extern void A_BspiAttack();  extern void A_CPosAttack();
-extern void A_CPosRefire();  extern void A_CyberAttack(); extern void A_FatAttack1();
-extern void A_FatAttack2();  extern void A_FatAttack3();  extern void A_HeadAttack();
-extern void A_PainAttack();  extern void A_PosAttack();   extern void A_SargAttack();
-extern void A_SkelFist();    extern void A_SkelMissile(); extern void A_SkelWhoosh();
-extern void A_SkullAttack(); extern void A_SPosAttack();  extern void A_TroopAttack();
-extern void A_VileTarget();  extern void A_RandomJump();
-
 extern boolean flipcorpses;
 
 typedef struct {
@@ -541,7 +651,7 @@ static int F_SoundForState (int st)
 //
 // F_StartCast
 //
-void F_StartCast (void)
+static void F_StartCast(void)
 {
   // Ty 03/23/98 - clumsy but time is of the essence
   castorder[0].name = s_CC_ZOMBIE,  castorder[0].type = MT_POSSESSED;
@@ -568,7 +678,7 @@ void F_StartCast (void)
   caststate = &states[mobjinfo[castorder[castnum].type].seestate];
   casttics = caststate->tics;
   castdeath = false;
-  finalestage = 2;    
+  finalestage = FINALE_STAGE_CAST;    
   castframes = 0;
   castonmelee = 0;
   castattacking = false;
@@ -579,7 +689,7 @@ void F_StartCast (void)
 //
 // F_CastTicker
 //
-void F_CastTicker (void)
+static void F_CastTicker(void)
 {
   int st;
   int sfx;
@@ -693,7 +803,7 @@ void F_CastTicker (void)
 // F_CastResponder
 //
 
-boolean F_CastResponder (event_t* ev)
+static boolean F_CastResponder(event_t* ev)
 {
   if (ev->type != ev_keydown && ev->type != ev_mouseb_down && ev->type != ev_joyb_down)
     return false;
@@ -778,7 +888,7 @@ boolean F_CastResponder (event_t* ev)
 }
 
 
-void F_CastPrint (char* text)
+static void F_CastPrint(char* text)
 {
   char*       ch;
   int         c;
@@ -832,7 +942,7 @@ void F_CastPrint (char* text)
 // F_CastDrawer
 //
 
-void F_CastDrawer (void)
+static void F_CastDrawer(void)
 {
   spritedef_t*        sprdef;
   spriteframe_t*      sprframe;
@@ -866,7 +976,7 @@ void F_CastDrawer (void)
 //
 // F_BunnyScroll
 //
-void F_BunnyScroll (void)
+static void F_BunnyScroll(void)
 {
   int         scrolled;
   patch_t*    p1;
@@ -944,30 +1054,18 @@ void F_BunnyScroll (void)
 //
 void F_Drawer (void)
 {
-  if (using_FMI)
+  if (MapInfo_Drawer())
   {
-    if (!finalestage)
-    {
-      F_TextWrite();
-    }
-    else if (strcmp(gamemapinfo->endpic, "$BUNNY") == 0)
-    {
-      F_BunnyScroll();
-    }
-    else
-    {
-      V_DrawPatchFullScreen(V_CachePatchName(gamemapinfo->endpic, PU_CACHE));
-    }
-    return;
+      return;
   }
 
-  if (finalestage == 2)
+  if (finalestage == FINALE_STAGE_CAST)
   {
     F_CastDrawer ();
     return;
   }
 
-  if (!finalestage)
+  if (finalestage == FINALE_STAGE_TEXT)
     F_TextWrite ();
   else
   {
diff --git a/src/f_wipe.c b/src/f_wipe.c
index de0284fdd..08a994a59 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -260,6 +260,17 @@ int wipe_renderMelt(int width, int height, int ticks)
         }
     }
 
+    for (currcol = wipe_columns * horizblocksize / 100; currcol < width; ++currcol)
+    {
+        pixel_t *dest = wipe_scr + currcol;
+
+        for (int i = 0; i < height; ++i)
+        {
+            *dest = v_darkest_color;
+            dest += video.pitch;
+        }
+    }
+
     return done;
 }
 
diff --git a/src/g_compatibility.c b/src/g_compatibility.c
index 56207e06e..40ca10f85 100644
--- a/src/g_compatibility.c
+++ b/src/g_compatibility.c
@@ -20,6 +20,7 @@
 
 #include "doomstat.h"
 #include "doomtype.h"
+#include "g_game.h"
 #include "i_printf.h"
 #include "m_array.h"
 #include "m_misc.h"
@@ -77,6 +78,7 @@ typedef struct
 {
     md5_digest_t checksum;
     option_t *options;
+    char *complevel;
 } comp_record_t;
 
 static comp_record_t *comp_database;
@@ -125,6 +127,14 @@ void G_ParseCompDatabase(void)
             I_Printf(VB_ERROR, "COMPDB: wrong key %s", md5);
             continue;
         }
+
+        record.complevel = NULL;
+        const char *complevel = JS_GetStringValue(level, "complevel");
+        if (complevel)
+        {
+            record.complevel = M_StringDuplicate(complevel);
+        }
+
         json_t *js_options = JS_GetObject(level, "options");
         json_t *js_option = NULL;
         JS_ArrayForEach(js_option, js_options)
@@ -184,19 +194,23 @@ static void GetLevelCheckSum(int lump, md5_checksum_t* cksum)
 
 void G_ApplyLevelCompatibility(int lump)
 {
-    if (demorecording || demoplayback || netgame || !mbf21)
-    {
-        return;
-    }
-
     static boolean restore_comp;
     static int old_comp[COMP_TOTAL];
 
     if (restore_comp)
     {
+        if (demo_version != DV_MBF21)
+        {
+            demo_version = DV_MBF21;
+            G_ReloadDefaults(true);
+        }
         memcpy(comp, old_comp, sizeof(*comp));
         restore_comp = false;
     }
+    else if (demorecording || demoplayback || netgame || !mbf21)
+    {
+        return;
+    }
 
     md5_checksum_t cksum;
 
@@ -212,6 +226,20 @@ void G_ApplyLevelCompatibility(int lump)
             memcpy(old_comp, comp, sizeof(*comp));
             restore_comp = true;
 
+            char *new_demover = record->complevel;
+            if (new_demover)
+            {
+                demo_version = G_GetNamedComplevel(new_demover);
+                G_ReloadDefaults(true);
+                I_Printf(VB_INFO, "Automatically setting compatibility level \"%s\"",
+                         G_GetCurrentComplevelName());
+            }
+
+            if (!mbf21)
+            {
+                return;
+            }
+
             option_t *option;
             array_foreach(option, record->options)
             {
diff --git a/src/g_game.c b/src/g_game.c
index 99b8ef004..2d6e0bb08 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -42,6 +42,7 @@
 #include "f_finale.h"
 #include "g_game.h"
 #include "g_nextweapon.h"
+#include "g_umapinfo.h"
 #include "hu_command.h"
 #include "hu_obituary.h"
 #include "i_gamepad.h"
@@ -88,7 +89,6 @@
 #include "st_widgets.h"
 #include "statdump.h" // [FG] StatCopy()
 #include "tables.h"
-#include "u_mapinfo.h"
 #include "v_video.h"
 #include "version.h"
 #include "w_wad.h"
@@ -107,7 +107,7 @@
 static size_t   savegamesize = SAVEGAMESIZE; // killough
 static char     *demoname = NULL;
 // [crispy] the name originally chosen for the demo, i.e. without "-00000"
-static char     *orig_demoname = NULL;
+static const char *orig_demoname = NULL;
 static boolean  netdemo;
 static byte     *demobuffer;   // made some static -- killough
 static size_t   maxdemosize;
@@ -1481,7 +1481,7 @@ int G_GotoNextLevel(int *pEpi, int *pMap)
       next = gamemapinfo->nextsecret;
     else if (gamemapinfo->nextmap[0])
       next = gamemapinfo->nextmap;
-    else if (U_CheckField(gamemapinfo->endpic))
+    else if (gamemapinfo->flags & MapInfo_EndGame)
     {
       epsd = 1;
       map = 1;
@@ -1836,7 +1836,7 @@ static void CheckPlayersInNetGame(void)
 
 int playback_tic = 0, playback_totaltics = 0;
 
-static char *defdemoname;
+static const char *defdemoname;
 
 #define DEMOMARKER    0x80
 
@@ -1996,7 +1996,7 @@ static void G_PlayerFinishLevel(int player)
 
 // [crispy] format time for level statistics
 #define TIMESTRSIZE 16
-static void G_FormatLevelStatTime(char *str, int tics, boolean total)
+static void FormatLevelStatTime(char *str, int tics, boolean total)
 {
     int exitHours, exitMinutes;
     float exitTime, exitSeconds;
@@ -2027,31 +2027,39 @@ static void G_FormatLevelStatTime(char *str, int tics, boolean total)
 // [crispy] Write level statistics upon exit
 static void G_WriteLevelStat(void)
 {
-    static FILE *fstream = NULL;
+    int playerKills = 0, playerItems = 0, playerSecrets = 0;
 
-    int i, playerKills = 0, playerItems = 0, playerSecrets = 0;
+    char levelString[9] = {0};
+    char levelTimeString[TIMESTRSIZE] = {0};
+    char totalTimeString[TIMESTRSIZE] = {0};
 
-    char levelString[8];
-    char levelTimeString[TIMESTRSIZE];
-    char totalTimeString[TIMESTRSIZE];
+    static boolean firsttime = true;
 
-    if (fstream == NULL)
+    FILE *fstream = NULL;
+
+    if (firsttime)
     {
+        firsttime = false;
         fstream = M_fopen("levelstat.txt", "w");
+    }
+    else
+    {
+        fstream = M_fopen("levelstat.txt", "a");
+    }
 
-        if (fstream == NULL)
-        {
-            I_Printf(VB_ERROR, "G_WriteLevelStat: Unable to open levelstat.txt for writing!");
-            return;
-        }
+    if (fstream == NULL)
+    {
+        I_Printf(VB_ERROR,
+            "G_WriteLevelStat: Unable to open levelstat.txt for writing!");
+        return;
     }
 
     strcpy(levelString, MapName(gameepisode, gamemap));
 
-    G_FormatLevelStatTime(levelTimeString, leveltime, false);
-    G_FormatLevelStatTime(totalTimeString, totalleveltimes + leveltime, true);
+    FormatLevelStatTime(levelTimeString, leveltime, false);
+    FormatLevelStatTime(totalTimeString, totalleveltimes + leveltime, true);
 
-    for (i = 0; i < MAXPLAYERS; i++)
+    for (int i = 0; i < MAXPLAYERS; i++)
     {
         if (playeringame[i])
         {
@@ -2065,6 +2073,8 @@ static void G_WriteLevelStat(void)
             levelString, (secretexit ? "s" : ""),
             levelTimeString, totalTimeString, playerKills, totalkills,
             playerItems, totalitems, playerSecrets, totalsecret);
+
+    fclose(fstream);
 }
 
 // [Nugget] Custom Skill
@@ -2152,9 +2162,9 @@ static void G_DoCompleted(void)
     const char *next = NULL;
     boolean intermission = false;
 
-    if (U_CheckField(gamemapinfo->endpic))
+    if (gamemapinfo->flags & MapInfo_EndGame)
     {
-      if (gamemapinfo->nointermission)
+      if (gamemapinfo->flags & MapInfo_NoIntermission)
       {
         gameaction = ga_victory;
         return;
@@ -3011,6 +3021,8 @@ static void DoSaveGame(char *name)
 
   length = save_p - savebuffer;
 
+  M_MakeDirectory(basesavegame);
+
   if (!M_WriteFile(name, savebuffer, length))
     displaymsg("%s", errno ? strerror(errno) : "Could not save game: Error unknown");
   else if (show_save_messages && !is_periodic_autosave) // [Nugget]
@@ -4531,25 +4543,44 @@ void G_WorldDone(void)
 
   if (gamemapinfo)
   {
-    if (gamemapinfo->intertextsecret && secretexit)
-    {
-      if (U_CheckField(gamemapinfo->intertextsecret)) // if the intermission was not cleared
-        F_StartFinale();
-      return;
-    }
-    else if (gamemapinfo->intertext && !secretexit)
-    {
-      if (U_CheckField(gamemapinfo->intertext)) // if the intermission was not cleared
-        F_StartFinale();
-      return;
-    }
-    else if (U_CheckField(gamemapinfo->endpic) && !secretexit)
-    {
-      // game ends without a status screen.
-      gameaction = ga_victory;
-      return;
-    }
-    // if nothing applied, use the defaults.
+      if (gamemapinfo->flags & MapInfo_InterTextClear
+          && gamemapinfo->flags & MapInfo_EndGame)
+      {
+          I_Printf(VB_DEBUG,
+              "UMAPINFO: 'intertext = clear' with one of the end game keys.");
+      }
+
+      if (secretexit)
+      {
+          if (gamemapinfo->flags & MapInfo_InterTextSecretClear)
+          {
+              return;
+          }
+          if (gamemapinfo->intertextsecret)
+          {
+              F_StartFinale();
+              return;
+          }
+      }
+      else
+      {
+          if (gamemapinfo->flags & MapInfo_EndGame)
+          {
+              // game ends without a status screen.
+              gameaction = ga_victory;
+              return;
+          }
+          else if (gamemapinfo->flags & MapInfo_InterTextClear)
+          {
+              return;
+          }
+          else if (gamemapinfo->intertext)
+          {
+              F_StartFinale();
+              return;
+          }
+      }
+      // if nothing applied, use the defaults.
   }
 
   if (gamemode == commercial)
@@ -5067,65 +5098,6 @@ void G_SetFastParms(int fast_pending)
   }
 }
 
-mapentry_t *G_LookupMapinfo(int episode, int map)
-{
-  int i;
-  char lumpname[9];
-
-  strcpy(lumpname, MapName(episode, map));
-
-  for (i = 0; i < U_mapinfo.mapcount; i++)
-  {
-    if (!strcasecmp(lumpname, U_mapinfo.maps[i].mapname))
-      return &U_mapinfo.maps[i];
-  }
-
-  for (i = 0; i < default_mapinfo.mapcount; i++)
-  {
-    if (!strcasecmp(lumpname, default_mapinfo.maps[i].mapname))
-      return &default_mapinfo.maps[i];
-  }
-
-  return NULL;
-}
-
-// Check if the given map name can be expressed as a gameepisode/gamemap pair
-// and be reconstructed from it.
-int G_ValidateMapName(const char *mapname, int *pEpi, int *pMap)
-{
-  char lumpname[9], mapuname[9];
-  int epi = -1, map = -1;
-
-  if (strlen(mapname) > 8)
-    return 0;
-  strncpy(mapuname, mapname, 8);
-  mapuname[8] = 0;
-  M_StringToUpper(mapuname);
-
-  if (gamemode != commercial)
-  {
-    if (sscanf(mapuname, "E%dM%d", &epi, &map) != 2)
-      return 0;
-    strcpy(lumpname, MapName(epi, map));
-  }
-  else
-  {
-    if (sscanf(mapuname, "MAP%d", &map) != 1)
-      return 0;
-    strcpy(lumpname, MapName(epi = 1, map));
-  }
-
-  if (epi > 4)
-    EpiCustom = true;
-
-  if (pEpi)
-    *pEpi = epi;
-  if (pMap)
-    *pMap = map;
-
-  return !strcmp(mapuname, lumpname);
-}
-
 //
 // G_InitNew
 // Can be called by the startup code or the menu task,
@@ -5219,7 +5191,7 @@ void G_InitNew(skill_t skill, int episode, int map)
 // G_RecordDemo
 //
 
-void G_RecordDemo(char *name)
+void G_RecordDemo(const char *name)
 {
   int i;
   size_t demoname_size;
@@ -5235,9 +5207,13 @@ void G_RecordDemo(char *name)
   demo_insurance = 0;
 
   usergame = false;
-  demoname_size = strlen(name) + 5 + 6; // [crispy] + 6 for "-00000"
+  if (demoname)
+  {
+    free(demoname);
+  }
+  demoname = AddDefaultExtension(name, ".lmp");  // 1/18/98 killough
+  demoname_size = strlen(demoname) + 6; // [crispy] + 6 for "-00000"
   demoname = I_Realloc(demoname, demoname_size);
-  AddDefaultExtension(strcpy(demoname, name), ".lmp");  // 1/18/98 killough
 
   for(; j <= 99999 && !M_access(demoname, F_OK); ++j)
   {
@@ -5667,7 +5643,7 @@ void G_BeginRecording(void)
 
 void D_CheckNetPlaybackSkip(void);
 
-void G_DeferedPlayDemo(char* name)
+void G_DeferedPlayDemo(const char* name)
 {
   defdemoname = name;
   gameaction = ga_playdemo;
@@ -6128,7 +6104,7 @@ void G_BindCompVariables(void)
   BIND_COMP(comp_vile,      0, "Arch-viles can create ghost monsters");
   BIND_COMP(comp_pain,      0, "Pain elementals are limited to 20 lost souls");
   BIND_COMP(comp_skull,     0, "Lost souls can spawn past impassable lines");
-  BIND_COMP(comp_blazing,   0, "Blazing doors make double closing sounds");
+  BIND_COMP(comp_blazing,   0, "Incorrect sound behavior for blazing doors");
   BIND_COMP(comp_doorlight, 0, "Door lighting changes are immediate");
   BIND_COMP(comp_god,       0, "God mode isn't absolute");
   BIND_COMP(comp_skymap,    0, "Don't apply invulnerability palette to skies");
@@ -6164,7 +6140,6 @@ void G_BindCompVariables(void)
   M_BindBool("comp_nonbleeders",  &comp_nonbleeders,  NULL, false, ss_none, wad_yes, "Non-bleeders don't bleed when crushed");
   M_BindBool("comp_iosdeath",     &comp_iosdeath,     NULL, false, ss_none, wad_yes, "Fix lopsided Icon of Sin explosions");
   M_BindBool("comp_choppers",     &comp_choppers,     NULL, false, ss_none, wad_yes, "Permanent IDCHOPPERS invulnerability");
-  M_BindBool("comp_blazing2",     &comp_blazing2,     NULL, true,  ss_none, wad_yes, "Blazing doors reopen with wrong sound");
   M_BindBool("comp_manualdoor",   &comp_manualdoor,   NULL, true,  ss_none, wad_yes, "Manually toggled moving doors are silent");
   M_BindBool("comp_switchsource", &comp_switchsource, NULL, false, ss_none, wad_yes, "Corrected switch sound source");
   M_BindBool("comp_cgundblsnd",   &comp_cgundblsnd,   NULL, true,  ss_none, wad_yes, "Chaingun makes two sounds with one bullet");
diff --git a/src/g_game.h b/src/g_game.h
index 3eaf37edc..267432490 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -45,7 +45,7 @@ boolean G_CheckDemoStatus(void);
 void G_DeathMatchSpawnPlayer(int playernum);
 void G_InitNew(skill_t skill, int episode, int map);
 void G_DeferedInitNew(skill_t skill, int episode, int map);
-void G_DeferedPlayDemo(char *demo);
+void G_DeferedPlayDemo(const char *demo);
 void G_LoadAutoSave(char *name, boolean is_command);
 void G_LoadGame(char *name, int slot, boolean is_command); // killough 5/15/98
 void G_ForcedLoadAutoSave(void);
@@ -54,7 +54,7 @@ void G_SaveAutoSave(char *description);
 void G_SaveGame(int slot, char *description); // Called by M_Responder.
 boolean G_AutoSaveEnabled(void);
 boolean G_LoadAutoSaveDeathUse(void);
-void G_RecordDemo(char *name);              // Only called by startup code.
+void G_RecordDemo(const char *name);              // Only called by startup code.
 void G_BeginRecording(void);
 void G_PlayDemo(char *name);
 void G_ExitLevel(void);
@@ -75,8 +75,6 @@ byte *G_WriteOptions(byte *demo_p);        // killough 3/1/98
 void G_PlayerReborn(int player);
 void G_DoVictory(void);
 
-int G_ValidateMapName(const char *mapname, int *pEpi, int *pMap);
-
 void G_EnableWarp(boolean warp);
 void G_SetTimeScale(void);
 
diff --git a/src/g_umapinfo.c b/src/g_umapinfo.c
new file mode 100644
index 000000000..d2bafbeb4
--- /dev/null
+++ b/src/g_umapinfo.c
@@ -0,0 +1,707 @@
+//
+//  Copyright(C) 2017 Christoph Oelckers
+//  Copyright(C) 2021 Roman Fomin
+//
+//  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.
+
+#include "g_umapinfo.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomdef.h"
+#include "doomstat.h"
+#include "doomtype.h"
+#include "m_array.h"
+#include "m_misc.h"
+#include "m_scanner.h"
+#include "mn_menu.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+mapentry_t *umapinfo = NULL;
+
+static level_t *secretlevels;
+
+static const char *const actor_names[] =
+{
+    "DoomPlayer", "ZombieMan", "ShotgunGuy", "Archvile", "ArchvileFire",
+    "Revenant", "RevenantTracer", "RevenantTracerSmoke", "Fatso", "FatShot",
+    "ChaingunGuy", "DoomImp", "Demon", "Spectre", "Cacodemon", "BaronOfHell",
+    "BaronBall", "HellKnight", "LostSoul", "SpiderMastermind", "Arachnotron",
+    "Cyberdemon", "PainElemental", "WolfensteinSS", "CommanderKeen",
+    "BossBrain", "BossEye", "BossTarget", "SpawnShot", "SpawnFire",
+    "ExplosiveBarrel", "DoomImpBall", "CacodemonBall", "Rocket", "PlasmaBall",
+    "BFGBall", "ArachnotronPlasma", "BulletPuff", "Blood", "TeleportFog",
+    "ItemFog", "TeleportDest", "BFGExtra", "GreenArmor", "BlueArmor",
+    "HealthBonus", "ArmorBonus", "BlueCard", "RedCard", "YellowCard",
+    "YellowSkull", "RedSkull", "BlueSkull", "Stimpack", "Medikit", "Soulsphere",
+    "InvulnerabilitySphere", "Berserk", "BlurSphere", "RadSuit", "Allmap",
+    "Infrared", "Megasphere", "Clip", "ClipBox", "RocketAmmo", "RocketBox",
+    "Cell", "CellPack", "Shell", "ShellBox", "Backpack", "BFG9000", "Chaingun",
+    "Chainsaw", "RocketLauncher", "PlasmaRifle", "Shotgun", "SuperShotgun",
+    "TechLamp", "TechLamp2", "Column", "TallGreenColumn", "ShortGreenColumn",
+    "TallRedColumn", "ShortRedColumn", "SkullColumn", "HeartColumn", "EvilEye",
+    "FloatingSkull", "TorchTree", "BlueTorch", "GreenTorch", "RedTorch",
+    "ShortBlueTorch", "ShortGreenTorch", "ShortRedTorch", "Stalagtite",
+    "TechPillar", "CandleStick", "Candelabra", "BloodyTwitch", "Meat2", "Meat3",
+    "Meat4", "Meat5", "NonsolidMeat2", "NonsolidMeat4", "NonsolidMeat3",
+    "NonsolidMeat5", "NonsolidTwitch", "DeadCacodemon", "DeadMarine",
+    "DeadZombieMan", "DeadDemon", "DeadLostSoul", "DeadDoomImp",
+    "DeadShotgunGuy", "GibbedMarine", "GibbedMarineExtra", "HeadsOnAStick",
+    "Gibs", "HeadOnAStick", "HeadCandles", "DeadStick", "LiveStick", "BigTree",
+    "BurningBarrel", "HangNoGuts", "HangBNoBrain", "HangTLookingDown",
+    "HangTSkull", "HangTLookingUp", "HangTNoBrain", "ColonGibs",
+    "SmallBloodPool", "BrainStem",
+    // Boom/MBF additions
+    "PointPusher", "PointPuller", "MBFHelperDog", "PlasmaBall1", "PlasmaBall2",
+    "EvilSceptre", "UnholyBible", "MusicChanger", "Deh_Actor_145",
+    "Deh_Actor_146", "Deh_Actor_147", "Deh_Actor_148", "Deh_Actor_149",
+    // DEHEXTRA Actors start here
+    "Deh_Actor_150", // Extra thing 0
+    "Deh_Actor_151", // Extra thing 1
+    "Deh_Actor_152", // Extra thing 2
+    "Deh_Actor_153", // Extra thing 3
+    "Deh_Actor_154", // Extra thing 4
+    "Deh_Actor_155", // Extra thing 5
+    "Deh_Actor_156", // Extra thing 6
+    "Deh_Actor_157", // Extra thing 7
+    "Deh_Actor_158", // Extra thing 8
+    "Deh_Actor_159", // Extra thing 9
+    "Deh_Actor_160", // Extra thing 10
+    "Deh_Actor_161", // Extra thing 11
+    "Deh_Actor_162", // Extra thing 12
+    "Deh_Actor_163", // Extra thing 13
+    "Deh_Actor_164", // Extra thing 14
+    "Deh_Actor_165", // Extra thing 15
+    "Deh_Actor_166", // Extra thing 16
+    "Deh_Actor_167", // Extra thing 17
+    "Deh_Actor_168", // Extra thing 18
+    "Deh_Actor_169", // Extra thing 19
+    "Deh_Actor_170", // Extra thing 20
+    "Deh_Actor_171", // Extra thing 21
+    "Deh_Actor_172", // Extra thing 22
+    "Deh_Actor_173", // Extra thing 23
+    "Deh_Actor_174", // Extra thing 24
+    "Deh_Actor_175", // Extra thing 25
+    "Deh_Actor_176", // Extra thing 26
+    "Deh_Actor_177", // Extra thing 27
+    "Deh_Actor_178", // Extra thing 28
+    "Deh_Actor_179", // Extra thing 29
+    "Deh_Actor_180", // Extra thing 30
+    "Deh_Actor_181", // Extra thing 31
+    "Deh_Actor_182", // Extra thing 32
+    "Deh_Actor_183", // Extra thing 33
+    "Deh_Actor_184", // Extra thing 34
+    "Deh_Actor_185", // Extra thing 35
+    "Deh_Actor_186", // Extra thing 36
+    "Deh_Actor_187", // Extra thing 37
+    "Deh_Actor_188", // Extra thing 38
+    "Deh_Actor_189", // Extra thing 39
+    "Deh_Actor_190", // Extra thing 40
+    "Deh_Actor_191", // Extra thing 41
+    "Deh_Actor_192", // Extra thing 42
+    "Deh_Actor_193", // Extra thing 43
+    "Deh_Actor_194", // Extra thing 44
+    "Deh_Actor_195", // Extra thing 45
+    "Deh_Actor_196", // Extra thing 46
+    "Deh_Actor_197", // Extra thing 47
+    "Deh_Actor_198", // Extra thing 48
+    "Deh_Actor_199", // Extra thing 49
+    "Deh_Actor_200", // Extra thing 50
+    "Deh_Actor_201", // Extra thing 51
+    "Deh_Actor_202", // Extra thing 52
+    "Deh_Actor_203", // Extra thing 53
+    "Deh_Actor_204", // Extra thing 54
+    "Deh_Actor_205", // Extra thing 55
+    "Deh_Actor_206", // Extra thing 56
+    "Deh_Actor_207", // Extra thing 57
+    "Deh_Actor_208", // Extra thing 58
+    "Deh_Actor_209", // Extra thing 59
+    "Deh_Actor_210", // Extra thing 60
+    "Deh_Actor_211", // Extra thing 61
+    "Deh_Actor_212", // Extra thing 62
+    "Deh_Actor_213", // Extra thing 63
+    "Deh_Actor_214", // Extra thing 64
+    "Deh_Actor_215", // Extra thing 65
+    "Deh_Actor_216", // Extra thing 66
+    "Deh_Actor_217", // Extra thing 67
+    "Deh_Actor_218", // Extra thing 68
+    "Deh_Actor_219", // Extra thing 69
+    "Deh_Actor_220", // Extra thing 70
+    "Deh_Actor_221", // Extra thing 71
+    "Deh_Actor_222", // Extra thing 72
+    "Deh_Actor_223", // Extra thing 73
+    "Deh_Actor_224", // Extra thing 74
+    "Deh_Actor_225", // Extra thing 75
+    "Deh_Actor_226", // Extra thing 76
+    "Deh_Actor_227", // Extra thing 77
+    "Deh_Actor_228", // Extra thing 78
+    "Deh_Actor_229", // Extra thing 79
+    "Deh_Actor_230", // Extra thing 80
+    "Deh_Actor_231", // Extra thing 81
+    "Deh_Actor_232", // Extra thing 82
+    "Deh_Actor_233", // Extra thing 83
+    "Deh_Actor_234", // Extra thing 84
+    "Deh_Actor_235", // Extra thing 85
+    "Deh_Actor_236", // Extra thing 86
+    "Deh_Actor_237", // Extra thing 87
+    "Deh_Actor_238", // Extra thing 88
+    "Deh_Actor_239", // Extra thing 89
+    "Deh_Actor_240", // Extra thing 90
+    "Deh_Actor_241", // Extra thing 91
+    "Deh_Actor_242", // Extra thing 92
+    "Deh_Actor_243", // Extra thing 93
+    "Deh_Actor_244", // Extra thing 94
+    "Deh_Actor_245", // Extra thing 95
+    "Deh_Actor_246", // Extra thing 96
+    "Deh_Actor_247", // Extra thing 97
+    "Deh_Actor_248", // Extra thing 98
+    "Deh_Actor_249", // Extra thing 99
+};
+
+static void ReplaceString(char **to, const char *from)
+{
+    if (*to != NULL)
+    {
+        free(*to);
+    }
+    *to = M_StringDuplicate(from);
+}
+
+static void FreeMapEntry(mapentry_t *mape)
+{
+    if (mape->levelname)
+    {
+        free(mape->levelname);
+    }
+    if (mape->label)
+    {
+        free(mape->label);
+    }
+    if (mape->intertext)
+    {
+        free(mape->intertext);
+    }
+    if (mape->intertextsecret)
+    {
+        free(mape->intertextsecret);
+    }
+    if (mape->author)
+    {
+        free(mape->author);
+    }
+    array_free(mape->bossactions);
+    memset(mape, 0, sizeof(*mape));
+}
+
+// Parses a set of string and concatenates them
+// Returns a pointer to the string (must be freed)
+
+static char *ParseMultiString(scanner_t *s)
+{
+    char *build = NULL;
+
+    do
+    {
+        SC_MustGetToken(s, TK_StringConst);
+        if (build == NULL)
+        {
+            build = M_StringDuplicate(SC_GetString(s));
+        }
+        else
+        {
+            char *tmp = build;
+            build = M_StringJoin(tmp, "\n", SC_GetString(s));
+            free(tmp);
+        }
+    } while (SC_CheckToken(s, ','));
+
+    return build;
+}
+
+// Parses a lump name. The buffer must be at least 9 characters.
+
+static void ParseLumpName(scanner_t *s, char *buffer)
+{
+    SC_MustGetToken(s, TK_StringConst);
+    if (strlen(SC_GetString(s)) > 8)
+    {
+        SC_Error(s, "String too long. Maximum size is 8 characters.");
+    }
+    strncpy(buffer, SC_GetString(s), 8);
+    buffer[8] = 0;
+    M_StringToUpper(buffer);
+}
+
+// Parses a standard property that is already known
+// These do not get stored in the property list
+// but in dedicated struct member variables.
+
+static void ParseStandardProperty(scanner_t *s, mapentry_t *mape)
+{
+    SC_MustGetToken(s, TK_Identifier);
+    char *prop = M_StringDuplicate(SC_GetString(s));
+
+    SC_MustGetToken(s, '=');
+    if (!strcasecmp(prop, "levelname"))
+    {
+        SC_MustGetToken(s, TK_StringConst);
+        ReplaceString(&mape->levelname, SC_GetString(s));
+    }
+    else if (!strcasecmp(prop, "label"))
+    {
+        if (SC_CheckToken(s, TK_Identifier))
+        {
+            if (!strcasecmp(SC_GetString(s), "clear"))
+            {
+                mape->flags |= MapInfo_LabelClear;
+            }
+            else
+            {
+                SC_Error(s, "Either 'clear' or string constant expected");
+            }
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_LabelClear;
+            SC_MustGetToken(s, TK_StringConst);
+            ReplaceString(&mape->label, SC_GetString(s));
+        }
+    }
+    else if (!strcasecmp(prop, "author"))
+    {
+        SC_MustGetToken(s, TK_StringConst);
+        ReplaceString(&mape->author, SC_GetString(s));
+    }
+    else if (!strcasecmp(prop, "episode"))
+    {
+        if (SC_CheckToken(s, TK_Identifier))
+        {
+            if (!strcasecmp(SC_GetString(s), "clear"))
+            {
+                MN_ClearEpisodes();
+            }
+            else
+            {
+                SC_Error(s, "Either 'clear' or string constant expected");
+            }
+        }
+        else
+        {
+            char lumpname[9] = {0};
+            char *alttext = NULL;
+            char key = 0;
+
+            ParseLumpName(s, lumpname);
+            if (SC_CheckToken(s, ','))
+            {
+                SC_MustGetToken(s, TK_StringConst);
+                alttext = M_StringDuplicate(SC_GetString(s));
+                if (SC_CheckToken(s, ','))
+                {
+                    SC_MustGetToken(s, TK_StringConst);
+                    const char *tmp = SC_GetString(s);
+                    key = M_ToLower(tmp[0]);
+                }
+            }
+
+            MN_AddEpisode(mape->mapname, lumpname, alttext, key);
+
+            if (alttext)
+            {
+                free(alttext);
+            }
+        }
+    }
+    else if (!strcasecmp(prop, "next"))
+    {
+        ParseLumpName(s, mape->nextmap);
+        if (!G_ValidateMapName(mape->nextmap, NULL, NULL))
+        {
+            SC_Error(s, "Invalid map name %s.", mape->nextmap);
+        }
+    }
+    else if (!strcasecmp(prop, "nextsecret"))
+    {
+        ParseLumpName(s, mape->nextsecret);
+        level_t level = {0};
+        if (!G_ValidateMapName(mape->nextsecret, &level.episode, &level.map))
+        {
+            SC_Error(s, "Invalid map name %s", mape->nextsecret);
+        }
+        array_push(secretlevels, level);
+    }
+    else if (!strcasecmp(prop, "levelpic"))
+    {
+        ParseLumpName(s, mape->levelpic);
+    }
+    else if (!strcasecmp(prop, "skytexture"))
+    {
+        ParseLumpName(s, mape->skytexture);
+    }
+    else if (!strcasecmp(prop, "music"))
+    {
+        ParseLumpName(s, mape->music);
+    }
+    else if (!strcasecmp(prop, "endpic"))
+    {
+        mape->flags |= MapInfo_EndGameArt;
+        ParseLumpName(s, mape->endpic);
+    }
+    else if (!strcasecmp(prop, "endcast"))
+    {
+        SC_MustGetToken(s, TK_BoolConst);
+        if (SC_GetBoolean(s))
+        {
+            mape->flags |= MapInfo_EndGameCast;
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_EndGameCast;
+            mape->flags |= MapInfo_EndGameClear;
+        }
+    }
+    else if (!strcasecmp(prop, "endbunny"))
+    {
+        SC_MustGetToken(s, TK_BoolConst);
+        if (SC_GetBoolean(s))
+        {
+            mape->flags |= MapInfo_EndGameBunny;
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_EndGameBunny;
+            mape->flags |= MapInfo_EndGameClear;
+        }
+    }
+    else if (!strcasecmp(prop, "endgame"))
+    {
+        SC_MustGetToken(s, TK_BoolConst);
+        if (SC_GetBoolean(s))
+        {
+            mape->flags |= MapInfo_EndGameStandard;
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_EndGameStandard;
+            mape->flags |= MapInfo_EndGameClear;
+        }
+    }
+    else if (!strcasecmp(prop, "exitpic"))
+    {
+        ParseLumpName(s, mape->exitpic);
+    }
+    else if (!strcasecmp(prop, "enterpic"))
+    {
+        ParseLumpName(s, mape->enterpic);
+    }
+    else if (!strcasecmp(prop, "exitanim"))
+    {
+        ParseLumpName(s, mape->exitanim);
+    }
+    else if (!strcasecmp(prop, "enteranim"))
+    {
+        ParseLumpName(s, mape->enteranim);
+    }
+    else if (!strcasecmp(prop, "nointermission"))
+    {
+        SC_MustGetToken(s, TK_BoolConst);
+        if (SC_GetBoolean(s))
+        {
+            mape->flags |= MapInfo_NoIntermission;
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_NoIntermission;
+        }
+    }
+    else if (!strcasecmp(prop, "partime"))
+    {
+        SC_MustGetToken(s, TK_IntConst);
+        mape->partime = SC_GetNumber(s);
+    }
+    else if (!strcasecmp(prop, "intertext"))
+    {
+        if (SC_CheckToken(s, TK_Identifier))
+        {
+            if (!strcasecmp(SC_GetString(s), "clear"))
+            {
+                mape->flags |= MapInfo_InterTextClear;
+            }
+            else
+            {
+                SC_Error(s, "Either 'clear' or string constant expected");
+            }
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_InterTextClear;
+            if (mape->intertext)
+            {
+                free(mape->intertext);
+            }
+            mape->intertext = ParseMultiString(s);
+        }
+    }
+    else if (!strcasecmp(prop, "intertextsecret"))
+    {
+        if (SC_CheckToken(s, TK_Identifier))
+        {
+            if (!strcasecmp(SC_GetString(s), "clear"))
+            {
+                mape->flags |= MapInfo_InterTextSecretClear;
+            }
+            else
+            {
+                SC_Error(s, "Either 'clear' or string constant expected");
+            }
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_InterTextSecretClear;
+            if (mape->intertextsecret)
+            {
+                free(mape->intertextsecret);
+            }
+            mape->intertextsecret = ParseMultiString(s);
+        }
+    }
+    else if (!strcasecmp(prop, "interbackdrop"))
+    {
+        ParseLumpName(s, mape->interbackdrop);
+    }
+    else if (!strcasecmp(prop, "intermusic"))
+    {
+        ParseLumpName(s, mape->intermusic);
+    }
+    else if (!strcasecmp(prop, "bossaction"))
+    {
+        SC_MustGetToken(s, TK_Identifier);
+        if (!strcasecmp(SC_GetString(s), "clear"))
+        {
+            mape->flags |= MapInfo_BossActionClear;
+            array_free(mape->bossactions);
+        }
+        else
+        {
+            mape->flags &= ~MapInfo_BossActionClear;
+            int type, special, tag;
+            for (type = 0; arrlen(actor_names); ++type)
+            {
+                if (!strcasecmp(SC_GetString(s), actor_names[type]))
+                {
+                    break;
+                }
+            }
+            if (type == arrlen(actor_names))
+            {
+                SC_Error(s, "bossaction: unknown thing type '%s'",
+                         SC_GetString(s));
+            }
+            SC_MustGetToken(s, ',');
+            SC_MustGetToken(s, TK_IntConst);
+            special = SC_GetNumber(s);
+            SC_MustGetToken(s, ',');
+            SC_MustGetToken(s, TK_IntConst);
+            tag = SC_GetNumber(s);
+            // allow no 0-tag specials here, unless a level exit.
+            if (tag != 0 || special == 11 || special == 51 || special == 52
+                || special == 124)
+            {
+                bossaction_t bossaction = {type, special, tag};
+                array_push(mape->bossactions, bossaction);
+            }
+        }
+    }
+    // If no known property name was given, skip all comma-separated values
+    // after the = sign
+    else
+    {
+        do
+        {
+            SC_GetNextToken(s, true);
+        } while (SC_CheckToken(s, ','));
+    }
+
+    free(prop);
+}
+
+static void ParseMapEntry(scanner_t *s, mapentry_t *entry)
+{
+    SC_MustGetToken(s, TK_Identifier);
+    if (strcasecmp(SC_GetString(s), "map"))
+    {
+        SC_Error(s, "Expected 'map' but got '%s' instead", SC_GetString(s));
+    }
+
+    SC_MustGetToken(s, TK_Identifier);
+    if (!G_ValidateMapName(SC_GetString(s), NULL, NULL))
+    {
+        SC_Error(s, "Invalid map name %s", SC_GetString(s));
+    }
+    ReplaceString(&entry->mapname, SC_GetString(s));
+
+    SC_MustGetToken(s, '{');
+    while (!SC_CheckToken(s, '}'))
+    {
+        ParseStandardProperty(s, entry);
+    }
+}
+
+void G_ParseMapInfo(int lumpnum)
+{
+    scanner_t *s = SC_Open("UMAPINFO", W_CacheLumpNum(lumpnum, PU_CACHE),
+                           W_LumpLength(lumpnum));
+    while (SC_TokensLeft(s))
+    {
+        mapentry_t parsed = {0};
+        ParseMapEntry(s, &parsed);
+
+        // Set default level progression here to simplify the checks elsewhere.
+        // Doing this lets us skip all normal code for this if nothing has been
+        // defined.
+        if (parsed.flags & MapInfo_EndGame)
+        {
+            parsed.nextmap[0] = 0;
+        }
+        else if (!parsed.nextmap[0] && !(parsed.flags & MapInfo_EndGameClear))
+        {
+            if (!strcasecmp(parsed.mapname, "MAP30"))
+            {
+                parsed.flags |= MapInfo_EndGameCast;
+            }
+            else if (!strcasecmp(parsed.mapname, "E1M8"))
+            {
+                parsed.flags |= MapInfo_EndGameArt;
+                strcpy(parsed.endpic, gamemode == retail ? "CREDIT" : "HELP2");
+            }
+            else if (!strcasecmp(parsed.mapname, "E2M8"))
+            {
+                parsed.flags |= MapInfo_EndGameArt;
+                strcpy(parsed.endpic, "VICTORY2");
+            }
+            else if (!strcasecmp(parsed.mapname, "E3M8"))
+            {
+                parsed.flags |= MapInfo_EndGameBunny;
+            }
+            else if (!strcasecmp(parsed.mapname, "E4M8"))
+            {
+                parsed.flags |= MapInfo_EndGameArt;
+                strcpy(parsed.endpic, "ENDPIC");
+            }
+            else
+            {
+                int ep, map;
+                if (G_ValidateMapName(parsed.mapname, &ep, &map))
+                {
+                    strcpy(parsed.nextmap, MapName(ep, map + 1));
+                }
+            }
+        }
+
+        // Does this entry already exist? If yes, replace it.
+        int i;
+        for (i = 0; i < array_size(umapinfo); ++i)
+        {
+            if (!strcmp(parsed.mapname, umapinfo[i].mapname))
+            {
+                FreeMapEntry(&umapinfo[i]);
+                umapinfo[i] = parsed;
+                break;
+            }
+        }
+        // Not found so create a new one.
+        if (i == array_size(umapinfo))
+        {
+            array_push(umapinfo, parsed);
+        }
+    }
+
+    SC_Close(s);
+}
+
+mapentry_t *G_LookupMapinfo(int episode, int map)
+{
+    char lumpname[9] = {0};
+    M_StringCopy(lumpname, MapName(episode, map), sizeof(lumpname));
+
+    mapentry_t *entry;
+    array_foreach(entry, umapinfo)
+    {
+        if (!strcasecmp(lumpname, entry->mapname))
+        {
+            return entry;
+        }
+    }
+
+    return NULL;
+}
+
+// Check if the given map name can be expressed as a gameepisode/gamemap pair
+// and be reconstructed from it.
+
+boolean G_ValidateMapName(const char *mapname, int *episode, int *map)
+{
+    if (strlen(mapname) > 8)
+    {
+        return false;
+    }
+
+    char lumpname[9], mapuname[9];
+    int e = -1, m = -1;
+
+    M_StringCopy(mapuname, mapname, 8);
+    mapuname[8] = 0;
+    M_StringToUpper(mapuname);
+
+    if (gamemode != commercial)
+    {
+        if (sscanf(mapuname, "E%dM%d", &e, &m) != 2)
+        {
+            return false;
+        }
+        strcpy(lumpname, MapName(e, m));
+    }
+    else
+    {
+        if (sscanf(mapuname, "MAP%d", &m) != 1)
+        {
+            return false;
+        }
+        strcpy(lumpname, MapName(e = 1, m));
+    }
+
+    if (episode)
+    {
+        *episode = e;
+    }
+    if (map)
+    {
+        *map = m;
+    }
+
+    return strcmp(mapuname, lumpname) == 0;
+}
+
+boolean G_IsSecretMap(int episode, int map)
+{
+    level_t *level;
+    array_foreach(level, secretlevels)
+    {
+        if (level->episode == episode && level->map == map)
+        {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/g_umapinfo.h b/src/g_umapinfo.h
new file mode 100644
index 000000000..487c24b0c
--- /dev/null
+++ b/src/g_umapinfo.h
@@ -0,0 +1,83 @@
+//
+//  Copyright(C) 2017 Christoph Oelckers
+//  Copyright(C) 2021 Roman Fomin
+//
+//  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.
+
+#ifndef G_UMAPINFO_H
+#define G_UMAPINFO_H
+
+#include "doomtype.h"
+
+typedef enum
+{
+    MapInfo_LabelClear = (1u << 0),
+
+    MapInfo_EndGameArt = (1u << 2),
+    MapInfo_EndGameStandard = (1u << 3),
+    MapInfo_EndGameCast = (1u << 4),
+    MapInfo_EndGameBunny = (1u << 5),
+    MapInfo_EndGame = (MapInfo_EndGameArt | MapInfo_EndGameStandard
+                       | MapInfo_EndGameCast | MapInfo_EndGameBunny),
+    MapInfo_EndGameClear = (1u << 6),
+
+    MapInfo_NoIntermission = (1u << 7),
+    MapInfo_InterTextClear = (1u << 8),
+    MapInfo_InterTextSecretClear = (1u << 9),
+
+    MapInfo_BossActionClear = (1u << 10)
+} mapinfo_flags_t;
+
+typedef struct
+{
+    int type;
+    int special;
+    int tag;
+} bossaction_t;
+
+typedef struct mapentry_s
+{
+    char *mapname;
+    char *levelname;
+    char *label;
+    char *intertext;
+    char *intertextsecret;
+    char *author;
+    char levelpic[9];
+    char nextmap[9];
+    char nextsecret[9];
+    char music[9];
+    char skytexture[9];
+    char endpic[9];
+    char exitpic[9];
+    char enterpic[9];
+    char exitanim[9];
+    char enteranim[9];
+    char interbackdrop[9];
+    char intermusic[9];
+    int partime;
+    bossaction_t *bossactions;
+    mapinfo_flags_t flags;
+} mapentry_t;
+
+extern mapentry_t *umapinfo;
+
+extern boolean EpiCustom;
+
+mapentry_t *G_LookupMapinfo(int episode, int map);
+
+boolean G_ValidateMapName(const char *mapname, int *episode, int *map);
+
+void G_ParseMapInfo(int lumpnum);
+
+boolean G_IsSecretMap(int episode, int map);
+
+#endif
diff --git a/src/hu_crosshair.c b/src/hu_crosshair.c
index 3d8f77c4b..763a4e267 100644
--- a/src/hu_crosshair.c
+++ b/src/hu_crosshair.c
@@ -95,11 +95,6 @@ void HU_InitCrosshair(void)
 
 void HU_StartCrosshair(void)
 {
-    if (crosshair.patch)
-    {
-        Z_ChangeTag(crosshair.patch, PU_CACHE);
-    }
-
     if (crosshair_lumps[hud_crosshair])
     {
         crosshair.patch =
diff --git a/src/i_flmusic.c b/src/i_flmusic.c
index a7c265010..a8ccc10b6 100644
--- a/src/i_flmusic.c
+++ b/src/i_flmusic.c
@@ -121,21 +121,10 @@ static void ScanDir(const char *dir, boolean recursion)
         const char usr_share[] = "/usr/share";
         if (strncmp(dir, usr_share, strlen(usr_share)) == 0)
         {
-            char *home_dir = M_getenv("XDG_DATA_HOME");
-
-            if (home_dir == NULL)
-            {
-                home_dir = M_getenv("HOME");
-            }
-
-            if (home_dir)
-            {
-                char *local_share = M_StringJoin(home_dir, "/.local/share");
-                char *local_dir = M_StringReplace(dir, usr_share, local_share);
-                free(local_share);
-                ScanDir(local_dir, true);
-                free(local_dir);
-            }
+            char *local_share = M_DataDir();
+            char *local_dir = M_StringReplace(dir, usr_share, local_share);
+            ScanDir(local_dir, true);
+            free(local_dir);
         }
         else if (dir[0] == '.')
         {
diff --git a/src/i_gyro.c b/src/i_gyro.c
index fe8d10290..35108c256 100644
--- a/src/i_gyro.c
+++ b/src/i_gyro.c
@@ -192,13 +192,13 @@ static void SaveCalibration(void)
 static void ProcessAccelCalibration(void)
 {
     cal.accel_count++;
-    cal.accel_sum += vec_length(&motion.accel);
+    cal.accel_sum += vec_length(motion.accel);
 }
 
 static void ProcessGyroCalibration(void)
 {
     cal.gyro_count++;
-    cal.gyro_sum = vec_add(&cal.gyro_sum, &motion.gyro);
+    cal.gyro_sum = vec_add(cal.gyro_sum, motion.gyro);
 }
 
 static void PostProcessCalibration(void)
@@ -211,8 +211,8 @@ static void PostProcessCalibration(void)
     motion.accel_magnitude = cal.accel_sum / cal.accel_count;
     motion.accel_magnitude = BETWEEN(0.0f, 2.0f, motion.accel_magnitude);
 
-    motion.gyro_offset = vec_scale(&cal.gyro_sum, 1.0f / cal.gyro_count);
-    motion.gyro_offset = vec_clamp(-1.0f, 1.0f, &motion.gyro_offset);
+    motion.gyro_offset = vec_scale(cal.gyro_sum, 1.0f / cal.gyro_count);
+    motion.gyro_offset = vec_clamp(-1.0f, 1.0f, motion.gyro_offset);
 
     SaveCalibration();
 
@@ -505,7 +505,7 @@ static void ApplyGyroSpace_Local(void)
 
 static void ApplyGyroSpace_Player(void)
 {
-    const vec grav_norm = vec_normalize(&motion.gravity);
+    const vec grav_norm = vec_normalize(motion.gravity);
     const float world_yaw =
         motion.gyro.y * grav_norm.y + motion.gyro.z * grav_norm.z;
 
@@ -532,36 +532,36 @@ static void CalcGravityVector_Skip(void)
 static void CalcGravityVector_Full(void)
 {
     // Convert gyro input to reverse rotation.
-    const float angle_speed = vec_length(&motion.gyro);
+    const float angle_speed = vec_length(motion.gyro);
     const float angle = angle_speed * motion.delta_time;
-    const vec negative_gyro = vec_negative(&motion.gyro);
-    const quat reverse_rotation = angle_axis(angle, &negative_gyro);
+    const vec negative_gyro = vec_negative(motion.gyro);
+    const quat reverse_rotation = angle_axis(angle, negative_gyro);
 
     // Rotate gravity vector.
-    motion.gravity = vec_rotate(&motion.gravity, &reverse_rotation);
+    motion.gravity = vec_rotate(motion.gravity, reverse_rotation);
 
     // Check accelerometer magnitude now.
-    const float accel_magnitude = vec_length(&motion.accel);
+    const float accel_magnitude = vec_length(motion.accel);
     if (accel_magnitude <= 0.0f)
     {
         return;
     }
-    const vec accel_norm = vec_scale(&motion.accel, 1.0f / accel_magnitude);
+    const vec accel_norm = vec_scale(motion.accel, 1.0f / accel_magnitude);
 
     // Shakiness/smoothness.
-    motion.smooth_accel = vec_rotate(&motion.smooth_accel, &reverse_rotation);
+    motion.smooth_accel = vec_rotate(motion.smooth_accel, reverse_rotation);
     const float smooth_factor = exp2f(-motion.delta_time * SMOOTH_HALF_TIME);
     motion.shakiness *= smooth_factor;
-    const vec delta_accel = vec_subtract(&motion.accel, &motion.smooth_accel);
-    const float delta_accel_magnitude = vec_length(&delta_accel);
+    const vec delta_accel = vec_subtract(motion.accel, motion.smooth_accel);
+    const float delta_accel_magnitude = vec_length(delta_accel);
     motion.shakiness = MAX(motion.shakiness, delta_accel_magnitude);
     motion.smooth_accel =
-        vec_lerp(&motion.accel, &motion.smooth_accel, smooth_factor);
+        vec_lerp(motion.accel, motion.smooth_accel, smooth_factor);
 
     // Find the difference between gravity and raw acceleration.
-    const vec new_gravity = vec_scale(&accel_norm, -motion.accel_magnitude);
-    const vec gravity_delta = vec_subtract(&new_gravity, &motion.gravity);
-    const vec gravity_direction = vec_normalize(&gravity_delta);
+    const vec new_gravity = vec_scale(accel_norm, -motion.accel_magnitude);
+    const vec gravity_delta = vec_subtract(new_gravity, motion.gravity);
+    const vec gravity_direction = vec_normalize(gravity_delta);
 
     // Calculate correction rate.
     float still_or_shaky = (motion.shakiness - SHAKINESS_MIN_THRESH)
@@ -575,7 +575,7 @@ static void CalcGravityVector_Full(void)
     const float correction_limit = MAX(angle_speed_adjusted, COR_MIN_SPEED);
     if (correction_rate > correction_limit)
     {
-        const float gravity_delta_magnitude = vec_length(&gravity_delta);
+        const float gravity_delta_magnitude = vec_length(gravity_delta);
         float close_factor = (gravity_delta_magnitude - COR_GYRO_MIN_THRESH)
                              / (COR_GYRO_MAX_THRESH - COR_GYRO_MIN_THRESH);
         close_factor = BETWEEN(0.0f, 1.0f, close_factor);
@@ -585,20 +585,20 @@ static void CalcGravityVector_Full(void)
 
     // Apply correction to gravity vector.
     const vec correction =
-        vec_scale(&gravity_direction, correction_rate * motion.delta_time);
-    if (vec_lengthsquared(&correction) < vec_lengthsquared(&gravity_delta))
+        vec_scale(gravity_direction, correction_rate * motion.delta_time);
+    if (vec_lengthsquared(correction) < vec_lengthsquared(gravity_delta))
     {
-        motion.gravity = vec_add(&motion.gravity, &correction);
+        motion.gravity = vec_add(motion.gravity, correction);
     }
     else
     {
-        motion.gravity = vec_scale(&accel_norm, -motion.accel_magnitude);
+        motion.gravity = vec_scale(accel_norm, -motion.accel_magnitude);
     }
 }
 
 static void ApplyCalibration(void)
 {
-    motion.gyro = vec_subtract(&motion.gyro, &motion.gyro_offset);
+    motion.gyro = vec_subtract(motion.gyro, motion.gyro_offset);
 }
 
 static float GetDeltaTime(void)
diff --git a/src/i_oalmusic.c b/src/i_oalmusic.c
index a9d538f58..fa1c64668 100644
--- a/src/i_oalmusic.c
+++ b/src/i_oalmusic.c
@@ -439,7 +439,11 @@ static boolean I_OAL_InitMusic(int device)
     return false;
 }
 
-static int fl_gain, opl_gain;
+#if defined(HAVE_FLUIDSYNTH)
+static int fl_gain;
+#endif
+
+static int opl_gain;
 
 static void I_OAL_SetMusicVolume(int volume)
 {
@@ -609,7 +613,7 @@ static const char **I_OAL_DeviceList(void)
 
 static midiplayertype_t I_OAL_MidiPlayerType(void)
 {
-#ifdef HAVE_FLUIDSYNTH
+#if defined (HAVE_FLUIDSYNTH)
     if (active_module == &stream_fl_module)
     {
         return midiplayer_fluidsynth;
diff --git a/src/i_printf.h b/src/i_printf.h
index cb91c69aa..d404c9b6f 100644
--- a/src/i_printf.h
+++ b/src/i_printf.h
@@ -31,7 +31,7 @@ typedef enum
     VB_MAX
 } verbosity_t;
 
-#define VB_DEMO (gameaction == ga_playdemo ? VB_INFO : VB_DEBUG)
+#define VB_DEMO (gameaction == ga_playdemo || demoplayback ? VB_INFO : VB_DEBUG)
 
 int I_ConsoleStdout(void);
 
diff --git a/src/i_video.c b/src/i_video.c
index 42ebcbc73..530f7691e 100644
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -1810,7 +1810,8 @@ static void I_InitGraphicsMode(void)
     SDL_RendererInfo info;
     if (SDL_GetRendererInfo(renderer, &info) == 0)
     {
-        I_Printf(VB_DEBUG, "SDL render driver: %s", info.name);
+        I_Printf(VB_DEBUG, "SDL render driver: %s (%s)", info.name,
+                 SDL_GetCurrentVideoDriver());
 #ifdef _WIN32
         d3d_renderer = !strncmp(info.name, "direct3d", strlen(info.name));
 #endif
diff --git a/src/info.c b/src/info.c
index 2ad98bea0..22795092e 100644
--- a/src/info.c
+++ b/src/info.c
@@ -109,53 +109,53 @@ char *original_sprnames[NUMSPRITES+1] = {
 
 state_t original_states[NUMSTATES] = {
   {SPR_TROO,0,-1,{NULL},S_NULL},  // S_NULL
-  {SPR_SHTG,4,0,{A_Light0},S_NULL}, // S_LIGHTDONE
-  {SPR_PUNG,0,1,{A_WeaponReady},S_PUNCH}, // S_PUNCH
-  {SPR_PUNG,0,1,{A_Lower},S_PUNCHDOWN}, // S_PUNCHDOWN
-  {SPR_PUNG,0,1,{A_Raise},S_PUNCHUP}, // S_PUNCHUP
+  {SPR_SHTG,4,0,{.p2 = A_Light0},S_NULL}, // S_LIGHTDONE
+  {SPR_PUNG,0,1,{.p2 = A_WeaponReady},S_PUNCH}, // S_PUNCH
+  {SPR_PUNG,0,1,{.p2 = A_Lower},S_PUNCHDOWN}, // S_PUNCHDOWN
+  {SPR_PUNG,0,1,{.p2 = A_Raise},S_PUNCHUP}, // S_PUNCHUP
   {SPR_PUNG,1,4,{NULL},S_PUNCH2},   // S_PUNCH1
-  {SPR_PUNG,2,4,{A_Punch},S_PUNCH3},  // S_PUNCH2
+  {SPR_PUNG,2,4,{.p2 = A_Punch},S_PUNCH3},  // S_PUNCH2
   {SPR_PUNG,3,5,{NULL},S_PUNCH4},   // S_PUNCH3
   {SPR_PUNG,2,4,{NULL},S_PUNCH5},   // S_PUNCH4
-  {SPR_PUNG,1,5,{A_ReFire},S_PUNCH},  // S_PUNCH5
-  {SPR_PISG,0,1,{A_WeaponReady},S_PISTOL},// S_PISTOL
-  {SPR_PISG,0,1,{A_Lower},S_PISTOLDOWN},  // S_PISTOLDOWN
-  {SPR_PISG,0,1,{A_Raise},S_PISTOLUP},  // S_PISTOLUP
+  {SPR_PUNG,1,5,{.p2 = A_ReFire},S_PUNCH},  // S_PUNCH5
+  {SPR_PISG,0,1,{.p2 = A_WeaponReady},S_PISTOL},// S_PISTOL
+  {SPR_PISG,0,1,{.p2 = A_Lower},S_PISTOLDOWN},  // S_PISTOLDOWN
+  {SPR_PISG,0,1,{.p2 = A_Raise},S_PISTOLUP},  // S_PISTOLUP
   {SPR_PISG,0,4,{NULL},S_PISTOL2},  // S_PISTOL1
-  {SPR_PISG,1,6,{A_FirePistol},S_PISTOL3},// S_PISTOL2
+  {SPR_PISG,1,6,{.p2 = A_FirePistol},S_PISTOL3},// S_PISTOL2
   {SPR_PISG,2,4,{NULL},S_PISTOL4},  // S_PISTOL3
-  {SPR_PISG,1,5,{A_ReFire},S_PISTOL}, // S_PISTOL4
-  {SPR_PISF,0|FF_FULLBRIGHT,7,{A_Light1},S_LIGHTDONE},  // S_PISTOLFLASH
-  {SPR_SHTG,0,1,{A_WeaponReady},S_SGUN},  // S_SGUN
-  {SPR_SHTG,0,1,{A_Lower},S_SGUNDOWN},  // S_SGUNDOWN
-  {SPR_SHTG,0,1,{A_Raise},S_SGUNUP},  // S_SGUNUP
+  {SPR_PISG,1,5,{.p2 = A_ReFire},S_PISTOL}, // S_PISTOL4
+  {SPR_PISF,0|FF_FULLBRIGHT,7,{.p2 = A_Light1},S_LIGHTDONE},  // S_PISTOLFLASH
+  {SPR_SHTG,0,1,{.p2 = A_WeaponReady},S_SGUN},  // S_SGUN
+  {SPR_SHTG,0,1,{.p2 = A_Lower},S_SGUNDOWN},  // S_SGUNDOWN
+  {SPR_SHTG,0,1,{.p2 = A_Raise},S_SGUNUP},  // S_SGUNUP
   {SPR_SHTG,0,3,{NULL},S_SGUN2},  // S_SGUN1
-  {SPR_SHTG,0,7,{A_FireShotgun},S_SGUN3}, // S_SGUN2
+  {SPR_SHTG,0,7,{.p2 = A_FireShotgun},S_SGUN3}, // S_SGUN2
   {SPR_SHTG,1,5,{NULL},S_SGUN4},  // S_SGUN3
   {SPR_SHTG,2,5,{NULL},S_SGUN5},  // S_SGUN4
   {SPR_SHTG,3,4,{NULL},S_SGUN6},  // S_SGUN5
   {SPR_SHTG,2,5,{NULL},S_SGUN7},  // S_SGUN6
   {SPR_SHTG,1,5,{NULL},S_SGUN8},  // S_SGUN7
   {SPR_SHTG,0,3,{NULL},S_SGUN9},  // S_SGUN8
-  {SPR_SHTG,0,7,{A_ReFire},S_SGUN}, // S_SGUN9
-  {SPR_SHTF,0|FF_FULLBRIGHT,4,{A_Light1},S_SGUNFLASH2}, // S_SGUNFLASH1
-  {SPR_SHTF,1|FF_FULLBRIGHT,3,{A_Light2},S_LIGHTDONE},  // S_SGUNFLASH2
-  {SPR_SHT2,0,1,{A_WeaponReady},S_DSGUN}, // S_DSGUN
-  {SPR_SHT2,0,1,{A_Lower},S_DSGUNDOWN}, // S_DSGUNDOWN
-  {SPR_SHT2,0,1,{A_Raise},S_DSGUNUP}, // S_DSGUNUP
+  {SPR_SHTG,0,7,{.p2 = A_ReFire},S_SGUN}, // S_SGUN9
+  {SPR_SHTF,0|FF_FULLBRIGHT,4,{.p2 = A_Light1},S_SGUNFLASH2}, // S_SGUNFLASH1
+  {SPR_SHTF,1|FF_FULLBRIGHT,3,{.p2 = A_Light2},S_LIGHTDONE},  // S_SGUNFLASH2
+  {SPR_SHT2,0,1,{.p2 = A_WeaponReady},S_DSGUN}, // S_DSGUN
+  {SPR_SHT2,0,1,{.p2 = A_Lower},S_DSGUNDOWN}, // S_DSGUNDOWN
+  {SPR_SHT2,0,1,{.p2 = A_Raise},S_DSGUNUP}, // S_DSGUNUP
   {SPR_SHT2,0,3,{NULL},S_DSGUN2}, // S_DSGUN1
 
   // killough 9/5/98: make SSG lighting flash more uniform along super shotgun:
 
-  {SPR_SHT2,0|FF_FULLBRIGHT /* killough */,7,{A_FireShotgun2},S_DSGUN3}, // S_DSGUN2
+  {SPR_SHT2,0|FF_FULLBRIGHT /* killough */,7,{.p2 = A_FireShotgun2},S_DSGUN3}, // S_DSGUN2
   {SPR_SHT2,1,7,{NULL},S_DSGUN4}, // S_DSGUN3
-  {SPR_SHT2,2,7,{A_CheckReload},S_DSGUN5},  // S_DSGUN4
-  {SPR_SHT2,3,7,{A_OpenShotgun2},S_DSGUN6}, // S_DSGUN5
+  {SPR_SHT2,2,7,{.p2 = A_CheckReload},S_DSGUN5},  // S_DSGUN4
+  {SPR_SHT2,3,7,{.p2 = A_OpenShotgun2},S_DSGUN6}, // S_DSGUN5
   {SPR_SHT2,4,7,{NULL},S_DSGUN7}, // S_DSGUN6
-  {SPR_SHT2,5,7,{A_LoadShotgun2},S_DSGUN8}, // S_DSGUN7
+  {SPR_SHT2,5,7,{.p2 = A_LoadShotgun2},S_DSGUN8}, // S_DSGUN7
   {SPR_SHT2,6,6,{NULL},S_DSGUN9}, // S_DSGUN8
-  {SPR_SHT2,7,6,{A_CloseShotgun2},S_DSGUN10}, // S_DSGUN9
-  {SPR_SHT2,0,5,{A_ReFire},S_DSGUN},  // S_DSGUN10
+  {SPR_SHT2,7,6,{.p2 = A_CloseShotgun2},S_DSGUN10}, // S_DSGUN9
+  {SPR_SHT2,0,5,{.p2 = A_ReFire},S_DSGUN},  // S_DSGUN10
   {SPR_SHT2,1,7,{NULL},S_DSNR2},  // S_DSNR1
   {SPR_SHT2,0,3,{NULL},S_DSGUNDOWN},  // S_DSNR2
 
@@ -163,52 +163,52 @@ state_t original_states[NUMSTATES] = {
   // killough 8/20/98: reduce first SSG flash frame one tic, to fix
   // Doom II SSG flash bug, in which SSG raises before flash finishes
 
-  {SPR_SHT2,8|FF_FULLBRIGHT,4/*killough*/,{A_Light1},S_DSGUNFLASH2}, // S_DSGUNFLASH1
+  {SPR_SHT2,8|FF_FULLBRIGHT,4/*killough*/,{.p2 = A_Light1},S_DSGUNFLASH2}, // S_DSGUNFLASH1
 #else
-  {SPR_SHT2,8|FF_FULLBRIGHT,5,{A_Light1},S_DSGUNFLASH2}, // S_DSGUNFLASH1
+  {SPR_SHT2,8|FF_FULLBRIGHT,5,{.p2 = A_Light1},S_DSGUNFLASH2}, // S_DSGUNFLASH1
 #endif
-  {SPR_SHT2,9|FF_FULLBRIGHT,4,{A_Light2},S_LIGHTDONE},  // S_DSGUNFLASH2
-  {SPR_CHGG,0,1,{A_WeaponReady},S_CHAIN}, // S_CHAIN
-  {SPR_CHGG,0,1,{A_Lower},S_CHAINDOWN}, // S_CHAINDOWN
-  {SPR_CHGG,0,1,{A_Raise},S_CHAINUP}, // S_CHAINUP
-  {SPR_CHGG,0,4,{A_FireCGun},S_CHAIN2}, // S_CHAIN1
-  {SPR_CHGG,1,4,{A_FireCGun},S_CHAIN3}, // S_CHAIN2
-  {SPR_CHGG,1,0,{A_ReFire},S_CHAIN},  // S_CHAIN3
-  {SPR_CHGF,0|FF_FULLBRIGHT,5,{A_Light1},S_LIGHTDONE},  // S_CHAINFLASH1
-  {SPR_CHGF,1|FF_FULLBRIGHT,5,{A_Light2},S_LIGHTDONE},  // S_CHAINFLASH2
-  {SPR_MISG,0,1,{A_WeaponReady},S_MISSILE}, // S_MISSILE
-  {SPR_MISG,0,1,{A_Lower},S_MISSILEDOWN}, // S_MISSILEDOWN
-  {SPR_MISG,0,1,{A_Raise},S_MISSILEUP}, // S_MISSILEUP
-  {SPR_MISG,1,8,{A_GunFlash},S_MISSILE2}, // S_MISSILE1
-  {SPR_MISG,1,12,{A_FireMissile},S_MISSILE3}, // S_MISSILE2
-  {SPR_MISG,1,0,{A_ReFire},S_MISSILE},  // S_MISSILE3
-  {SPR_MISF,0|FF_FULLBRIGHT,3,{A_Light1},S_MISSILEFLASH2},  // S_MISSILEFLASH1
+  {SPR_SHT2,9|FF_FULLBRIGHT,4,{.p2 = A_Light2},S_LIGHTDONE},  // S_DSGUNFLASH2
+  {SPR_CHGG,0,1,{.p2 = A_WeaponReady},S_CHAIN}, // S_CHAIN
+  {SPR_CHGG,0,1,{.p2 = A_Lower},S_CHAINDOWN}, // S_CHAINDOWN
+  {SPR_CHGG,0,1,{.p2 = A_Raise},S_CHAINUP}, // S_CHAINUP
+  {SPR_CHGG,0,4,{.p2 = A_FireCGun},S_CHAIN2}, // S_CHAIN1
+  {SPR_CHGG,1,4,{.p2 = A_FireCGun},S_CHAIN3}, // S_CHAIN2
+  {SPR_CHGG,1,0,{.p2 = A_ReFire},S_CHAIN},  // S_CHAIN3
+  {SPR_CHGF,0|FF_FULLBRIGHT,5,{.p2 = A_Light1},S_LIGHTDONE},  // S_CHAINFLASH1
+  {SPR_CHGF,1|FF_FULLBRIGHT,5,{.p2 = A_Light2},S_LIGHTDONE},  // S_CHAINFLASH2
+  {SPR_MISG,0,1,{.p2 = A_WeaponReady},S_MISSILE}, // S_MISSILE
+  {SPR_MISG,0,1,{.p2 = A_Lower},S_MISSILEDOWN}, // S_MISSILEDOWN
+  {SPR_MISG,0,1,{.p2 = A_Raise},S_MISSILEUP}, // S_MISSILEUP
+  {SPR_MISG,1,8,{.p2 = A_GunFlash},S_MISSILE2}, // S_MISSILE1
+  {SPR_MISG,1,12,{.p2 = A_FireMissile},S_MISSILE3}, // S_MISSILE2
+  {SPR_MISG,1,0,{.p2 = A_ReFire},S_MISSILE},  // S_MISSILE3
+  {SPR_MISF,0|FF_FULLBRIGHT,3,{.p2 = A_Light1},S_MISSILEFLASH2},  // S_MISSILEFLASH1
   {SPR_MISF,1|FF_FULLBRIGHT,4,{NULL},S_MISSILEFLASH3},  // S_MISSILEFLASH2
-  {SPR_MISF,2|FF_FULLBRIGHT,4,{A_Light2},S_MISSILEFLASH4},  // S_MISSILEFLASH3
-  {SPR_MISF,3|FF_FULLBRIGHT,4,{A_Light2},S_LIGHTDONE},  // S_MISSILEFLASH4
-  {SPR_SAWG,2,4,{A_WeaponReady},S_SAWB},  // S_SAW
-  {SPR_SAWG,3,4,{A_WeaponReady},S_SAW}, // S_SAWB
-  {SPR_SAWG,2,1,{A_Lower},S_SAWDOWN}, // S_SAWDOWN
-  {SPR_SAWG,2,1,{A_Raise},S_SAWUP}, // S_SAWUP
-  {SPR_SAWG,0,4,{A_Saw},S_SAW2}, // S_SAW1
-  {SPR_SAWG,1,4,{A_Saw},S_SAW3},  // S_SAW2
-  {SPR_SAWG,1,0,{A_ReFire},S_SAW},  // S_SAW3
-  {SPR_PLSG,0,1,{A_WeaponReady},S_PLASMA},  // S_PLASMA
-  {SPR_PLSG,0,1,{A_Lower},S_PLASMADOWN},  // S_PLASMADOWN
-  {SPR_PLSG,0,1,{A_Raise},S_PLASMAUP},  // S_PLASMAUP
-  {SPR_PLSG,0,3,{A_FirePlasma},S_PLASMA2},  // S_PLASMA1
-  {SPR_PLSG,1,20,{A_ReFire},S_PLASMA},  // S_PLASMA2
-  {SPR_PLSF,0|FF_FULLBRIGHT,4,{A_Light1},S_LIGHTDONE},  // S_PLASMAFLASH1
-  {SPR_PLSF,1|FF_FULLBRIGHT,4,{A_Light1},S_LIGHTDONE},  // S_PLASMAFLASH2
-  {SPR_BFGG,0,1,{A_WeaponReady},S_BFG}, // S_BFG
-  {SPR_BFGG,0,1,{A_Lower},S_BFGDOWN}, // S_BFGDOWN
-  {SPR_BFGG,0,1,{A_Raise},S_BFGUP}, // S_BFGUP
-  {SPR_BFGG,0,20,{A_BFGsound},S_BFG2},  // S_BFG1
-  {SPR_BFGG,1,10,{A_GunFlash},S_BFG3},  // S_BFG2
-  {SPR_BFGG,1,10,{A_FireBFG},S_BFG4}, // S_BFG3
-  {SPR_BFGG,1,20,{A_ReFire},S_BFG}, // S_BFG4
-  {SPR_BFGF,0|FF_FULLBRIGHT,11,{A_Light1},S_BFGFLASH2}, // S_BFGFLASH1
-  {SPR_BFGF,1|FF_FULLBRIGHT,6,{A_Light2},S_LIGHTDONE},  // S_BFGFLASH2
+  {SPR_MISF,2|FF_FULLBRIGHT,4,{.p2 = A_Light2},S_MISSILEFLASH4},  // S_MISSILEFLASH3
+  {SPR_MISF,3|FF_FULLBRIGHT,4,{.p2 = A_Light2},S_LIGHTDONE},  // S_MISSILEFLASH4
+  {SPR_SAWG,2,4,{.p2 = A_WeaponReady},S_SAWB},  // S_SAW
+  {SPR_SAWG,3,4,{.p2 = A_WeaponReady},S_SAW}, // S_SAWB
+  {SPR_SAWG,2,1,{.p2 = A_Lower},S_SAWDOWN}, // S_SAWDOWN
+  {SPR_SAWG,2,1,{.p2 = A_Raise},S_SAWUP}, // S_SAWUP
+  {SPR_SAWG,0,4,{.p2 = A_Saw},S_SAW2}, // S_SAW1
+  {SPR_SAWG,1,4,{.p2 = A_Saw},S_SAW3},  // S_SAW2
+  {SPR_SAWG,1,0,{.p2 = A_ReFire},S_SAW},  // S_SAW3
+  {SPR_PLSG,0,1,{.p2 = A_WeaponReady},S_PLASMA},  // S_PLASMA
+  {SPR_PLSG,0,1,{.p2 = A_Lower},S_PLASMADOWN},  // S_PLASMADOWN
+  {SPR_PLSG,0,1,{.p2 = A_Raise},S_PLASMAUP},  // S_PLASMAUP
+  {SPR_PLSG,0,3,{.p2 = A_FirePlasma},S_PLASMA2},  // S_PLASMA1
+  {SPR_PLSG,1,20,{.p2 = A_ReFire},S_PLASMA},  // S_PLASMA2
+  {SPR_PLSF,0|FF_FULLBRIGHT,4,{.p2 = A_Light1},S_LIGHTDONE},  // S_PLASMAFLASH1
+  {SPR_PLSF,1|FF_FULLBRIGHT,4,{.p2 = A_Light1},S_LIGHTDONE},  // S_PLASMAFLASH2
+  {SPR_BFGG,0,1,{.p2 = A_WeaponReady},S_BFG}, // S_BFG
+  {SPR_BFGG,0,1,{.p2 = A_Lower},S_BFGDOWN}, // S_BFGDOWN
+  {SPR_BFGG,0,1,{.p2 = A_Raise},S_BFGUP}, // S_BFGUP
+  {SPR_BFGG,0,20,{.p2 = A_BFGsound},S_BFG2},  // S_BFG1
+  {SPR_BFGG,1,10,{.p2 = A_GunFlash},S_BFG3},  // S_BFG2
+  {SPR_BFGG,1,10,{.p2 = A_FireBFG},S_BFG4}, // S_BFG3
+  {SPR_BFGG,1,20,{.p2 = A_ReFire},S_BFG}, // S_BFG4
+  {SPR_BFGF,0|FF_FULLBRIGHT,11,{.p2 = A_Light1},S_BFGFLASH2}, // S_BFGFLASH1
+  {SPR_BFGF,1|FF_FULLBRIGHT,6,{.p2 = A_Light2},S_LIGHTDONE},  // S_BFGFLASH2
   {SPR_BLUD,2,8,{NULL},S_BLOOD2}, // S_BLOOD1
   {SPR_BLUD,1,8,{NULL},S_BLOOD3}, // S_BLOOD2
   {SPR_BLUD,0,8,{NULL},S_NULL}, // S_BLOOD3
@@ -238,7 +238,7 @@ state_t original_states[NUMSTATES] = {
   {SPR_BFS1,1|FF_FULLBRIGHT,4,{NULL},S_BFGSHOT},  // S_BFGSHOT2
   {SPR_BFE1,0|FF_FULLBRIGHT,8,{NULL},S_BFGLAND2}, // S_BFGLAND
   {SPR_BFE1,1|FF_FULLBRIGHT,8,{NULL},S_BFGLAND3}, // S_BFGLAND2
-  {SPR_BFE1,2|FF_FULLBRIGHT,8,{A_BFGSpray},S_BFGLAND4}, // S_BFGLAND3
+  {SPR_BFE1,2|FF_FULLBRIGHT,8,{.p1 = A_BFGSpray},S_BFGLAND4}, // S_BFGLAND3
   {SPR_BFE1,3|FF_FULLBRIGHT,8,{NULL},S_BFGLAND5}, // S_BFGLAND4
   {SPR_BFE1,4|FF_FULLBRIGHT,8,{NULL},S_BFGLAND6}, // S_BFGLAND5
   {SPR_BFE1,5|FF_FULLBRIGHT,8,{NULL},S_NULL}, // S_BFGLAND6
@@ -246,7 +246,7 @@ state_t original_states[NUMSTATES] = {
   {SPR_BFE2,1|FF_FULLBRIGHT,8,{NULL},S_BFGEXP3},  // S_BFGEXP2
   {SPR_BFE2,2|FF_FULLBRIGHT,8,{NULL},S_BFGEXP4},  // S_BFGEXP3
   {SPR_BFE2,3|FF_FULLBRIGHT,8,{NULL},S_NULL}, // S_BFGEXP4
-  {SPR_MISL,1|FF_FULLBRIGHT,8,{A_Explode},S_EXPLODE2},  // S_EXPLODE1
+  {SPR_MISL,1|FF_FULLBRIGHT,8,{.p1 = A_Explode},S_EXPLODE2},  // S_EXPLODE1
   {SPR_MISL,2|FF_FULLBRIGHT,6,{NULL},S_EXPLODE3}, // S_EXPLODE2
   {SPR_MISL,3|FF_FULLBRIGHT,4,{NULL},S_NULL}, // S_EXPLODE3
   {SPR_TFOG,0|FF_FULLBRIGHT,6,{NULL},S_TFOG01}, // S_TFOG
@@ -276,46 +276,46 @@ state_t original_states[NUMSTATES] = {
   {SPR_PLAY,4,12,{NULL},S_PLAY},  // S_PLAY_ATK1
   {SPR_PLAY,5|FF_FULLBRIGHT,6,{NULL},S_PLAY_ATK1},  // S_PLAY_ATK2
   {SPR_PLAY,6,4,{NULL},S_PLAY_PAIN2}, // S_PLAY_PAIN
-  {SPR_PLAY,6,4,{A_Pain},S_PLAY}, // S_PLAY_PAIN2
+  {SPR_PLAY,6,4,{.p1 = A_Pain},S_PLAY}, // S_PLAY_PAIN2
   {SPR_PLAY,7,10,{NULL},S_PLAY_DIE2}, // S_PLAY_DIE1
-  {SPR_PLAY,8,10,{A_PlayerScream},S_PLAY_DIE3}, // S_PLAY_DIE2
-  {SPR_PLAY,9,10,{A_Fall},S_PLAY_DIE4}, // S_PLAY_DIE3
+  {SPR_PLAY,8,10,{.p1 = A_PlayerScream},S_PLAY_DIE3}, // S_PLAY_DIE2
+  {SPR_PLAY,9,10,{.p1 = A_Fall},S_PLAY_DIE4}, // S_PLAY_DIE3
   {SPR_PLAY,10,10,{NULL},S_PLAY_DIE5},  // S_PLAY_DIE4
   {SPR_PLAY,11,10,{NULL},S_PLAY_DIE6},  // S_PLAY_DIE5
   {SPR_PLAY,12,10,{NULL},S_PLAY_DIE7},  // S_PLAY_DIE6
   {SPR_PLAY,13,-1,{NULL},S_NULL}, // S_PLAY_DIE7
   {SPR_PLAY,14,5,{NULL},S_PLAY_XDIE2},  // S_PLAY_XDIE1
-  {SPR_PLAY,15,5,{A_XScream},S_PLAY_XDIE3}, // S_PLAY_XDIE2
-  {SPR_PLAY,16,5,{A_Fall},S_PLAY_XDIE4},  // S_PLAY_XDIE3
+  {SPR_PLAY,15,5,{.p1 = A_XScream},S_PLAY_XDIE3}, // S_PLAY_XDIE2
+  {SPR_PLAY,16,5,{.p1 = A_Fall},S_PLAY_XDIE4},  // S_PLAY_XDIE3
   {SPR_PLAY,17,5,{NULL},S_PLAY_XDIE5},  // S_PLAY_XDIE4
   {SPR_PLAY,18,5,{NULL},S_PLAY_XDIE6},  // S_PLAY_XDIE5
   {SPR_PLAY,19,5,{NULL},S_PLAY_XDIE7},  // S_PLAY_XDIE6
   {SPR_PLAY,20,5,{NULL},S_PLAY_XDIE8},  // S_PLAY_XDIE7
   {SPR_PLAY,21,5,{NULL},S_PLAY_XDIE9},  // S_PLAY_XDIE8
   {SPR_PLAY,22,-1,{NULL},S_NULL}, // S_PLAY_XDIE9
-  {SPR_POSS,0,10,{A_Look},S_POSS_STND2},  // S_POSS_STND
-  {SPR_POSS,1,10,{A_Look},S_POSS_STND}, // S_POSS_STND2
-  {SPR_POSS,0,4,{A_Chase},S_POSS_RUN2}, // S_POSS_RUN1
-  {SPR_POSS,0,4,{A_Chase},S_POSS_RUN3}, // S_POSS_RUN2
-  {SPR_POSS,1,4,{A_Chase},S_POSS_RUN4}, // S_POSS_RUN3
-  {SPR_POSS,1,4,{A_Chase},S_POSS_RUN5}, // S_POSS_RUN4
-  {SPR_POSS,2,4,{A_Chase},S_POSS_RUN6}, // S_POSS_RUN5
-  {SPR_POSS,2,4,{A_Chase},S_POSS_RUN7}, // S_POSS_RUN6
-  {SPR_POSS,3,4,{A_Chase},S_POSS_RUN8}, // S_POSS_RUN7
-  {SPR_POSS,3,4,{A_Chase},S_POSS_RUN1}, // S_POSS_RUN8
-  {SPR_POSS,4,10,{A_FaceTarget},S_POSS_ATK2}, // S_POSS_ATK1
-  {SPR_POSS,5,8,{A_PosAttack},S_POSS_ATK3}, // S_POSS_ATK2
+  {SPR_POSS,0,10,{.p1 = A_Look},S_POSS_STND2},  // S_POSS_STND
+  {SPR_POSS,1,10,{.p1 = A_Look},S_POSS_STND}, // S_POSS_STND2
+  {SPR_POSS,0,4,{.p1 = A_Chase},S_POSS_RUN2}, // S_POSS_RUN1
+  {SPR_POSS,0,4,{.p1 = A_Chase},S_POSS_RUN3}, // S_POSS_RUN2
+  {SPR_POSS,1,4,{.p1 = A_Chase},S_POSS_RUN4}, // S_POSS_RUN3
+  {SPR_POSS,1,4,{.p1 = A_Chase},S_POSS_RUN5}, // S_POSS_RUN4
+  {SPR_POSS,2,4,{.p1 = A_Chase},S_POSS_RUN6}, // S_POSS_RUN5
+  {SPR_POSS,2,4,{.p1 = A_Chase},S_POSS_RUN7}, // S_POSS_RUN6
+  {SPR_POSS,3,4,{.p1 = A_Chase},S_POSS_RUN8}, // S_POSS_RUN7
+  {SPR_POSS,3,4,{.p1 = A_Chase},S_POSS_RUN1}, // S_POSS_RUN8
+  {SPR_POSS,4,10,{.p1 = A_FaceTarget},S_POSS_ATK2}, // S_POSS_ATK1
+  {SPR_POSS,5,8,{.p1 = A_PosAttack},S_POSS_ATK3}, // S_POSS_ATK2
   {SPR_POSS,4,8,{NULL},S_POSS_RUN1},  // S_POSS_ATK3
   {SPR_POSS,6,3,{NULL},S_POSS_PAIN2}, // S_POSS_PAIN
-  {SPR_POSS,6,3,{A_Pain},S_POSS_RUN1},  // S_POSS_PAIN2
+  {SPR_POSS,6,3,{.p1 = A_Pain},S_POSS_RUN1},  // S_POSS_PAIN2
   {SPR_POSS,7,5,{NULL},S_POSS_DIE2},  // S_POSS_DIE1
-  {SPR_POSS,8,5,{A_Scream},S_POSS_DIE3},  // S_POSS_DIE2
-  {SPR_POSS,9,5,{A_Fall},S_POSS_DIE4},  // S_POSS_DIE3
+  {SPR_POSS,8,5,{.p1 = A_Scream},S_POSS_DIE3},  // S_POSS_DIE2
+  {SPR_POSS,9,5,{.p1 = A_Fall},S_POSS_DIE4},  // S_POSS_DIE3
   {SPR_POSS,10,5,{NULL},S_POSS_DIE5}, // S_POSS_DIE4
   {SPR_POSS,11,-1,{NULL},S_NULL}, // S_POSS_DIE5
   {SPR_POSS,12,5,{NULL},S_POSS_XDIE2},  // S_POSS_XDIE1
-  {SPR_POSS,13,5,{A_XScream},S_POSS_XDIE3}, // S_POSS_XDIE2
-  {SPR_POSS,14,5,{A_Fall},S_POSS_XDIE4},  // S_POSS_XDIE3
+  {SPR_POSS,13,5,{.p1 = A_XScream},S_POSS_XDIE3}, // S_POSS_XDIE2
+  {SPR_POSS,14,5,{.p1 = A_Fall},S_POSS_XDIE4},  // S_POSS_XDIE3
   {SPR_POSS,15,5,{NULL},S_POSS_XDIE5},  // S_POSS_XDIE4
   {SPR_POSS,16,5,{NULL},S_POSS_XDIE6},  // S_POSS_XDIE5
   {SPR_POSS,17,5,{NULL},S_POSS_XDIE7},  // S_POSS_XDIE6
@@ -326,29 +326,29 @@ state_t original_states[NUMSTATES] = {
   {SPR_POSS,9,5,{NULL},S_POSS_RAISE3},  // S_POSS_RAISE2
   {SPR_POSS,8,5,{NULL},S_POSS_RAISE4},  // S_POSS_RAISE3
   {SPR_POSS,7,5,{NULL},S_POSS_RUN1},  // S_POSS_RAISE4
-  {SPR_SPOS,0,10,{A_Look},S_SPOS_STND2},  // S_SPOS_STND
-  {SPR_SPOS,1,10,{A_Look},S_SPOS_STND}, // S_SPOS_STND2
-  {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN2}, // S_SPOS_RUN1
-  {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN3}, // S_SPOS_RUN2
-  {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN4}, // S_SPOS_RUN3
-  {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN5}, // S_SPOS_RUN4
-  {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN6}, // S_SPOS_RUN5
-  {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN7}, // S_SPOS_RUN6
-  {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN8}, // S_SPOS_RUN7
-  {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN1}, // S_SPOS_RUN8
-  {SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2}, // S_SPOS_ATK1
-  {SPR_SPOS,5|FF_FULLBRIGHT,10,{A_SPosAttack},S_SPOS_ATK3}, // S_SPOS_ATK2
+  {SPR_SPOS,0,10,{.p1 = A_Look},S_SPOS_STND2},  // S_SPOS_STND
+  {SPR_SPOS,1,10,{.p1 = A_Look},S_SPOS_STND}, // S_SPOS_STND2
+  {SPR_SPOS,0,3,{.p1 = A_Chase},S_SPOS_RUN2}, // S_SPOS_RUN1
+  {SPR_SPOS,0,3,{.p1 = A_Chase},S_SPOS_RUN3}, // S_SPOS_RUN2
+  {SPR_SPOS,1,3,{.p1 = A_Chase},S_SPOS_RUN4}, // S_SPOS_RUN3
+  {SPR_SPOS,1,3,{.p1 = A_Chase},S_SPOS_RUN5}, // S_SPOS_RUN4
+  {SPR_SPOS,2,3,{.p1 = A_Chase},S_SPOS_RUN6}, // S_SPOS_RUN5
+  {SPR_SPOS,2,3,{.p1 = A_Chase},S_SPOS_RUN7}, // S_SPOS_RUN6
+  {SPR_SPOS,3,3,{.p1 = A_Chase},S_SPOS_RUN8}, // S_SPOS_RUN7
+  {SPR_SPOS,3,3,{.p1 = A_Chase},S_SPOS_RUN1}, // S_SPOS_RUN8
+  {SPR_SPOS,4,10,{.p1 = A_FaceTarget},S_SPOS_ATK2}, // S_SPOS_ATK1
+  {SPR_SPOS,5|FF_FULLBRIGHT,10,{.p1 = A_SPosAttack},S_SPOS_ATK3}, // S_SPOS_ATK2
   {SPR_SPOS,4,10,{NULL},S_SPOS_RUN1}, // S_SPOS_ATK3
   {SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2}, // S_SPOS_PAIN
-  {SPR_SPOS,6,3,{A_Pain},S_SPOS_RUN1},  // S_SPOS_PAIN2
+  {SPR_SPOS,6,3,{.p1 = A_Pain},S_SPOS_RUN1},  // S_SPOS_PAIN2
   {SPR_SPOS,7,5,{NULL},S_SPOS_DIE2},  // S_SPOS_DIE1
-  {SPR_SPOS,8,5,{A_Scream},S_SPOS_DIE3},  // S_SPOS_DIE2
-  {SPR_SPOS,9,5,{A_Fall},S_SPOS_DIE4},  // S_SPOS_DIE3
+  {SPR_SPOS,8,5,{.p1 = A_Scream},S_SPOS_DIE3},  // S_SPOS_DIE2
+  {SPR_SPOS,9,5,{.p1 = A_Fall},S_SPOS_DIE4},  // S_SPOS_DIE3
   {SPR_SPOS,10,5,{NULL},S_SPOS_DIE5}, // S_SPOS_DIE4
   {SPR_SPOS,11,-1,{NULL},S_NULL}, // S_SPOS_DIE5
   {SPR_SPOS,12,5,{NULL},S_SPOS_XDIE2},  // S_SPOS_XDIE1
-  {SPR_SPOS,13,5,{A_XScream},S_SPOS_XDIE3}, // S_SPOS_XDIE2
-  {SPR_SPOS,14,5,{A_Fall},S_SPOS_XDIE4},  // S_SPOS_XDIE3
+  {SPR_SPOS,13,5,{.p1 = A_XScream},S_SPOS_XDIE3}, // S_SPOS_XDIE2
+  {SPR_SPOS,14,5,{.p1 = A_Fall},S_SPOS_XDIE4},  // S_SPOS_XDIE3
   {SPR_SPOS,15,5,{NULL},S_SPOS_XDIE5},  // S_SPOS_XDIE4
   {SPR_SPOS,16,5,{NULL},S_SPOS_XDIE6},  // S_SPOS_XDIE5
   {SPR_SPOS,17,5,{NULL},S_SPOS_XDIE7},  // S_SPOS_XDIE6
@@ -360,39 +360,39 @@ state_t original_states[NUMSTATES] = {
   {SPR_SPOS,9,5,{NULL},S_SPOS_RAISE4},  // S_SPOS_RAISE3
   {SPR_SPOS,8,5,{NULL},S_SPOS_RAISE5},  // S_SPOS_RAISE4
   {SPR_SPOS,7,5,{NULL},S_SPOS_RUN1},  // S_SPOS_RAISE5
-  {SPR_VILE,0,10,{A_Look},S_VILE_STND2},  // S_VILE_STND
-  {SPR_VILE,1,10,{A_Look},S_VILE_STND}, // S_VILE_STND2
-  {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN2}, // S_VILE_RUN1
-  {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN3}, // S_VILE_RUN2
-  {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN4}, // S_VILE_RUN3
-  {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN5}, // S_VILE_RUN4
-  {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN6}, // S_VILE_RUN5
-  {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN7}, // S_VILE_RUN6
-  {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN8}, // S_VILE_RUN7
-  {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN9}, // S_VILE_RUN8
-  {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN10},  // S_VILE_RUN9
-  {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN11},  // S_VILE_RUN10
-  {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN12},  // S_VILE_RUN11
-  {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN1}, // S_VILE_RUN12
-  {SPR_VILE,6|FF_FULLBRIGHT,0,{A_VileStart},S_VILE_ATK2}, // S_VILE_ATK1
-  {SPR_VILE,6|FF_FULLBRIGHT,10,{A_FaceTarget},S_VILE_ATK3}, // S_VILE_ATK2
-  {SPR_VILE,7|FF_FULLBRIGHT,8,{A_VileTarget},S_VILE_ATK4},  // S_VILE_ATK3
-  {SPR_VILE,8|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK5},  // S_VILE_ATK4
-  {SPR_VILE,9|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK6},  // S_VILE_ATK5
-  {SPR_VILE,10|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK7},  // S_VILE_ATK6
-  {SPR_VILE,11|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK8},  // S_VILE_ATK7
-  {SPR_VILE,12|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK9},  // S_VILE_ATK8
-  {SPR_VILE,13|FF_FULLBRIGHT,8,{A_FaceTarget},S_VILE_ATK10}, // S_VILE_ATK9
-  {SPR_VILE,14|FF_FULLBRIGHT,8,{A_VileAttack},S_VILE_ATK11}, // S_VILE_ATK10
+  {SPR_VILE,0,10,{.p1 = A_Look},S_VILE_STND2},  // S_VILE_STND
+  {SPR_VILE,1,10,{.p1 = A_Look},S_VILE_STND}, // S_VILE_STND2
+  {SPR_VILE,0,2,{.p1 = A_VileChase},S_VILE_RUN2}, // S_VILE_RUN1
+  {SPR_VILE,0,2,{.p1 = A_VileChase},S_VILE_RUN3}, // S_VILE_RUN2
+  {SPR_VILE,1,2,{.p1 = A_VileChase},S_VILE_RUN4}, // S_VILE_RUN3
+  {SPR_VILE,1,2,{.p1 = A_VileChase},S_VILE_RUN5}, // S_VILE_RUN4
+  {SPR_VILE,2,2,{.p1 = A_VileChase},S_VILE_RUN6}, // S_VILE_RUN5
+  {SPR_VILE,2,2,{.p1 = A_VileChase},S_VILE_RUN7}, // S_VILE_RUN6
+  {SPR_VILE,3,2,{.p1 = A_VileChase},S_VILE_RUN8}, // S_VILE_RUN7
+  {SPR_VILE,3,2,{.p1 = A_VileChase},S_VILE_RUN9}, // S_VILE_RUN8
+  {SPR_VILE,4,2,{.p1 = A_VileChase},S_VILE_RUN10},  // S_VILE_RUN9
+  {SPR_VILE,4,2,{.p1 = A_VileChase},S_VILE_RUN11},  // S_VILE_RUN10
+  {SPR_VILE,5,2,{.p1 = A_VileChase},S_VILE_RUN12},  // S_VILE_RUN11
+  {SPR_VILE,5,2,{.p1 = A_VileChase},S_VILE_RUN1}, // S_VILE_RUN12
+  {SPR_VILE,6|FF_FULLBRIGHT,0,{.p1 = A_VileStart},S_VILE_ATK2}, // S_VILE_ATK1
+  {SPR_VILE,6|FF_FULLBRIGHT,10,{.p1 = A_FaceTarget},S_VILE_ATK3}, // S_VILE_ATK2
+  {SPR_VILE,7|FF_FULLBRIGHT,8,{.p1 = A_VileTarget},S_VILE_ATK4},  // S_VILE_ATK3
+  {SPR_VILE,8|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK5},  // S_VILE_ATK4
+  {SPR_VILE,9|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK6},  // S_VILE_ATK5
+  {SPR_VILE,10|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK7},  // S_VILE_ATK6
+  {SPR_VILE,11|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK8},  // S_VILE_ATK7
+  {SPR_VILE,12|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK9},  // S_VILE_ATK8
+  {SPR_VILE,13|FF_FULLBRIGHT,8,{.p1 = A_FaceTarget},S_VILE_ATK10}, // S_VILE_ATK9
+  {SPR_VILE,14|FF_FULLBRIGHT,8,{.p1 = A_VileAttack},S_VILE_ATK11}, // S_VILE_ATK10
   {SPR_VILE,15|FF_FULLBRIGHT,20,{NULL},S_VILE_RUN1}, // S_VILE_ATK11
   {SPR_VILE,26|FF_FULLBRIGHT,10,{NULL},S_VILE_HEAL2},  // S_VILE_HEAL1
   {SPR_VILE,27|FF_FULLBRIGHT,10,{NULL},S_VILE_HEAL3},  // S_VILE_HEAL2
   {SPR_VILE,28|FF_FULLBRIGHT,10,{NULL},S_VILE_RUN1}, // S_VILE_HEAL3
   {SPR_VILE,16,5,{NULL},S_VILE_PAIN2},  // S_VILE_PAIN
-  {SPR_VILE,16,5,{A_Pain},S_VILE_RUN1}, // S_VILE_PAIN2
+  {SPR_VILE,16,5,{.p1 = A_Pain},S_VILE_RUN1}, // S_VILE_PAIN2
   {SPR_VILE,16,7,{NULL},S_VILE_DIE2}, // S_VILE_DIE1
-  {SPR_VILE,17,7,{A_Scream},S_VILE_DIE3},  // S_VILE_DIE2
-  {SPR_VILE,18,7,{A_Fall},S_VILE_DIE4}, // S_VILE_DIE3
+  {SPR_VILE,17,7,{.p1 = A_Scream},S_VILE_DIE3},  // S_VILE_DIE2
+  {SPR_VILE,18,7,{.p1 = A_Fall},S_VILE_DIE4}, // S_VILE_DIE3
   {SPR_VILE,19,7,{NULL},S_VILE_DIE5}, // S_VILE_DIE4
   {SPR_VILE,20,7,{NULL},S_VILE_DIE6}, // S_VILE_DIE5
   {SPR_VILE,21,7,{NULL},S_VILE_DIE7}, // S_VILE_DIE6
@@ -400,74 +400,74 @@ state_t original_states[NUMSTATES] = {
   {SPR_VILE,23,5,{NULL},S_VILE_DIE9}, // S_VILE_DIE8
   {SPR_VILE,24,5,{NULL},S_VILE_DIE10},  // S_VILE_DIE9
   {SPR_VILE,25,-1,{NULL},S_NULL}, // S_VILE_DIE10
-  {SPR_FIRE,0|FF_FULLBRIGHT,2,{A_StartFire},S_FIRE2}, // S_FIRE1
-  {SPR_FIRE,1|FF_FULLBRIGHT,2,{A_Fire},S_FIRE3},  // S_FIRE2
-  {SPR_FIRE,0|FF_FULLBRIGHT,2,{A_Fire},S_FIRE4},  // S_FIRE3
-  {SPR_FIRE,1|FF_FULLBRIGHT,2,{A_Fire},S_FIRE5},  // S_FIRE4
-  {SPR_FIRE,2|FF_FULLBRIGHT,2,{A_FireCrackle},S_FIRE6}, // S_FIRE5
-  {SPR_FIRE,1|FF_FULLBRIGHT,2,{A_Fire},S_FIRE7},  // S_FIRE6
-  {SPR_FIRE,2|FF_FULLBRIGHT,2,{A_Fire},S_FIRE8},  // S_FIRE7
-  {SPR_FIRE,1|FF_FULLBRIGHT,2,{A_Fire},S_FIRE9},  // S_FIRE8
-  {SPR_FIRE,2|FF_FULLBRIGHT,2,{A_Fire},S_FIRE10}, // S_FIRE9
-  {SPR_FIRE,3|FF_FULLBRIGHT,2,{A_Fire},S_FIRE11}, // S_FIRE10
-  {SPR_FIRE,2|FF_FULLBRIGHT,2,{A_Fire},S_FIRE12}, // S_FIRE11
-  {SPR_FIRE,3|FF_FULLBRIGHT,2,{A_Fire},S_FIRE13}, // S_FIRE12
-  {SPR_FIRE,2|FF_FULLBRIGHT,2,{A_Fire},S_FIRE14}, // S_FIRE13
-  {SPR_FIRE,3|FF_FULLBRIGHT,2,{A_Fire},S_FIRE15}, // S_FIRE14
-  {SPR_FIRE,4|FF_FULLBRIGHT,2,{A_Fire},S_FIRE16}, // S_FIRE15
-  {SPR_FIRE,3|FF_FULLBRIGHT,2,{A_Fire},S_FIRE17}, // S_FIRE16
-  {SPR_FIRE,4|FF_FULLBRIGHT,2,{A_Fire},S_FIRE18}, // S_FIRE17
-  {SPR_FIRE,3|FF_FULLBRIGHT,2,{A_Fire},S_FIRE19}, // S_FIRE18
-  {SPR_FIRE,4|FF_FULLBRIGHT,2,{A_FireCrackle},S_FIRE20},  // S_FIRE19
-  {SPR_FIRE,5|FF_FULLBRIGHT,2,{A_Fire},S_FIRE21}, // S_FIRE20
-  {SPR_FIRE,4|FF_FULLBRIGHT,2,{A_Fire},S_FIRE22}, // S_FIRE21
-  {SPR_FIRE,5|FF_FULLBRIGHT,2,{A_Fire},S_FIRE23}, // S_FIRE22
-  {SPR_FIRE,4|FF_FULLBRIGHT,2,{A_Fire},S_FIRE24}, // S_FIRE23
-  {SPR_FIRE,5|FF_FULLBRIGHT,2,{A_Fire},S_FIRE25}, // S_FIRE24
-  {SPR_FIRE,6|FF_FULLBRIGHT,2,{A_Fire},S_FIRE26}, // S_FIRE25
-  {SPR_FIRE,7|FF_FULLBRIGHT,2,{A_Fire},S_FIRE27}, // S_FIRE26
-  {SPR_FIRE,6|FF_FULLBRIGHT,2,{A_Fire},S_FIRE28}, // S_FIRE27
-  {SPR_FIRE,7|FF_FULLBRIGHT,2,{A_Fire},S_FIRE29}, // S_FIRE28
-  {SPR_FIRE,6|FF_FULLBRIGHT,2,{A_Fire},S_FIRE30}, // S_FIRE29
-  {SPR_FIRE,7|FF_FULLBRIGHT,2,{A_Fire},S_NULL}, // S_FIRE30
+  {SPR_FIRE,0|FF_FULLBRIGHT,2,{.p1 = A_StartFire},S_FIRE2}, // S_FIRE1
+  {SPR_FIRE,1|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE3},  // S_FIRE2
+  {SPR_FIRE,0|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE4},  // S_FIRE3
+  {SPR_FIRE,1|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE5},  // S_FIRE4
+  {SPR_FIRE,2|FF_FULLBRIGHT,2,{.p1 = A_FireCrackle},S_FIRE6}, // S_FIRE5
+  {SPR_FIRE,1|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE7},  // S_FIRE6
+  {SPR_FIRE,2|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE8},  // S_FIRE7
+  {SPR_FIRE,1|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE9},  // S_FIRE8
+  {SPR_FIRE,2|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE10}, // S_FIRE9
+  {SPR_FIRE,3|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE11}, // S_FIRE10
+  {SPR_FIRE,2|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE12}, // S_FIRE11
+  {SPR_FIRE,3|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE13}, // S_FIRE12
+  {SPR_FIRE,2|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE14}, // S_FIRE13
+  {SPR_FIRE,3|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE15}, // S_FIRE14
+  {SPR_FIRE,4|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE16}, // S_FIRE15
+  {SPR_FIRE,3|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE17}, // S_FIRE16
+  {SPR_FIRE,4|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE18}, // S_FIRE17
+  {SPR_FIRE,3|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE19}, // S_FIRE18
+  {SPR_FIRE,4|FF_FULLBRIGHT,2,{.p1 = A_FireCrackle},S_FIRE20},  // S_FIRE19
+  {SPR_FIRE,5|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE21}, // S_FIRE20
+  {SPR_FIRE,4|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE22}, // S_FIRE21
+  {SPR_FIRE,5|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE23}, // S_FIRE22
+  {SPR_FIRE,4|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE24}, // S_FIRE23
+  {SPR_FIRE,5|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE25}, // S_FIRE24
+  {SPR_FIRE,6|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE26}, // S_FIRE25
+  {SPR_FIRE,7|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE27}, // S_FIRE26
+  {SPR_FIRE,6|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE28}, // S_FIRE27
+  {SPR_FIRE,7|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE29}, // S_FIRE28
+  {SPR_FIRE,6|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_FIRE30}, // S_FIRE29
+  {SPR_FIRE,7|FF_FULLBRIGHT,2,{.p1 = A_Fire},S_NULL}, // S_FIRE30
   {SPR_PUFF,1,4,{NULL},S_SMOKE2}, // S_SMOKE1
   {SPR_PUFF,2,4,{NULL},S_SMOKE3}, // S_SMOKE2
   {SPR_PUFF,1,4,{NULL},S_SMOKE4}, // S_SMOKE3
   {SPR_PUFF,2,4,{NULL},S_SMOKE5}, // S_SMOKE4
   {SPR_PUFF,3,4,{NULL},S_NULL}, // S_SMOKE5
-  {SPR_FATB,0|FF_FULLBRIGHT,2,{A_Tracer},S_TRACER2},  // S_TRACER
-  {SPR_FATB,1|FF_FULLBRIGHT,2,{A_Tracer},S_TRACER}, // S_TRACER2
+  {SPR_FATB,0|FF_FULLBRIGHT,2,{.p1 = A_Tracer},S_TRACER2},  // S_TRACER
+  {SPR_FATB,1|FF_FULLBRIGHT,2,{.p1 = A_Tracer},S_TRACER}, // S_TRACER2
   {SPR_FBXP,0|FF_FULLBRIGHT,8,{NULL},S_TRACEEXP2},  // S_TRACEEXP1
   {SPR_FBXP,1|FF_FULLBRIGHT,6,{NULL},S_TRACEEXP3},  // S_TRACEEXP2
   {SPR_FBXP,2|FF_FULLBRIGHT,4,{NULL},S_NULL}, // S_TRACEEXP3
-  {SPR_SKEL,0,10,{A_Look},S_SKEL_STND2},  // S_SKEL_STND
-  {SPR_SKEL,1,10,{A_Look},S_SKEL_STND}, // S_SKEL_STND2
-  {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN2}, // S_SKEL_RUN1
-  {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN3}, // S_SKEL_RUN2
-  {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN4}, // S_SKEL_RUN3
-  {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN5}, // S_SKEL_RUN4
-  {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN6}, // S_SKEL_RUN5
-  {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN7}, // S_SKEL_RUN6
-  {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN8}, // S_SKEL_RUN7
-  {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN9}, // S_SKEL_RUN8
-  {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN10},  // S_SKEL_RUN9
-  {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN11},  // S_SKEL_RUN10
-  {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN12},  // S_SKEL_RUN11
-  {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN1}, // S_SKEL_RUN12
-  {SPR_SKEL,6,0,{A_FaceTarget},S_SKEL_FIST2}, // S_SKEL_FIST1
-  {SPR_SKEL,6,6,{A_SkelWhoosh},S_SKEL_FIST3}, // S_SKEL_FIST2
-  {SPR_SKEL,7,6,{A_FaceTarget},S_SKEL_FIST4}, // S_SKEL_FIST3
-  {SPR_SKEL,8,6,{A_SkelFist},S_SKEL_RUN1},  // S_SKEL_FIST4
-  {SPR_SKEL,9|FF_FULLBRIGHT,0,{A_FaceTarget},S_SKEL_MISS2}, // S_SKEL_MISS1
-  {SPR_SKEL,9|FF_FULLBRIGHT,10,{A_FaceTarget},S_SKEL_MISS3},  // S_SKEL_MISS2
-  {SPR_SKEL,10,10,{A_SkelMissile},S_SKEL_MISS4},  // S_SKEL_MISS3
-  {SPR_SKEL,10,10,{A_FaceTarget},S_SKEL_RUN1},  // S_SKEL_MISS4
+  {SPR_SKEL,0,10,{.p1 = A_Look},S_SKEL_STND2},  // S_SKEL_STND
+  {SPR_SKEL,1,10,{.p1 = A_Look},S_SKEL_STND}, // S_SKEL_STND2
+  {SPR_SKEL,0,2,{.p1 = A_Chase},S_SKEL_RUN2}, // S_SKEL_RUN1
+  {SPR_SKEL,0,2,{.p1 = A_Chase},S_SKEL_RUN3}, // S_SKEL_RUN2
+  {SPR_SKEL,1,2,{.p1 = A_Chase},S_SKEL_RUN4}, // S_SKEL_RUN3
+  {SPR_SKEL,1,2,{.p1 = A_Chase},S_SKEL_RUN5}, // S_SKEL_RUN4
+  {SPR_SKEL,2,2,{.p1 = A_Chase},S_SKEL_RUN6}, // S_SKEL_RUN5
+  {SPR_SKEL,2,2,{.p1 = A_Chase},S_SKEL_RUN7}, // S_SKEL_RUN6
+  {SPR_SKEL,3,2,{.p1 = A_Chase},S_SKEL_RUN8}, // S_SKEL_RUN7
+  {SPR_SKEL,3,2,{.p1 = A_Chase},S_SKEL_RUN9}, // S_SKEL_RUN8
+  {SPR_SKEL,4,2,{.p1 = A_Chase},S_SKEL_RUN10},  // S_SKEL_RUN9
+  {SPR_SKEL,4,2,{.p1 = A_Chase},S_SKEL_RUN11},  // S_SKEL_RUN10
+  {SPR_SKEL,5,2,{.p1 = A_Chase},S_SKEL_RUN12},  // S_SKEL_RUN11
+  {SPR_SKEL,5,2,{.p1 = A_Chase},S_SKEL_RUN1}, // S_SKEL_RUN12
+  {SPR_SKEL,6,0,{.p1 = A_FaceTarget},S_SKEL_FIST2}, // S_SKEL_FIST1
+  {SPR_SKEL,6,6,{.p1 = A_SkelWhoosh},S_SKEL_FIST3}, // S_SKEL_FIST2
+  {SPR_SKEL,7,6,{.p1 = A_FaceTarget},S_SKEL_FIST4}, // S_SKEL_FIST3
+  {SPR_SKEL,8,6,{.p1 = A_SkelFist},S_SKEL_RUN1},  // S_SKEL_FIST4
+  {SPR_SKEL,9|FF_FULLBRIGHT,0,{.p1 = A_FaceTarget},S_SKEL_MISS2}, // S_SKEL_MISS1
+  {SPR_SKEL,9|FF_FULLBRIGHT,10,{.p1 = A_FaceTarget},S_SKEL_MISS3},  // S_SKEL_MISS2
+  {SPR_SKEL,10,10,{.p1 = A_SkelMissile},S_SKEL_MISS4},  // S_SKEL_MISS3
+  {SPR_SKEL,10,10,{.p1 = A_FaceTarget},S_SKEL_RUN1},  // S_SKEL_MISS4
   {SPR_SKEL,11,5,{NULL},S_SKEL_PAIN2},  // S_SKEL_PAIN
-  {SPR_SKEL,11,5,{A_Pain},S_SKEL_RUN1}, // S_SKEL_PAIN2
+  {SPR_SKEL,11,5,{.p1 = A_Pain},S_SKEL_RUN1}, // S_SKEL_PAIN2
   {SPR_SKEL,11,7,{NULL},S_SKEL_DIE2}, // S_SKEL_DIE1
   {SPR_SKEL,12,7,{NULL},S_SKEL_DIE3}, // S_SKEL_DIE2
-  {SPR_SKEL,13,7,{A_Scream},S_SKEL_DIE4}, // S_SKEL_DIE3
-  {SPR_SKEL,14,7,{A_Fall},S_SKEL_DIE5}, // S_SKEL_DIE4
+  {SPR_SKEL,13,7,{.p1 = A_Scream},S_SKEL_DIE4}, // S_SKEL_DIE3
+  {SPR_SKEL,14,7,{.p1 = A_Fall},S_SKEL_DIE5}, // S_SKEL_DIE4
   {SPR_SKEL,15,7,{NULL},S_SKEL_DIE6}, // S_SKEL_DIE5
   {SPR_SKEL,16,-1,{NULL},S_NULL}, // S_SKEL_DIE6
   {SPR_SKEL,16,5,{NULL},S_SKEL_RAISE2}, // S_SKEL_RAISE1
@@ -481,42 +481,42 @@ state_t original_states[NUMSTATES] = {
   {SPR_MISL,1|FF_FULLBRIGHT,8,{NULL},S_FATSHOTX2},  // S_FATSHOTX1
   {SPR_MISL,2|FF_FULLBRIGHT,6,{NULL},S_FATSHOTX3},  // S_FATSHOTX2
   {SPR_MISL,3|FF_FULLBRIGHT,4,{NULL},S_NULL}, // S_FATSHOTX3
-  {SPR_FATT,0,15,{A_Look},S_FATT_STND2},  // S_FATT_STND
-  {SPR_FATT,1,15,{A_Look},S_FATT_STND}, // S_FATT_STND2
-  {SPR_FATT,0,4,{A_Chase},S_FATT_RUN2}, // S_FATT_RUN1
-  {SPR_FATT,0,4,{A_Chase},S_FATT_RUN3}, // S_FATT_RUN2
-  {SPR_FATT,1,4,{A_Chase},S_FATT_RUN4}, // S_FATT_RUN3
-  {SPR_FATT,1,4,{A_Chase},S_FATT_RUN5}, // S_FATT_RUN4
-  {SPR_FATT,2,4,{A_Chase},S_FATT_RUN6}, // S_FATT_RUN5
-  {SPR_FATT,2,4,{A_Chase},S_FATT_RUN7}, // S_FATT_RUN6
-  {SPR_FATT,3,4,{A_Chase},S_FATT_RUN8}, // S_FATT_RUN7
-  {SPR_FATT,3,4,{A_Chase},S_FATT_RUN9}, // S_FATT_RUN8
-  {SPR_FATT,4,4,{A_Chase},S_FATT_RUN10},  // S_FATT_RUN9
-  {SPR_FATT,4,4,{A_Chase},S_FATT_RUN11},  // S_FATT_RUN10
-  {SPR_FATT,5,4,{A_Chase},S_FATT_RUN12},  // S_FATT_RUN11
-  {SPR_FATT,5,4,{A_Chase},S_FATT_RUN1}, // S_FATT_RUN12
-  {SPR_FATT,6,20,{A_FatRaise},S_FATT_ATK2}, // S_FATT_ATK1
-  {SPR_FATT,7|FF_FULLBRIGHT,10,{A_FatAttack1},S_FATT_ATK3}, // S_FATT_ATK2
-  {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK4},  // S_FATT_ATK3
-  {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK5},  // S_FATT_ATK4
-  {SPR_FATT,7|FF_FULLBRIGHT,10,{A_FatAttack2},S_FATT_ATK6}, // S_FATT_ATK5
-  {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK7},  // S_FATT_ATK6
-  {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK8},  // S_FATT_ATK7
-  {SPR_FATT,7|FF_FULLBRIGHT,10,{A_FatAttack3},S_FATT_ATK9}, // S_FATT_ATK8
-  {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK10}, // S_FATT_ATK9
-  {SPR_FATT,6,5,{A_FaceTarget},S_FATT_RUN1},  // S_FATT_ATK10
+  {SPR_FATT,0,15,{.p1 = A_Look},S_FATT_STND2},  // S_FATT_STND
+  {SPR_FATT,1,15,{.p1 = A_Look},S_FATT_STND}, // S_FATT_STND2
+  {SPR_FATT,0,4,{.p1 = A_Chase},S_FATT_RUN2}, // S_FATT_RUN1
+  {SPR_FATT,0,4,{.p1 = A_Chase},S_FATT_RUN3}, // S_FATT_RUN2
+  {SPR_FATT,1,4,{.p1 = A_Chase},S_FATT_RUN4}, // S_FATT_RUN3
+  {SPR_FATT,1,4,{.p1 = A_Chase},S_FATT_RUN5}, // S_FATT_RUN4
+  {SPR_FATT,2,4,{.p1 = A_Chase},S_FATT_RUN6}, // S_FATT_RUN5
+  {SPR_FATT,2,4,{.p1 = A_Chase},S_FATT_RUN7}, // S_FATT_RUN6
+  {SPR_FATT,3,4,{.p1 = A_Chase},S_FATT_RUN8}, // S_FATT_RUN7
+  {SPR_FATT,3,4,{.p1 = A_Chase},S_FATT_RUN9}, // S_FATT_RUN8
+  {SPR_FATT,4,4,{.p1 = A_Chase},S_FATT_RUN10},  // S_FATT_RUN9
+  {SPR_FATT,4,4,{.p1 = A_Chase},S_FATT_RUN11},  // S_FATT_RUN10
+  {SPR_FATT,5,4,{.p1 = A_Chase},S_FATT_RUN12},  // S_FATT_RUN11
+  {SPR_FATT,5,4,{.p1 = A_Chase},S_FATT_RUN1}, // S_FATT_RUN12
+  {SPR_FATT,6,20,{.p1 = A_FatRaise},S_FATT_ATK2}, // S_FATT_ATK1
+  {SPR_FATT,7|FF_FULLBRIGHT,10,{.p1 = A_FatAttack1},S_FATT_ATK3}, // S_FATT_ATK2
+  {SPR_FATT,8,5,{.p1 = A_FaceTarget},S_FATT_ATK4},  // S_FATT_ATK3
+  {SPR_FATT,6,5,{.p1 = A_FaceTarget},S_FATT_ATK5},  // S_FATT_ATK4
+  {SPR_FATT,7|FF_FULLBRIGHT,10,{.p1 = A_FatAttack2},S_FATT_ATK6}, // S_FATT_ATK5
+  {SPR_FATT,8,5,{.p1 = A_FaceTarget},S_FATT_ATK7},  // S_FATT_ATK6
+  {SPR_FATT,6,5,{.p1 = A_FaceTarget},S_FATT_ATK8},  // S_FATT_ATK7
+  {SPR_FATT,7|FF_FULLBRIGHT,10,{.p1 = A_FatAttack3},S_FATT_ATK9}, // S_FATT_ATK8
+  {SPR_FATT,8,5,{.p1 = A_FaceTarget},S_FATT_ATK10}, // S_FATT_ATK9
+  {SPR_FATT,6,5,{.p1 = A_FaceTarget},S_FATT_RUN1},  // S_FATT_ATK10
   {SPR_FATT,9,3,{NULL},S_FATT_PAIN2}, // S_FATT_PAIN
-  {SPR_FATT,9,3,{A_Pain},S_FATT_RUN1},  // S_FATT_PAIN2
+  {SPR_FATT,9,3,{.p1 = A_Pain},S_FATT_RUN1},  // S_FATT_PAIN2
   {SPR_FATT,10,6,{NULL},S_FATT_DIE2}, // S_FATT_DIE1
-  {SPR_FATT,11,6,{A_Scream},S_FATT_DIE3}, // S_FATT_DIE2
-  {SPR_FATT,12,6,{A_Fall},S_FATT_DIE4}, // S_FATT_DIE3
+  {SPR_FATT,11,6,{.p1 = A_Scream},S_FATT_DIE3}, // S_FATT_DIE2
+  {SPR_FATT,12,6,{.p1 = A_Fall},S_FATT_DIE4}, // S_FATT_DIE3
   {SPR_FATT,13,6,{NULL},S_FATT_DIE5}, // S_FATT_DIE4
   {SPR_FATT,14,6,{NULL},S_FATT_DIE6}, // S_FATT_DIE5
   {SPR_FATT,15,6,{NULL},S_FATT_DIE7}, // S_FATT_DIE6
   {SPR_FATT,16,6,{NULL},S_FATT_DIE8}, // S_FATT_DIE7
   {SPR_FATT,17,6,{NULL},S_FATT_DIE9}, // S_FATT_DIE8
   {SPR_FATT,18,6,{NULL},S_FATT_DIE10},  // S_FATT_DIE9
-  {SPR_FATT,19,-1,{A_BossDeath},S_NULL},  // S_FATT_DIE10
+  {SPR_FATT,19,-1,{.p1 = A_BossDeath},S_NULL},  // S_FATT_DIE10
   {SPR_FATT,17,5,{NULL},S_FATT_RAISE2}, // S_FATT_RAISE1
   {SPR_FATT,16,5,{NULL},S_FATT_RAISE3}, // S_FATT_RAISE2
   {SPR_FATT,15,5,{NULL},S_FATT_RAISE4}, // S_FATT_RAISE3
@@ -525,32 +525,32 @@ state_t original_states[NUMSTATES] = {
   {SPR_FATT,12,5,{NULL},S_FATT_RAISE7}, // S_FATT_RAISE6
   {SPR_FATT,11,5,{NULL},S_FATT_RAISE8}, // S_FATT_RAISE7
   {SPR_FATT,10,5,{NULL},S_FATT_RUN1}, // S_FATT_RAISE8
-  {SPR_CPOS,0,10,{A_Look},S_CPOS_STND2},  // S_CPOS_STND
-  {SPR_CPOS,1,10,{A_Look},S_CPOS_STND}, // S_CPOS_STND2
-  {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN2}, // S_CPOS_RUN1
-  {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN3}, // S_CPOS_RUN2
-  {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN4}, // S_CPOS_RUN3
-  {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN5}, // S_CPOS_RUN4
-  {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN6}, // S_CPOS_RUN5
-  {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN7}, // S_CPOS_RUN6
-  {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN8}, // S_CPOS_RUN7
-  {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN1}, // S_CPOS_RUN8
-  {SPR_CPOS,4,10,{A_FaceTarget},S_CPOS_ATK2}, // S_CPOS_ATK1
-  {SPR_CPOS,5|FF_FULLBRIGHT,4,{A_CPosAttack},S_CPOS_ATK3},  // S_CPOS_ATK2
-  {SPR_CPOS,4|FF_FULLBRIGHT,4,{A_CPosAttack},S_CPOS_ATK4},  // S_CPOS_ATK3
-  {SPR_CPOS,5,1,{A_CPosRefire},S_CPOS_ATK2},  // S_CPOS_ATK4
+  {SPR_CPOS,0,10,{.p1 = A_Look},S_CPOS_STND2},  // S_CPOS_STND
+  {SPR_CPOS,1,10,{.p1 = A_Look},S_CPOS_STND}, // S_CPOS_STND2
+  {SPR_CPOS,0,3,{.p1 = A_Chase},S_CPOS_RUN2}, // S_CPOS_RUN1
+  {SPR_CPOS,0,3,{.p1 = A_Chase},S_CPOS_RUN3}, // S_CPOS_RUN2
+  {SPR_CPOS,1,3,{.p1 = A_Chase},S_CPOS_RUN4}, // S_CPOS_RUN3
+  {SPR_CPOS,1,3,{.p1 = A_Chase},S_CPOS_RUN5}, // S_CPOS_RUN4
+  {SPR_CPOS,2,3,{.p1 = A_Chase},S_CPOS_RUN6}, // S_CPOS_RUN5
+  {SPR_CPOS,2,3,{.p1 = A_Chase},S_CPOS_RUN7}, // S_CPOS_RUN6
+  {SPR_CPOS,3,3,{.p1 = A_Chase},S_CPOS_RUN8}, // S_CPOS_RUN7
+  {SPR_CPOS,3,3,{.p1 = A_Chase},S_CPOS_RUN1}, // S_CPOS_RUN8
+  {SPR_CPOS,4,10,{.p1 = A_FaceTarget},S_CPOS_ATK2}, // S_CPOS_ATK1
+  {SPR_CPOS,5|FF_FULLBRIGHT,4,{.p1 = A_CPosAttack},S_CPOS_ATK3},  // S_CPOS_ATK2
+  {SPR_CPOS,4|FF_FULLBRIGHT,4,{.p1 = A_CPosAttack},S_CPOS_ATK4},  // S_CPOS_ATK3
+  {SPR_CPOS,5,1,{.p1 = A_CPosRefire},S_CPOS_ATK2},  // S_CPOS_ATK4
   {SPR_CPOS,6,3,{NULL},S_CPOS_PAIN2}, // S_CPOS_PAIN
-  {SPR_CPOS,6,3,{A_Pain},S_CPOS_RUN1},  // S_CPOS_PAIN2
+  {SPR_CPOS,6,3,{.p1 = A_Pain},S_CPOS_RUN1},  // S_CPOS_PAIN2
   {SPR_CPOS,7,5,{NULL},S_CPOS_DIE2},  // S_CPOS_DIE1
-  {SPR_CPOS,8,5,{A_Scream},S_CPOS_DIE3},  // S_CPOS_DIE2
-  {SPR_CPOS,9,5,{A_Fall},S_CPOS_DIE4},  // S_CPOS_DIE3
+  {SPR_CPOS,8,5,{.p1 = A_Scream},S_CPOS_DIE3},  // S_CPOS_DIE2
+  {SPR_CPOS,9,5,{.p1 = A_Fall},S_CPOS_DIE4},  // S_CPOS_DIE3
   {SPR_CPOS,10,5,{NULL},S_CPOS_DIE5}, // S_CPOS_DIE4
   {SPR_CPOS,11,5,{NULL},S_CPOS_DIE6}, // S_CPOS_DIE5
   {SPR_CPOS,12,5,{NULL},S_CPOS_DIE7}, // S_CPOS_DIE6
   {SPR_CPOS,13,-1,{NULL},S_NULL}, // S_CPOS_DIE7
   {SPR_CPOS,14,5,{NULL},S_CPOS_XDIE2},  // S_CPOS_XDIE1
-  {SPR_CPOS,15,5,{A_XScream},S_CPOS_XDIE3}, // S_CPOS_XDIE2
-  {SPR_CPOS,16,5,{A_Fall},S_CPOS_XDIE4},  // S_CPOS_XDIE3
+  {SPR_CPOS,15,5,{.p1 = A_XScream},S_CPOS_XDIE3}, // S_CPOS_XDIE2
+  {SPR_CPOS,16,5,{.p1 = A_Fall},S_CPOS_XDIE4},  // S_CPOS_XDIE3
   {SPR_CPOS,17,5,{NULL},S_CPOS_XDIE5},  // S_CPOS_XDIE4
   {SPR_CPOS,18,5,{NULL},S_CPOS_XDIE6},  // S_CPOS_XDIE5
   {SPR_CPOS,19,-1,{NULL},S_NULL}, // S_CPOS_XDIE6
@@ -561,30 +561,30 @@ state_t original_states[NUMSTATES] = {
   {SPR_CPOS,9,5,{NULL},S_CPOS_RAISE6},  // S_CPOS_RAISE5
   {SPR_CPOS,8,5,{NULL},S_CPOS_RAISE7},  // S_CPOS_RAISE6
   {SPR_CPOS,7,5,{NULL},S_CPOS_RUN1},  // S_CPOS_RAISE7
-  {SPR_TROO,0,10,{A_Look},S_TROO_STND2},  // S_TROO_STND
-  {SPR_TROO,1,10,{A_Look},S_TROO_STND}, // S_TROO_STND2
-  {SPR_TROO,0,3,{A_Chase},S_TROO_RUN2}, // S_TROO_RUN1
-  {SPR_TROO,0,3,{A_Chase},S_TROO_RUN3}, // S_TROO_RUN2
-  {SPR_TROO,1,3,{A_Chase},S_TROO_RUN4}, // S_TROO_RUN3
-  {SPR_TROO,1,3,{A_Chase},S_TROO_RUN5}, // S_TROO_RUN4
-  {SPR_TROO,2,3,{A_Chase},S_TROO_RUN6}, // S_TROO_RUN5
-  {SPR_TROO,2,3,{A_Chase},S_TROO_RUN7}, // S_TROO_RUN6
-  {SPR_TROO,3,3,{A_Chase},S_TROO_RUN8}, // S_TROO_RUN7
-  {SPR_TROO,3,3,{A_Chase},S_TROO_RUN1}, // S_TROO_RUN8
-  {SPR_TROO,4,8,{A_FaceTarget},S_TROO_ATK2},  // S_TROO_ATK1
-  {SPR_TROO,5,8,{A_FaceTarget},S_TROO_ATK3},  // S_TROO_ATK2
-  {SPR_TROO,6,6,{A_TroopAttack},S_TROO_RUN1}, // S_TROO_ATK3
+  {SPR_TROO,0,10,{.p1 = A_Look},S_TROO_STND2},  // S_TROO_STND
+  {SPR_TROO,1,10,{.p1 = A_Look},S_TROO_STND}, // S_TROO_STND2
+  {SPR_TROO,0,3,{.p1 = A_Chase},S_TROO_RUN2}, // S_TROO_RUN1
+  {SPR_TROO,0,3,{.p1 = A_Chase},S_TROO_RUN3}, // S_TROO_RUN2
+  {SPR_TROO,1,3,{.p1 = A_Chase},S_TROO_RUN4}, // S_TROO_RUN3
+  {SPR_TROO,1,3,{.p1 = A_Chase},S_TROO_RUN5}, // S_TROO_RUN4
+  {SPR_TROO,2,3,{.p1 = A_Chase},S_TROO_RUN6}, // S_TROO_RUN5
+  {SPR_TROO,2,3,{.p1 = A_Chase},S_TROO_RUN7}, // S_TROO_RUN6
+  {SPR_TROO,3,3,{.p1 = A_Chase},S_TROO_RUN8}, // S_TROO_RUN7
+  {SPR_TROO,3,3,{.p1 = A_Chase},S_TROO_RUN1}, // S_TROO_RUN8
+  {SPR_TROO,4,8,{.p1 = A_FaceTarget},S_TROO_ATK2},  // S_TROO_ATK1
+  {SPR_TROO,5,8,{.p1 = A_FaceTarget},S_TROO_ATK3},  // S_TROO_ATK2
+  {SPR_TROO,6,6,{.p1 = A_TroopAttack},S_TROO_RUN1}, // S_TROO_ATK3
   {SPR_TROO,7,2,{NULL},S_TROO_PAIN2}, // S_TROO_PAIN
-  {SPR_TROO,7,2,{A_Pain},S_TROO_RUN1},  // S_TROO_PAIN2
+  {SPR_TROO,7,2,{.p1 = A_Pain},S_TROO_RUN1},  // S_TROO_PAIN2
   {SPR_TROO,8,8,{NULL},S_TROO_DIE2},  // S_TROO_DIE1
-  {SPR_TROO,9,8,{A_Scream},S_TROO_DIE3},  // S_TROO_DIE2
+  {SPR_TROO,9,8,{.p1 = A_Scream},S_TROO_DIE3},  // S_TROO_DIE2
   {SPR_TROO,10,6,{NULL},S_TROO_DIE4}, // S_TROO_DIE3
-  {SPR_TROO,11,6,{A_Fall},S_TROO_DIE5}, // S_TROO_DIE4
+  {SPR_TROO,11,6,{.p1 = A_Fall},S_TROO_DIE5}, // S_TROO_DIE4
   {SPR_TROO,12,-1,{NULL},S_NULL}, // S_TROO_DIE5
   {SPR_TROO,13,5,{NULL},S_TROO_XDIE2},  // S_TROO_XDIE1
-  {SPR_TROO,14,5,{A_XScream},S_TROO_XDIE3}, // S_TROO_XDIE2
+  {SPR_TROO,14,5,{.p1 = A_XScream},S_TROO_XDIE3}, // S_TROO_XDIE2
   {SPR_TROO,15,5,{NULL},S_TROO_XDIE4},  // S_TROO_XDIE3
-  {SPR_TROO,16,5,{A_Fall},S_TROO_XDIE5},  // S_TROO_XDIE4
+  {SPR_TROO,16,5,{.p1 = A_Fall},S_TROO_XDIE5},  // S_TROO_XDIE4
   {SPR_TROO,17,5,{NULL},S_TROO_XDIE6},  // S_TROO_XDIE5
   {SPR_TROO,18,5,{NULL},S_TROO_XDIE7},  // S_TROO_XDIE6
   {SPR_TROO,19,5,{NULL},S_TROO_XDIE8},  // S_TROO_XDIE7
@@ -594,25 +594,25 @@ state_t original_states[NUMSTATES] = {
   {SPR_TROO,10,6,{NULL},S_TROO_RAISE4}, // S_TROO_RAISE3
   {SPR_TROO,9,6,{NULL},S_TROO_RAISE5},  // S_TROO_RAISE4
   {SPR_TROO,8,6,{NULL},S_TROO_RUN1},  // S_TROO_RAISE5
-  {SPR_SARG,0,10,{A_Look},S_SARG_STND2},  // S_SARG_STND
-  {SPR_SARG,1,10,{A_Look},S_SARG_STND}, // S_SARG_STND2
-  {SPR_SARG,0,2,{A_Chase},S_SARG_RUN2}, // S_SARG_RUN1
-  {SPR_SARG,0,2,{A_Chase},S_SARG_RUN3}, // S_SARG_RUN2
-  {SPR_SARG,1,2,{A_Chase},S_SARG_RUN4}, // S_SARG_RUN3
-  {SPR_SARG,1,2,{A_Chase},S_SARG_RUN5}, // S_SARG_RUN4
-  {SPR_SARG,2,2,{A_Chase},S_SARG_RUN6}, // S_SARG_RUN5
-  {SPR_SARG,2,2,{A_Chase},S_SARG_RUN7}, // S_SARG_RUN6
-  {SPR_SARG,3,2,{A_Chase},S_SARG_RUN8}, // S_SARG_RUN7
-  {SPR_SARG,3,2,{A_Chase},S_SARG_RUN1}, // S_SARG_RUN8
-  {SPR_SARG,4,8,{A_FaceTarget},S_SARG_ATK2},  // S_SARG_ATK1
-  {SPR_SARG,5,8,{A_FaceTarget},S_SARG_ATK3},  // S_SARG_ATK2
-  {SPR_SARG,6,8,{A_SargAttack},S_SARG_RUN1},  // S_SARG_ATK3
+  {SPR_SARG,0,10,{.p1 = A_Look},S_SARG_STND2},  // S_SARG_STND
+  {SPR_SARG,1,10,{.p1 = A_Look},S_SARG_STND}, // S_SARG_STND2
+  {SPR_SARG,0,2,{.p1 = A_Chase},S_SARG_RUN2}, // S_SARG_RUN1
+  {SPR_SARG,0,2,{.p1 = A_Chase},S_SARG_RUN3}, // S_SARG_RUN2
+  {SPR_SARG,1,2,{.p1 = A_Chase},S_SARG_RUN4}, // S_SARG_RUN3
+  {SPR_SARG,1,2,{.p1 = A_Chase},S_SARG_RUN5}, // S_SARG_RUN4
+  {SPR_SARG,2,2,{.p1 = A_Chase},S_SARG_RUN6}, // S_SARG_RUN5
+  {SPR_SARG,2,2,{.p1 = A_Chase},S_SARG_RUN7}, // S_SARG_RUN6
+  {SPR_SARG,3,2,{.p1 = A_Chase},S_SARG_RUN8}, // S_SARG_RUN7
+  {SPR_SARG,3,2,{.p1 = A_Chase},S_SARG_RUN1}, // S_SARG_RUN8
+  {SPR_SARG,4,8,{.p1 = A_FaceTarget},S_SARG_ATK2},  // S_SARG_ATK1
+  {SPR_SARG,5,8,{.p1 = A_FaceTarget},S_SARG_ATK3},  // S_SARG_ATK2
+  {SPR_SARG,6,8,{.p1 = A_SargAttack},S_SARG_RUN1},  // S_SARG_ATK3
   {SPR_SARG,7,2,{NULL},S_SARG_PAIN2}, // S_SARG_PAIN
-  {SPR_SARG,7,2,{A_Pain},S_SARG_RUN1},  // S_SARG_PAIN2
+  {SPR_SARG,7,2,{.p1 = A_Pain},S_SARG_RUN1},  // S_SARG_PAIN2
   {SPR_SARG,8,8,{NULL},S_SARG_DIE2},  // S_SARG_DIE1
-  {SPR_SARG,9,8,{A_Scream},S_SARG_DIE3},  // S_SARG_DIE2
+  {SPR_SARG,9,8,{.p1 = A_Scream},S_SARG_DIE3},  // S_SARG_DIE2
   {SPR_SARG,10,4,{NULL},S_SARG_DIE4}, // S_SARG_DIE3
-  {SPR_SARG,11,4,{A_Fall},S_SARG_DIE5}, // S_SARG_DIE4
+  {SPR_SARG,11,4,{.p1 = A_Fall},S_SARG_DIE5}, // S_SARG_DIE4
   {SPR_SARG,12,4,{NULL},S_SARG_DIE6}, // S_SARG_DIE5
   {SPR_SARG,13,-1,{NULL},S_NULL}, // S_SARG_DIE6
   {SPR_SARG,13,5,{NULL},S_SARG_RAISE2}, // S_SARG_RAISE1
@@ -621,19 +621,19 @@ state_t original_states[NUMSTATES] = {
   {SPR_SARG,10,5,{NULL},S_SARG_RAISE5}, // S_SARG_RAISE4
   {SPR_SARG,9,5,{NULL},S_SARG_RAISE6},  // S_SARG_RAISE5
   {SPR_SARG,8,5,{NULL},S_SARG_RUN1},  // S_SARG_RAISE6
-  {SPR_HEAD,0,10,{A_Look},S_HEAD_STND}, // S_HEAD_STND
-  {SPR_HEAD,0,3,{A_Chase},S_HEAD_RUN1}, // S_HEAD_RUN1
-  {SPR_HEAD,1,5,{A_FaceTarget},S_HEAD_ATK2},  // S_HEAD_ATK1
-  {SPR_HEAD,2,5,{A_FaceTarget},S_HEAD_ATK3},  // S_HEAD_ATK2
-  {SPR_HEAD,3|FF_FULLBRIGHT,5,{A_HeadAttack},S_HEAD_RUN1},  // S_HEAD_ATK3
+  {SPR_HEAD,0,10,{.p1 = A_Look},S_HEAD_STND}, // S_HEAD_STND
+  {SPR_HEAD,0,3,{.p1 = A_Chase},S_HEAD_RUN1}, // S_HEAD_RUN1
+  {SPR_HEAD,1,5,{.p1 = A_FaceTarget},S_HEAD_ATK2},  // S_HEAD_ATK1
+  {SPR_HEAD,2,5,{.p1 = A_FaceTarget},S_HEAD_ATK3},  // S_HEAD_ATK2
+  {SPR_HEAD,3|FF_FULLBRIGHT,5,{.p1 = A_HeadAttack},S_HEAD_RUN1},  // S_HEAD_ATK3
   {SPR_HEAD,4,3,{NULL},S_HEAD_PAIN2}, // S_HEAD_PAIN
-  {SPR_HEAD,4,3,{A_Pain},S_HEAD_PAIN3}, // S_HEAD_PAIN2
+  {SPR_HEAD,4,3,{.p1 = A_Pain},S_HEAD_PAIN3}, // S_HEAD_PAIN2
   {SPR_HEAD,5,6,{NULL},S_HEAD_RUN1},  // S_HEAD_PAIN3
   {SPR_HEAD,6,8,{NULL},S_HEAD_DIE2},  // S_HEAD_DIE1
-  {SPR_HEAD,7,8,{A_Scream},S_HEAD_DIE3},  // S_HEAD_DIE2
+  {SPR_HEAD,7,8,{.p1 = A_Scream},S_HEAD_DIE3},  // S_HEAD_DIE2
   {SPR_HEAD,8,8,{NULL},S_HEAD_DIE4},  // S_HEAD_DIE3
   {SPR_HEAD,9,8,{NULL},S_HEAD_DIE5},  // S_HEAD_DIE4
-  {SPR_HEAD,10,8,{A_Fall},S_HEAD_DIE6}, // S_HEAD_DIE5
+  {SPR_HEAD,10,8,{.p1 = A_Fall},S_HEAD_DIE6}, // S_HEAD_DIE5
   {SPR_HEAD,11,-1,{NULL},S_NULL}, // S_HEAD_DIE6
   {SPR_HEAD,11,8,{NULL},S_HEAD_RAISE2}, // S_HEAD_RAISE1
   {SPR_HEAD,10,8,{NULL},S_HEAD_RAISE3}, // S_HEAD_RAISE2
@@ -646,28 +646,28 @@ state_t original_states[NUMSTATES] = {
   {SPR_BAL7,2|FF_FULLBRIGHT,6,{NULL},S_BRBALLX2}, // S_BRBALLX1
   {SPR_BAL7,3|FF_FULLBRIGHT,6,{NULL},S_BRBALLX3}, // S_BRBALLX2
   {SPR_BAL7,4|FF_FULLBRIGHT,6,{NULL},S_NULL}, // S_BRBALLX3
-  {SPR_BOSS,0,10,{A_Look},S_BOSS_STND2},  // S_BOSS_STND
-  {SPR_BOSS,1,10,{A_Look},S_BOSS_STND}, // S_BOSS_STND2
-  {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN2}, // S_BOSS_RUN1
-  {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN3}, // S_BOSS_RUN2
-  {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN4}, // S_BOSS_RUN3
-  {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN5}, // S_BOSS_RUN4
-  {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN6}, // S_BOSS_RUN5
-  {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN7}, // S_BOSS_RUN6
-  {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN8}, // S_BOSS_RUN7
-  {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN1}, // S_BOSS_RUN8
-  {SPR_BOSS,4,8,{A_FaceTarget},S_BOSS_ATK2},  // S_BOSS_ATK1
-  {SPR_BOSS,5,8,{A_FaceTarget},S_BOSS_ATK3},  // S_BOSS_ATK2
-  {SPR_BOSS,6,8,{A_BruisAttack},S_BOSS_RUN1}, // S_BOSS_ATK3
+  {SPR_BOSS,0,10,{.p1 = A_Look},S_BOSS_STND2},  // S_BOSS_STND
+  {SPR_BOSS,1,10,{.p1 = A_Look},S_BOSS_STND}, // S_BOSS_STND2
+  {SPR_BOSS,0,3,{.p1 = A_Chase},S_BOSS_RUN2}, // S_BOSS_RUN1
+  {SPR_BOSS,0,3,{.p1 = A_Chase},S_BOSS_RUN3}, // S_BOSS_RUN2
+  {SPR_BOSS,1,3,{.p1 = A_Chase},S_BOSS_RUN4}, // S_BOSS_RUN3
+  {SPR_BOSS,1,3,{.p1 = A_Chase},S_BOSS_RUN5}, // S_BOSS_RUN4
+  {SPR_BOSS,2,3,{.p1 = A_Chase},S_BOSS_RUN6}, // S_BOSS_RUN5
+  {SPR_BOSS,2,3,{.p1 = A_Chase},S_BOSS_RUN7}, // S_BOSS_RUN6
+  {SPR_BOSS,3,3,{.p1 = A_Chase},S_BOSS_RUN8}, // S_BOSS_RUN7
+  {SPR_BOSS,3,3,{.p1 = A_Chase},S_BOSS_RUN1}, // S_BOSS_RUN8
+  {SPR_BOSS,4,8,{.p1 = A_FaceTarget},S_BOSS_ATK2},  // S_BOSS_ATK1
+  {SPR_BOSS,5,8,{.p1 = A_FaceTarget},S_BOSS_ATK3},  // S_BOSS_ATK2
+  {SPR_BOSS,6,8,{.p1 = A_BruisAttack},S_BOSS_RUN1}, // S_BOSS_ATK3
   {SPR_BOSS,7,2,{NULL},S_BOSS_PAIN2}, // S_BOSS_PAIN
-  {SPR_BOSS,7,2,{A_Pain},S_BOSS_RUN1},  // S_BOSS_PAIN2
+  {SPR_BOSS,7,2,{.p1 = A_Pain},S_BOSS_RUN1},  // S_BOSS_PAIN2
   {SPR_BOSS,8,8,{NULL},S_BOSS_DIE2},  // S_BOSS_DIE1
-  {SPR_BOSS,9,8,{A_Scream},S_BOSS_DIE3},  // S_BOSS_DIE2
+  {SPR_BOSS,9,8,{.p1 = A_Scream},S_BOSS_DIE3},  // S_BOSS_DIE2
   {SPR_BOSS,10,8,{NULL},S_BOSS_DIE4}, // S_BOSS_DIE3
-  {SPR_BOSS,11,8,{A_Fall},S_BOSS_DIE5}, // S_BOSS_DIE4
+  {SPR_BOSS,11,8,{.p1 = A_Fall},S_BOSS_DIE5}, // S_BOSS_DIE4
   {SPR_BOSS,12,8,{NULL},S_BOSS_DIE6}, // S_BOSS_DIE5
   {SPR_BOSS,13,8,{NULL},S_BOSS_DIE7}, // S_BOSS_DIE6
-  {SPR_BOSS,14,-1,{A_BossDeath},S_NULL},  // S_BOSS_DIE7
+  {SPR_BOSS,14,-1,{.p1 = A_BossDeath},S_NULL},  // S_BOSS_DIE7
   {SPR_BOSS,14,8,{NULL},S_BOSS_RAISE2}, // S_BOSS_RAISE1
   {SPR_BOSS,13,8,{NULL},S_BOSS_RAISE3}, // S_BOSS_RAISE2
   {SPR_BOSS,12,8,{NULL},S_BOSS_RAISE4}, // S_BOSS_RAISE3
@@ -675,25 +675,25 @@ state_t original_states[NUMSTATES] = {
   {SPR_BOSS,10,8,{NULL},S_BOSS_RAISE6}, // S_BOSS_RAISE5
   {SPR_BOSS,9,8,{NULL},S_BOSS_RAISE7},  // S_BOSS_RAISE6
   {SPR_BOSS,8,8,{NULL},S_BOSS_RUN1},  // S_BOSS_RAISE7
-  {SPR_BOS2,0,10,{A_Look},S_BOS2_STND2},  // S_BOS2_STND
-  {SPR_BOS2,1,10,{A_Look},S_BOS2_STND}, // S_BOS2_STND2
-  {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN2}, // S_BOS2_RUN1
-  {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN3}, // S_BOS2_RUN2
-  {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN4}, // S_BOS2_RUN3
-  {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN5}, // S_BOS2_RUN4
-  {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN6}, // S_BOS2_RUN5
-  {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN7}, // S_BOS2_RUN6
-  {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN8}, // S_BOS2_RUN7
-  {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN1}, // S_BOS2_RUN8
-  {SPR_BOS2,4,8,{A_FaceTarget},S_BOS2_ATK2},  // S_BOS2_ATK1
-  {SPR_BOS2,5,8,{A_FaceTarget},S_BOS2_ATK3},  // S_BOS2_ATK2
-  {SPR_BOS2,6,8,{A_BruisAttack},S_BOS2_RUN1}, // S_BOS2_ATK3
+  {SPR_BOS2,0,10,{.p1 = A_Look},S_BOS2_STND2},  // S_BOS2_STND
+  {SPR_BOS2,1,10,{.p1 = A_Look},S_BOS2_STND}, // S_BOS2_STND2
+  {SPR_BOS2,0,3,{.p1 = A_Chase},S_BOS2_RUN2}, // S_BOS2_RUN1
+  {SPR_BOS2,0,3,{.p1 = A_Chase},S_BOS2_RUN3}, // S_BOS2_RUN2
+  {SPR_BOS2,1,3,{.p1 = A_Chase},S_BOS2_RUN4}, // S_BOS2_RUN3
+  {SPR_BOS2,1,3,{.p1 = A_Chase},S_BOS2_RUN5}, // S_BOS2_RUN4
+  {SPR_BOS2,2,3,{.p1 = A_Chase},S_BOS2_RUN6}, // S_BOS2_RUN5
+  {SPR_BOS2,2,3,{.p1 = A_Chase},S_BOS2_RUN7}, // S_BOS2_RUN6
+  {SPR_BOS2,3,3,{.p1 = A_Chase},S_BOS2_RUN8}, // S_BOS2_RUN7
+  {SPR_BOS2,3,3,{.p1 = A_Chase},S_BOS2_RUN1}, // S_BOS2_RUN8
+  {SPR_BOS2,4,8,{.p1 = A_FaceTarget},S_BOS2_ATK2},  // S_BOS2_ATK1
+  {SPR_BOS2,5,8,{.p1 = A_FaceTarget},S_BOS2_ATK3},  // S_BOS2_ATK2
+  {SPR_BOS2,6,8,{.p1 = A_BruisAttack},S_BOS2_RUN1}, // S_BOS2_ATK3
   {SPR_BOS2,7,2,{NULL},S_BOS2_PAIN2}, // S_BOS2_PAIN
-  {SPR_BOS2,7,2,{A_Pain},S_BOS2_RUN1},  // S_BOS2_PAIN2
+  {SPR_BOS2,7,2,{.p1 = A_Pain},S_BOS2_RUN1},  // S_BOS2_PAIN2
   {SPR_BOS2,8,8,{NULL},S_BOS2_DIE2},  // S_BOS2_DIE1
-  {SPR_BOS2,9,8,{A_Scream},S_BOS2_DIE3},  // S_BOS2_DIE2
+  {SPR_BOS2,9,8,{.p1 = A_Scream},S_BOS2_DIE3},  // S_BOS2_DIE2
   {SPR_BOS2,10,8,{NULL},S_BOS2_DIE4}, // S_BOS2_DIE3
-  {SPR_BOS2,11,8,{A_Fall},S_BOS2_DIE5}, // S_BOS2_DIE4
+  {SPR_BOS2,11,8,{.p1 = A_Fall},S_BOS2_DIE5}, // S_BOS2_DIE4
   {SPR_BOS2,12,8,{NULL},S_BOS2_DIE6}, // S_BOS2_DIE5
   {SPR_BOS2,13,8,{NULL},S_BOS2_DIE7}, // S_BOS2_DIE6
   {SPR_BOS2,14,-1,{NULL},S_NULL}, // S_BOS2_DIE7
@@ -704,44 +704,44 @@ state_t original_states[NUMSTATES] = {
   {SPR_BOS2,10,8,{NULL},S_BOS2_RAISE6}, // S_BOS2_RAISE5
   {SPR_BOS2,9,8,{NULL},S_BOS2_RAISE7},  // S_BOS2_RAISE6
   {SPR_BOS2,8,8,{NULL},S_BOS2_RUN1},  // S_BOS2_RAISE7
-  {SPR_SKUL,0|FF_FULLBRIGHT,10,{A_Look},S_SKULL_STND2}, // S_SKULL_STND
-  {SPR_SKUL,1|FF_FULLBRIGHT,10,{A_Look},S_SKULL_STND},  // S_SKULL_STND2
-  {SPR_SKUL,0|FF_FULLBRIGHT,6,{A_Chase},S_SKULL_RUN2},  // S_SKULL_RUN1
-  {SPR_SKUL,1|FF_FULLBRIGHT,6,{A_Chase},S_SKULL_RUN1},  // S_SKULL_RUN2
-  {SPR_SKUL,2|FF_FULLBRIGHT,10,{A_FaceTarget},S_SKULL_ATK2},  // S_SKULL_ATK1
-  {SPR_SKUL,3|FF_FULLBRIGHT,4,{A_SkullAttack},S_SKULL_ATK3},  // S_SKULL_ATK2
+  {SPR_SKUL,0|FF_FULLBRIGHT,10,{.p1 = A_Look},S_SKULL_STND2}, // S_SKULL_STND
+  {SPR_SKUL,1|FF_FULLBRIGHT,10,{.p1 = A_Look},S_SKULL_STND},  // S_SKULL_STND2
+  {SPR_SKUL,0|FF_FULLBRIGHT,6,{.p1 = A_Chase},S_SKULL_RUN2},  // S_SKULL_RUN1
+  {SPR_SKUL,1|FF_FULLBRIGHT,6,{.p1 = A_Chase},S_SKULL_RUN1},  // S_SKULL_RUN2
+  {SPR_SKUL,2|FF_FULLBRIGHT,10,{.p1 = A_FaceTarget},S_SKULL_ATK2},  // S_SKULL_ATK1
+  {SPR_SKUL,3|FF_FULLBRIGHT,4,{.p1 = A_SkullAttack},S_SKULL_ATK3},  // S_SKULL_ATK2
   {SPR_SKUL,2|FF_FULLBRIGHT,4,{NULL},S_SKULL_ATK4}, // S_SKULL_ATK3
   {SPR_SKUL,3|FF_FULLBRIGHT,4,{NULL},S_SKULL_ATK3}, // S_SKULL_ATK4
   {SPR_SKUL,4|FF_FULLBRIGHT,3,{NULL},S_SKULL_PAIN2},  // S_SKULL_PAIN
-  {SPR_SKUL,4|FF_FULLBRIGHT,3,{A_Pain},S_SKULL_RUN1}, // S_SKULL_PAIN2
+  {SPR_SKUL,4|FF_FULLBRIGHT,3,{.p1 = A_Pain},S_SKULL_RUN1}, // S_SKULL_PAIN2
   {SPR_SKUL,5|FF_FULLBRIGHT,6,{NULL},S_SKULL_DIE2}, // S_SKULL_DIE1
-  {SPR_SKUL,6|FF_FULLBRIGHT,6,{A_Scream},S_SKULL_DIE3}, // S_SKULL_DIE2
+  {SPR_SKUL,6|FF_FULLBRIGHT,6,{.p1 = A_Scream},S_SKULL_DIE3}, // S_SKULL_DIE2
   {SPR_SKUL,7|FF_FULLBRIGHT,6,{NULL},S_SKULL_DIE4}, // S_SKULL_DIE3
-  {SPR_SKUL,8|FF_FULLBRIGHT,6,{A_Fall},S_SKULL_DIE5}, // S_SKULL_DIE4
+  {SPR_SKUL,8|FF_FULLBRIGHT,6,{.p1 = A_Fall},S_SKULL_DIE5}, // S_SKULL_DIE4
   {SPR_SKUL,9,6,{NULL},S_SKULL_DIE6}, // S_SKULL_DIE5
   {SPR_SKUL,10,6,{NULL},S_NULL},  // S_SKULL_DIE6
-  {SPR_SPID,0,10,{A_Look},S_SPID_STND2},  // S_SPID_STND
-  {SPR_SPID,1,10,{A_Look},S_SPID_STND}, // S_SPID_STND2
-  {SPR_SPID,0,3,{A_Metal},S_SPID_RUN2}, // S_SPID_RUN1
-  {SPR_SPID,0,3,{A_Chase},S_SPID_RUN3}, // S_SPID_RUN2
-  {SPR_SPID,1,3,{A_Chase},S_SPID_RUN4}, // S_SPID_RUN3
-  {SPR_SPID,1,3,{A_Chase},S_SPID_RUN5}, // S_SPID_RUN4
-  {SPR_SPID,2,3,{A_Metal},S_SPID_RUN6}, // S_SPID_RUN5
-  {SPR_SPID,2,3,{A_Chase},S_SPID_RUN7}, // S_SPID_RUN6
-  {SPR_SPID,3,3,{A_Chase},S_SPID_RUN8}, // S_SPID_RUN7
-  {SPR_SPID,3,3,{A_Chase},S_SPID_RUN9}, // S_SPID_RUN8
-  {SPR_SPID,4,3,{A_Metal},S_SPID_RUN10},  // S_SPID_RUN9
-  {SPR_SPID,4,3,{A_Chase},S_SPID_RUN11},  // S_SPID_RUN10
-  {SPR_SPID,5,3,{A_Chase},S_SPID_RUN12},  // S_SPID_RUN11
-  {SPR_SPID,5,3,{A_Chase},S_SPID_RUN1}, // S_SPID_RUN12
-  {SPR_SPID,0|FF_FULLBRIGHT,20,{A_FaceTarget},S_SPID_ATK2}, // S_SPID_ATK1
-  {SPR_SPID,6|FF_FULLBRIGHT,4,{A_SPosAttack},S_SPID_ATK3},  // S_SPID_ATK2
-  {SPR_SPID,7|FF_FULLBRIGHT,4,{A_SPosAttack},S_SPID_ATK4},  // S_SPID_ATK3
-  {SPR_SPID,7|FF_FULLBRIGHT,1,{A_SpidRefire},S_SPID_ATK2},  // S_SPID_ATK4
+  {SPR_SPID,0,10,{.p1 = A_Look},S_SPID_STND2},  // S_SPID_STND
+  {SPR_SPID,1,10,{.p1 = A_Look},S_SPID_STND}, // S_SPID_STND2
+  {SPR_SPID,0,3,{.p1 = A_Metal},S_SPID_RUN2}, // S_SPID_RUN1
+  {SPR_SPID,0,3,{.p1 = A_Chase},S_SPID_RUN3}, // S_SPID_RUN2
+  {SPR_SPID,1,3,{.p1 = A_Chase},S_SPID_RUN4}, // S_SPID_RUN3
+  {SPR_SPID,1,3,{.p1 = A_Chase},S_SPID_RUN5}, // S_SPID_RUN4
+  {SPR_SPID,2,3,{.p1 = A_Metal},S_SPID_RUN6}, // S_SPID_RUN5
+  {SPR_SPID,2,3,{.p1 = A_Chase},S_SPID_RUN7}, // S_SPID_RUN6
+  {SPR_SPID,3,3,{.p1 = A_Chase},S_SPID_RUN8}, // S_SPID_RUN7
+  {SPR_SPID,3,3,{.p1 = A_Chase},S_SPID_RUN9}, // S_SPID_RUN8
+  {SPR_SPID,4,3,{.p1 = A_Metal},S_SPID_RUN10},  // S_SPID_RUN9
+  {SPR_SPID,4,3,{.p1 = A_Chase},S_SPID_RUN11},  // S_SPID_RUN10
+  {SPR_SPID,5,3,{.p1 = A_Chase},S_SPID_RUN12},  // S_SPID_RUN11
+  {SPR_SPID,5,3,{.p1 = A_Chase},S_SPID_RUN1}, // S_SPID_RUN12
+  {SPR_SPID,0|FF_FULLBRIGHT,20,{.p1 = A_FaceTarget},S_SPID_ATK2}, // S_SPID_ATK1
+  {SPR_SPID,6|FF_FULLBRIGHT,4,{.p1 = A_SPosAttack},S_SPID_ATK3},  // S_SPID_ATK2
+  {SPR_SPID,7|FF_FULLBRIGHT,4,{.p1 = A_SPosAttack},S_SPID_ATK4},  // S_SPID_ATK3
+  {SPR_SPID,7|FF_FULLBRIGHT,1,{.p1 = A_SpidRefire},S_SPID_ATK2},  // S_SPID_ATK4
   {SPR_SPID,8,3,{NULL},S_SPID_PAIN2}, // S_SPID_PAIN
-  {SPR_SPID,8,3,{A_Pain},S_SPID_RUN1},  // S_SPID_PAIN2
-  {SPR_SPID,9,20,{A_Scream},S_SPID_DIE2}, // S_SPID_DIE1
-  {SPR_SPID,10,10,{A_Fall},S_SPID_DIE3},  // S_SPID_DIE2
+  {SPR_SPID,8,3,{.p1 = A_Pain},S_SPID_RUN1},  // S_SPID_PAIN2
+  {SPR_SPID,9,20,{.p1 = A_Scream},S_SPID_DIE2}, // S_SPID_DIE1
+  {SPR_SPID,10,10,{.p1 = A_Fall},S_SPID_DIE3},  // S_SPID_DIE2
   {SPR_SPID,11,10,{NULL},S_SPID_DIE4},  // S_SPID_DIE3
   {SPR_SPID,12,10,{NULL},S_SPID_DIE5},  // S_SPID_DIE4
   {SPR_SPID,13,10,{NULL},S_SPID_DIE6},  // S_SPID_DIE5
@@ -750,35 +750,35 @@ state_t original_states[NUMSTATES] = {
   {SPR_SPID,16,10,{NULL},S_SPID_DIE9},  // S_SPID_DIE8
   {SPR_SPID,17,10,{NULL},S_SPID_DIE10}, // S_SPID_DIE9
   {SPR_SPID,18,30,{NULL},S_SPID_DIE11}, // S_SPID_DIE10
-  {SPR_SPID,18,-1,{A_BossDeath},S_NULL},  // S_SPID_DIE11
-  {SPR_BSPI,0,10,{A_Look},S_BSPI_STND2},  // S_BSPI_STND
-  {SPR_BSPI,1,10,{A_Look},S_BSPI_STND}, // S_BSPI_STND2
+  {SPR_SPID,18,-1,{.p1 = A_BossDeath},S_NULL},  // S_SPID_DIE11
+  {SPR_BSPI,0,10,{.p1 = A_Look},S_BSPI_STND2},  // S_BSPI_STND
+  {SPR_BSPI,1,10,{.p1 = A_Look},S_BSPI_STND}, // S_BSPI_STND2
   {SPR_BSPI,0,20,{NULL},S_BSPI_RUN1}, // S_BSPI_SIGHT
-  {SPR_BSPI,0,3,{A_BabyMetal},S_BSPI_RUN2}, // S_BSPI_RUN1
-  {SPR_BSPI,0,3,{A_Chase},S_BSPI_RUN3}, // S_BSPI_RUN2
-  {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN4}, // S_BSPI_RUN3
-  {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN5}, // S_BSPI_RUN4
-  {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN6}, // S_BSPI_RUN5
-  {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN7}, // S_BSPI_RUN6
-  {SPR_BSPI,3,3,{A_BabyMetal},S_BSPI_RUN8}, // S_BSPI_RUN7
-  {SPR_BSPI,3,3,{A_Chase},S_BSPI_RUN9}, // S_BSPI_RUN8
-  {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN10},  // S_BSPI_RUN9
-  {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN11},  // S_BSPI_RUN10
-  {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN12},  // S_BSPI_RUN11
-  {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN1}, // S_BSPI_RUN12
-  {SPR_BSPI,0|FF_FULLBRIGHT,20,{A_FaceTarget},S_BSPI_ATK2}, // S_BSPI_ATK1
-  {SPR_BSPI,6|FF_FULLBRIGHT,4,{A_BspiAttack},S_BSPI_ATK3},  // S_BSPI_ATK2
+  {SPR_BSPI,0,3,{.p1 = A_BabyMetal},S_BSPI_RUN2}, // S_BSPI_RUN1
+  {SPR_BSPI,0,3,{.p1 = A_Chase},S_BSPI_RUN3}, // S_BSPI_RUN2
+  {SPR_BSPI,1,3,{.p1 = A_Chase},S_BSPI_RUN4}, // S_BSPI_RUN3
+  {SPR_BSPI,1,3,{.p1 = A_Chase},S_BSPI_RUN5}, // S_BSPI_RUN4
+  {SPR_BSPI,2,3,{.p1 = A_Chase},S_BSPI_RUN6}, // S_BSPI_RUN5
+  {SPR_BSPI,2,3,{.p1 = A_Chase},S_BSPI_RUN7}, // S_BSPI_RUN6
+  {SPR_BSPI,3,3,{.p1 = A_BabyMetal},S_BSPI_RUN8}, // S_BSPI_RUN7
+  {SPR_BSPI,3,3,{.p1 = A_Chase},S_BSPI_RUN9}, // S_BSPI_RUN8
+  {SPR_BSPI,4,3,{.p1 = A_Chase},S_BSPI_RUN10},  // S_BSPI_RUN9
+  {SPR_BSPI,4,3,{.p1 = A_Chase},S_BSPI_RUN11},  // S_BSPI_RUN10
+  {SPR_BSPI,5,3,{.p1 = A_Chase},S_BSPI_RUN12},  // S_BSPI_RUN11
+  {SPR_BSPI,5,3,{.p1 = A_Chase},S_BSPI_RUN1}, // S_BSPI_RUN12
+  {SPR_BSPI,0|FF_FULLBRIGHT,20,{.p1 = A_FaceTarget},S_BSPI_ATK2}, // S_BSPI_ATK1
+  {SPR_BSPI,6|FF_FULLBRIGHT,4,{.p1 = A_BspiAttack},S_BSPI_ATK3},  // S_BSPI_ATK2
   {SPR_BSPI,7|FF_FULLBRIGHT,4,{NULL},S_BSPI_ATK4},  // S_BSPI_ATK3
-  {SPR_BSPI,7|FF_FULLBRIGHT,1,{A_SpidRefire},S_BSPI_ATK2},  // S_BSPI_ATK4
+  {SPR_BSPI,7|FF_FULLBRIGHT,1,{.p1 = A_SpidRefire},S_BSPI_ATK2},  // S_BSPI_ATK4
   {SPR_BSPI,8,3,{NULL},S_BSPI_PAIN2}, // S_BSPI_PAIN
-  {SPR_BSPI,8,3,{A_Pain},S_BSPI_RUN1},  // S_BSPI_PAIN2
-  {SPR_BSPI,9,20,{A_Scream},S_BSPI_DIE2}, // S_BSPI_DIE1
-  {SPR_BSPI,10,7,{A_Fall},S_BSPI_DIE3}, // S_BSPI_DIE2
+  {SPR_BSPI,8,3,{.p1 = A_Pain},S_BSPI_RUN1},  // S_BSPI_PAIN2
+  {SPR_BSPI,9,20,{.p1 = A_Scream},S_BSPI_DIE2}, // S_BSPI_DIE1
+  {SPR_BSPI,10,7,{.p1 = A_Fall},S_BSPI_DIE3}, // S_BSPI_DIE2
   {SPR_BSPI,11,7,{NULL},S_BSPI_DIE4}, // S_BSPI_DIE3
   {SPR_BSPI,12,7,{NULL},S_BSPI_DIE5}, // S_BSPI_DIE4
   {SPR_BSPI,13,7,{NULL},S_BSPI_DIE6}, // S_BSPI_DIE5
   {SPR_BSPI,14,7,{NULL},S_BSPI_DIE7}, // S_BSPI_DIE6
-  {SPR_BSPI,15,-1,{A_BossDeath},S_NULL},  // S_BSPI_DIE7
+  {SPR_BSPI,15,-1,{.p1 = A_BossDeath},S_NULL},  // S_BSPI_DIE7
   {SPR_BSPI,15,5,{NULL},S_BSPI_RAISE2}, // S_BSPI_RAISE1
   {SPR_BSPI,14,5,{NULL},S_BSPI_RAISE3}, // S_BSPI_RAISE2
   {SPR_BSPI,13,5,{NULL},S_BSPI_RAISE4}, // S_BSPI_RAISE3
@@ -793,51 +793,51 @@ state_t original_states[NUMSTATES] = {
   {SPR_APBX,2|FF_FULLBRIGHT,5,{NULL},S_ARACH_PLEX4},  // S_ARACH_PLEX3
   {SPR_APBX,3|FF_FULLBRIGHT,5,{NULL},S_ARACH_PLEX5},  // S_ARACH_PLEX4
   {SPR_APBX,4|FF_FULLBRIGHT,5,{NULL},S_NULL}, // S_ARACH_PLEX5
-  {SPR_CYBR,0,10,{A_Look},S_CYBER_STND2}, // S_CYBER_STND
-  {SPR_CYBR,1,10,{A_Look},S_CYBER_STND},  // S_CYBER_STND2
-  {SPR_CYBR,0,3,{A_Hoof},S_CYBER_RUN2}, // S_CYBER_RUN1
-  {SPR_CYBR,0,3,{A_Chase},S_CYBER_RUN3},  // S_CYBER_RUN2
-  {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN4},  // S_CYBER_RUN3
-  {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN5},  // S_CYBER_RUN4
-  {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN6},  // S_CYBER_RUN5
-  {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN7},  // S_CYBER_RUN6
-  {SPR_CYBR,3,3,{A_Metal},S_CYBER_RUN8},  // S_CYBER_RUN7
-  {SPR_CYBR,3,3,{A_Chase},S_CYBER_RUN1},  // S_CYBER_RUN8
-  {SPR_CYBR,4,6,{A_FaceTarget},S_CYBER_ATK2}, // S_CYBER_ATK1
-  {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK3}, // S_CYBER_ATK2
-  {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK4},  // S_CYBER_ATK3
-  {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK5}, // S_CYBER_ATK4
-  {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK6},  // S_CYBER_ATK5
-  {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_RUN1}, // S_CYBER_ATK6
-  {SPR_CYBR,6,10,{A_Pain},S_CYBER_RUN1},  // S_CYBER_PAIN
+  {SPR_CYBR,0,10,{.p1 = A_Look},S_CYBER_STND2}, // S_CYBER_STND
+  {SPR_CYBR,1,10,{.p1 = A_Look},S_CYBER_STND},  // S_CYBER_STND2
+  {SPR_CYBR,0,3,{.p1 = A_Hoof},S_CYBER_RUN2}, // S_CYBER_RUN1
+  {SPR_CYBR,0,3,{.p1 = A_Chase},S_CYBER_RUN3},  // S_CYBER_RUN2
+  {SPR_CYBR,1,3,{.p1 = A_Chase},S_CYBER_RUN4},  // S_CYBER_RUN3
+  {SPR_CYBR,1,3,{.p1 = A_Chase},S_CYBER_RUN5},  // S_CYBER_RUN4
+  {SPR_CYBR,2,3,{.p1 = A_Chase},S_CYBER_RUN6},  // S_CYBER_RUN5
+  {SPR_CYBR,2,3,{.p1 = A_Chase},S_CYBER_RUN7},  // S_CYBER_RUN6
+  {SPR_CYBR,3,3,{.p1 = A_Metal},S_CYBER_RUN8},  // S_CYBER_RUN7
+  {SPR_CYBR,3,3,{.p1 = A_Chase},S_CYBER_RUN1},  // S_CYBER_RUN8
+  {SPR_CYBR,4,6,{.p1 = A_FaceTarget},S_CYBER_ATK2}, // S_CYBER_ATK1
+  {SPR_CYBR,5,12,{.p1 = A_CyberAttack},S_CYBER_ATK3}, // S_CYBER_ATK2
+  {SPR_CYBR,4,12,{.p1 = A_FaceTarget},S_CYBER_ATK4},  // S_CYBER_ATK3
+  {SPR_CYBR,5,12,{.p1 = A_CyberAttack},S_CYBER_ATK5}, // S_CYBER_ATK4
+  {SPR_CYBR,4,12,{.p1 = A_FaceTarget},S_CYBER_ATK6},  // S_CYBER_ATK5
+  {SPR_CYBR,5,12,{.p1 = A_CyberAttack},S_CYBER_RUN1}, // S_CYBER_ATK6
+  {SPR_CYBR,6,10,{.p1 = A_Pain},S_CYBER_RUN1},  // S_CYBER_PAIN
   {SPR_CYBR,7,10,{NULL},S_CYBER_DIE2},  // S_CYBER_DIE1
-  {SPR_CYBR,8,10,{A_Scream},S_CYBER_DIE3},  // S_CYBER_DIE2
+  {SPR_CYBR,8,10,{.p1 = A_Scream},S_CYBER_DIE3},  // S_CYBER_DIE2
   {SPR_CYBR,9,10,{NULL},S_CYBER_DIE4},  // S_CYBER_DIE3
   {SPR_CYBR,10,10,{NULL},S_CYBER_DIE5}, // S_CYBER_DIE4
   {SPR_CYBR,11,10,{NULL},S_CYBER_DIE6}, // S_CYBER_DIE5
-  {SPR_CYBR,12,10,{A_Fall},S_CYBER_DIE7}, // S_CYBER_DIE6
+  {SPR_CYBR,12,10,{.p1 = A_Fall},S_CYBER_DIE7}, // S_CYBER_DIE6
   {SPR_CYBR,13,10,{NULL},S_CYBER_DIE8}, // S_CYBER_DIE7
   {SPR_CYBR,14,10,{NULL},S_CYBER_DIE9}, // S_CYBER_DIE8
   {SPR_CYBR,15,30,{NULL},S_CYBER_DIE10},  // S_CYBER_DIE9
-  {SPR_CYBR,15,-1,{A_BossDeath},S_NULL},  // S_CYBER_DIE10
-  {SPR_PAIN,0,10,{A_Look},S_PAIN_STND}, // S_PAIN_STND
-  {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN2}, // S_PAIN_RUN1
-  {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN3}, // S_PAIN_RUN2
-  {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN4}, // S_PAIN_RUN3
-  {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN5}, // S_PAIN_RUN4
-  {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN6}, // S_PAIN_RUN5
-  {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN1}, // S_PAIN_RUN6
-  {SPR_PAIN,3,5,{A_FaceTarget},S_PAIN_ATK2},  // S_PAIN_ATK1
-  {SPR_PAIN,4,5,{A_FaceTarget},S_PAIN_ATK3},  // S_PAIN_ATK2
-  {SPR_PAIN,5|FF_FULLBRIGHT,5,{A_FaceTarget},S_PAIN_ATK4},  // S_PAIN_ATK3
-  {SPR_PAIN,5|FF_FULLBRIGHT,0,{A_PainAttack},S_PAIN_RUN1},  // S_PAIN_ATK4
+  {SPR_CYBR,15,-1,{.p1 = A_BossDeath},S_NULL},  // S_CYBER_DIE10
+  {SPR_PAIN,0,10,{.p1 = A_Look},S_PAIN_STND}, // S_PAIN_STND
+  {SPR_PAIN,0,3,{.p1 = A_Chase},S_PAIN_RUN2}, // S_PAIN_RUN1
+  {SPR_PAIN,0,3,{.p1 = A_Chase},S_PAIN_RUN3}, // S_PAIN_RUN2
+  {SPR_PAIN,1,3,{.p1 = A_Chase},S_PAIN_RUN4}, // S_PAIN_RUN3
+  {SPR_PAIN,1,3,{.p1 = A_Chase},S_PAIN_RUN5}, // S_PAIN_RUN4
+  {SPR_PAIN,2,3,{.p1 = A_Chase},S_PAIN_RUN6}, // S_PAIN_RUN5
+  {SPR_PAIN,2,3,{.p1 = A_Chase},S_PAIN_RUN1}, // S_PAIN_RUN6
+  {SPR_PAIN,3,5,{.p1 = A_FaceTarget},S_PAIN_ATK2},  // S_PAIN_ATK1
+  {SPR_PAIN,4,5,{.p1 = A_FaceTarget},S_PAIN_ATK3},  // S_PAIN_ATK2
+  {SPR_PAIN,5|FF_FULLBRIGHT,5,{.p1 = A_FaceTarget},S_PAIN_ATK4},  // S_PAIN_ATK3
+  {SPR_PAIN,5|FF_FULLBRIGHT,0,{.p1 = A_PainAttack},S_PAIN_RUN1},  // S_PAIN_ATK4
   {SPR_PAIN,6,6,{NULL},S_PAIN_PAIN2}, // S_PAIN_PAIN
-  {SPR_PAIN,6,6,{A_Pain},S_PAIN_RUN1},  // S_PAIN_PAIN2
+  {SPR_PAIN,6,6,{.p1 = A_Pain},S_PAIN_RUN1},  // S_PAIN_PAIN2
   {SPR_PAIN,7|FF_FULLBRIGHT,8,{NULL},S_PAIN_DIE2},  // S_PAIN_DIE1
-  {SPR_PAIN,8|FF_FULLBRIGHT,8,{A_Scream},S_PAIN_DIE3},  // S_PAIN_DIE2
+  {SPR_PAIN,8|FF_FULLBRIGHT,8,{.p1 = A_Scream},S_PAIN_DIE3},  // S_PAIN_DIE2
   {SPR_PAIN,9|FF_FULLBRIGHT,8,{NULL},S_PAIN_DIE4},  // S_PAIN_DIE3
   {SPR_PAIN,10|FF_FULLBRIGHT,8,{NULL},S_PAIN_DIE5},  // S_PAIN_DIE4
-  {SPR_PAIN,11|FF_FULLBRIGHT,8,{A_PainDie},S_PAIN_DIE6}, // S_PAIN_DIE5
+  {SPR_PAIN,11|FF_FULLBRIGHT,8,{.p1 = A_PainDie},S_PAIN_DIE6}, // S_PAIN_DIE5
   {SPR_PAIN,12|FF_FULLBRIGHT,8,{NULL},S_NULL}, // S_PAIN_DIE6
   {SPR_PAIN,12,8,{NULL},S_PAIN_RAISE2}, // S_PAIN_RAISE1
   {SPR_PAIN,11,8,{NULL},S_PAIN_RAISE3}, // S_PAIN_RAISE2
@@ -845,32 +845,32 @@ state_t original_states[NUMSTATES] = {
   {SPR_PAIN,9,8,{NULL},S_PAIN_RAISE5},  // S_PAIN_RAISE4
   {SPR_PAIN,8,8,{NULL},S_PAIN_RAISE6},  // S_PAIN_RAISE5
   {SPR_PAIN,7,8,{NULL},S_PAIN_RUN1},  // S_PAIN_RAISE6
-  {SPR_SSWV,0,10,{A_Look},S_SSWV_STND2},  // S_SSWV_STND
-  {SPR_SSWV,1,10,{A_Look},S_SSWV_STND}, // S_SSWV_STND2
-  {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN2}, // S_SSWV_RUN1
-  {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN3}, // S_SSWV_RUN2
-  {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN4}, // S_SSWV_RUN3
-  {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN5}, // S_SSWV_RUN4
-  {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN6}, // S_SSWV_RUN5
-  {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN7}, // S_SSWV_RUN6
-  {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN8}, // S_SSWV_RUN7
-  {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN1}, // S_SSWV_RUN8
-  {SPR_SSWV,4,10,{A_FaceTarget},S_SSWV_ATK2}, // S_SSWV_ATK1
-  {SPR_SSWV,5,10,{A_FaceTarget},S_SSWV_ATK3}, // S_SSWV_ATK2
-  {SPR_SSWV,6|FF_FULLBRIGHT,4,{A_CPosAttack},S_SSWV_ATK4},  // S_SSWV_ATK3
-  {SPR_SSWV,5,6,{A_FaceTarget},S_SSWV_ATK5},  // S_SSWV_ATK4
-  {SPR_SSWV,6|FF_FULLBRIGHT,4,{A_CPosAttack},S_SSWV_ATK6},  // S_SSWV_ATK5
-  {SPR_SSWV,5,1,{A_CPosRefire},S_SSWV_ATK2},  // S_SSWV_ATK6
+  {SPR_SSWV,0,10,{.p1 = A_Look},S_SSWV_STND2},  // S_SSWV_STND
+  {SPR_SSWV,1,10,{.p1 = A_Look},S_SSWV_STND}, // S_SSWV_STND2
+  {SPR_SSWV,0,3,{.p1 = A_Chase},S_SSWV_RUN2}, // S_SSWV_RUN1
+  {SPR_SSWV,0,3,{.p1 = A_Chase},S_SSWV_RUN3}, // S_SSWV_RUN2
+  {SPR_SSWV,1,3,{.p1 = A_Chase},S_SSWV_RUN4}, // S_SSWV_RUN3
+  {SPR_SSWV,1,3,{.p1 = A_Chase},S_SSWV_RUN5}, // S_SSWV_RUN4
+  {SPR_SSWV,2,3,{.p1 = A_Chase},S_SSWV_RUN6}, // S_SSWV_RUN5
+  {SPR_SSWV,2,3,{.p1 = A_Chase},S_SSWV_RUN7}, // S_SSWV_RUN6
+  {SPR_SSWV,3,3,{.p1 = A_Chase},S_SSWV_RUN8}, // S_SSWV_RUN7
+  {SPR_SSWV,3,3,{.p1 = A_Chase},S_SSWV_RUN1}, // S_SSWV_RUN8
+  {SPR_SSWV,4,10,{.p1 = A_FaceTarget},S_SSWV_ATK2}, // S_SSWV_ATK1
+  {SPR_SSWV,5,10,{.p1 = A_FaceTarget},S_SSWV_ATK3}, // S_SSWV_ATK2
+  {SPR_SSWV,6|FF_FULLBRIGHT,4,{.p1 = A_CPosAttack},S_SSWV_ATK4},  // S_SSWV_ATK3
+  {SPR_SSWV,5,6,{.p1 = A_FaceTarget},S_SSWV_ATK5},  // S_SSWV_ATK4
+  {SPR_SSWV,6|FF_FULLBRIGHT,4,{.p1 = A_CPosAttack},S_SSWV_ATK6},  // S_SSWV_ATK5
+  {SPR_SSWV,5,1,{.p1 = A_CPosRefire},S_SSWV_ATK2},  // S_SSWV_ATK6
   {SPR_SSWV,7,3,{NULL},S_SSWV_PAIN2}, // S_SSWV_PAIN
-  {SPR_SSWV,7,3,{A_Pain},S_SSWV_RUN1},  // S_SSWV_PAIN2
+  {SPR_SSWV,7,3,{.p1 = A_Pain},S_SSWV_RUN1},  // S_SSWV_PAIN2
   {SPR_SSWV,8,5,{NULL},S_SSWV_DIE2},  // S_SSWV_DIE1
-  {SPR_SSWV,9,5,{A_Scream},S_SSWV_DIE3},  // S_SSWV_DIE2
-  {SPR_SSWV,10,5,{A_Fall},S_SSWV_DIE4}, // S_SSWV_DIE3
+  {SPR_SSWV,9,5,{.p1 = A_Scream},S_SSWV_DIE3},  // S_SSWV_DIE2
+  {SPR_SSWV,10,5,{.p1 = A_Fall},S_SSWV_DIE4}, // S_SSWV_DIE3
   {SPR_SSWV,11,5,{NULL},S_SSWV_DIE5}, // S_SSWV_DIE4
   {SPR_SSWV,12,-1,{NULL},S_NULL}, // S_SSWV_DIE5
   {SPR_SSWV,13,5,{NULL},S_SSWV_XDIE2},  // S_SSWV_XDIE1
-  {SPR_SSWV,14,5,{A_XScream},S_SSWV_XDIE3}, // S_SSWV_XDIE2
-  {SPR_SSWV,15,5,{A_Fall},S_SSWV_XDIE4},  // S_SSWV_XDIE3
+  {SPR_SSWV,14,5,{.p1 = A_XScream},S_SSWV_XDIE3}, // S_SSWV_XDIE2
+  {SPR_SSWV,15,5,{.p1 = A_Fall},S_SSWV_XDIE4},  // S_SSWV_XDIE3
   {SPR_SSWV,16,5,{NULL},S_SSWV_XDIE5},  // S_SSWV_XDIE4
   {SPR_SSWV,17,5,{NULL},S_SSWV_XDIE6},  // S_SSWV_XDIE5
   {SPR_SSWV,18,5,{NULL},S_SSWV_XDIE7},  // S_SSWV_XDIE6
@@ -885,7 +885,7 @@ state_t original_states[NUMSTATES] = {
   {SPR_KEEN,0,-1,{NULL},S_KEENSTND},  // S_KEENSTND
   {SPR_KEEN,0,6,{NULL},S_COMMKEEN2},  // S_COMMKEEN
   {SPR_KEEN,1,6,{NULL},S_COMMKEEN3},  // S_COMMKEEN2
-  {SPR_KEEN,2,6,{A_Scream},S_COMMKEEN4},  // S_COMMKEEN3
+  {SPR_KEEN,2,6,{.p1 = A_Scream},S_COMMKEEN4},  // S_COMMKEEN3
   {SPR_KEEN,3,6,{NULL},S_COMMKEEN5},  // S_COMMKEEN4
   {SPR_KEEN,4,6,{NULL},S_COMMKEEN6},  // S_COMMKEEN5
   {SPR_KEEN,5,6,{NULL},S_COMMKEEN7},  // S_COMMKEEN6
@@ -893,34 +893,34 @@ state_t original_states[NUMSTATES] = {
   {SPR_KEEN,7,6,{NULL},S_COMMKEEN9},  // S_COMMKEEN8
   {SPR_KEEN,8,6,{NULL},S_COMMKEEN10}, // S_COMMKEEN9
   {SPR_KEEN,9,6,{NULL},S_COMMKEEN11}, // S_COMMKEEN10
-  {SPR_KEEN,10,6,{A_KeenDie},S_COMMKEEN12},// S_COMMKEEN11
+  {SPR_KEEN,10,6,{.p1 = A_KeenDie},S_COMMKEEN12},// S_COMMKEEN11
   {SPR_KEEN,11,-1,{NULL},S_NULL},   // S_COMMKEEN12
   {SPR_KEEN,12,4,{NULL},S_KEENPAIN2}, // S_KEENPAIN
-  {SPR_KEEN,12,8,{A_Pain},S_KEENSTND},  // S_KEENPAIN2
+  {SPR_KEEN,12,8,{.p1 = A_Pain},S_KEENSTND},  // S_KEENPAIN2
   {SPR_BBRN,0,-1,{NULL},S_NULL},    // S_BRAIN
-  {SPR_BBRN,1,36,{A_BrainPain},S_BRAIN},  // S_BRAIN_PAIN
-  {SPR_BBRN,0,100,{A_BrainScream},S_BRAIN_DIE2},  // S_BRAIN_DIE1
+  {SPR_BBRN,1,36,{.p1 = A_BrainPain},S_BRAIN},  // S_BRAIN_PAIN
+  {SPR_BBRN,0,100,{.p1 = A_BrainScream},S_BRAIN_DIE2},  // S_BRAIN_DIE1
   {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE3},  // S_BRAIN_DIE2
   {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE4},  // S_BRAIN_DIE3
-  {SPR_BBRN,0,-1,{A_BrainDie},S_NULL},  // S_BRAIN_DIE4
-  {SPR_SSWV,0,10,{A_Look},S_BRAINEYE},  // S_BRAINEYE
-  {SPR_SSWV,0,181,{A_BrainAwake},S_BRAINEYE1},  // S_BRAINEYESEE
-  {SPR_SSWV,0,150,{A_BrainSpit},S_BRAINEYE1}, // S_BRAINEYE1
-  {SPR_BOSF,0|FF_FULLBRIGHT,3,{A_SpawnSound},S_SPAWN2}, // S_SPAWN1
-  {SPR_BOSF,1|FF_FULLBRIGHT,3,{A_SpawnFly},S_SPAWN3}, // S_SPAWN2
-  {SPR_BOSF,2|FF_FULLBRIGHT,3,{A_SpawnFly},S_SPAWN4}, // S_SPAWN3
-  {SPR_BOSF,3|FF_FULLBRIGHT,3,{A_SpawnFly},S_SPAWN1}, // S_SPAWN4
-  {SPR_FIRE,0|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE2}, // S_SPAWNFIRE1
-  {SPR_FIRE,1|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE3}, // S_SPAWNFIRE2
-  {SPR_FIRE,2|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE4}, // S_SPAWNFIRE3
-  {SPR_FIRE,3|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE5}, // S_SPAWNFIRE4
-  {SPR_FIRE,4|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE6}, // S_SPAWNFIRE5
-  {SPR_FIRE,5|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE7}, // S_SPAWNFIRE6
-  {SPR_FIRE,6|FF_FULLBRIGHT,4,{A_Fire},S_SPAWNFIRE8}, // S_SPAWNFIRE7
-  {SPR_FIRE,7|FF_FULLBRIGHT,4,{A_Fire},S_NULL},   // S_SPAWNFIRE8
+  {SPR_BBRN,0,-1,{.p1 = A_BrainDie},S_NULL},  // S_BRAIN_DIE4
+  {SPR_SSWV,0,10,{.p1 = A_Look},S_BRAINEYE},  // S_BRAINEYE
+  {SPR_SSWV,0,181,{.p1 = A_BrainAwake},S_BRAINEYE1},  // S_BRAINEYESEE
+  {SPR_SSWV,0,150,{.p1 = A_BrainSpit},S_BRAINEYE1}, // S_BRAINEYE1
+  {SPR_BOSF,0|FF_FULLBRIGHT,3,{.p1 = A_SpawnSound},S_SPAWN2}, // S_SPAWN1
+  {SPR_BOSF,1|FF_FULLBRIGHT,3,{.p1 = A_SpawnFly},S_SPAWN3}, // S_SPAWN2
+  {SPR_BOSF,2|FF_FULLBRIGHT,3,{.p1 = A_SpawnFly},S_SPAWN4}, // S_SPAWN3
+  {SPR_BOSF,3|FF_FULLBRIGHT,3,{.p1 = A_SpawnFly},S_SPAWN1}, // S_SPAWN4
+  {SPR_FIRE,0|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE2}, // S_SPAWNFIRE1
+  {SPR_FIRE,1|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE3}, // S_SPAWNFIRE2
+  {SPR_FIRE,2|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE4}, // S_SPAWNFIRE3
+  {SPR_FIRE,3|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE5}, // S_SPAWNFIRE4
+  {SPR_FIRE,4|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE6}, // S_SPAWNFIRE5
+  {SPR_FIRE,5|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE7}, // S_SPAWNFIRE6
+  {SPR_FIRE,6|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_SPAWNFIRE8}, // S_SPAWNFIRE7
+  {SPR_FIRE,7|FF_FULLBRIGHT,4,{.p1 = A_Fire},S_NULL},   // S_SPAWNFIRE8
   {SPR_MISL,1|FF_FULLBRIGHT,10,{NULL},S_BRAINEXPLODE2}, // S_BRAINEXPLODE1
   {SPR_MISL,2|FF_FULLBRIGHT,10,{NULL},S_BRAINEXPLODE3}, // S_BRAINEXPLODE2
-  {SPR_MISL,3|FF_FULLBRIGHT,10,{A_BrainExplode},S_NULL},  // S_BRAINEXPLODE3
+  {SPR_MISL,3|FF_FULLBRIGHT,10,{.p1 = A_BrainExplode},S_NULL},  // S_BRAINEXPLODE3
   {SPR_ARM1,0,6,{NULL},S_ARM1A},  // S_ARM1
   {SPR_ARM1,1|FF_FULLBRIGHT,7,{NULL},S_ARM1}, // S_ARM1A
   {SPR_ARM2,0,6,{NULL},S_ARM2A},  // S_ARM2
@@ -928,9 +928,9 @@ state_t original_states[NUMSTATES] = {
   {SPR_BAR1,0,6,{NULL},S_BAR2}, // S_BAR1
   {SPR_BAR1,1,6,{NULL},S_BAR1}, // S_BAR2
   {SPR_BEXP,0|FF_FULLBRIGHT,5,{NULL},S_BEXP2},  // S_BEXP
-  {SPR_BEXP,1|FF_FULLBRIGHT,5,{A_Scream},S_BEXP3},  // S_BEXP2
+  {SPR_BEXP,1|FF_FULLBRIGHT,5,{.p1 = A_Scream},S_BEXP3},  // S_BEXP2
   {SPR_BEXP,2|FF_FULLBRIGHT,5,{NULL},S_BEXP4},  // S_BEXP3
-  {SPR_BEXP,3|FF_FULLBRIGHT,10,{A_Explode},S_BEXP5},  // S_BEXP4
+  {SPR_BEXP,3|FF_FULLBRIGHT,10,{.p1 = A_Explode},S_BEXP5},  // S_BEXP4
   {SPR_BEXP,4|FF_FULLBRIGHT,10,{NULL},S_NULL},  // S_BEXP5
   {SPR_FCAN,0|FF_FULLBRIGHT,4,{NULL},S_BBAR2},  // S_BBAR1
   {SPR_FCAN,1|FF_FULLBRIGHT,4,{NULL},S_BBAR3},  // S_BBAR2
@@ -1090,33 +1090,33 @@ state_t original_states[NUMSTATES] = {
   {SPR_TNT1,0,-1,{NULL},S_TNT1},          // S_TNT1    // phares 3/8/98
 
   // killough 8/9/98: grenade
-  {SPR_MISL,0|FF_FULLBRIGHT,1000,{A_Die},S_GRENADE},      // S_GRENADE
+  {SPR_MISL,0|FF_FULLBRIGHT,1000,{.p1 = A_Die},S_GRENADE},      // S_GRENADE
 
   // killough 8/10/98: variable damage explosion
-  {SPR_MISL,1|FF_FULLBRIGHT,4,{A_Scream},S_DETONATE2},    // S_DETONATE
-  {SPR_MISL,2|FF_FULLBRIGHT,6,{A_Detonate},S_DETONATE3},  // S_DETONATE2
+  {SPR_MISL,1|FF_FULLBRIGHT,4,{.p1 = A_Scream},S_DETONATE2},    // S_DETONATE
+  {SPR_MISL,2|FF_FULLBRIGHT,6,{.p1 = A_Detonate},S_DETONATE3},  // S_DETONATE2
   {SPR_MISL,3|FF_FULLBRIGHT,10,{NULL},S_NULL},            // S_DETONATE3
 
   // killough 7/19/98: Marine's best friend :)
-  {SPR_DOGS,0,10,{A_Look},S_DOGS_STND2},  // S_DOGS_STND
-  {SPR_DOGS,1,10,{A_Look},S_DOGS_STND}, // S_DOGS_STND2
-  {SPR_DOGS,0,2,{A_Chase},S_DOGS_RUN2}, // S_DOGS_RUN1
-  {SPR_DOGS,0,2,{A_Chase},S_DOGS_RUN3}, // S_DOGS_RUN2
-  {SPR_DOGS,1,2,{A_Chase},S_DOGS_RUN4}, // S_DOGS_RUN3
-  {SPR_DOGS,1,2,{A_Chase},S_DOGS_RUN5}, // S_DOGS_RUN4
-  {SPR_DOGS,2,2,{A_Chase},S_DOGS_RUN6}, // S_DOGS_RUN5
-  {SPR_DOGS,2,2,{A_Chase},S_DOGS_RUN7}, // S_DOGS_RUN6
-  {SPR_DOGS,3,2,{A_Chase},S_DOGS_RUN8}, // S_DOGS_RUN7
-  {SPR_DOGS,3,2,{A_Chase},S_DOGS_RUN1}, // S_DOGS_RUN8
-  {SPR_DOGS,4,8,{A_FaceTarget},S_DOGS_ATK2},  // S_DOGS_ATK1
-  {SPR_DOGS,5,8,{A_FaceTarget},S_DOGS_ATK3},  // S_DOGS_ATK2
-  {SPR_DOGS,6,8,{A_SargAttack},S_DOGS_RUN1},  // S_DOGS_ATK3
+  {SPR_DOGS,0,10,{.p1 = A_Look},S_DOGS_STND2},  // S_DOGS_STND
+  {SPR_DOGS,1,10,{.p1 = A_Look},S_DOGS_STND}, // S_DOGS_STND2
+  {SPR_DOGS,0,2,{.p1 = A_Chase},S_DOGS_RUN2}, // S_DOGS_RUN1
+  {SPR_DOGS,0,2,{.p1 = A_Chase},S_DOGS_RUN3}, // S_DOGS_RUN2
+  {SPR_DOGS,1,2,{.p1 = A_Chase},S_DOGS_RUN4}, // S_DOGS_RUN3
+  {SPR_DOGS,1,2,{.p1 = A_Chase},S_DOGS_RUN5}, // S_DOGS_RUN4
+  {SPR_DOGS,2,2,{.p1 = A_Chase},S_DOGS_RUN6}, // S_DOGS_RUN5
+  {SPR_DOGS,2,2,{.p1 = A_Chase},S_DOGS_RUN7}, // S_DOGS_RUN6
+  {SPR_DOGS,3,2,{.p1 = A_Chase},S_DOGS_RUN8}, // S_DOGS_RUN7
+  {SPR_DOGS,3,2,{.p1 = A_Chase},S_DOGS_RUN1}, // S_DOGS_RUN8
+  {SPR_DOGS,4,8,{.p1 = A_FaceTarget},S_DOGS_ATK2},  // S_DOGS_ATK1
+  {SPR_DOGS,5,8,{.p1 = A_FaceTarget},S_DOGS_ATK3},  // S_DOGS_ATK2
+  {SPR_DOGS,6,8,{.p1 = A_SargAttack},S_DOGS_RUN1},  // S_DOGS_ATK3
   {SPR_DOGS,7,2,{NULL},S_DOGS_PAIN2}, // S_DOGS_PAIN
-  {SPR_DOGS,7,2,{A_Pain},S_DOGS_RUN1},  // S_DOGS_PAIN2
+  {SPR_DOGS,7,2,{.p1 = A_Pain},S_DOGS_RUN1},  // S_DOGS_PAIN2
   {SPR_DOGS,8,8,{NULL},S_DOGS_DIE2},  // S_DOGS_DIE1
-  {SPR_DOGS,9,8,{A_Scream},S_DOGS_DIE3},  // S_DOGS_DIE2
+  {SPR_DOGS,9,8,{.p1 = A_Scream},S_DOGS_DIE3},  // S_DOGS_DIE2
   {SPR_DOGS,10,4,{NULL},S_DOGS_DIE4}, // S_DOGS_DIE3
-  {SPR_DOGS,11,4,{A_Fall},S_DOGS_DIE5}, // S_DOGS_DIE4
+  {SPR_DOGS,11,4,{.p1 = A_Fall},S_DOGS_DIE5}, // S_DOGS_DIE4
   {SPR_DOGS,12,4,{NULL},S_DOGS_DIE6}, // S_DOGS_DIE5
   {SPR_DOGS,13,-1,{NULL},S_NULL}, // S_DOGS_DIE6
   {SPR_DOGS,13,5,{NULL},S_DOGS_RAISE2}, // S_DOGS_RAISE1
@@ -1130,11 +1130,11 @@ state_t original_states[NUMSTATES] = {
   // S_OLDBFG1
 
 #define BFGDELAY 1
-#define OLDBFG_1FRAMES(x) {SPR_BFGG,1,BFGDELAY,{A_FireOldBFG},x+S_OLDBFG1+2},
+#define OLDBFG_1FRAMES(x) {SPR_BFGG,1,BFGDELAY,{.p2 = A_FireOldBFG},x+S_OLDBFG1+2},
 #define OLDBFG_2FRAMES(x) OLDBFG_1FRAMES(x) OLDBFG_1FRAMES(x+1)
 #define OLDBFG_4FRAMES(x) OLDBFG_2FRAMES(x) OLDBFG_2FRAMES(x+2)
 #define OLDBFG_8FRAMES(x) OLDBFG_4FRAMES(x) OLDBFG_4FRAMES(x+4)
-  {SPR_BFGG,0,10,{A_BFGsound},S_OLDBFG1+1},  // S_OLDBFG1
+  {SPR_BFGG,0,10,{.p2 = A_BFGsound},S_OLDBFG1+1},  // S_OLDBFG1
 
   OLDBFG_8FRAMES(0)
   OLDBFG_8FRAMES(8)
@@ -1142,8 +1142,8 @@ state_t original_states[NUMSTATES] = {
   OLDBFG_8FRAMES(24)
   OLDBFG_8FRAMES(32)
 
-  {SPR_BFGG,1,0,{A_Light0},S_OLDBFG43}, // S_OLDBFG42
-  {SPR_BFGG,1,20,{A_ReFire},S_BFG},   // S_OLDBFG43
+  {SPR_BFGG,1,0,{.p2 = A_Light0},S_OLDBFG43}, // S_OLDBFG42
+  {SPR_BFGG,1,20,{.p2 = A_ReFire},S_BFG},   // S_OLDBFG43
 
   // killough 7/11/98: end of beta BFG
 
@@ -1171,22 +1171,22 @@ state_t original_states[NUMSTATES] = {
   // This is an approximation, but I'm sure it can be improved.
 
   // spawnstate
-  {SPR_SKUL,0,10,{A_Look},S_BSKUL_STND},  // S_BSKUL_STND
+  {SPR_SKUL,0,10,{.p1 = A_Look},S_BSKUL_STND},  // S_BSKUL_STND
 
   // chasestate
-  {SPR_SKUL,1,5,{A_Chase},S_BSKUL_RUN2},  // S_BSKUL_RUN1
-  {SPR_SKUL,2,5,{A_Chase},S_BSKUL_RUN3},  // S_BSKUL_RUN2
-  {SPR_SKUL,3,5,{A_Chase},S_BSKUL_RUN4},  // S_BSKUL_RUN3
-  {SPR_SKUL,0,5,{A_Chase},S_BSKUL_RUN1},  // S_BSKUL_RUN4
+  {SPR_SKUL,1,5,{.p1 = A_Chase},S_BSKUL_RUN2},  // S_BSKUL_RUN1
+  {SPR_SKUL,2,5,{.p1 = A_Chase},S_BSKUL_RUN3},  // S_BSKUL_RUN2
+  {SPR_SKUL,3,5,{.p1 = A_Chase},S_BSKUL_RUN4},  // S_BSKUL_RUN3
+  {SPR_SKUL,0,5,{.p1 = A_Chase},S_BSKUL_RUN1},  // S_BSKUL_RUN4
 
   // missilestate
-  {SPR_SKUL,4,4,{A_FaceTarget},S_BSKUL_ATK2},     // S_BSKUL_ATK1
-  {SPR_SKUL,5,5,{A_BetaSkullAttack},S_BSKUL_ATK3}, // S_BSKUL_ATK2
+  {SPR_SKUL,4,4,{.p1 = A_FaceTarget},S_BSKUL_ATK2},     // S_BSKUL_ATK1
+  {SPR_SKUL,5,5,{.p1 = A_BetaSkullAttack},S_BSKUL_ATK3}, // S_BSKUL_ATK2
   {SPR_SKUL,5,4,{NULL},S_BSKUL_RUN1},              // S_BSKUL_ATK3
 
   // painstate
   {SPR_SKUL,6,4,{NULL},S_BSKUL_PAIN2},     // S_BSKUL_PAIN1
-  {SPR_SKUL,7,2,{A_Pain},S_BSKUL_RUN1},   // S_BSKUL_PAIN2
+  {SPR_SKUL,7,2,{.p1 = A_Pain},S_BSKUL_RUN1},   // S_BSKUL_PAIN2
   {SPR_SKUL,8,4,{NULL},S_BSKUL_RUN1},      // S_BSKUL_PAIN3
 
   // deathstate
@@ -1194,13 +1194,13 @@ state_t original_states[NUMSTATES] = {
   {SPR_SKUL,10,5,{NULL},S_BSKUL_DIE3},     // S_BSKUL_DIE2
   {SPR_SKUL,11,5,{NULL},S_BSKUL_DIE4},     // S_BSKUL_DIE3
   {SPR_SKUL,12,5,{NULL},S_BSKUL_DIE5},     // S_BSKUL_DIE4
-  {SPR_SKUL,13,5,{A_Scream},S_BSKUL_DIE6}, // S_BSKUL_DIE5
+  {SPR_SKUL,13,5,{.p1 = A_Scream},S_BSKUL_DIE6}, // S_BSKUL_DIE5
   {SPR_SKUL,14,5,{NULL},S_BSKUL_DIE7},     // S_BSKUL_DIE6
-  {SPR_SKUL,15,5,{A_Fall},S_BSKUL_DIE8},   // S_BSKUL_DIE7
-  {SPR_SKUL,16,5,{A_Stop},S_BSKUL_DIE8},   // S_BSKUL_DIE8
+  {SPR_SKUL,15,5,{.p1 = A_Fall},S_BSKUL_DIE8},   // S_BSKUL_DIE7
+  {SPR_SKUL,16,5,{.p1 = A_Stop},S_BSKUL_DIE8},   // S_BSKUL_DIE8
 
   // killough 10/98: mushroom effect
-  {SPR_MISL,1|FF_FULLBRIGHT,8,{A_Mushroom},S_EXPLODE2},  // S_MUSHROOM
+  {SPR_MISL,1|FF_FULLBRIGHT,8,{.p1 = A_Mushroom},S_EXPLODE2},  // S_MUSHROOM
 };
 
 // ********************************************************************
diff --git a/src/m_array.h b/src/m_array.h
index 2955c663b..641f70213 100644
--- a/src/m_array.h
+++ b/src/m_array.h
@@ -72,20 +72,20 @@ inline static void array_clear(const void *v)
     }
 }
 
-#define array_grow(v, n) ((v) = M_ArrayGrow((v), sizeof(*(v)), n))
+#define array_grow(v, n) ((v) = M_ArrayGrow(v, sizeof(*(v)), n))
 
 #define array_push(v, e)                                                    \
     do                                                                      \
     {                                                                       \
         if (!(v))                                                           \
         {                                                                   \
-            (v) = M_ArrayGrow((v), sizeof(*(v)), M_ARRAY_INIT_CAPACITY);    \
+            (v) = M_ArrayGrow(v, sizeof(*(v)), M_ARRAY_INIT_CAPACITY);      \
         }                                                                   \
-        else if (array_ptr((v))->size == array_ptr((v))->capacity)          \
+        else if (array_ptr(v)->size == array_ptr(v)->capacity)              \
         {                                                                   \
-            (v) = M_ArrayGrow((v), sizeof(*(v)), array_ptr((v))->capacity); \
+            (v) = M_ArrayGrow(v, sizeof(*(v)), array_ptr(v)->capacity);     \
         }                                                                   \
-        (v)[array_ptr((v))->size++] = (e);                                  \
+        (v)[array_ptr(v)->size++] = (e);                                    \
     } while (0)
 
 #define array_free(v)                     \
@@ -93,13 +93,15 @@ inline static void array_clear(const void *v)
     {                                     \
         if (v)                            \
         {                                 \
-            M_ARRAY_FREE(array_ptr((v))); \
+            M_ARRAY_FREE(array_ptr(v));   \
             (v) = NULL;                   \
         }                                 \
     } while (0)
 
+#define array_end(v) ((v) + array_size(v))
+
 #define array_foreach(ptr, v) \
-    for (ptr = (v); ptr != &(v)[array_size((v))]; ++ptr)
+    for (ptr = (v); ptr < array_end(v); ++ptr)
 
 inline static void *M_ArrayGrow(void *v, size_t esize, int n)
 {
diff --git a/src/m_cheat.c b/src/m_cheat.c
index fb4579f5e..f61b835c7 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -30,8 +30,10 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "g_game.h"
+#include "g_umapinfo.h"
 #include "info.h"
 #include "m_cheat.h"
+#include "m_array.h"
 #include "m_fixed.h"
 #include "m_input.h"
 #include "m_misc.h"
@@ -50,7 +52,6 @@
 #include "sounds.h"
 #include "st_widgets.h"
 #include "tables.h"
-#include "u_mapinfo.h"
 #include "w_wad.h"
 #include "ws_stuff.h"
 
@@ -87,100 +88,100 @@ static void cheat_magic2(void)
 //-----------------------------------------------------------------------------
 
 static void cheat_mus(char *buf);
-static void cheat_choppers();
-static void cheat_god();
-static void cheat_fa();
-static void cheat_k();
-static void cheat_kfa();
-static void cheat_noclip();
+static void cheat_choppers(void);
+static void cheat_god(void);
+static void cheat_fa(void);
+static void cheat_k(void);
+static void cheat_kfa(void);
+static void cheat_noclip(void);
 static void cheat_pw(int pw);
-static void cheat_behold();
+static void cheat_behold(void);
 static void cheat_clev(char *buf);
-static void cheat_clev0();
-static void cheat_mypos();
+static void cheat_clev0(void);
+static void cheat_mypos(void);
 static void cheat_comp(char *buf);
-static void cheat_comp0();
+static void cheat_comp0(void);
 static void cheat_skill(char *buf);
-static void cheat_skill0();
-static void cheat_friction();
-static void cheat_pushers();
-static void cheat_tran();
-static void cheat_massacre();
-static void cheat_ddt();
-static void cheat_hom();
-static void cheat_fast();
-static void cheat_key();
-static void cheat_keyx();
+static void cheat_skill0(void);
+static void cheat_friction(void);
+static void cheat_pushers(void);
+static void cheat_tran(void);
+static void cheat_massacre(void);
+static void cheat_ddt(void);
+static void cheat_hom(void);
+static void cheat_fast(void);
+static void cheat_key(void);
+static void cheat_keyx(void);
 static void cheat_keyxx(int key);
-static void cheat_weap();
+static void cheat_weap(void);
 static void cheat_weapx(char *buf);
-static void cheat_ammo();
+static void cheat_ammo(void);
 static void cheat_ammox(char *buf);
-static void cheat_smart();
-static void cheat_pitch();
-static void cheat_nuke();
-static void cheat_rate();
-static void cheat_buddha();
-static void cheat_spechits();
-static void cheat_notarget();
-static void cheat_freeze();
-static void cheat_health();
-static void cheat_megaarmour();
-static void cheat_reveal_secret();
-static void cheat_reveal_kill();
-static void cheat_reveal_item();
-
-static void cheat_autoaim();      // killough 7/19/98
-static void cheat_tst();
-static void cheat_showfps(); // [FG] FPS counter widget
-static void cheat_speed();
+static void cheat_smart(void);
+static void cheat_pitch(void);
+static void cheat_nuke(void);
+static void cheat_rate(void);
+static void cheat_buddha(void);
+static void cheat_spechits(void);
+static void cheat_notarget(void);
+static void cheat_freeze(void);
+static void cheat_health(void);
+static void cheat_megaarmour(void);
+static void cheat_reveal_secret(void);
+static void cheat_reveal_kill(void);
+static void cheat_reveal_item(void);
+
+static void cheat_autoaim(void);      // killough 7/19/98
+static void cheat_tst(void);
+static void cheat_showfps(void); // [FG] FPS counter widget
+static void cheat_speed(void);
 
 // [Nugget] /-----------------------------------------------------------------
 
-static void cheat_nomomentum();
-static void cheat_fauxdemo();   // Emulates demo/net play state, for debugging
-static void cheat_infammo();    // Infinite ammo cheat
-static void cheat_fastweaps();  // Fast weapons cheat
-static void cheat_bobbers();    // Shortcut to the two cheats above
+static void cheat_nomomentum(void);
+static void cheat_fauxdemo(void);   // Emulates demo/net play state, for debugging
+static void cheat_infammo(void);    // Infinite ammo cheat
+static void cheat_fastweaps(void);  // Fast weapons cheat
+static void cheat_bobbers(void);    // Shortcut to the two cheats above
 
 boolean gibbers;                // Used for 'GIBBERS'
-static void cheat_gibbers();    // Everything gibs
+static void cheat_gibbers(void);    // Everything gibs
 
 static void cheat_riotmode(void);
-static void cheat_resurrect();
-static void cheat_fly();
-static void cheat_normalexit(); // Emulate normal level exit
-static void cheat_secretexit(); // Emulate secret level exit
+static void cheat_resurrect(void);
+static void cheat_fly(void);
+static void cheat_normalexit(void); // Emulate normal level exit
+static void cheat_secretexit(void); // Emulate secret level exit
 static void cheat_turbo(char *buf);
 
 // Summon a mobj
-static void cheat_summon();
+static void cheat_summon(void);
 // Enemy
-static void cheat_summone0();
+static void cheat_summone0(void);
 static void cheat_summone(char *buf);
 // Friend
-static void cheat_summonf0();
+static void cheat_summonf0(void);
 static void cheat_summonf(char *buf);
 // Repeat last
-static void cheat_summonr();
+static void cheat_summonr(void);
 static int spawneetype = -1;
 static boolean spawneefriend;
 
-static void cheat_reveal_key();
-static void cheat_reveal_keyx();
+static void cheat_reveal_key(void);
+static void cheat_reveal_keyx(void);
 static void cheat_reveal_keyxx(int key);
 
-static void cheat_linetarget(); // Give info on the current linetarget
-static void cheat_trails();     // Show hitscan trails
-static void cheat_mdk();        // Inspired by ZDoom's console command
-static void cheat_saitama();    // MDK Fist
+static void cheat_linetarget(void); // Give info on the current linetarget
+static void cheat_trails(void);     // Show hitscan trails
+static void cheat_mdk(void);        // Inspired by ZDoom's console command
+static void cheat_saitama(void);    // MDK Fist
 
-static void cheat_boomcan();    // Explosive hitscan
+static void cheat_boomcan(void);    // Explosive hitscan
 
-static void cheat_cheese();
+static void cheat_cheese(void);
 
 boolean idgaf;
-static void cheat_idgaf();
+static void cheat_idgaf(void);
 
 // [Nugget] -----------------------------------------------------------------/
 
@@ -207,246 +208,247 @@ static void cheat_idgaf();
 
 struct cheat_s cheat[] = {
   {"idmus",      "Change music",      always,
-   {cheat_mus}, -2 },
+   {.s = cheat_mus}, -2 },
 
   {"idchoppers", "Chainsaw",          not_net | not_demo,
-   {cheat_choppers} },
+   {.v = cheat_choppers} },
 
   {"iddqd",      "God mode",          not_net | not_demo,
-   {cheat_god} },
+   {.v = cheat_god} },
 
   {"buddha",     "Buddha mode",       not_net | not_demo,
-   {cheat_buddha} },
+   {.v = cheat_buddha} },
 
   {"idk",        NULL,                not_net | not_demo | not_deh,
-   {cheat_k} }, // The most controversial cheat code in Doom history!!!
+   {.v = cheat_k} }, // The most controversial cheat code in Doom history!!!
 
   {"idkfa",      "Ammo & Keys",       not_net | not_demo,
-   {cheat_kfa} },
+   {.v = cheat_kfa} },
 
   {"idfa",       "Ammo",              not_net | not_demo,
-   {cheat_fa} },
+   {.v = cheat_fa} },
 
   {"idspispopd", "No Clipping 1",     not_net | not_demo,
-   {cheat_noclip} },
+   {.v = cheat_noclip} },
 
   {"idclip",     "No Clipping 2",     not_net | not_demo,
-   {cheat_noclip} },
+   {.v = cheat_noclip} },
 
   {"idbeholdo",  NULL,                not_net | not_demo | not_deh,
-   {cheat_pw}, NUMPOWERS }, // [FG] disable all powerups at once
+   {.i = cheat_pw}, NUMPOWERS }, // [FG] disable all powerups at once
 
   {"idbeholdh",  "Health",            not_net | not_demo,
-   {cheat_health} },
+   {.v = cheat_health} },
 
   {"idbeholdm",  "Mega Armor",        not_net | not_demo,
-   {cheat_megaarmour} },
+   {.v = cheat_megaarmour} },
 
   {"idbeholdv",  "Invincibility",     not_net | not_demo,
-   {cheat_pw}, pw_invulnerability },
+   {.i = cheat_pw}, pw_invulnerability },
 
   {"idbeholds",  "Berserk",           not_net | not_demo,
-   {cheat_pw}, pw_strength },
+   {.i = cheat_pw}, pw_strength },
 
-  {"idbeholdi",  "Invisibility",      not_net | not_demo,
-   {cheat_pw}, pw_invisibility },
+  {"idbeholdi",  "Invisibility",      not_net | not_demo,  
+   {.i = cheat_pw}, pw_invisibility },
 
   {"idbeholdr",  "Radiation Suit",    not_net | not_demo,
-   {cheat_pw}, pw_ironfeet },
+   {.i = cheat_pw}, pw_ironfeet },
 
   {"idbeholda",  "Auto-map",          not_dm,
-   {cheat_pw}, pw_allmap },
+   {.i = cheat_pw}, pw_allmap },
 
   {"idbeholdl",  "Lite-Amp Goggles",  not_dm,
-   {cheat_pw}, pw_infrared },
+   {.i = cheat_pw}, pw_infrared },
 
   {"idbehold",   "BEHOLD menu",       not_net | not_demo,
-   {cheat_behold} },
+   {.v = cheat_behold} },
 
   {"idclev",     "Level Warp",        not_net | not_demo | not_menu,
-   {cheat_clev}, -2 },
+   {.s = cheat_clev}, -2 },
 
   {"idclev",     "Level Warp",        not_net | not_demo | not_menu,
-   {cheat_clev0} },
+   {.v = cheat_clev0} },
 
   {"idmypos",    "Player Position",   not_dm, // [FG] not_net | not_demo,
-   {cheat_mypos} },
+   {.v = cheat_mypos} },
 
   {"comp",    NULL,                   not_net | not_demo | not_menu,
-   {cheat_comp}, -2 },
+   {.s = cheat_comp}, -2 },
 
   {"comp",    NULL,                   not_net | not_demo | not_menu,
-   {cheat_comp0} },
+   {.v = cheat_comp0} },
 
   {"skill",    NULL,                  not_net | not_demo | not_menu,
-   {cheat_skill}, -1 },
+   {.s = cheat_skill}, -1 },
 
   {"skill",    NULL,                  not_net | not_demo | not_menu,
-   {cheat_skill0} },
+   {.v = cheat_skill0} },
 
   {"killem",     NULL,                not_net | not_demo,
-   {cheat_massacre} },   // jff 2/01/98 kill all monsters
+   {.v = cheat_massacre} },   // jff 2/01/98 kill all monsters
 
   {"tntem",     NULL,                not_net | not_demo,
    {cheat_massacre} },     // [Nugget] 'KILLEM' alternative
 
   {"spechits",     NULL,              not_net | not_demo,
-   {cheat_spechits} },
+   {.v = cheat_spechits} },
 
   {"notarget",   "Notarget mode",     not_net | not_demo,
-   {cheat_notarget} },
+   {.v = cheat_notarget} },
 
   {"freeze",     "Freeze",            not_net | not_demo,
-   {cheat_freeze} },
+   {.v = cheat_freeze} },
 
   {"iddt",       "Map cheat",         not_dm,
-   {cheat_ddt} },        // killough 2/07/98: moved from am_map.c
+   {.v = cheat_ddt} },        // killough 2/07/98: moved from am_map.c
 
   {"iddst",      NULL,                not_dm,
-   {cheat_reveal_secret} },
+   {.v = cheat_reveal_secret} },
 
   {"iddkt",      NULL,                not_dm,
-   {cheat_reveal_kill} },
+   {.v = cheat_reveal_kill} },
 
   {"iddit",      NULL,                not_dm,
-   {cheat_reveal_item} },
+   {.v = cheat_reveal_item} },
 
   {"hom",     NULL,                   always,
-   {cheat_hom} },        // killough 2/07/98: HOM autodetector
+   {.v = cheat_hom} },        // killough 2/07/98: HOM autodetector
 
-  {"key",     NULL,                   not_net | not_demo,
-   {cheat_key} },     // killough 2/16/98: generalized key cheats
+  {"key",     NULL,                   not_net | not_demo, 
+   {.v = cheat_key} },     // killough 2/16/98: generalized key cheats
 
   {"keyr",    NULL,                   not_net | not_demo,
-   {cheat_keyx} },
+   {.v = cheat_keyx} },
 
   {"keyy",    NULL,                   not_net | not_demo,
-   {cheat_keyx} },
+   {.v = cheat_keyx} },
 
   {"keyb",    NULL,                   not_net | not_demo,
-   {cheat_keyx} },
+   {.v = cheat_keyx} },
 
-  {"keyrc",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_redcard },
+  {"keyrc",   NULL,                   not_net | not_demo, 
+   {.i = cheat_keyxx}, it_redcard },
 
   {"keyyc",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_yellowcard },
+   {.i = cheat_keyxx}, it_yellowcard },
 
-  {"keybc",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_bluecard },
+  {"keybc",   NULL,                   not_net | not_demo, 
+   {.i = cheat_keyxx}, it_bluecard },
 
   {"keyrs",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_redskull },
+   {.i = cheat_keyxx}, it_redskull },
 
   {"keyys",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_yellowskull },
+   {.i = cheat_keyxx}, it_yellowskull },
 
   {"keybs",   NULL,                   not_net | not_demo,
-   {cheat_keyxx}, it_blueskull }, // killough 2/16/98: end generalized keys
+   {.i = cheat_keyxx}, it_blueskull }, // killough 2/16/98: end generalized keys
 
   {"weap",    NULL,                   not_net | not_demo,
-   {cheat_weap} },    // killough 2/16/98: generalized weapon cheats
+   {.v = cheat_weap} },    // killough 2/16/98: generalized weapon cheats
 
   {"weap",    NULL,                   not_net | not_demo,
-   {cheat_weapx}, -1 },
+   {.s = cheat_weapx}, -1 },
 
   {"ammo",    NULL,                   not_net | not_demo,
-   {cheat_ammo} },
+   {.v = cheat_ammo} },
 
   {"ammo",    NULL,                   not_net | not_demo,
-   {cheat_ammox}, -1 }, // killough 2/16/98: end generalized weapons
+   {.s = cheat_ammox}, -1 }, // killough 2/16/98: end generalized weapons
 
   {"tran",    NULL,                   always,
-   {cheat_tran} },    // invoke translucency         // phares
+   {.v = cheat_tran} },    // invoke translucency         // phares
 
   {"smart",   NULL,                   not_net | not_demo,
-   {cheat_smart} },      // killough 2/21/98: smart monster toggle
+   {.v = cheat_smart} },      // killough 2/21/98: smart monster toggle
 
   {"pitch",   NULL,                   always,
-   {cheat_pitch} },      // killough 2/21/98: pitched sound toggle
+   {.v = cheat_pitch} },      // killough 2/21/98: pitched sound toggle
 
   // killough 2/21/98: reduce RSI injury by adding simpler alias sequences:
-  {"mbfran",     NULL,                always,
-   {cheat_tran} },    // killough 2/21/98: same as mbftran
+  {"mbfran",     NULL,                always, 
+   {.v = cheat_tran} },    // killough 2/21/98: same as mbftran
 
   {"fast",    NULL,                   not_net | not_demo,
-   {cheat_fast} },       // killough 3/6/98: -fast toggle
+   {.v = cheat_fast} },       // killough 3/6/98: -fast toggle
 
   {"ice",     NULL,                   not_net | not_demo,
-   {cheat_friction} },   // phares 3/10/98: toggle variable friction effects
+   {.v = cheat_friction} },   // phares 3/10/98: toggle variable friction effects
 
-  {"push",    NULL,                   not_net | not_demo,
-   {cheat_pushers} },    // phares 3/10/98: toggle pushers
+  {"push",    NULL,                   not_net | not_demo, 
+   {.v = cheat_pushers} },    // phares 3/10/98: toggle pushers
 
   {"nuke",    NULL,                   not_net | not_demo,
-   {cheat_nuke} },       // killough 12/98: disable nukage damage
+   {.v = cheat_nuke} },       // killough 12/98: disable nukage damage
 
   {"rate",    NULL,                   always,
-   {cheat_rate} },
+   {.v = cheat_rate} },
 
   {"aim",        NULL,                not_net | not_demo | beta_only,
-   {cheat_autoaim} },
+   {.v = cheat_autoaim} },
 
   {"eek",        NULL,                not_dm  | not_demo | beta_only,
-   {cheat_ddt} },        // killough 2/07/98: moved from am_map.c
+   {.v = cheat_ddt} },        // killough 2/07/98: moved from am_map.c
 
   {"amo",        NULL,                not_net | not_demo | beta_only,
-   {cheat_kfa} },
+   {.v = cheat_kfa} },
 
   {"tst",        NULL,                not_net | not_demo | beta_only,
-   {cheat_tst} },
+   {.v = cheat_tst} },
 
   {"nc",         NULL,                not_net | not_demo | beta_only,
-   {cheat_noclip} },
+   {.v = cheat_noclip} },
 
-  // [Nugget] Change to just "fps"
-  {"fps",        NULL,                always,
-   {cheat_showfps} },
+// [FG] FPS counter widget
+// [Nugget] Change to just "fps"
+  {"fps",    NULL,                always,
+   {.v = cheat_showfps} },
 
   {"speed",      NULL,                not_dm,
-   {cheat_speed} },
+   {.v = cheat_speed} },
 
   // [Nugget] /---------------------------------------------------------------
 
-  {"nomomentum", NULL, not_net | not_demo, {cheat_nomomentum}     },
-  {"fauxdemo",   NULL, not_net | not_demo, {cheat_fauxdemo}       }, // Emulates demo/net play state, for debugging
-  {"fullclip",   NULL, not_net | not_demo, {cheat_infammo}        }, // Infinite ammo cheat
-  {"valiant",    NULL, not_net | not_demo, {cheat_fastweaps}      }, // Fast weapons cheat
-  {"bobbers",    NULL, not_net | not_demo, {cheat_bobbers}        }, // Shortcut for the two above cheats
-  {"gibbers",    NULL, not_net | not_demo, {cheat_gibbers}        }, // Everything gibs
-  {"riotmode",   NULL, not_net | not_demo, {cheat_riotmode}   },
-  {"resurrect",  NULL, not_net | not_demo, {cheat_resurrect}      },
-  {"idres",      NULL, not_net | not_demo, {cheat_resurrect}      }, // 'RESURRECT' alternative
-  {"idfly",      NULL, not_net | not_demo, {cheat_fly}            },
-  {"nextmap",    NULL, not_net | not_demo, {cheat_normalexit}     },
-  {"nextsecret", NULL, not_net | not_demo, {cheat_secretexit}     },
-  {"turbo",      NULL, not_net | not_demo, {cheat_turbo},      -3 },
-
-  {"summon",  NULL, not_net | not_demo, {cheat_summon}      }, // Summon "Menu"
-  {"summone", NULL, not_net | not_demo, {cheat_summone0}    }, // Summon Enemy "Menu"
-  {"summone", NULL, not_net | not_demo, {cheat_summone}, -3 }, // Summon a hostile mobj
-  {"summonf", NULL, not_net | not_demo, {cheat_summonf0}    }, // Summon Friend "Menu"
-  {"summonf", NULL, not_net | not_demo, {cheat_summonf}, -3 }, // Summon a friendly mobj
-  {"summonr", NULL, not_net | not_demo, {cheat_summonr}     }, // Repeat last summon
-
-  {"iddf",   NULL, not_net | not_demo, {cheat_reveal_key}      },
-  {"iddfb",  NULL, not_net | not_demo, {cheat_reveal_keyx}     },
-  {"iddfy",  NULL, not_net | not_demo, {cheat_reveal_keyx}     },
-  {"iddfr",  NULL, not_net | not_demo, {cheat_reveal_keyx}     },
-  {"iddfbc", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 0 },
-  {"iddfyc", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 2 },
-  {"iddfrc", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 1 },
-  {"iddfbs", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 5 },
-  {"iddfys", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 3 },
-  {"iddfrs", NULL, not_net | not_demo, {cheat_reveal_keyxx}, 4 },
-
-  {"linetarget", NULL, not_net | not_demo, {cheat_linetarget} }, // Give info on the current linetarget
-  {"trails",     NULL, not_net | not_demo, {cheat_trails}     }, // Show hitscan trails
-  {"mdk",        NULL, not_net | not_demo, {cheat_mdk}        },
-  {"saitama",    NULL, not_net | not_demo, {cheat_saitama}    }, // MDK Fist
-  {"boomcan",    NULL, not_net | not_demo, {cheat_boomcan}    }, // Explosive hitscan
-  {"cheese",     NULL, not_net | not_demo, {cheat_cheese}     },
-  {"idgaf",      NULL, not_net | not_demo, {cheat_idgaf}      },
+  {"nomomentum", NULL, not_net | not_demo, {.v = cheat_nomomentum}     },
+  {"fauxdemo",   NULL, not_net | not_demo, {.v = cheat_fauxdemo}       }, // Emulates demo/net play state, for debugging
+  {"fullclip",   NULL, not_net | not_demo, {.v = cheat_infammo}        }, // Infinite ammo cheat
+  {"valiant",    NULL, not_net | not_demo, {.v = cheat_fastweaps}      }, // Fast weapons cheat
+  {"bobbers",    NULL, not_net | not_demo, {.v = cheat_bobbers}        }, // Shortcut for the two above cheats
+  {"gibbers",    NULL, not_net | not_demo, {.v = cheat_gibbers}        }, // Everything gibs
+  {"riotmode",   NULL, not_net | not_demo, {.v = cheat_riotmode}       },
+  {"resurrect",  NULL, not_net | not_demo, {.v = cheat_resurrect}      },
+  {"idres",      NULL, not_net | not_demo, {.v = cheat_resurrect}      }, // 'RESURRECT' alternative
+  {"idfly",      NULL, not_net | not_demo, {.v = cheat_fly}            },
+  {"nextmap",    NULL, not_net | not_demo, {.v = cheat_normalexit}     },
+  {"nextsecret", NULL, not_net | not_demo, {.v = cheat_secretexit}     },
+  {"turbo",      NULL, not_net | not_demo, {.s = cheat_turbo},      -3 },
+
+  {"summon",  NULL, not_net | not_demo, {.v = cheat_summon}      }, // Summon "Menu"
+  {"summone", NULL, not_net | not_demo, {.v = cheat_summone0}    }, // Summon Enemy "Menu"
+  {"summone", NULL, not_net | not_demo, {.s = cheat_summone}, -3 }, // Summon a hostile mobj
+  {"summonf", NULL, not_net | not_demo, {.v = cheat_summonf0}    }, // Summon Friend "Menu"
+  {"summonf", NULL, not_net | not_demo, {.s = cheat_summonf}, -3 }, // Summon a friendly mobj
+  {"summonr", NULL, not_net | not_demo, {.v = cheat_summonr}     }, // Repeat last summon
+
+  {"iddf",   NULL, not_net | not_demo, {.v = cheat_reveal_key}      },
+  {"iddfb",  NULL, not_net | not_demo, {.v = cheat_reveal_keyx}     },
+  {"iddfy",  NULL, not_net | not_demo, {.v = cheat_reveal_keyx}     },
+  {"iddfr",  NULL, not_net | not_demo, {.v = cheat_reveal_keyx}     },
+  {"iddfbc", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 0 },
+  {"iddfyc", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 2 },
+  {"iddfrc", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 1 },
+  {"iddfbs", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 5 },
+  {"iddfys", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 3 },
+  {"iddfrs", NULL, not_net | not_demo, {.i = cheat_reveal_keyxx}, 4 },
+
+  {"linetarget", NULL, not_net | not_demo, {.v = cheat_linetarget} }, // Give info on the current linetarget
+  {"trails",     NULL, not_net | not_demo, {.v = cheat_trails}     }, // Show hitscan trails
+  {"mdk",        NULL, not_net | not_demo, {.v = cheat_mdk}        },
+  {"saitama",    NULL, not_net | not_demo, {.v = cheat_saitama}    }, // MDK Fist
+  {"boomcan",    NULL, not_net | not_demo, {.v = cheat_boomcan}    }, // Explosive hitscan
+  {"cheese",     NULL, not_net | not_demo, {.v = cheat_cheese}     },
+  {"idgaf",      NULL, not_net | not_demo, {.v = cheat_idgaf}      },
 
   #ifdef NUGMAGIC
 
@@ -464,14 +466,14 @@ struct cheat_s cheat[] = {
 
 // [Nugget] /=================================================================
 
-static void cheat_nomomentum()
+static void cheat_nomomentum(void)
 {
   plyr->cheats ^= CF_NOMOMENTUM;
   displaymsg("No Momentum Mode %s", (plyr->cheats & CF_NOMOMENTUM) ? "ON" : "OFF");
 }
 
 // Emulates demo and/or net play state, for debugging
-static void cheat_fauxdemo()
+static void cheat_fauxdemo(void)
 {
   extern void D_UpdateCasualPlay(void);
 
@@ -483,21 +485,21 @@ static void cheat_fauxdemo()
 }
 
 // Infinite ammo
-static void cheat_infammo()
+static void cheat_infammo(void)
 {
   plyr->cheats ^= CF_INFAMMO;
   displaymsg("Infinite Ammo %s", (plyr->cheats & CF_INFAMMO) ? "ON" : "OFF");
 }
 
 // Fast weapons
-static void cheat_fastweaps()
+static void cheat_fastweaps(void)
 {
   plyr->cheats ^= CF_FASTWEAPS;
   displaymsg("Fast Weapons %s", (plyr->cheats & CF_FASTWEAPS) ? "ON" : "OFF");
 }
 
 // Shortcut for the two above cheats
-static void cheat_bobbers()
+static void cheat_bobbers(void)
 {
   if (!(plyr->cheats & CF_INFAMMO) || !(plyr->cheats & CF_FASTWEAPS))
   {
@@ -514,7 +516,7 @@ static void cheat_bobbers()
 }
 
 // Everything gibs
-static void cheat_gibbers()
+static void cheat_gibbers(void)
 {
   gibbers = !gibbers;
   displaymsg("%s", gibbers ? "Ludicrous Gibs!" : "Ludicrous Gibs no more.");
@@ -554,7 +556,7 @@ static void DoResurrect(void)
 }
 
 // Resurrection cheat adapted from Crispy's IDDQD
-static void cheat_resurrect()
+static void cheat_resurrect(void)
 {
   // [crispy] dead players are first respawned at the current position
   if (plyr->playerstate == PST_DEAD)
@@ -569,7 +571,7 @@ static void cheat_resurrect()
 
 // ---------------------------------------------------------------------------
 
-static void cheat_fly()
+static void cheat_fly(void)
 {
   plyr->cheats ^= CF_FLY;
 
@@ -579,12 +581,12 @@ static void cheat_fly()
   displaymsg("Fly Mode %s", (plyr->cheats & CF_FLY) ? "ON" : "OFF");
 }
 
-static void cheat_normalexit()
+static void cheat_normalexit(void)
 {
   G_ExitLevel();
 }
 
-static void cheat_secretexit()
+static void cheat_secretexit(void)
 {
   G_SecretExitLevel();
 }
@@ -614,7 +616,7 @@ static void cheat_turbo(char *buf)
 
 // Summoning -----------------------------------------------------------------
 
-static void cheat_summon()
+static void cheat_summon(void)
 {
   if (spawneetype == -1)
   { displaymsg("Summon: Enemy or Friend?"); }
@@ -705,7 +707,7 @@ static void SummonMobj(boolean friendly)
              spawneefriend ? "Friend" : "Enemy", spawneetype);
 }
 
-static void cheat_summone0()
+static void cheat_summone0(void)
 {
   displaymsg("Summon Enemy: Enter mobj index");
 }
@@ -716,7 +718,7 @@ static void cheat_summone(char *buf)
   if (GetMobjType(buf)) { SummonMobj(false); }
 }
 
-static void cheat_summonf0()
+static void cheat_summonf0(void)
 {
   displaymsg("Summon Friend: Enter mobj index");
 }
@@ -728,21 +730,21 @@ static void cheat_summonf(char *buf)
 }
 
 // Summon the last summoned mobj
-static void cheat_summonr()
+static void cheat_summonr(void)
 {
   SummonMobj(spawneefriend);
 }
 
 // Key finder ----------------------------------------------------------------
 
-static void cheat_reveal_key()
+static void cheat_reveal_key(void)
 {
   if (automapactive != AM_FULL) { return; }
 
   displaymsg("Key Finder: Red, Yellow or Blue?");
 }
 
-static void cheat_reveal_keyx()
+static void cheat_reveal_keyx(void)
 {
   if (automapactive != AM_FULL) { return; }
 
@@ -796,14 +798,14 @@ static void cheat_reveal_keyxx(int key)
 // ---------------------------------------------------------------------------
 
 // Give info on the current `linetarget`
-static void cheat_linetarget()
+static void cheat_linetarget(void)
 {
   plyr->cheats ^= CF_LINETARGET;
   displaymsg("Linetarget Query %s", (plyr->cheats & CF_LINETARGET) ? "ON" : "OFF");
 }
 
 // Show hitscan trails
-static void cheat_trails()
+static void cheat_trails(void)
 {
   const int value = P_CycleShowHitscanTrails();
 
@@ -816,7 +818,7 @@ static void cheat_trails()
 }
 
 // 1-million-damage hitscan attack
-static void cheat_mdk()
+static void cheat_mdk(void)
 {
   fixed_t slope;
 
@@ -841,14 +843,14 @@ static void cheat_mdk()
 }
 
 // MDK Fist
-static void cheat_saitama()
+static void cheat_saitama(void)
 {
   plyr->cheats ^= CF_SAITAMA;
   displaymsg("MDK Fist %s", (plyr->cheats & CF_SAITAMA) ? "ON" : "OFF");
 }
 
 // Explosive hitscan
-static void cheat_boomcan()
+static void cheat_boomcan(void)
 {
   plyr->cheats ^= CF_BOOMCAN;
   displaymsg("Explosive Hitscan %s", (plyr->cheats & CF_BOOMCAN) ? "ON" : "OFF");
@@ -859,13 +861,13 @@ static void cheat_riotmode(void)
   displaymsg("Riot Mode %s", (riotmode = !riotmode) ? "ON" : "OFF");
 }
 
-static void cheat_cheese()
+static void cheat_cheese(void)
 {
   cheese = !cheese;
   displaymsg("%s", cheese ? "cheese :)" : "no cheese :(");
 }
 
-static void cheat_idgaf()
+static void cheat_idgaf(void)
 {
   idgaf = !idgaf;
   displaymsg("I %s.", idgaf ? "don't" : "do");
@@ -874,18 +876,18 @@ static void cheat_idgaf()
 // [Nugget] =================================================================/
 
 // [FG] FPS counter widget
-static void cheat_showfps()
+static void cheat_showfps(void)
 {
   plyr->cheats ^= CF_SHOWFPS;
 }
 
-static void cheat_speed()
+static void cheat_speed(void)
 {
   speedometer = (speedometer + 1) % 4;
 }
 
 // killough 7/19/98: Autoaiming optional in beta emulation mode
-static void cheat_autoaim()
+static void cheat_autoaim(void)
 {
   displaymsg((autoaim=!autoaim) ?
     "Projectile autoaiming on" : 
@@ -956,7 +958,7 @@ static void cheat_mus(char *buf)
 boolean comp_choppers; // [Nugget]
 
 // 'choppers' invulnerability & chainsaw
-static void cheat_choppers()
+static void cheat_choppers(void)
 {
   plyr->weaponowned[wp_chainsaw] = true;
   
@@ -969,7 +971,7 @@ static void cheat_choppers()
   displaymsg("%s", s_STSTR_CHOPPERS); // Ty 03/27/98 - externalized
 }
 
-static void cheat_god()
+static void cheat_god(void)
 {                                    // 'dqd' cheat for toggleable god mode
   // [crispy] dead players are first respawned at the current position
   if (plyr->playerstate == PST_DEAD)
@@ -990,7 +992,7 @@ static void cheat_god()
     displaymsg("%s", s_STSTR_DQDOFF); // Ty 03/27/98 - externalized
 }
 
-static void cheat_buddha()
+static void cheat_buddha(void)
 {
   plyr->cheats ^= CF_BUDDHA;
   if (plyr->cheats & CF_BUDDHA)
@@ -999,7 +1001,7 @@ static void cheat_buddha()
     displaymsg("Buddha Mode OFF");
 }
 
-static void cheat_notarget()
+static void cheat_notarget(void)
 {
   plyr->cheats ^= CF_NOTARGET;
 
@@ -1030,7 +1032,7 @@ static void cheat_notarget()
 
 boolean frozen_mode;
 
-static void cheat_freeze()
+static void cheat_freeze(void)
 {
   frozen_mode = !frozen_mode;
   if (frozen_mode)
@@ -1039,7 +1041,7 @@ static void cheat_freeze()
     displaymsg("Freeze OFF");
 }
 
-static void cheat_avj()
+static void cheat_avj(void)
 {
   void A_VileJump(mobj_t *mo);
 
@@ -1048,7 +1050,7 @@ static void cheat_avj()
 }
 
 // CPhipps - new health and armour cheat codes
-static void cheat_health()
+static void cheat_health(void)
 {
   if (!(plyr->cheats & CF_GODMODE))
   {
@@ -1059,20 +1061,20 @@ static void cheat_health()
   }
 }
 
-static void cheat_megaarmour()
+static void cheat_megaarmour(void)
 {
   plyr->armorpoints = idfa_armor;      // Ty 03/09/98 - deh
   plyr->armortype = idfa_armor_class;  // Ty 03/09/98 - deh
   displaymsg("%s", s_STSTR_BEHOLDX); // Ty 03/27/98 - externalized
 }
 
-static void cheat_tst()
+static void cheat_tst(void)
 { // killough 10/98: same as iddqd except for message
   cheat_god();
   displaymsg(plyr->cheats & CF_GODMODE ? "God Mode On" : "God Mode Off");
 }
 
-static void cheat_fa()
+static void cheat_fa(void)
 {
   int i;
 
@@ -1099,7 +1101,7 @@ static void cheat_fa()
   displaymsg("%s", s_STSTR_FAADDED);
 }
 
-static void cheat_k()
+static void cheat_k(void)
 {
   int i;
   for (i=0;i<NUMCARDS;i++)
@@ -1110,14 +1112,14 @@ static void cheat_k()
       }
 }
 
-static void cheat_kfa()
+static void cheat_kfa(void)
 {
   cheat_k();
   cheat_fa();
   displaymsg("%s", s_STSTR_KFAADDED);
 }
 
-static void cheat_noclip()
+static void cheat_noclip(void)
 {
   // Simplified, accepting both "noclip" and "idspispopd".
   // no clipping mode cheat
@@ -1154,13 +1156,13 @@ static void cheat_pw(int pw)
 }
 
 // 'behold' power-up menu
-static void cheat_behold()
+static void cheat_behold(void)
 {
   displaymsg("%s", s_STSTR_BEHOLD); // Ty 03/27/98 - externalized
 }
 
 // 'clev' change-level cheat
-static void cheat_clev0()
+static void cheat_clev0(void)
 {
   int epsd, map;
   char *cur, *next;
@@ -1240,14 +1242,14 @@ static void cheat_clev(char *buf)
 
 // 'mypos' for player position
 // killough 2/7/98: simplified using dprintf and made output more user-friendly
-static void cheat_mypos()
+static void cheat_mypos(void)
 {
   plyr->cheats ^= CF_MAPCOORDS;
   if ((plyr->cheats & CF_MAPCOORDS) == 0)
     plyr->message = "";
 }
 
-void cheat_mypos_print()
+void cheat_mypos_print(void)
 {
   displaymsg("X=%.10f Y=%.10f A=%-.0f",
           (double)players[consoleplayer].mo->x / FRACUNIT,
@@ -1257,7 +1259,7 @@ void cheat_mypos_print()
 
 // compatibility cheat
 
-static void cheat_comp0()
+static void cheat_comp0(void)
 {
   displaymsg("Complevel: %s", G_GetCurrentComplevelName());
 }
@@ -1282,14 +1284,14 @@ static void cheat_comp(char *buf)
 }
 
 // variable friction cheat
-static void cheat_friction()
+static void cheat_friction(void)
 {
   displaymsg(            // Ty 03/27/98 - *not* externalized
     (variable_friction = !variable_friction) ? "Variable Friction enabled" : 
                                                "Variable Friction disabled");
 }
 
-static void cheat_skill0()
+static void cheat_skill0(void)
 {
   displaymsg("Skill: %s", default_skill_strings[gameskill + 1]);
 }
@@ -1309,20 +1311,20 @@ static void cheat_skill(char *buf)
 
 // Pusher cheat
 // phares 3/10/98
-static void cheat_pushers()
+static void cheat_pushers(void)
 {
   displaymsg(           // Ty 03/27/98 - *not* externalized
     (allow_pushers = !allow_pushers) ? "Pushers enabled" : "Pushers disabled");
 }
 
 // translucency cheat
-static void cheat_tran()
+static void cheat_tran(void)
 {
   displaymsg(            // Ty 03/27/98 - *not* externalized
     (translucency = !translucency) ? "Translucency enabled" : "Translucency disabled");
 }
 
-static void cheat_massacre()    // jff 2/01/98 kill all monsters
+static void cheat_massacre(void)    // jff 2/01/98 kill all monsters
 {
   // jff 02/01/98 'em' cheat - kill all monsters
   // partially taken from Chi's .46 port
@@ -1374,7 +1376,7 @@ static void cheat_massacre()    // jff 2/01/98 kill all monsters
   bloodier_gibbing = oldgibbing; // [Nugget]
 }
 
-static void cheat_spechits()
+static void cheat_spechits(void)
 {
   int i, speciallines = 0;
   boolean origcards[NUMCARDS];
@@ -1443,7 +1445,7 @@ static void cheat_spechits()
     plyr->cards[i] = origcards[i];
   }
 
-  if (gamemapinfo && gamemapinfo->numbossactions > 0)
+  if (gamemapinfo && array_size(gamemapinfo->bossactions))
   {
     thinker_t *th;
 
@@ -1453,13 +1455,14 @@ static void cheat_spechits()
       {
         mobj_t *mo = (mobj_t *) th;
 
-        for (i = 0; i < gamemapinfo->numbossactions; i++)
+        bossaction_t *bossaction;
+        array_foreach(bossaction, gamemapinfo->bossactions)
         {
-          if (gamemapinfo->bossactions[i].type == mo->type)
+          if (bossaction->type == mo->type)
           {
             dummy = *lines;
-            dummy.special = (short)gamemapinfo->bossactions[i].special;
-            dummy.tag = (short)gamemapinfo->bossactions[i].tag;
+            dummy.special = (short)bossaction->special;
+            dummy.tag = (short)bossaction->tag;
             // use special semantics for line activation to block problem types.
             if (!P_UseSpecialLine(mo, &dummy, 0, true))
               P_CrossSpecialLine(&dummy, 0, mo, true);
@@ -1533,13 +1536,13 @@ static void cheat_spechits()
 
 // killough 2/7/98: move iddt cheat from am_map.c to here
 // killough 3/26/98: emulate Doom better
-static void cheat_ddt()
+static void cheat_ddt(void)
 {
   if (automapactive)
     ddt_cheating = (ddt_cheating+1) % 3;
 }
 
-static void cheat_reveal_secret()
+static void cheat_reveal_secret(void)
 {
   static int last_secret = -1;
 
@@ -1615,7 +1618,7 @@ static void cheat_cycle_mobj(mobj_t **last_mobj, int *last_count,
   } while (th != start_th);
 }
 
-static void cheat_reveal_kill()
+static void cheat_reveal_kill(void)
 {
   if (automapactive == AM_FULL)
   {
@@ -1626,7 +1629,7 @@ static void cheat_reveal_kill()
   }
 }
 
-static void cheat_reveal_item()
+static void cheat_reveal_item(void)
 {
   if (automapactive == AM_FULL)
   {
@@ -1638,14 +1641,14 @@ static void cheat_reveal_item()
 }
 
 // killough 2/7/98: HOM autodetection
-static void cheat_hom()
+static void cheat_hom(void)
 {
   displaymsg((autodetect_hom = !autodetect_hom) ? "HOM Detection On" :
     "HOM Detection Off");
 }
 
 // killough 3/6/98: -fast parameter toggle
-static void cheat_fast()
+static void cheat_fast(void)
 {
   // [Nugget] Custom Skill
   if (gameskill == sk_custom)
@@ -1663,12 +1666,12 @@ static void cheat_fast()
 }
 
 // killough 2/16/98: keycard/skullkey cheat functions
-static void cheat_key()
+static void cheat_key(void)
 {
   displaymsg("Red, Yellow, Blue");  // Ty 03/27/98 - *not* externalized
 }
 
-static void cheat_keyx()
+static void cheat_keyx(void)
 {
   displaymsg("Card, Skull");        // Ty 03/27/98 - *not* externalized
 }
@@ -1681,7 +1684,7 @@ static void cheat_keyxx(int key)
 
 // killough 2/16/98: generalized weapon cheats
 
-static void cheat_weap()
+static void cheat_weap(void)
 {                                   // Ty 03/27/98 - *not* externalized
   displaymsg(ALLOW_SSG ? // killough 2/28/98
     "Weapon number 1-9" : "Weapon number 1-8");
@@ -1712,7 +1715,7 @@ static void cheat_weapx(char *buf)
 }
 
 // killough 2/16/98: generalized ammo cheats
-static void cheat_ammo()
+static void cheat_ammo(void)
 {
   displaymsg("Ammo 1-4, Backpack");  // Ty 03/27/98 - *not* externalized
 }
@@ -1744,25 +1747,25 @@ static void cheat_ammox(char *buf)
       }
 }
 
-static void cheat_smart()
+static void cheat_smart(void)
 {
   displaymsg((monsters_remember = !monsters_remember) ? 
     "Smart Monsters Enabled" : "Smart Monsters Disabled");
 }
 
-static void cheat_pitch()
+static void cheat_pitch(void)
 {
   displaymsg((pitched_sounds = !pitched_sounds) ? "Pitch Effects Enabled" :
     "Pitch Effects Disabled");
 }
 
-static void cheat_nuke()
+static void cheat_nuke(void)
 {
   displaymsg((disable_nuke = !disable_nuke) ? "Nukage Disabled" :
     "Nukage Enabled");
 }
 
-static void cheat_rate()
+static void cheat_rate(void)
 {
   plyr->cheats ^= CF_RENDERSTATS;
 }
@@ -1863,32 +1866,34 @@ static const struct {
   const cheatf_t func;
   const int arg;
 } cheat_input[] = {
-  { input_iddqd,     not_net|not_demo, {cheat_god},      0 },
-  { input_idkfa,     not_net|not_demo, {cheat_kfa},      0 },
-  { input_idfa,      not_net|not_demo, {cheat_fa},       0 },
-  { input_idclip,    not_net|not_demo, {cheat_noclip},   0 },
-  { input_idbeholdh, not_net|not_demo, {cheat_health},   0 },
-  { input_idbeholdm, not_net|not_demo, {cheat_megaarmour}, 0 },
-  { input_idbeholdv, not_net|not_demo, {cheat_pw},       pw_invulnerability },
-  { input_idbeholds, not_net|not_demo, {cheat_pw},       pw_strength },
-  { input_idbeholdi, not_net|not_demo, {cheat_pw},       pw_invisibility },
-  { input_idbeholdr, not_net|not_demo, {cheat_pw},       pw_ironfeet },
-  { input_idbeholdl, not_dm,           {cheat_pw},       pw_infrared },
-  { input_iddt,      not_dm,           {cheat_ddt},      0 },
-  { input_notarget,  not_net|not_demo, {cheat_notarget}, 0 },
-  { input_freeze,    not_net|not_demo, {cheat_freeze},   0 },
-  { input_avj,       not_net|not_demo, {cheat_avj},      0 },
+  { input_iddqd,     not_net|not_demo, {.v = cheat_god},      0 },
+  { input_idkfa,     not_net|not_demo, {.v = cheat_kfa},      0 },
+  { input_idfa,      not_net|not_demo, {.v = cheat_fa},       0 },
+  { input_idclip,    not_net|not_demo, {.v = cheat_noclip},   0 },
+  { input_idbeholdh, not_net|not_demo, {.v = cheat_health},   0 },
+  { input_idbeholdm, not_net|not_demo, {.v = cheat_megaarmour}, 0 },
+  { input_idbeholdv, not_net|not_demo, {.i = cheat_pw},       pw_invulnerability },
+  { input_idbeholds, not_net|not_demo, {.i = cheat_pw},       pw_strength },
+  { input_idbeholdi, not_net|not_demo, {.i = cheat_pw},       pw_invisibility },
+  { input_idbeholdr, not_net|not_demo, {.i = cheat_pw},       pw_ironfeet },
+  { input_idbeholdl, not_dm,           {.i = cheat_pw},       pw_infrared },
+  { input_iddt,      not_dm,           {.v = cheat_ddt},      0 },
+  { input_notarget,  not_net|not_demo, {.v = cheat_notarget}, 0 },
+  { input_freeze,    not_net|not_demo, {.v = cheat_freeze},   0 },
+  { input_avj,       not_net|not_demo, {.v = cheat_avj},      0 },
+
   // [Nugget] ----------------------------------------------------------------
-  { input_idbeholda,  not_net|not_demo, {cheat_pw},         pw_allmap },
-  { input_infammo,    not_net|not_demo, {cheat_infammo},    0 },
-  { input_fastweaps,  not_net|not_demo, {cheat_fastweaps},  0 },
-  { input_resurrect,  not_net|not_demo, {cheat_resurrect},  0 },
-  { input_fly,        not_net|not_demo, {cheat_fly},        0 },
-  { input_summonr,    not_net|not_demo, {cheat_summonr},    0 },
-  { input_linetarget, not_net|not_demo, {cheat_linetarget}, 0 },
-  { input_mdk,        not_net|not_demo, {cheat_mdk},        0 },
-  { input_saitama,    not_net|not_demo, {cheat_saitama},    0 },
-  { input_boomcan,    not_net|not_demo, {cheat_boomcan},    0 },
+
+  { input_idbeholda,  not_net|not_demo, {.i = cheat_pw},         pw_allmap },
+  { input_infammo,    not_net|not_demo, {.v = cheat_infammo},    0 },
+  { input_fastweaps,  not_net|not_demo, {.v = cheat_fastweaps},  0 },
+  { input_resurrect,  not_net|not_demo, {.v = cheat_resurrect},  0 },
+  { input_fly,        not_net|not_demo, {.v = cheat_fly},        0 },
+  { input_summonr,    not_net|not_demo, {.v = cheat_summonr},    0 },
+  { input_linetarget, not_net|not_demo, {.v = cheat_linetarget}, 0 },
+  { input_mdk,        not_net|not_demo, {.v = cheat_mdk},        0 },
+  { input_saitama,    not_net|not_demo, {.v = cheat_saitama},    0 },
+  { input_boomcan,    not_net|not_demo, {.v = cheat_boomcan},    0 },
 };
 
 boolean M_CheatResponder(event_t *ev)
diff --git a/src/m_cheat.h b/src/m_cheat.h
index cc1ca7c2a..60cf0ca98 100644
--- a/src/m_cheat.h
+++ b/src/m_cheat.h
@@ -27,7 +27,7 @@ struct event_s;
 // [Nugget] CVARs
 extern boolean comp_choppers;
 
-typedef void (*cheatf_v)();
+typedef void (*cheatf_v)(void);
 typedef void (*cheatf_i)(int i);
 typedef void (*cheatf_s)(char *s);
 
diff --git a/src/m_config.c b/src/m_config.c
index 92d856ae9..988868766 100644
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -30,6 +30,7 @@
 #include "am_map.h"
 #include "config.h"
 #include "d_main.h"
+#include "d_iwad.h"
 #include "doomdef.h"
 #include "doomstat.h"
 #include "doomtype.h"
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 9e740a623..b73dc0180 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -30,6 +30,20 @@
 
   #define div64_32(a, b) _div64((a), (b), NULL)
 
+#elif defined(__GNUC__) && defined(__x86_64__)
+
+  inline static int32_t div64_32(int64_t a, int32_t b)
+  {
+      if (__builtin_constant_p(b))
+      {
+          return a / b;
+      }
+      int32_t lo = a;
+      int32_t hi = a >> 32;
+      asm("idivl %[divisor]" : "+a" (lo), "+d" (hi) : [divisor] "r" (b));
+      return lo;
+  }
+
 #else
 
   #define div64_32(a, b) ((fixed_t)((a) / (b)))
@@ -72,7 +86,7 @@ inline static int64_t FixedMul64(int64_t a, int64_t b)
 inline static fixed_t FixedDiv(fixed_t a, fixed_t b)
 {
     // [FG] avoid 31-bit shift (from Chocolate Doom)
-    if ((abs(a) >> 14) >= abs(b))
+    if (((unsigned)abs(a) >> 14) >= (unsigned)abs(b))
     {
         return (a ^ b) < 0 ? INT_MIN : INT_MAX;
     }
diff --git a/src/m_misc.c b/src/m_misc.c
index 59cc68d8e..2af9fa42c 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -27,9 +27,16 @@
 #include "m_misc.h"
 #include "z_zone.h"
 
+#include "config.h"
+#ifdef HAVE_GETPWUID
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#endif
+
 // Check if a file exists
 
-boolean M_FileExists(const char *filename)
+static boolean M_FileExistsNotDir(const char *filename)
 {
     FILE *fstream;
 
@@ -38,14 +45,11 @@ boolean M_FileExists(const char *filename)
     if (fstream != NULL)
     {
         fclose(fstream);
-        return true;
+        return M_DirExists(filename) == false;
     }
     else
     {
-        // If we can't open because the file is a directory, the
-        // "file" exists at least!
-
-        return errno == EISDIR;
+        return false;
     }
 }
 
@@ -111,7 +115,7 @@ char *M_FileCaseExists(const char *path)
     path_dup = M_StringDuplicate(path);
 
     // 0: actual path
-    if (M_FileExists(path_dup))
+    if (M_FileExistsNotDir(path_dup))
     {
         return path_dup;
     }
@@ -123,7 +127,7 @@ char *M_FileCaseExists(const char *path)
     // 1: lowercase filename, e.g. doom2.wad
     M_StringToLower(filename);
 
-    if (M_FileExists(path_dup))
+    if (M_FileExistsNotDir(path_dup))
     {
         return path_dup;
     }
@@ -131,7 +135,7 @@ char *M_FileCaseExists(const char *path)
     // 2: uppercase filename, e.g. DOOM2.WAD
     M_StringToUpper(filename);
 
-    if (M_FileExists(path_dup))
+    if (M_FileExistsNotDir(path_dup))
     {
         return path_dup;
     }
@@ -142,7 +146,7 @@ char *M_FileCaseExists(const char *path)
     {
         M_StringToLower(ext + 1);
 
-        if (M_FileExists(path_dup))
+        if (M_FileExistsNotDir(path_dup))
         {
             return path_dup;
         }
@@ -153,7 +157,7 @@ char *M_FileCaseExists(const char *path)
     {
         M_StringToLower(filename + 1);
 
-        if (M_FileExists(path_dup))
+        if (M_FileExistsNotDir(path_dup))
         {
             return path_dup;
         }
@@ -231,23 +235,73 @@ const char *M_BaseName(const char *path)
     }
 }
 
+char *M_HomeDir(void)
+{
+    static char *home_dir;
+
+    if (home_dir == NULL)
+    {
+        home_dir = M_getenv("HOME");
+
+        if (home_dir == NULL)
+        {
+#ifdef HAVE_GETPWUID
+            struct passwd *user_info = getpwuid(getuid());
+            if (user_info != NULL)
+                home_dir = user_info->pw_dir;
+            else
+#endif
+                home_dir = "/";
+        }
+    }
+
+    return home_dir;
+}
+
+// Quote:
+// > $XDG_DATA_HOME defines the base directory relative to which
+// > user specific data files should be stored. If $XDG_DATA_HOME
+// > is either not set or empty, a default equal to
+// > $HOME/.local/share should be used.
+
+char *M_DataDir(void)
+{
+    static char *data_dir;
+
+    if (data_dir == NULL)
+    {
+        data_dir = M_getenv("XDG_DATA_HOME");
+
+        if (data_dir == NULL || *data_dir == '\0')
+        {
+            const char *home_dir = M_HomeDir();
+            data_dir = M_StringJoin(home_dir, "/.local/share");
+        }
+    }
+
+    return data_dir;
+}
+
 // Change string to uppercase.
 
 char M_ToUpper(const char c)
 {
-  if (c >= 'a' && c <= 'z')
-    return c + 'A' - 'a';
-  else
-    return c;
+    if (c >= 'a' && c <= 'z')
+    {
+        return c + 'A' - 'a';
+    }
+    else
+    {
+        return c;
+    }
 }
 
-void M_StringToUpper(char *text)
+void M_StringToUpper(char *str)
 {
-    char *p;
-
-    for (p = text; *p != '\0'; ++p)
+    while (*str)
     {
-        *p = M_ToUpper(*p);
+        *str = M_ToUpper(*str);
+        ++str;
     }
 }
 
@@ -255,19 +309,22 @@ void M_StringToUpper(char *text)
 
 char M_ToLower(const char c)
 {
-  if (c >= 'A' && c <= 'Z')
-    return c - 'A' + 'a';
-  else
-    return c;
+    if (c >= 'A' && c <= 'Z')
+    {
+        return c - 'A' + 'a';
+    }
+    else
+    {
+        return c;
+    }
 }
 
-void M_StringToLower(char *text)
+void M_StringToLower(char *str)
 {
-    char *p;
-
-    for (p = text; *p != '\0'; ++p)
+    while (*str)
     {
-        *p = M_ToLower(*p);
+        *str = M_ToLower(*str);
+        ++str;
     }
 }
 
@@ -505,30 +562,19 @@ void M_CopyLumpName(char *dest, const char *src)
 
 //
 // 1/18/98 killough: adds a default extension to a path
-// Note: Backslashes are treated specially, for MS-DOS.
 //
 
-char *AddDefaultExtension(char *path, const char *ext)
+char *AddDefaultExtension(const char *path, const char *ext)
 {
-    char *p = path;
-
-    while (*p++)
-        ;
-
-    while (p-- > path && *p != '/' && *p != '\\')
+    if (strrchr(M_BaseName(path), '.') != NULL)
     {
-        if (*p == '.')
-        {
-            return path;
-        }
+        // path already has an extension
+        return M_StringDuplicate(path);
     }
-
-    if (*ext != '.')
+    else
     {
-        strcat(path, ".");
+        return M_StringJoin(path, ext);
     }
-
-    return strcat(path, ext);
 }
 
 //
diff --git a/src/m_misc.h b/src/m_misc.h
index fe549abd2..e8a948433 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -23,7 +23,6 @@
 
 #include "doomtype.h"
 
-boolean M_FileExists(const char *file);
 boolean M_DirExists(const char *path);
 int M_FileLength(const char *path);
 char *M_TempFile(const char *s);
@@ -31,6 +30,8 @@ char *M_FileCaseExists(const char *file);
 boolean M_StrToInt(const char *str, int *result);
 char *M_DirName(const char *path);
 const char *M_BaseName(const char *path);
+char *M_HomeDir(void);
+char *M_DataDir(void);
 char M_ToUpper(const char c);
 void M_StringToUpper(char *text);
 char M_ToLower(const char c);
@@ -55,7 +56,7 @@ int M_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args)
 int M_snprintf(char *buf, size_t buf_len, const char *s, ...) PRINTF_ATTR(3, 4);
 
 void M_CopyLumpName(char *dest, const char *src);
-char *AddDefaultExtension(char *path, const char *ext);
+char *AddDefaultExtension(const char *path, const char *ext);
 boolean M_WriteFile(const char *name, void *source, int length);
 int M_ReadFile(const char *name, byte **buffer);
 boolean M_StringToDigest(const char *string, byte *digest, int size);
diff --git a/src/m_scanner.c b/src/m_scanner.c
new file mode 100644
index 000000000..eca0fce69
--- /dev/null
+++ b/src/m_scanner.c
@@ -0,0 +1,706 @@
+//
+//  Copyright (c) 2015, Braden "Blzut3" Obrzut
+//  Copyright (c) 2024, Roman Fomin
+//
+// 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 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 COPYRIGHT HOLDER 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 "m_scanner.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "doomtype.h"
+#include "i_system.h"
+#include "m_misc.h"
+
+static const char* const token_names[] =
+{
+    [TK_Identifier] = "Identifier",
+    [TK_StringConst] = "String Constant",
+    [TK_IntConst] = "Integer Constant",
+    [TK_BoolConst] = "boolean Constant",
+    [TK_FloatConst] = "Float Constant",
+    [TK_AnnotateStart] = "Annotation Start",
+    [TK_AnnotateEnd] = "Annotation End",
+    [TK_LumpName] = "Lump Name"
+};
+
+typedef struct
+{
+    char *string;
+    int number;
+    double decimal;
+    char token;
+
+    int tokenline;
+    int tokenlinepos;
+    int scanpos;
+} parserstate_t;
+
+struct scanner_s
+{
+    parserstate_t state;
+    parserstate_t nextstate, prevstate;
+
+    char *data;
+    size_t length;
+
+    int line;
+    int linestart;
+    int logicalpos;
+    int scanpos;
+
+    boolean neednext; // If CheckToken() returns false this will be false.
+
+    const char *scriptname;
+};
+
+static void IncrementLine(scanner_t *s)
+{
+    s->line++;
+    s->linestart = s->scanpos;
+}
+
+static void CheckForWhitespace(scanner_t *s)
+{
+    int comment = 0; // 1 = till next new line, 2 = till end block
+
+    while (s->scanpos < s->length)
+    {
+        char cur = s->data[s->scanpos];
+        char next = (s->scanpos + 1 < s->length) ? s->data[s->scanpos + 1] : 0;
+
+        if (comment == 2)
+        {
+            if (cur != '*' || next != '/')
+            {
+                if (cur == '\n' || cur == '\r')
+                {
+                    s->scanpos++;
+
+                    // Do a quick check for Windows style new line
+                    if (cur == '\r' && next == '\n')
+                    {
+                        s->scanpos++;
+                    }
+                    IncrementLine(s);
+                }
+                else
+                {
+                    s->scanpos++;
+                }
+            }
+            else
+            {
+                comment = 0;
+                s->scanpos += 2;
+            }
+            continue;
+        }
+
+        if (cur == ' ' || cur == '\t' || cur == 0)
+        {
+            s->scanpos++;
+        }
+        else if (cur == '\n' || cur == '\r')
+        {
+            s->scanpos++;
+            if (comment == 1)
+            {
+                comment = 0;
+            }
+
+            // Do a quick check for Windows style new line
+            if (cur == '\r' && next == '\n')
+            {
+                s->scanpos++;
+            }
+            IncrementLine(s);
+        }
+        else if (cur == '/' && comment == 0)
+        {
+            switch (next)
+            {
+                case '/':
+                    comment = 1;
+                    break;
+                case '*':
+                    comment = 2;
+                    break;
+                default:
+                    return;
+            }
+            s->scanpos += 2;
+        }
+        else
+        {
+            if (comment == 0)
+            {
+                return;
+            }
+            else
+            {
+                s->scanpos++;
+            }
+        }
+    }
+}
+
+static void CopyState(parserstate_t *to, const parserstate_t *from)
+{
+    if (to->string)
+    {
+        free(to->string);
+        to->string = NULL;
+    }
+    if (from->string)
+    {
+        to->string = M_StringDuplicate(from->string);
+    }
+    to->number = from->number;
+    to->decimal = from->decimal;
+    to->token = from->token;
+    to->tokenline = from->tokenline;
+    to->tokenlinepos = from->tokenlinepos;
+    to->scanpos = from->scanpos;
+}
+
+static void ExpandState(scanner_t *s)
+{
+    s->scanpos = s->nextstate.scanpos;
+    s->logicalpos = s->scanpos;
+    CheckForWhitespace(s);
+
+    CopyState(&s->prevstate, &s->state);
+    CopyState(&s->state, &s->nextstate);
+}
+
+static void Unescape(char *str)
+{
+    char *p = str, c;
+
+    while ((c = *p++))
+    {
+        if (c != '\\')
+        {
+            *str++ = c;
+        }
+        else
+        {
+            switch (*p)
+            {
+                case 'n':
+                    *str++ = '\n';
+                    break;
+                case 'r':
+                    *str++ = '\r';
+                    break;
+                case 't':
+                    *str++ = '\t';
+                    break;
+                case '"':
+                    *str++ = '\"';
+                    break;
+                default:
+                    *str++ = *p;
+                    break;
+            }
+            p++;
+        }
+    }
+    *str = 0;
+}
+
+boolean SC_GetNextToken(scanner_t *s, boolean expandstate)
+{
+    if (!s->neednext)
+    {
+        s->neednext = true;
+        if (expandstate)
+        {
+            ExpandState(s);
+        }
+        return true;
+    }
+
+    s->nextstate.tokenline = s->line;
+    s->nextstate.tokenlinepos = s->scanpos - s->linestart;
+    s->nextstate.token = TK_NoToken;
+    if (s->scanpos >= s->length)
+    {
+        if (expandstate)
+        {
+            ExpandState(s);
+        }
+        return false;
+    }
+
+    int start = s->scanpos;
+    int end = s->scanpos;
+    int integer_base = 10;
+    boolean float_has_decimal = false;
+    boolean float_has_exponent = false;
+    boolean string_finished =
+        false; // Strings are the only things that can have 0 length tokens.
+
+    char cur = s->data[s->scanpos++];
+    // Determine by first character
+    if (cur == '_' || (cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z'))
+    {
+        s->nextstate.token = TK_Identifier;
+    }
+    else if (cur >= '0' && cur <= '9')
+    {
+        if (cur == '0')
+        {
+            integer_base = 8;
+        }
+        s->nextstate.token = TK_IntConst;
+    }
+    else if (cur == '.' && s->scanpos < s->length && s->data[s->scanpos] != '.')
+    {
+        float_has_decimal = true;
+        s->nextstate.token = TK_FloatConst;
+    }
+    else if (cur == '"')
+    {
+        end = ++start; // Move the start up one character so we don't have to
+                       // trim it later.
+        s->nextstate.token = TK_StringConst;
+    }
+    else
+    {
+        end = s->scanpos;
+        s->nextstate.token = cur;
+
+        // Now check for operator tokens
+        if (s->scanpos < s->length)
+        {
+            char next = s->data[s->scanpos];
+
+            if (cur == ':' && next == ':')
+            {
+                s->nextstate.token = TK_ScopeResolution;
+            }
+            else if (cur == '/' && next == '*')
+            {
+                s->nextstate.token = TK_AnnotateStart;
+            }
+            else if (cur == '*' && next == '/')
+            {
+                s->nextstate.token = TK_AnnotateEnd;
+            }
+
+            if (s->nextstate.token != cur)
+            {
+                s->scanpos++;
+                end = s->scanpos;
+            }
+        }
+    }
+
+    if (start == end)
+    {
+        while (s->scanpos < s->length)
+        {
+            cur = s->data[s->scanpos];
+            switch (s->nextstate.token)
+            {
+                default:
+                    break;
+
+                case TK_Identifier:
+                    if (cur != '_' && (cur < 'A' || cur > 'Z')
+                        && (cur < 'a' || cur > 'z') && (cur < '0' || cur > '9'))
+                    {
+                        end = s->scanpos;
+                    }
+                    break;
+
+                case TK_IntConst:
+                    if (cur == '.' || (s->scanpos - 1 != start && cur == 'e'))
+                    {
+                        s->nextstate.token = TK_FloatConst;
+                    }
+                    else if ((cur == 'x' || cur == 'X')
+                             && s->scanpos - 1 == start)
+                    {
+                        integer_base = 16;
+                        break;
+                    }
+                    else
+                    {
+                        switch (integer_base)
+                        {
+                            default:
+                                if (cur < '0' || cur > '9')
+                                {
+                                    end = s->scanpos;
+                                }
+                                break;
+                            case 8:
+                                if (cur < '0' || cur > '7')
+                                {
+                                    end = s->scanpos;
+                                }
+                                break;
+                            case 16:
+                                if ((cur < '0' || cur > '9')
+                                    && (cur < 'A' || cur > 'F')
+                                    && (cur < 'a' || cur > 'f'))
+                                {
+                                    end = s->scanpos;
+                                }
+                                break;
+                        }
+                        break;
+                    }
+
+                case TK_FloatConst:
+                    if (cur < '0' || cur > '9')
+                    {
+                        if (!float_has_decimal && cur == '.')
+                        {
+                            float_has_decimal = true;
+                            break;
+                        }
+                        else if (!float_has_exponent && cur == 'e')
+                        {
+                            float_has_decimal = true;
+                            float_has_exponent = true;
+                            if (s->scanpos + 1 < s->length)
+                            {
+                                char next = s->data[s->scanpos + 1];
+                                if ((next < '0' || next > '9') && next != '+'
+                                    && next != '-')
+                                {
+                                    end = s->scanpos;
+                                }
+                                else
+                                {
+                                    s->scanpos++;
+                                }
+                            }
+                            break;
+                        }
+                        end = s->scanpos;
+                    }
+                    break;
+
+                case TK_StringConst:
+                    if (cur == '"')
+                    {
+                        string_finished = true;
+                        end = s->scanpos;
+                        s->scanpos++;
+                    }
+                    else if (cur == '\\')
+                    {
+                        s->scanpos++; // Will add two since the loop
+                                      // automatically adds one
+                    }
+                    break;
+            }
+            if (start == end && !string_finished)
+            {
+                s->scanpos++;
+            }
+            else
+            {
+                break;
+            }
+        }
+        // Handle small tokens at the end of a file.
+        if (s->scanpos == s->length && !string_finished)
+        {
+            end = s->scanpos;
+        }
+    }
+
+    s->nextstate.scanpos = s->scanpos;
+    if (end - start > 0 || string_finished)
+    {
+        if (s->nextstate.string)
+        {
+            free(s->nextstate.string);
+        }
+        int length = end - start;
+        s->nextstate.string = malloc(length + 1);
+        memcpy(s->nextstate.string, s->data + start, length);
+        s->nextstate.string[length] = '\0';
+
+        if (s->nextstate.token == TK_FloatConst)
+        {
+            if (float_has_decimal && strlen(s->nextstate.string) == 1)
+            {
+                // Don't treat a lone '.' as a decimal.
+                s->nextstate.token = '.';
+            }
+            else
+            {
+                s->nextstate.decimal = atof(s->nextstate.string);
+                s->nextstate.number = (int)s->nextstate.decimal;
+            }
+        }
+        else if (s->nextstate.token == TK_IntConst)
+        {
+            s->nextstate.number =
+                strtol(s->nextstate.string, NULL, integer_base);
+            s->nextstate.decimal = (double)s->nextstate.number;
+        }
+        else if (s->nextstate.token == TK_Identifier)
+        {
+            // Check for a boolean constant.
+            if (!strcasecmp(s->nextstate.string, "true"))
+            {
+                s->nextstate.token = TK_BoolConst;
+                s->nextstate.number = true;
+            }
+            else if (!strcasecmp(s->nextstate.string, "false"))
+            {
+                s->nextstate.token = TK_BoolConst;
+                s->nextstate.number = false;
+            }
+        }
+        else if (s->nextstate.token == TK_StringConst)
+        {
+            Unescape(s->nextstate.string);
+        }
+        if (expandstate)
+        {
+            ExpandState(s);
+        }
+        return true;
+    }
+    s->nextstate.token = TK_NoToken;
+    if (expandstate)
+    {
+        ExpandState(s);
+    }
+    return false;
+}
+
+void SC_GetNextTokenLumpName(scanner_t *s)
+{
+    if (!s->neednext)
+    {
+        s->neednext = true;
+        ExpandState(s);
+        return;
+    }
+
+    s->nextstate.tokenline = s->line;
+    s->nextstate.tokenlinepos = s->scanpos - s->linestart;
+    s->nextstate.token = TK_NoToken;
+    if (s->scanpos >= s->length)
+    {
+        ExpandState(s);
+        return;
+    }
+
+    int start = s->scanpos++;
+    while (s->scanpos < s->length)
+    {
+        char cur = s->data[s->scanpos];
+        if (cur == ' ' || cur == '\t' || cur == '\n' || cur == '\r' || cur == 0)
+        {
+            break;
+        }
+        s->scanpos++;
+    }
+    s->nextstate.scanpos = s->scanpos;
+
+    int length = s->scanpos - start;
+    if (length > 0)
+    {
+        s->nextstate.token = TK_LumpName;
+        if (s->nextstate.string)
+        {
+            free(s->nextstate.string);
+        }
+        s->nextstate.string = malloc(length + 1);
+        memcpy(s->nextstate.string, s->data + start, length);
+        s->nextstate.string[length] = '\0';
+    }
+
+    ExpandState(s);
+}
+
+// Skips all Tokens in current line and parses the first token on the next
+// line.
+void SC_GetNextLineToken(scanner_t *s)
+{
+    int line = s->line;
+    while (SC_GetNextToken(s, true) && s->line == line)
+        ;
+}
+
+boolean SC_CheckToken(scanner_t *s, char token)
+{
+    if (s->neednext)
+    {
+        if (!SC_GetNextToken(s, false))
+        {
+            return false;
+        }
+    }
+
+    // An int can also be a float.
+    if (s->nextstate.token == token
+        || (s->nextstate.token == TK_IntConst && token == TK_FloatConst))
+    {
+        s->neednext = true;
+        ExpandState(s);
+        return true;
+    }
+    s->neednext = false;
+    return false;
+}
+
+void SC_Error(scanner_t *s, const char *msg, ...)
+{
+    char buffer[1024];
+    va_list args;
+    va_start(args, msg);
+    M_vsnprintf(buffer, sizeof(buffer), msg, args);
+    va_end(args);
+
+    I_Error("%s(%d:%d): %s", s->scriptname, s->state.tokenline + 1,
+            s->state.tokenlinepos + 1, buffer);
+}
+
+void SC_MustGetToken(scanner_t *s, char token)
+{
+    if (SC_CheckToken(s, token))
+    {
+        return;
+    }
+
+    ExpandState(s);
+    if (s->state.token == TK_NoToken)
+    {
+        SC_Error(s, "Unexpected end of script.");
+    }
+    else if (token < TK_NumSpecialTokens
+             && s->state.token < TK_NumSpecialTokens)
+    {
+        SC_Error(s, "Expected '%s' but got '%s' instead.",
+                token_names[(int)token], token_names[(int)s->state.token]);
+    }
+    else if (token < TK_NumSpecialTokens
+             && s->state.token >= TK_NumSpecialTokens)
+    {
+        SC_Error(s, "Expected '%s' but got '%c' instead.",
+                token_names[(int)token], s->state.token);
+    }
+    else if (token >= TK_NumSpecialTokens
+             && s->state.token < TK_NumSpecialTokens)
+    {
+        SC_Error(s, "Expected '%c' but got '%s' instead.", token,
+                token_names[(int)s->state.token]);
+    }
+    else
+    {
+        SC_Error(s, "Expected '%c' but got '%c' instead.", token,
+                s->state.token);
+    }
+}
+
+void SC_Rewind(scanner_t *s) // Only can rewind one step.
+{
+    s->neednext = false;
+
+    CopyState(&s->nextstate, &s->state);
+    CopyState(&s->state, &s->prevstate);
+
+    s->line = s->prevstate.tokenline;
+    s->logicalpos = s->prevstate.tokenlinepos;
+    s->scanpos = s->prevstate.scanpos;
+}
+
+boolean SC_TokensLeft(scanner_t *s)
+{
+    return s->scanpos < s->length;
+}
+
+const char *SC_GetString(scanner_t *s)
+{
+    return s->state.string;
+}
+
+int SC_GetNumber(scanner_t *s)
+{
+    return s->state.number;
+}
+
+boolean SC_GetBoolean(scanner_t *s)
+{
+    return (boolean)s->state.number;
+}
+
+double SC_GetDecimal(scanner_t *s)
+{
+    return s->state.decimal;
+}
+
+scanner_t *SC_Open(const char *scriptname, const char *data, int length)
+{
+    scanner_t *s = calloc(1, sizeof(*s));
+
+    s->line = 1;
+    s->neednext = true;
+
+    s->length = length;
+    s->data = malloc(length);
+    memcpy(s->data, data, length);
+
+    CheckForWhitespace(s);
+    s->state.scanpos = s->scanpos;
+    s->scriptname = M_StringDuplicate(scriptname);
+    return s;
+}
+
+void SC_Close(scanner_t *s)
+{
+    if (s->state.string)
+    {
+        free(s->state.string);
+    }
+    if (s->prevstate.string)
+    {
+        free(s->prevstate.string);
+    }
+    if (s->nextstate.string)
+    {
+        free(s->nextstate.string);
+    }
+    if (s->scriptname)
+    {
+        free((char *)s->scriptname);
+    }
+    free(s->data);
+    free(s);
+}
diff --git a/src/m_scanner.h b/src/m_scanner.h
new file mode 100644
index 000000000..f275cdfe7
--- /dev/null
+++ b/src/m_scanner.h
@@ -0,0 +1,76 @@
+//
+//  Copyright (c) 2015, Braden "Blzut3" Obrzut
+//  Copyright (c) 2024, Roman Fomin
+//
+// 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 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 COPYRIGHT HOLDER 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 M_SCANNER_H
+#define M_SCANNER_H
+
+#include "doomtype.h"
+
+typedef struct scanner_s scanner_t;
+
+enum
+{
+    TK_Identifier,      // Ex: SomeIdentifier
+    TK_StringConst,     // Ex: "Some String"
+    TK_IntConst,        // Ex: 27
+    TK_BoolConst,       // Ex: true
+    TK_FloatConst,      // Ex: 1.5
+
+    TK_LumpName,
+
+    TK_AnnotateStart,   // Block comment start
+    TK_AnnotateEnd,     // Block comment end
+
+    TK_ScopeResolution, // ::
+
+    TK_NumSpecialTokens,
+
+    TK_NoToken = -1
+};
+
+scanner_t *SC_Open(const char *scriptname, const char *data, int length);
+void SC_Close(scanner_t *s);
+
+const char *SC_GetString(scanner_t *s);
+int SC_GetNumber(scanner_t *s);
+boolean SC_GetBoolean(scanner_t *s);
+double SC_GetDecimal(scanner_t *s);
+
+boolean SC_TokensLeft(scanner_t *s);
+boolean SC_CheckToken(scanner_t *s, char token);
+boolean SC_GetNextToken(scanner_t *s, boolean expandstate);
+void SC_GetNextLineToken(scanner_t *s);
+void SC_MustGetToken(scanner_t *s, char token);
+void SC_Rewind(scanner_t *s); // Only can rewind one step.
+
+void SC_GetNextTokenLumpName(scanner_t *s);
+
+void SC_Error(scanner_t *s, const char *msg, ...) PRINTF_ATTR(2, 3);
+
+#endif
diff --git a/src/m_vector.h b/src/m_vector.h
index b5f5573c4..896a16981 100644
--- a/src/m_vector.h
+++ b/src/m_vector.h
@@ -20,95 +20,104 @@
 
 #include <math.h>
 
+#include "config.h"
 #include "doomtype.h"
 
+#if defined(HAVE_EXT_VECTOR_TYPE)
+typedef float vec __attribute__((ext_vector_type(3)));
+#else
 typedef struct
 {
     float x;
     float y;
     float z;
 } vec;
+#endif
 
+#if defined(HAVE_EXT_VECTOR_TYPE)
+typedef float quat __attribute__((ext_vector_type(4)));
+#else
 typedef struct
 {
-    float w;
     float x;
     float y;
     float z;
+    float w;
 } quat;
+#endif
 
 //
 // Adds two vectors.
 //
-inline static vec vec_add(const vec *a, const vec *b)
+inline static vec vec_add(const vec a, const vec b)
 {
-    return (vec){a->x + b->x, a->y + b->y, a->z + b->z};
+    return (vec){a.x + b.x, a.y + b.y, a.z + b.z};
 }
 
 //
 // Subtracts two vectors.
 //
-inline static vec vec_subtract(const vec *a, const vec *b)
+inline static vec vec_subtract(const vec a, const vec b)
 {
-    return (vec){a->x - b->x, a->y - b->y, a->z - b->z};
+    return (vec){a.x - b.x, a.y - b.y, a.z - b.z};
 }
 
 //
 // Cross product of two vectors.
 //
-inline static vec vec_crossproduct(const vec *a, const vec *b)
+inline static vec vec_crossproduct(const vec a, const vec b)
 {
-    return (vec){a->y * b->z - a->z * b->y,
-                 a->z * b->x - a->x * b->z,
-                 a->x * b->y - a->y * b->x};
+    return (vec){a.y * b.z - a.z * b.y,
+                 a.z * b.x - a.x * b.z,
+                 a.x * b.y - a.y * b.x};
 }
 
 //
 // Dot product of two vectors.
 //
-inline static float vec_dotproduct(const vec *a, const vec *b)
+inline static float vec_dotproduct(const vec a, const vec b)
 {
-    return (a->x * b->x + a->y * b->y + a->z * b->z);
+    return (a.x * b.x + a.y * b.y + a.z * b.z);
 }
 
 //
 // Returns the negative of the input vector.
 //
-inline static vec vec_negative(const vec *v)
+inline static vec vec_negative(const vec v)
 {
-    return (vec){-v->x, -v->y, -v->z};
+    return (vec){-v.x, -v.y, -v.z};
 }
 
 //
 // Multiplies a vector by a scalar value.
 //
-inline static vec vec_scale(const vec *v, float scalar)
+inline static vec vec_scale(const vec v, float scalar)
 {
-    return (vec){v->x * scalar, v->y * scalar, v->z * scalar};
+    return (vec){v.x * scalar, v.y * scalar, v.z * scalar};
 }
 
 //
 // Clamps each vector component.
 //
-inline static vec vec_clamp(float min, float max, const vec *v)
+inline static vec vec_clamp(float min, float max, const vec v)
 {
-    return (vec){BETWEEN(min, max, v->x),
-                 BETWEEN(min, max, v->y),
-                 BETWEEN(min, max, v->z)};
+    return (vec){BETWEEN(min, max, v.x),
+                 BETWEEN(min, max, v.y),
+                 BETWEEN(min, max, v.z)};
 }
 
 //
 // Vector length squared.
 //
-inline static float vec_lengthsquared(const vec *v)
+inline static float vec_lengthsquared(const vec v)
 {
-    return (v->x * v->x + v->y * v->y + v->z * v->z);
+    return (v.x * v.x + v.y * v.y + v.z * v.z);
 }
 
 //
 // Vector magnitude.
 //
-inline static float vec_length(const vec *v)
+inline static float vec_length(const vec v)
 {
     return sqrtf(vec_lengthsquared(v));
 }
@@ -116,15 +125,15 @@ inline static float vec_length(const vec *v)
 //
 // Normalizes a vector using a given magnitude.
 //
-inline static vec vec_normalize_mag(const vec *v, float mag)
+inline static vec vec_normalize_mag(const vec v, float mag)
 {
-    return (mag > 0.0f ? vec_scale(v, 1.0f / mag) : *v);
+    return (mag > 0.0f ? vec_scale(v, 1.0f / mag) : v);
 }
 
 //
 // Normalizes a vector.
 //
-inline static vec vec_normalize(const vec *v)
+inline static vec vec_normalize(const vec v)
 {
     return vec_normalize_mag(v, vec_length(v));
 }
@@ -132,68 +141,68 @@ inline static vec vec_normalize(const vec *v)
 //
 // Is this a zero vector?
 //
-inline static boolean is_zero_vec(const vec *v)
+inline static boolean is_zero_vec(const vec v)
 {
-    return (v->x == 0.0f && v->y == 0.0f && v->z == 0.0f);
+    return (v.x == 0.0f && v.y == 0.0f && v.z == 0.0f);
 }
 
 //
 // Returns the conjugate of a unit quaternion (same as its inverse).
 //
-inline static quat quat_inverse(const quat *q)
+inline static quat quat_inverse(const quat q)
 {
-    return (quat){q->w, -q->x, -q->y, -q->z};
+    return (quat){-q.x, -q.y, -q.z, q.w};
 }
 
 //
 // Converts a vector to a quaternion.
 //
-inline static quat vec_to_quat(const vec *v)
+inline static quat vec_to_quat(const vec v)
 {
-    return (quat){0.0f, v->x, v->y, v->z};
+    return (quat){v.x, v.y, v.z, 0.0f};
 }
 
 //
 // Multiplies two quaternions.
 //
-inline static quat quat_multiply(const quat *a, const quat *b)
+inline static quat quat_multiply(const quat a, const quat b)
 {
-    return (quat){a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z,
-                  a->w * b->x + a->x * b->w + a->y * b->z - a->z * b->y,
-                  a->w * b->y - a->x * b->z + a->y * b->w + a->z * b->x,
-                  a->w * b->z + a->x * b->y - a->y * b->x + a->z * b->w};
+    return (quat){a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
+                  a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
+                  a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
+                  a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z};
 }
 
 //
 // Returns a unit quaternion from an angle and vector.
 //
-inline static quat angle_axis(float angle, const vec *v)
+inline static quat angle_axis(float angle, const vec v)
 {
     vec temp = vec_normalize(v);
-    temp = vec_scale(&temp, sinf(angle * 0.5f));
-    return (quat){cosf(angle * 0.5f), temp.x, temp.y, temp.z};
+    temp = vec_scale(temp, sinf(angle * 0.5f));
+    return (quat){temp.x, temp.y, temp.z, cosf(angle * 0.5f)};
 }
 
 //
 // Rotates a vector by a unit quaternion.
 //
-inline static vec vec_rotate(const vec *v, const quat *q)
+inline static vec vec_rotate(const vec v, const quat q)
 {
     const quat q_inv = quat_inverse(q);
     const quat v_quat = vec_to_quat(v);
-    quat temp = quat_multiply(q, &v_quat);
-    temp = quat_multiply(&temp, &q_inv);
+    quat temp = quat_multiply(q, v_quat);
+    temp = quat_multiply(temp, q_inv);
     return (vec){temp.x, temp.y, temp.z};
 }
 
 //
 // Linear interpolation between two vectors.
 //
-inline static vec vec_lerp(const vec *a, const vec *b, float factor)
+inline static vec vec_lerp(const vec a, const vec b, float factor)
 {
     vec temp = vec_subtract(b, a);
-    temp = vec_scale(&temp, factor);
-    return vec_add(a, &temp);
+    temp = vec_scale(temp, factor);
+    return vec_add(a, temp);
 }
 
 #endif
diff --git a/src/mn_internal.h b/src/mn_internal.h
index 5de7e31ea..523a6b465 100644
--- a/src/mn_internal.h
+++ b/src/mn_internal.h
@@ -45,6 +45,8 @@ typedef enum
     key_mode
 } menu_input_mode_t;
 
+extern int maxscreenblocks;
+
 extern int bigfont_priority;
 
 extern menu_input_mode_t help_input, old_help_input; // pad_mode or key_mode.
@@ -110,6 +112,8 @@ void MN_DrawColor(void);
 void MN_CustomSkill(void);
 void MN_DrawCustomSkill(void);
 
+void MN_UpdateNughudItem(void);
+
 // [Nugget] -----------------------------------------------------------------/
 
 /////////////////////////////
diff --git a/src/mn_menu.c b/src/mn_menu.c
index 7ed8cdc05..29953210e 100644
--- a/src/mn_menu.c
+++ b/src/mn_menu.c
@@ -37,6 +37,7 @@
 #include "doomtype.h"
 #include "dstrings.h"
 #include "g_game.h"
+#include "g_umapinfo.h"
 #include "i_input.h"
 #include "i_printf.h"
 #include "i_system.h"
@@ -59,7 +60,6 @@
 #include "st_sbardef.h"
 #include "st_stuff.h"
 #include "st_widgets.h"
-#include "u_mapinfo.h"
 #include "v_fmt.h"
 #include "v_video.h"
 #include "w_wad.h"
@@ -82,7 +82,7 @@
 
 // Blocky mode, has default, 0 = high, 1 = normal
 // int     detailLevel;    obsolete -- killough
-int screenblocks; // has default
+int screenblocks, maxscreenblocks; // has default
 
 static int quickSaveSlot; // -1 = no quicksave slot picked!
 
@@ -528,14 +528,13 @@ static short EpiMenuEpi[MAX_EPISODES] = {1, 2, 3, 4, -1, -1, -1, -1, -1, -1};
 //
 static int epiChoice;
 
-void M_ClearEpisodes(void)
+void MN_ClearEpisodes(void)
 {
     EpiDef.numitems = 0;
     NewDef.prevMenu = &MainDef;
 }
 
-void M_AddEpisode(const char *map, const char *gfx, const char *txt,
-                  const char *alpha)
+void MN_AddEpisode(const char *map, const char *gfx, const char *txt, char key)
 {
     int epi, mapnum;
 
@@ -552,7 +551,8 @@ void M_AddEpisode(const char *map, const char *gfx, const char *txt,
 
     if (EpiDef.numitems == 8)
     {
-        I_Printf(VB_WARNING, "M_AddEpisode: UMAPINFO spec limit of 8 episodes exceeded!");
+        I_Printf(VB_WARNING,
+                 "MN_AddEpisode: UMAPINFO spec limit of 8 episodes exceeded!");
     }
     else if (EpiDef.numitems >= MAX_EPISODES)
     {
@@ -565,7 +565,7 @@ void M_AddEpisode(const char *map, const char *gfx, const char *txt,
     strncpy(EpisodeMenu[EpiDef.numitems].name, gfx, 8);
     EpisodeMenu[EpiDef.numitems].name[9] = 0;
     EpisodeMenu[EpiDef.numitems].alttext = txt ? strdup(txt) : NULL;
-    EpisodeMenu[EpiDef.numitems].alphaKey = alpha ? *alpha : 0;
+    EpisodeMenu[EpiDef.numitems].alphaKey = key;
     EpiDef.numitems++;
 
     if (EpiDef.numitems <= 4)
@@ -1405,7 +1405,8 @@ static void SetDefaultSaveName(char *name, const char *append)
     char *maplump = MapName(gameepisode, gamemap);
     int maplumpnum = W_CheckNumForName(maplump);
 
-    if (gamemapinfo && U_CheckField(gamemapinfo->label))
+    if (gamemapinfo && gamemapinfo->label
+        && !(gamemapinfo->flags & MapInfo_LabelClear))
     {
         maplump = gamemapinfo->label;
     }
@@ -1914,8 +1915,10 @@ static void M_SizeDisplay(int choice)
         default:
             break;
     }
-    screenblocks = BETWEEN(3, 12, screenblocks);
+    screenblocks = BETWEEN(3, maxscreenblocks, screenblocks);
     R_SetViewSize(screenblocks /*, detailLevel obsolete -- killough */);
+
+    MN_UpdateNughudItem(); // [Nugget] NUGHUD
 }
 
 /////////////////////////////////////////////////////////////////////////////
@@ -2691,13 +2694,14 @@ boolean M_ShortcutResponder(const event_t *ev)
         else
         {
             ++screenblocks;
-            if (screenblocks > 12)
+            if (screenblocks > maxscreenblocks)
             {
                 screenblocks = 10;
             }
         }
 
         R_SetViewSize(screenblocks);
+        MN_UpdateNughudItem(); // [Nugget] NUGHUD
         return true;
     }
 
diff --git a/src/mn_menu.h b/src/mn_menu.h
index b1165fa76..521b4a667 100644
--- a/src/mn_menu.h
+++ b/src/mn_menu.h
@@ -60,6 +60,9 @@ void M_Init(void);
 
 void MN_StartControlPanel(void);
 
+void MN_AddEpisode(const char *map, const char *gfx, const char *txt, char key);
+void MN_ClearEpisodes(void);
+
 void MN_ForcedLoadAutoSave(const char *msg);
 void MN_ForcedLoadGame(const char *msg); // killough 5/15/98: forced loadgames
 void MN_Trans(void);     // killough 11/98: reset translucency
diff --git a/src/mn_setup.c b/src/mn_setup.c
index 21f9cbe7a..2dfc21410 100644
--- a/src/mn_setup.c
+++ b/src/mn_setup.c
@@ -334,6 +334,7 @@ enum
     str_overlay,
     str_automap_preset,
     str_automap_keyed_door,
+    str_fuzzmode,
     str_weapon_slots_activation,
     str_weapon_slots_selection,
     str_weapon_slots,
@@ -368,7 +369,6 @@ enum
 
     str_bobbing_style,
     str_force_carousel,
-    str_hud_type,
     str_crosshair_lockon,
     str_vertical_aiming,
     str_over_under,
@@ -2034,6 +2034,7 @@ static setup_tab_t stat_tabs[] = {
 static void SizeDisplayAlt(void)
 {
     R_SetViewSize(screenblocks);
+    MN_UpdateNughudItem(); // [Nugget] NUGHUD
 }
 
 static void RefreshSolidBackground(void)
@@ -2041,17 +2042,6 @@ static void RefreshSolidBackground(void)
     ST_refreshBackground(); // [Nugget] NUGHUD
 }
 
-static const char *screensize_strings[] = {
-    "",           "",           "",           "Status Bar", "Status Bar",
-    "Status Bar", "Status Bar", "Status Bar", "Status Bar", "Status Bar",
-    "Status Bar", "Fullscreen", "Fullscreen"
-};
-
-// [Nugget] NUGHUD
-static const char *hud_type_strings[] = {
-    "SBARDEF", "NUGHUD"
-};
-
 static const char *st_layout_strings[] = {
     "Original", "Wide"
 };
@@ -2067,8 +2057,7 @@ static setup_menu_t stat_settings1[] = {
     MI_GAP,
 
     // [Nugget] NUGHUD
-    {"Fullscreen HUD Type", S_CHOICE, H_X, M_SPC, {"fullscreen_hud_type"},
-     .strings_id = str_hud_type},
+    {"Use NUGHUD", S_ONOFF, H_X, M_SPC, {"use_nughud"}},
 
     {"Layout", S_CHOICE, H_X, M_SPC, {"st_layout"},
      .strings_id = str_stlayout, .action = AM_Start}, // [Nugget] Minimap
@@ -2079,8 +2068,6 @@ static setup_menu_t stat_settings1[] = {
 
     {"Colored Numbers", S_ONOFF | S_COSMETIC, H_X, M_SPC, {"sts_colored_numbers"}},
 
-    {"Gray Percent Sign", S_ONOFF | S_COSMETIC, H_X, M_SPC, {"sts_pct_always_gray"}},
-
     {"Solid Background Color", S_ONOFF, H_X, M_SPC, {"st_solidbackground"},
      .action = RefreshSolidBackground},
 
@@ -2092,6 +2079,12 @@ static setup_menu_t stat_settings1[] = {
     MI_END
 };
 
+// [Nugget] NUGHUD
+void MN_UpdateNughudItem(void)
+{
+    DisableItem(screenblocks != maxscreenblocks - 1, stat_settings1, "use_nughud");
+}
+
 static void UpdateStatsFormatItem(void);
 
 static const char *show_widgets_strings[] = {"Off", "Automap", "HUD", "Always"};
@@ -2497,21 +2490,13 @@ static setup_menu_t enem_settings1[] = {
 
     // [Nugget] /-------------------------------------------------------------
 
-    // Restored menu item
-    // [FG] spectre drawing mode
-    {"Blocky Spectre Drawing", S_ONOFF, M_X, M_SPC, {"fuzzcolumn_mode"},
-     .action = R_SetFuzzColumnMode},
-
     MI_GAP,
-    {"Nugget", S_SKIP|S_TITLE, M_X, M_SPC},
 
-      {"Extra Gibbing",            S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"extra_gibbing"}},
-      {"Bloodier Gibbing",         S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"bloodier_gibbing"}},
-      {"Toss Items Upon Death",    S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"tossdrop"}},
+    {"Nugget", S_SKIP|S_TITLE, M_X, M_SPC},
 
-      // [Nugget - ceski] Selective fuzz darkening
-      {"Selective Fuzz Darkening", S_ONOFF|S_STRICT, M_X, M_SPC,
-       {"fuzzdark_mode"}, .action = R_SetFuzzColumnMode},
+      {"Extra Gibbing",         S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"extra_gibbing"}},
+      {"Bloodier Gibbing",      S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"bloodier_gibbing"}},
+      {"Toss Items Upon Death", S_ONOFF|S_STRICT|S_CRITICAL, M_X, M_SPC, {"tossdrop"}},
 
     // [Nugget] -------------------------------------------------------------/
 
@@ -2915,6 +2900,7 @@ static void SetMidiPlayerOpl(void)
     }
 }
 
+#if defined(HAVE_FLUIDSYNTH)
 static void SetMidiPlayerFluidSynth(void)
 {
     if (I_MidiPlayerType() == midiplayer_fluidsynth)
@@ -2922,6 +2908,7 @@ static void SetMidiPlayerFluidSynth(void)
         SetMidiPlayer();
     }
 }
+#endif
 
 static void RestartMusic(void)
 {
@@ -3080,7 +3067,10 @@ static setup_menu_t music_settings1[] = {
 
 static void UpdateGainItems(void)
 {
+#if defined (HAVE_FLUIDSYNTH)
     DisableItem(auto_gain, music_settings1, "fl_gain");
+#endif
+
     DisableItem(auto_gain, music_settings1, "opl_gain");
 }
 
@@ -3661,7 +3651,11 @@ static void SmoothLight(void)
 static const char *menu_backdrop_strings[] = {"Off", "Dark", "Texture"};
 
 static const char *exit_sequence_strings[] = {
-    "Off", "Sound Only", "PWAD ENDOOM", "Full"
+    "Off", "Sound Only", "ENDOOM Only", "Full"
+};
+
+static const char *fuzzmode_strings[] = {
+    "Vanilla", "Refraction", "Shadow"
 };
 
 static setup_menu_t gen_settings5[] = {
@@ -3672,8 +3666,8 @@ static setup_menu_t gen_settings5[] = {
     {"Sprite Translucency", S_ONOFF | S_STRICT, OFF_CNTR_X, M_SPC,
      {"translucency"}},
 
-    {"Translucency Filter", S_NUM | S_ACTION | S_PCT, OFF_CNTR_X, M_SPC,
-     {"tran_filter_pct"}, .action = MN_Trans},
+    {"Partial Invisibility", S_CHOICE | S_STRICT, OFF_CNTR_X, M_SPC, {"fuzzmode"},
+     .strings_id = str_fuzzmode, .action = R_SetFuzzColumnMode},
 
     MI_GAP,
 
@@ -3753,7 +3747,7 @@ static setup_menu_t gen_settings6[] = {
      .action = AutoSaveStuff},
 
     {"Organize save files", S_ONOFF | S_PRGWARN, OFF_CNTR_X, M_SPC,
-     {"organize_savefiles"}},
+     {"organize_savefiles"}, .action = D_SetSavegameDirectory},
 
     MI_GAP,
 
@@ -3768,6 +3762,8 @@ static setup_menu_t gen_settings6[] = {
     {"Exit Sequence", S_CHOICE, OFF_CNTR_X, M_SPC, {"exit_sequence"},
     .strings_id = str_exit_sequence},
 
+    {"PWAD ENDOOM Only", S_ONOFF, OFF_CNTR_X, M_SPC, {"endoom_pwad_only"}},
+
     MI_END
 };
 
@@ -5004,6 +5000,8 @@ static boolean NextPage(int inc)
     return true;
 }
 
+static setup_menu_t *active_thermo = NULL;
+
 boolean MN_SetupResponder(menu_action_t action, int ch)
 {
     // phares 3/26/98 - 4/11/98:
@@ -5252,6 +5250,7 @@ boolean MN_SetupResponder(menu_action_t action, int ch)
         set_weapon_active = false;
         default_verify = false;              // phares 4/19/98
         print_warning_about_changes = false; // [FG] reset
+        active_thermo = NULL;
         M_StartSoundOptional(sfx_mnucls, sfx_swtchx); // [Nugget]: [NS] Optional menu sounds.
         return true;
     }
@@ -5318,8 +5317,6 @@ boolean MN_SetupMouseResponder(int x, int y)
         return true;
     }
 
-    static setup_menu_t *active_thermo = NULL;
-
     if (M_InputDeactivated(input_menu_enter) && active_thermo)
     {
         int64_t flags = active_thermo->m_flags;
@@ -5568,7 +5565,7 @@ static const char **selectstrings[] = {
     percent_strings,
     curve_strings,
     center_weapon_strings,
-    screensize_strings,
+    NULL, // str_screensize
     st_layout_strings,
     show_widgets_strings,
     show_adv_widgets_strings,
@@ -5580,6 +5577,7 @@ static const char **selectstrings[] = {
     overlay_strings,
     automap_preset_strings,
     automap_keyed_door_strings,
+    fuzzmode_strings,
     weapon_slots_activation_strings,
     weapon_slots_selection_strings,
     NULL, // str_weapon_slots
@@ -5609,7 +5607,6 @@ static const char **selectstrings[] = {
 
     bobbing_style_strings,
     force_carousel_strings,
-    hud_type_strings,
     crosshair_lockon_strings,
     vertical_aiming_strings,
     over_under_strings,
@@ -5641,6 +5638,41 @@ static const char **GetMidiPlayerStrings(void)
     return I_DeviceList();
 }
 
+static const char **GetScreenSizeStrings(void)
+{
+    const char **strings = NULL;
+
+    for (int i = 0; i < 3; ++i)
+    {
+        array_push(strings, "");
+    }
+    for (int i = 3; i < 10; ++i)
+    {
+        array_push(strings, "Status Bar");
+    }
+
+    const char **st_strings = ST_StatusbarList();
+    for (int i = 0; i < array_size(st_strings); ++i)
+    {
+        array_push(strings, st_strings[i]);
+    }
+
+    // [Nugget] NUGHUD /------------------------------------------------------
+
+    // `maxscreenblocks` is now calculated in `ST_StatusbarList()`
+
+    if (!st_strings) {
+        array_push(strings, "Status Bar");
+        array_push(strings, "NUGHUD");
+    }
+
+    MN_UpdateNughudItem();
+
+    // [Nugget] -------------------------------------------------------------/
+
+    return strings;
+}
+
 void MN_InitMenuStrings(void)
 {
     UpdateWeaponSlotLabels();
@@ -5653,6 +5685,7 @@ void MN_InitMenuStrings(void)
     selectstrings[str_gyro_sens] = GetGyroSensitivityStrings();
     selectstrings[str_gyro_accel] = GetGyroAccelStrings();
     selectstrings[str_resampler] = GetResamplerStrings();
+    selectstrings[str_screensize] = GetScreenSizeStrings();
 }
 
 void MN_SetupResetMenu(void)
@@ -5666,6 +5699,7 @@ void MN_SetupResetMenu(void)
     DisableItem(!brightmaps_found || force_brightmaps, gen_settings5,
                 "brightmaps");
     DisableItem(!trakinfo_found, gen_settings2, "extra_music");
+    DisableItem(M_ParmExists("-save"), gen_settings6, "organize_savefiles");
     UpdateInterceptsEmuItem();
     UpdateStatsFormatItem();
     UpdateCrosshairItems();
@@ -5682,6 +5716,7 @@ void MN_SetupResetMenu(void)
                 enem_settings1, "extra_gibbing");
 
     UpdatePaletteItems();
+    MN_UpdateNughudItem(); // NUGHUD
 }
 
 void MN_BindMenuVariables(void)
diff --git a/src/p_action.h b/src/p_action.h
index c4cad6d32..c0cbe1a88 100644
--- a/src/p_action.h
+++ b/src/p_action.h
@@ -30,126 +30,126 @@ struct mobj_s;
 // modified for years by Dehacked enthusiasts.  The new BEX format
 // allows more extensive changes (see d_deh.c)
 
-void A_Light0(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponReady(struct player_s *player, struct pspdef_s *psp);
-void A_Lower(struct player_s *player, struct pspdef_s *psp);
-void A_Raise(struct player_s *player, struct pspdef_s *psp);
-void A_Punch(struct player_s *player, struct pspdef_s *psp);
-void A_ReFire(struct player_s *player, struct pspdef_s *psp);
-void A_FirePistol(struct player_s *player, struct pspdef_s *psp);
-void A_Light1(struct player_s *player, struct pspdef_s *psp);
-void A_FireShotgun(struct player_s *player, struct pspdef_s *psp);
-void A_Light2(struct player_s *player, struct pspdef_s *psp);
-void A_FireShotgun2(struct player_s *player, struct pspdef_s *psp);
-void A_CheckReload(struct player_s *player, struct pspdef_s *psp);
-void A_OpenShotgun2(struct player_s *player, struct pspdef_s *psp);
-void A_LoadShotgun2(struct player_s *player, struct pspdef_s *psp);
-void A_CloseShotgun2(struct player_s *player, struct pspdef_s *psp);
-void A_FireCGun(struct player_s *player, struct pspdef_s *psp);
-void A_GunFlash(struct player_s *player, struct pspdef_s *psp);
-void A_FireMissile(struct player_s *player, struct pspdef_s *psp);
-void A_Saw(struct player_s *player, struct pspdef_s *psp);
-void A_FirePlasma(struct player_s *player, struct pspdef_s *psp);
-void A_BFGsound(struct player_s *player, struct pspdef_s *psp);
-void A_FireBFG(struct player_s *player, struct pspdef_s *psp);
-void A_BFGSpray(struct mobj_s *mo);
-void A_Explode(struct mobj_s *thingy);
-void A_Pain(struct mobj_s *actor);
-void A_PlayerScream(struct mobj_s *mo);
-void A_Fall(struct mobj_s *actor);
-void A_XScream(struct mobj_s *actor);
-void A_Look(struct mobj_s *actor);
-void A_Chase(struct mobj_s *actor);
-void A_FaceTarget(struct mobj_s *actor);
-void A_PosAttack(struct mobj_s *actor);
-void A_Scream(struct mobj_s *actor);
-void A_SPosAttack(struct mobj_s *actor);
-void A_VileChase(struct mobj_s *actor);
-void A_VileStart(struct mobj_s *actor);
-void A_VileTarget(struct mobj_s *actor);
-void A_VileAttack(struct mobj_s *actor);
-void A_StartFire(struct mobj_s *actor);
-void A_Fire(struct mobj_s *actor);
-void A_FireCrackle(struct mobj_s *actor);
-void A_Tracer(struct mobj_s *actor);
-void A_SkelWhoosh(struct mobj_s *actor);
-void A_SkelFist(struct mobj_s *actor);
-void A_SkelMissile(struct mobj_s *actor);
-void A_FatRaise(struct mobj_s *actor);
-void A_FatAttack1(struct mobj_s *actor);
-void A_FatAttack2(struct mobj_s *actor);
-void A_FatAttack3(struct mobj_s *actor);
-void A_BossDeath(struct mobj_s *mo);
-void A_CPosAttack(struct mobj_s *actor);
-void A_CPosRefire(struct mobj_s *actor);
-void A_TroopAttack(struct mobj_s *actor);
-void A_SargAttack(struct mobj_s *actor);
-void A_HeadAttack(struct mobj_s *actor);
-void A_BruisAttack(struct mobj_s *actor);
-void A_SkullAttack(struct mobj_s *actor);
-void A_Metal(struct mobj_s *mo);
-void A_SpidRefire(struct mobj_s *actor);
-void A_BabyMetal(struct mobj_s *mo);
-void A_BspiAttack(struct mobj_s *actor);
-void A_Hoof(struct mobj_s *mo);
-void A_CyberAttack(struct mobj_s *actor);
-void A_PainAttack(struct mobj_s *actor);
-void A_PainDie(struct mobj_s *actor);
-void A_KeenDie(struct mobj_s *mo);
-void A_BrainPain(struct mobj_s *mo);
-void A_BrainScream(struct mobj_s *mo);
-void A_BrainDie(struct mobj_s *mo);
-void A_BrainAwake(struct mobj_s *mo);
-void A_BrainSpit(struct mobj_s *mo);
-void A_SpawnSound(struct mobj_s *mo);
-void A_SpawnFly(struct mobj_s *mo);
-void A_BrainExplode(struct mobj_s *mo);
-void A_Detonate(struct mobj_s *mo);    // killough 8/9/98
-void A_Mushroom(struct mobj_s *actor); // killough 10/98
-void A_Die(struct mobj_s *actor);      // killough 11/98
-void A_Spawn(struct mobj_s *mo);       // killough 11/98
-void A_Turn(struct mobj_s *mo);        // killough 11/98
-void A_Face(struct mobj_s *mo);        // killough 11/98
-void A_Scratch(struct mobj_s *mo);     // killough 11/98
-void A_PlaySound(struct mobj_s *mo);   // killough 11/98
-void A_RandomJump(struct mobj_s *mo);  // killough 11/98
-void A_LineEffect(struct mobj_s *mo);  // killough 11/98
+void A_Light0(struct player_s *, struct pspdef_s *);
+void A_WeaponReady(struct player_s *, struct pspdef_s *);
+void A_Lower(struct player_s *, struct pspdef_s *);
+void A_Raise(struct player_s *, struct pspdef_s *);
+void A_Punch(struct player_s *, struct pspdef_s *);
+void A_ReFire(struct player_s *, struct pspdef_s *);
+void A_FirePistol(struct player_s *, struct pspdef_s *);
+void A_Light1(struct player_s *, struct pspdef_s *);
+void A_FireShotgun(struct player_s *, struct pspdef_s *);
+void A_Light2(struct player_s *, struct pspdef_s *);
+void A_FireShotgun2(struct player_s *, struct pspdef_s *);
+void A_CheckReload(struct player_s *, struct pspdef_s *);
+void A_OpenShotgun2(struct player_s *, struct pspdef_s *);
+void A_LoadShotgun2(struct player_s *, struct pspdef_s *);
+void A_CloseShotgun2(struct player_s *, struct pspdef_s *);
+void A_FireCGun(struct player_s *, struct pspdef_s *);
+void A_GunFlash(struct player_s *, struct pspdef_s *);
+void A_FireMissile(struct player_s *, struct pspdef_s *);
+void A_Saw(struct player_s *, struct pspdef_s *);
+void A_FirePlasma(struct player_s *, struct pspdef_s *);
+void A_BFGsound(struct player_s *, struct pspdef_s *);
+void A_FireBFG(struct player_s *, struct pspdef_s *);
+void A_BFGSpray(struct mobj_s *);
+void A_Explode(struct mobj_s *);
+void A_Pain(struct mobj_s *);
+void A_PlayerScream(struct mobj_s *);
+void A_Fall(struct mobj_s *);
+void A_XScream(struct mobj_s *);
+void A_Look(struct mobj_s *);
+void A_Chase(struct mobj_s *);
+void A_FaceTarget(struct mobj_s *);
+void A_PosAttack(struct mobj_s *);
+void A_Scream(struct mobj_s *);
+void A_SPosAttack(struct mobj_s *);
+void A_VileChase(struct mobj_s *);
+void A_VileStart(struct mobj_s *);
+void A_VileTarget(struct mobj_s *);
+void A_VileAttack(struct mobj_s *);
+void A_StartFire(struct mobj_s *);
+void A_Fire(struct mobj_s *);
+void A_FireCrackle(struct mobj_s *);
+void A_Tracer(struct mobj_s *);
+void A_SkelWhoosh(struct mobj_s *);
+void A_SkelFist(struct mobj_s *);
+void A_SkelMissile(struct mobj_s *);
+void A_FatRaise(struct mobj_s *);
+void A_FatAttack1(struct mobj_s *);
+void A_FatAttack2(struct mobj_s *);
+void A_FatAttack3(struct mobj_s *);
+void A_BossDeath(struct mobj_s *);
+void A_CPosAttack(struct mobj_s *);
+void A_CPosRefire(struct mobj_s *);
+void A_TroopAttack(struct mobj_s *);
+void A_SargAttack(struct mobj_s *);
+void A_HeadAttack(struct mobj_s *);
+void A_BruisAttack(struct mobj_s *);
+void A_SkullAttack(struct mobj_s *);
+void A_Metal(struct mobj_s *);
+void A_SpidRefire(struct mobj_s *);
+void A_BabyMetal(struct mobj_s *);
+void A_BspiAttack(struct mobj_s *);
+void A_Hoof(struct mobj_s *);
+void A_CyberAttack(struct mobj_s *);
+void A_PainAttack(struct mobj_s *);
+void A_PainDie(struct mobj_s *);
+void A_KeenDie(struct mobj_s *);
+void A_BrainPain(struct mobj_s *);
+void A_BrainScream(struct mobj_s *);
+void A_BrainDie(struct mobj_s *);
+void A_BrainAwake(struct mobj_s *);
+void A_BrainSpit(struct mobj_s *);
+void A_SpawnSound(struct mobj_s *);
+void A_SpawnFly(struct mobj_s *);
+void A_BrainExplode(struct mobj_s *);
+void A_Detonate(struct mobj_s *);    // killough 8/9/98
+void A_Mushroom(struct mobj_s *); // killough 10/98
+void A_Die(struct mobj_s *);      // killough 11/98
+void A_Spawn(struct mobj_s *);       // killough 11/98
+void A_Turn(struct mobj_s *);        // killough 11/98
+void A_Face(struct mobj_s *);        // killough 11/98
+void A_Scratch(struct mobj_s *);     // killough 11/98
+void A_PlaySound(struct mobj_s *);   // killough 11/98
+void A_RandomJump(struct mobj_s *);  // killough 11/98
+void A_LineEffect(struct mobj_s *);  // killough 11/98
 
 // killough 7/19/98: classic BFG firing function
-void A_FireOldBFG(struct player_s *player, struct pspdef_s *psp);
+void A_FireOldBFG(struct player_s *, struct pspdef_s *);
 // killough 10/98: beta lost souls attacked different
-void A_BetaSkullAttack(struct mobj_s *actor);
-void A_Stop(struct mobj_s *actor);
+void A_BetaSkullAttack(struct mobj_s *);
+void A_Stop(struct mobj_s *);
 
 // [XA] New mbf21 codepointers
 
-void A_SpawnObject(struct mobj_s *actor);
-void A_MonsterProjectile(struct mobj_s *actor);
-void A_MonsterBulletAttack(struct mobj_s *actor);
-void A_MonsterMeleeAttack(struct mobj_s *actor);
-void A_RadiusDamage(struct mobj_s *actor);
-void A_NoiseAlert(struct mobj_s *actor);
-void A_HealChase(struct mobj_s *actor);
-void A_SeekTracer(struct mobj_s *actor);
-void A_FindTracer(struct mobj_s *actor);
-void A_ClearTracer(struct mobj_s *actor);
-void A_JumpIfHealthBelow(struct mobj_s *actor);
-void A_JumpIfTargetInSight(struct mobj_s *actor);
-void A_JumpIfTargetCloser(struct mobj_s *actor);
-void A_JumpIfTracerInSight(struct mobj_s *actor);
-void A_JumpIfTracerCloser(struct mobj_s *actor);
-void A_JumpIfFlagsSet(struct mobj_s *actor);
-void A_AddFlags(struct mobj_s *actor);
-void A_RemoveFlags(struct mobj_s *actor);
-void A_WeaponProjectile(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponBulletAttack(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponMeleeAttack(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponSound(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponAlert(struct player_s *player, struct pspdef_s *psp);
-void A_WeaponJump(struct player_s *player, struct pspdef_s *psp);
-void A_ConsumeAmmo(struct player_s *player, struct pspdef_s *psp);
-void A_CheckAmmo(struct player_s *player, struct pspdef_s *psp);
-void A_RefireTo(struct player_s *player, struct pspdef_s *psp);
-void A_GunFlashTo(struct player_s *player, struct pspdef_s *psp);
+void A_SpawnObject(struct mobj_s *);
+void A_MonsterProjectile(struct mobj_s *);
+void A_MonsterBulletAttack(struct mobj_s *);
+void A_MonsterMeleeAttack(struct mobj_s *);
+void A_RadiusDamage(struct mobj_s *);
+void A_NoiseAlert(struct mobj_s *);
+void A_HealChase(struct mobj_s *);
+void A_SeekTracer(struct mobj_s *);
+void A_FindTracer(struct mobj_s *);
+void A_ClearTracer(struct mobj_s *);
+void A_JumpIfHealthBelow(struct mobj_s *);
+void A_JumpIfTargetInSight(struct mobj_s *);
+void A_JumpIfTargetCloser(struct mobj_s *);
+void A_JumpIfTracerInSight(struct mobj_s *);
+void A_JumpIfTracerCloser(struct mobj_s *);
+void A_JumpIfFlagsSet(struct mobj_s *);
+void A_AddFlags(struct mobj_s *);
+void A_RemoveFlags(struct mobj_s *);
+void A_WeaponProjectile(struct player_s *, struct pspdef_s *);
+void A_WeaponBulletAttack(struct player_s *, struct pspdef_s *);
+void A_WeaponMeleeAttack(struct player_s *, struct pspdef_s *);
+void A_WeaponSound(struct player_s *, struct pspdef_s *);
+void A_WeaponAlert(struct player_s *, struct pspdef_s *);
+void A_WeaponJump(struct player_s *, struct pspdef_s *);
+void A_ConsumeAmmo(struct player_s *, struct pspdef_s *);
+void A_CheckAmmo(struct player_s *, struct pspdef_s *);
+void A_RefireTo(struct player_s *, struct pspdef_s *);
+void A_GunFlashTo(struct player_s *, struct pspdef_s *);
 
 #endif
diff --git a/src/p_doors.c b/src/p_doors.c
index 4d055110b..0fdf752ec 100644
--- a/src/p_doors.c
+++ b/src/p_doors.c
@@ -41,8 +41,6 @@
 //
 ///////////////////////////////////////////////////////////////
 
-boolean comp_blazing2; // [Nugget]
-
 //
 // T_VerticalDoor
 //
@@ -176,15 +174,15 @@ void T_VerticalDoor (vldoor_t *door)
             case doorClose:          // Close types do not bounce, merely wait
               break;
 
-            // [Nugget]: [crispy] fix "fast doors reopening with wrong sound"
             case blazeRaise:
             case genBlazeRaise:
-                if (STRICTMODE(!comp_blazing2))
-                {
-                    door->direction = 1;
-                    S_StartSound((mobj_t *)&door->sector->soundorg, sfx_bdopn);
-                    break;
-                }
+              door->direction = 1;
+              if (!STRICTMODE_COMP(comp_blazing))
+              {
+                S_StartSound((mobj_t *)&door->sector->soundorg,sfx_bdopn);
+                break;
+              }
+              // fallthrough
 
             default:             // other types bounce off the obstruction
               door->direction = 1;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index e437c5ad0..8df43db12 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -29,10 +29,12 @@
 #include "doomstat.h"
 #include "doomtype.h"
 #include "g_game.h"
+#include "g_umapinfo.h"
 #include "hu_obituary.h"
 #include "i_printf.h"
 #include "i_system.h"
 #include "info.h"
+#include "m_array.h"
 #include "m_bbox.h"
 #include "m_fixed.h"
 #include "m_random.h"
@@ -52,7 +54,6 @@
 #include "s_sound.h"
 #include "sounds.h"
 #include "tables.h"
-#include "u_mapinfo.h"
 #include "z_zone.h"
 
 // [Nugget]
@@ -348,7 +349,7 @@ static int P_IsUnderDamage(mobj_t *actor)
 static fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
 static fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};
 
-static boolean P_Move(mobj_t *actor, boolean dropoff) // killough 9/12/98
+static boolean P_Move(mobj_t *actor, int dropoff) // killough 9/12/98
 {
   fixed_t tryx, tryy, deltax, deltay;
   boolean try_ok;
@@ -545,7 +546,7 @@ static boolean P_SmartMove(mobj_t *actor)
       P_Random(pr_dropoff) < 235)
     dropoff = 2;
 
-  if (!P_Move(actor, !!dropoff))
+  if (!P_Move(actor, dropoff))
     return false;
 
   // killough 9/9/98: avoid crushing ceilings or other damaging areas
@@ -2426,52 +2427,70 @@ void A_BossDeath(mobj_t *mo)
   line_t    junk;
   int       i;
 
-  // numbossactions == 0 means to use the defaults.
-  // numbossactions == -1 means to do nothing.
-  // positive values mean to check the list of boss actions and run all that apply.
-  if (gamemapinfo && gamemapinfo->numbossactions != 0)
+  if (gamemapinfo && gamemapinfo->flags & MapInfo_BossActionClear)
   {
-    if (gamemapinfo->numbossactions < 0) return;
+      return;
+  }
 
-    // make sure there is a player alive for victory
-    for (i=0; i<MAXPLAYERS; i++)
-      if (playeringame[i] && players[i].health > 0)
-        break;
+  if (gamemapinfo && array_size(gamemapinfo->bossactions))
+  {
+      // make sure there is a player alive for victory
+      for (i = 0; i < MAXPLAYERS; i++)
+      {
+          if (playeringame[i] && players[i].health > 0)
+          {
+              break;
+          }
+      }
+      if (i == MAXPLAYERS)
+      {
+          return; // no one left alive, so do not end game
+      }
 
-    if (i==MAXPLAYERS)
-      return;     // no one left alive, so do not end game
+      bossaction_t *bossaction;
+      array_foreach(bossaction, gamemapinfo->bossactions)
+      {
+          if (bossaction->type == mo->type)
+          {
+              break;
+          }
+      }
+      if (bossaction == array_end(gamemapinfo->bossactions))
+      {
+          return; // no matches found
+      }
 
-    for (i = 0; i < gamemapinfo->numbossactions; i++)
+      // scan the remaining thinkers to see
+      // if all bosses are dead
+      for (th = thinkercap.next; th != &thinkercap; th = th->next)
       {
-        if (gamemapinfo->bossactions[i].type == mo->type)
-          break;
+          if (th->function.p1 == (actionf_p1)P_MobjThinker)
+          {
+              mobj_t *mo2 = (mobj_t *)th;
+              if (mo2 != mo && mo2->type == mo->type && mo2->health > 0)
+              {
+                  return; // other boss not dead
+              }
+          }
       }
-    if (i >= gamemapinfo->numbossactions)
-      return;  // no matches found
 
-    // scan the remaining thinkers to see
-    // if all bosses are dead
-    for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
-      if (th->function.p1 == (actionf_p1)P_MobjThinker)
-        {
-          mobj_t *mo2 = (mobj_t *) th;
-          if (mo2 != mo && mo2->type == mo->type && mo2->health > 0)
-            return;         // other boss not dead
-        }
-    for (i = 0; i < gamemapinfo->numbossactions; i++)
+      array_foreach(bossaction, gamemapinfo->bossactions)
       {
-        if (gamemapinfo->bossactions[i].type == mo->type)
+          if (bossaction->type == mo->type)
           {
-            junk = *lines;
-            junk.special = (short)gamemapinfo->bossactions[i].special;
-            junk.tag = (short)gamemapinfo->bossactions[i].tag;
-            // use special semantics for line activation to block problem types.
-            if (!P_UseSpecialLine(mo, &junk, 0, true))
-              P_CrossSpecialLine(&junk, 0, mo, true);
+              junk = *lines;
+              junk.special = (short)bossaction->special;
+              junk.tag = (short)bossaction->tag;
+              // use special semantics for line activation to block problem
+              // types.
+              if (!P_UseSpecialLine(mo, &junk, 0, true))
+              {
+                  P_CrossSpecialLine(&junk, 0, mo, true);
+              }
           }
       }
 
-    return;
+      return;
   }
 
   if (gamemode == commercial)
diff --git a/src/p_inter.c b/src/p_inter.c
index eaaf73ff9..36f36e17d 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -772,7 +772,6 @@ static boolean P_NuggetForceGibbing(
 )
 {
   extern boolean gibbers; // GIBBERS cheat
-  extern void A_Punch(), A_Saw(), A_FireShotgun2();
 
   if (!casual_play) { return false; }
 
diff --git a/src/p_map.c b/src/p_map.c
index 93e019fb2..62c73876a 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1023,7 +1023,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 //
 // killough 3/15/98: allow dropoff as option
 
-boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean dropoff)
+boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, int dropoff)
 {
   fixed_t oldx, oldy;
 
diff --git a/src/p_map.h b/src/p_map.h
index 19d593095..d48cb82bf 100644
--- a/src/p_map.h
+++ b/src/p_map.h
@@ -49,7 +49,7 @@ extern boolean comp_lsamnesia;
 #define CROSSHAIR_AIM 1
 
 // killough 3/15/98: add fourth argument to P_TryMove
-boolean P_TryMove(struct mobj_s *thing, fixed_t x, fixed_t y, boolean dropoff);
+boolean P_TryMove(struct mobj_s *thing, fixed_t x, fixed_t y, int dropoff);
 
 // killough 8/9/98: extra argument for telefragging
 boolean P_TeleportMove(struct mobj_s *thing, fixed_t x, fixed_t y, boolean boss);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d74311cfc..ef6e15374 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1287,8 +1287,6 @@ void P_SpawnPlayer (mapthing_t* mthing)
 
   p->momx = p->momy = 0;   // killough 10/98: initialize bobbing to 0.
 
-  pspr_interp = false;
-
   // setup gun psprite
 
   P_SetupPsprites (p);
diff --git a/src/p_pspr.c b/src/p_pspr.c
index 36dd7497d..e7360a098 100644
--- a/src/p_pspr.c
+++ b/src/p_pspr.c
@@ -189,22 +189,26 @@ static void P_BringUpWeapon(player_t *player)
 
   player->pendingweapon = wp_nochange;
 
+  pspdef_t *psp = &player->psprites[ps_weapon];
+
   // [Nugget] Weapon-switch interruption
   if (switch_interrupted)
   {
     switch_interrupted = false;
   }
   else
-  // killough 12/98: prevent pistol from starting visibly at bottom of screen:
-  player->psprites[ps_weapon].sy2 = // [Nugget]
-  player->psprites[ps_weapon].sy = demo_version >= DV_MBF ? 
-    WEAPONBOTTOM+FRACUNIT*2 : WEAPONBOTTOM;
+  {
+    // killough 12/98: prevent pistol from starting visibly at bottom of screen:
+    psp->sy = demo_version >= DV_MBF ? WEAPONBOTTOM + FRACUNIT * 2 : WEAPONBOTTOM;
+
+    psp->sy2 = psp->oldsy2 = psp->sy;
+  }
 
   // [Nugget]: [crispy] squat down weapon sprite
-  player->psprites[ps_weapon].dy = 0;
+  psp->dy = 0;
   // [Nugget] Reset offsets for weapon inertia
-  player->psprites[ps_weapon].wix = 0;
-  player->psprites[ps_weapon].wiy = 0;
+  psp->wix = 0;
+  psp->wiy = 0;
 
   P_SetPsprite(player, ps_weapon, newstate);
 }
@@ -1531,9 +1535,10 @@ void P_MovePsprites(player_t *player)
 
   weapon_ready = false; // [Nugget]
 
-  // [Nugget]
   psp[ps_weapon].oldsx2 = psp[ps_weapon].sx2;
   psp[ps_weapon].oldsy2 = psp[ps_weapon].sy2;
+
+  // [Nugget]
   psp[ps_weapon].oldwix = psp[ps_weapon].wix;
   psp[ps_weapon].oldwiy = psp[ps_weapon].wiy;
 
@@ -1606,10 +1611,10 @@ void P_MovePsprites(player_t *player)
 
   player->psprites[ps_flash].sx2 = player->psprites[ps_weapon].sx2;
   player->psprites[ps_flash].sy2 = player->psprites[ps_weapon].sy2;
-
-  // [Nugget]
   player->psprites[ps_flash].oldsx2 = player->psprites[ps_weapon].oldsx2;
   player->psprites[ps_flash].oldsy2 = player->psprites[ps_weapon].oldsy2;
+
+  // [Nugget]
   player->psprites[ps_flash].dy     = player->psprites[ps_weapon].dy;
   player->psprites[ps_flash].wix    = player->psprites[ps_weapon].wix;
   player->psprites[ps_flash].oldwix = player->psprites[ps_weapon].oldwix;
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 4ded0da29..14564251a 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -98,11 +98,11 @@ typedef struct pspdef_s
   // [FG] centered weapon sprite
   fixed_t sx2;
   fixed_t sy2;
+  fixed_t oldsx2;
+  fixed_t oldsy2;
 
   // [Nugget] ----------------------------------------------------------------
 
-  fixed_t oldsx2, oldsy2;
-
   // [crispy] squat down weapon sprite
   fixed_t dy;
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 63f820416..e3b1d9c59 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -850,12 +850,21 @@ static void saveg_read_pspdef_t(pspdef_t *str)
         str->sy2 = str->sy;
     }
 
-    // [Nugget]
-    if (saveg_compat > saveg_woof600) {
+    str->oldsx2 = str->sx2;
+    str->oldsy2 = str->sy2;
+
+    // [Nugget] --------------------------------------------------------------
+
+    if (saveg_compat > saveg_woof600)
+    {
       str->dy  = saveg_read32(); // fixed_t dy;
       str->wix = saveg_read32(); // fixed_t wix;
       str->wiy = saveg_read32(); // fixed_t wiy;
-    } else { str->dy = str->wix = str->wiy = 0; }
+    }
+    else { str->dy = str->wix = str->wiy = 0; }
+
+    str->oldwix = str->wix;
+    str->oldwiy = str->wiy;
 }
 
 static void saveg_write_pspdef_t(pspdef_t *str)
diff --git a/src/p_spec.h b/src/p_spec.h
index 6b4ad25e7..956ea787e 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -30,7 +30,6 @@ struct player_s;
 struct sector_s;
 
 // [Nugget] CVARs
-extern boolean comp_blazing2;
 extern boolean comp_manualdoor;
 extern boolean comp_switchsource;
 extern boolean comp_keynoway;
diff --git a/src/p_tick.c b/src/p_tick.c
index 62f78ab31..11c358c79 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -237,7 +237,7 @@ static void P_RunThinkers (void)
        currentthinker != &thinkercap;
        currentthinker = currentthinker->next)
     if (currentthinker->function.p1)
-      currentthinker->function.p1(currentthinker);
+      currentthinker->function.p1((mobj_t *)currentthinker);
 
   // [crispy] support MUSINFO lump (dynamic music changing)
   T_MusInfo();
diff --git a/src/params.h b/src/params.h
index 9ef1de67e..3bd35b542 100644
--- a/src/params.h
+++ b/src/params.h
@@ -44,6 +44,7 @@ static const char *params[] = {
 "-bsp",
 "-force_old_zdoom_nodes",
 "-noautoload",
+"-noextras",
 "-nocheats",
 "-nodeh",
 "-nomapinfo",
diff --git a/src/r_bmaps.c b/src/r_bmaps.c
index dd91768a5..314aa3175 100644
--- a/src/r_bmaps.c
+++ b/src/r_bmaps.c
@@ -29,7 +29,7 @@
 #include "m_array.h"
 #include "m_misc.h"
 #include "r_data.h"
-#include "u_scanner.h"
+#include "m_scanner.h"
 #include "w_wad.h"
 #include "z_zone.h"
 
@@ -49,40 +49,35 @@ typedef struct
     byte colormask[COLORMASK_SIZE];
 } brightmap_t;
 
-static void ReadColormask(u_scanner_t *s, byte *colormask)
+static void ReadColormask(scanner_t *s, byte *colormask)
 {
     memset(colormask, 0, COLORMASK_SIZE);
     do
     {
         unsigned int color1 = 0, color2 = 0;
 
-        if (U_MustGetInteger(s))
+        SC_MustGetToken(s, TK_IntConst);
+        color1 = SC_GetNumber(s);
+        if (color1 >= 0 && color1 < COLORMASK_SIZE)
         {
-            color1 = s->number;
-            if (color1 >= 0 && color1 < COLORMASK_SIZE)
-            {
-                colormask[color1] = 1;
-            }
+            colormask[color1] = 1;
         }
 
-        if (!U_CheckToken(s, '-'))
+        if (!SC_CheckToken(s, '-'))
         {
             continue;
         }
 
-        if (U_MustGetInteger(s))
+        SC_MustGetToken(s, TK_IntConst);
+        color2 = SC_GetNumber(s);
+        if (color2 >= 0 && color2 < COLORMASK_SIZE)
         {
-            color2 = s->number;
-            if (color2 >= 0 && color2 < COLORMASK_SIZE)
+            for (int i = color1 + 1; i <= color2; ++i)
             {
-                int i;
-                for (i = color1 + 1; i <= color2; ++i)
-                {
-                    colormask[i] = 1;
-                }
+                colormask[i] = 1;
             }
         }
-    } while (U_CheckToken(s, ','));
+    } while (SC_CheckToken(s, ','));
 }
 
 static brightmap_t *brightmaps_array = NULL;
@@ -119,43 +114,50 @@ enum
     DOOM2ONLY
 };
 
-static boolean ParseProperty(u_scanner_t *s, elem_t *elem)
+static boolean ParseProperty(scanner_t *s, elem_t *elem)
 {
     char *name;
     int idx;
 
     int game = DOOM1AND2;
 
-    U_GetString(s);
-    name = M_StringDuplicate(s->string);
-    U_MustGetToken(s, TK_Identifier);
-    idx = GetBrightmap(s->string);
+    SC_GetNextTokenLumpName(s);
+    name = M_StringDuplicate(SC_GetString(s));
+    SC_MustGetToken(s, TK_Identifier);
+    idx = GetBrightmap(SC_GetString(s));
     if (idx < 0)
     {
-        U_Error(s, "brightmap '%s' not found", s->string);
+        SC_Error(s, "brightmap '%s' not found", SC_GetString(s));
         free(name);
         return false;
     }
-    if (U_CheckToken(s, TK_Identifier))
+    if (SC_CheckToken(s, TK_Identifier))
     {
-        if (!strcasecmp("DOOM", s->string) || !strcasecmp("DOOM1", s->string))
+        if (!strcasecmp("DOOM", SC_GetString(s))
+            || !strcasecmp("DOOM1", SC_GetString(s)))
         {
             game = DOOM1ONLY;
-            if (U_CheckToken(s, '|'))
+            if (SC_CheckToken(s, '|'))
             {
-                if (U_MustGetIdentifier(s, "DOOM2"))
+                SC_MustGetToken(s, TK_Identifier);
+                if (!strcasecmp("DOOM2", SC_GetString(s)))
                 {
                     game = DOOM1AND2;
                 }
+                else
+                {
+                    SC_Error(s, "Expected 'DOOM2' but got '%s' instead.",
+                             SC_GetString(s));
+                }
             }
         }
-        else if (!strcasecmp("DOOM2", s->string))
+        else if (!strcasecmp("DOOM2", SC_GetString(s)))
         {
             game = DOOM2ONLY;
         }
         else
         {
-            U_Unget(s);
+            SC_Rewind(s);
         }
     }
     if ((gamemission == doom && game == DOOM2ONLY)
@@ -251,10 +253,6 @@ const byte *R_BrightmapForState(const int state)
 
 void R_ParseBrightmaps(int lumpnum)
 {
-    u_scanner_t *s;
-    const char *data = W_CacheLumpNum(lumpnum, PU_CACHE);
-    int length = W_LumpLength(lumpnum);
-
     force_brightmaps = W_IsWADLump(lumpnum);
 
     if (!array_size(brightmaps_array))
@@ -265,24 +263,25 @@ void R_ParseBrightmaps(int lumpnum)
         array_push(brightmaps_array, brightmap);
     }
 
-    s = U_ScanOpen(data, length, "BRGHTMPS");
+    scanner_t *s = SC_Open("BRGHTMPS", W_CacheLumpNum(lumpnum, PU_CACHE),
+                           W_LumpLength(lumpnum));
 
-    while (U_HasTokensLeft(s))
+    while (SC_TokensLeft(s))
     {
-        if (!U_CheckToken(s, TK_Identifier))
+        if (!SC_CheckToken(s, TK_Identifier))
         {
-            U_GetNextToken(s, true);
+            SC_GetNextToken(s, true);
             continue;
         }
-        if (!strcasecmp("BRIGHTMAP", s->string))
+        if (!strcasecmp("BRIGHTMAP", SC_GetString(s)))
         {
             brightmap_t brightmap;
-            U_MustGetToken(s, TK_Identifier);
-            brightmap.name = M_StringDuplicate(s->string);
+            SC_MustGetToken(s, TK_Identifier);
+            brightmap.name = M_StringDuplicate(SC_GetString(s));
             ReadColormask(s, brightmap.colormask);
             array_push(brightmaps_array, brightmap);
         }
-        else if (!strcasecmp("TEXTURE", s->string))
+        else if (!strcasecmp("TEXTURE", SC_GetString(s)))
         {
             elem_t elem;
             if (ParseProperty(s, &elem))
@@ -290,7 +289,7 @@ void R_ParseBrightmaps(int lumpnum)
                 array_push(textures_bm, elem);
             }
         }
-        else if (!strcasecmp("SPRITE", s->string))
+        else if (!strcasecmp("SPRITE", SC_GetString(s)))
         {
             elem_t elem;
             if (ParseProperty(s, &elem))
@@ -307,7 +306,7 @@ void R_ParseBrightmaps(int lumpnum)
                 }
             }
         }
-        else if (!strcasecmp("FLAT", s->string))
+        else if (!strcasecmp("FLAT", SC_GetString(s)))
         {
             elem_t elem;
             if (ParseProperty(s, &elem))
@@ -315,29 +314,29 @@ void R_ParseBrightmaps(int lumpnum)
                 array_push(flats_bm, elem);
             }
         }
-        else if (!strcasecmp("STATE", s->string))
+        else if (!strcasecmp("STATE", SC_GetString(s)))
         {
             elem_t elem;
             elem.name = NULL;
-            U_MustGetInteger(s);
-            elem.num = s->number;
+            SC_MustGetToken(s, TK_IntConst);
+            elem.num = SC_GetNumber(s);
             if (elem.num < 0 || elem.num >= num_states)
             {
-                U_Error(s, "state '%d' not found", elem.num);
+                SC_Error(s, "state '%d' not found", elem.num);
             }
-            U_MustGetToken(s, TK_Identifier);
-            elem.idx = GetBrightmap(s->string);
+            SC_MustGetToken(s, TK_Identifier);
+            elem.idx = GetBrightmap(SC_GetString(s));
             if (elem.idx >= 0)
             {
                 array_push(states_bm, elem);
             }
             else
             {
-                U_Error(s, "brightmap '%s' not found", s->string);
+                SC_Error(s, "brightmap '%s' not found", SC_GetString(s));
             }
         }
     }
-    U_ScanClose(s);
+    SC_Close(s);
 
     brightmaps_found = (array_size(brightmaps_array) > 1);
 }
diff --git a/src/r_data.c b/src/r_data.c
index 3cdfa97e0..32193abd1 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -25,7 +25,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "d_main.h"
+#include "d_iwad.h"
 #include "d_think.h"
 #include "doomdef.h"
 #include "doomstat.h"
@@ -1161,9 +1161,7 @@ void R_InitTranMap(int progress)
   int p = M_CheckParmWithArgs("-dumptranmap", 1);
   if (p > 0)
   {
-      char *path = malloc(strlen(myargv[p + 1]) + 5);
-      strcpy(path, myargv[p + 1]);
-      AddDefaultExtension(path, ".lmp");
+      char *path = AddDefaultExtension(myargv[p + 1], ".lmp");
 
       M_WriteFile(path, main_tranmap, 256 * 256);
 
diff --git a/src/r_draw.c b/src/r_draw.c
index 81011d60d..c6db31133 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -37,9 +37,6 @@
 #include "v_video.h"
 #include "z_zone.h"
 
-// [Nugget]
-#include "m_random.h"
-
 //
 // All drawing to the view buffer is accomplished in this file.
 // The other refresh files only know about ccordinates,
@@ -342,7 +339,7 @@ static const int fuzzoffset[FUZZTABLE] =
     FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,
     FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,
     FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF 
-}; 
+};
 
 static int fuzzpos = 0;
 
@@ -373,7 +370,7 @@ void R_SetFuzzPosDraw(void)
 //  i.e. spectres and invisible players.
 //
 
-static void R_DrawFuzzColumn_orig(void)
+static void DrawFuzzColumnOriginal(void)
 {
     boolean cutoff = false;
 
@@ -451,7 +448,7 @@ static void R_DrawFuzzColumn_orig(void)
 
 static int fuzzblocksize;
 
-static void R_DrawFuzzColumn_block(void)
+static void DrawFuzzColumnBlocky(void)
 {
     boolean cutoff = false;
 
@@ -491,6 +488,8 @@ static void R_DrawFuzzColumn_block(void)
 
     int lines = fuzzblocksize - (dc_yl % fuzzblocksize);
 
+    const int fuzzblockwidth = MIN(fuzzblocksize, video.width - dc_x);
+
     do
     {
         count -= lines;
@@ -509,7 +508,7 @@ static void R_DrawFuzzColumn_block(void)
 
         do
         {
-            memset(dest, fuzz, fuzzblocksize);
+            memset(dest, fuzz, fuzzblockwidth);
             dest += linesize;
         } while (--lines);
 
@@ -529,117 +528,162 @@ static void R_DrawFuzzColumn_block(void)
     }
 }
 
-// [Nugget - ceski] Selective fuzz darkening
-// Reference: https://www.doomworld.com/forum/post/1335769
-// /--------------------------------------------------------------------------
-
-boolean fuzzdark_mode;
+#define FUZZDARK (6 * 256)
+#define FUZZPAL  256
 
-#define FUZZDARK (256 * (Woof_Random() < 32 ? (Woof_Random() & 1 ? 4 : 8) : 6))
-#define FUZZSELECT ((fuzzoffset[fuzzpos] == -FUZZOFF) ? 0 : FUZZDARK)
-
-static void R_DrawSelectiveFuzzColumn(void)
+static const int fuzzdark[FUZZTABLE] =
+{
+    4 * FUZZPAL, 0, 6 * FUZZPAL, 0, 6 * FUZZPAL, 6 * FUZZPAL, 0,
+    6 * FUZZPAL, 6 * FUZZPAL, 0, 6 * FUZZPAL, 6 * FUZZPAL, 6 * FUZZPAL, 0,
+    6 * FUZZPAL, 8 * FUZZPAL, 6 * FUZZPAL, 0, 0, 0, 0,
+    4 * FUZZPAL, 0, 0, 6 * FUZZPAL, 6 * FUZZPAL, 6 * FUZZPAL, 6 * FUZZPAL, 0,
+    4 * FUZZPAL, 0, 6 * FUZZPAL, 6 * FUZZPAL, 0, 0, 6 * FUZZPAL,
+    6 * FUZZPAL, 0, 0, 0, 0, 6 * FUZZPAL, 6 * FUZZPAL,
+    6 * FUZZPAL, 6 * FUZZPAL, 0, 6 * FUZZPAL, 6 * FUZZPAL, 0, 6 * FUZZPAL,
+};
+
+static void DrawFuzzColumnRefraction(void)
 {
-  int count;
-  byte *dest;
-  boolean cutoff = false;
+    boolean cutoff = false;
 
-  if (fuzzblocksize > 1 && dc_x % fuzzblocksize)
-  {
-    return;
-  }
+    if (dc_x % fuzzblocksize)
+    {
+        return;
+    }
 
-  if (!dc_yl)
-    dc_yl = 1;
+    if (!dc_yl)
+    {
+        dc_yl = 1;
+    }
 
-  if (dc_yh == viewheight - 1)
-  {
-    dc_yh = viewheight - 2;
-    cutoff = true;
-  }
+    if (dc_yh == viewheight - 1)
+    {
+        dc_yh = viewheight - 2;
+        cutoff = true;
+    }
 
-  count = dc_yh - dc_yl;
+    int count = dc_yh - dc_yl;
 
-  if (count < 0)
-    return;
+    if (count < 0)
+    {
+        return;
+    }
 
 #ifdef RANGECHECK
-  if (dc_x >= video.width || dc_yl < 0 || dc_yh >= video.height)
-    I_Error("R_DrawSelectiveFuzzColumn: %i to %i at %i", dc_yl, dc_yh, dc_x);
+    if ((unsigned)dc_x >= video.width || dc_yl < 0 || dc_yh >= video.height)
+    {
+        I_Error("R_DrawFuzzColumn: %i to %i at %i", dc_yl, dc_yh, dc_x);
+    }
 #endif
 
-  ++count;
-
-  dest = ylookup[dc_yl] + columnofs[dc_x];
-
-  int lines = fuzzblocksize - (dc_yl % fuzzblocksize);
+    ++count;
 
-  do
-  {
-    count -= lines;
+    byte *dest = ylookup[dc_yl] + columnofs[dc_x];
 
-    const int mask = count >> (8 * sizeof(mask) - 1);
+    int lines = fuzzblocksize - (dc_yl % fuzzblocksize);
 
-    lines += count & mask;
-    count &= ~mask;
+    const int fuzzblockwidth = MIN(fuzzblocksize, video.width - dc_x);
 
-    const byte fuzz = fullcolormap[FUZZSELECT + dest[linesize * fuzzoffset[fuzzpos]]];
+    int dark = FUZZDARK;
+    int offset = 0;
 
     do
     {
-      memset(dest, fuzz, fuzzblocksize);
-      dest += linesize;
-    } while (--lines);
+        count -= lines;
 
-    ++fuzzpos;
+        // if (count < 0)
+        // {
+        //    lines += count;
+        //    count = 0;
+        // }
+        const int mask = count >> (8 * sizeof(mask) - 1);
+        lines += count & mask;
+        count &= ~mask;
 
-    // Clamp table lookup index.
-    fuzzpos &= (fuzzpos - FUZZTABLE) >> (8 * sizeof(fuzzpos) - 1); // killough 1/99
+        const byte fuzz = fullcolormap[dark + dest[linesize * offset]];
 
-    lines = fuzzblocksize;
-  } while (count);
+        do
+        {
+            memset(dest, fuzz, fuzzblockwidth);
+            dest += linesize;
+        } while (--lines);
 
-  if (cutoff)
-  {
-      const byte fuzz = fullcolormap[FUZZSELECT + dest[linesize * (fuzzoffset[fuzzpos] - FUZZOFF) / 2]];
+        ++fuzzpos;
 
-      memset(dest, fuzz, fuzzblocksize);
-  }
-}
+        // Clamp table lookup index.
+        fuzzpos &= (fuzzpos - FUZZTABLE) >> (8 * sizeof(fuzzpos) - 1); // killough 1/99
 
-// [Nugget] -----------------------------------------------------------------/
+        dark = fuzzdark[fuzzpos];
+        offset = fuzzoffset[fuzzpos];
 
-// [FG] spectre drawing mode: 0 original, 1 blocky (hires)
+        lines = fuzzblocksize;
+    } while (count);
 
-boolean fuzzcolumn_mode;
-void (*R_DrawFuzzColumn)(void) = R_DrawFuzzColumn_orig;
+    if (cutoff)
+    {
+        const byte fuzz =
+            fullcolormap[dark + dest[linesize * (offset - FUZZOFF) / 2]];
+        memset(dest, fuzz, fuzzblocksize);
+    }
+}
 
-void R_SetFuzzColumnMode(void)
+static void DrawFuzzColumnShadow(void)
 {
-    // [Nugget - ceski] Selective fuzz darkening
-    if (STRICTMODE(fuzzdark_mode))
-    {
-        if (fuzzcolumn_mode && current_video_height > SCREENHEIGHT)
-        {
-            fuzzblocksize = FixedToInt(video.yscale);
-        }
-        else
-        {
-            fuzzblocksize = 1;
-        }
+    int count = dc_yh - dc_yl;
 
-        R_DrawFuzzColumn = R_DrawSelectiveFuzzColumn;
+    // Zero length.
+    if (count < 0)
+    {
         return;
     }
 
-    if (fuzzcolumn_mode && current_video_height > SCREENHEIGHT)
+#ifdef RANGECHECK
+    if ((unsigned)dc_x >= video.width || dc_yl < 0 || dc_yh >= video.height)
     {
-        fuzzblocksize = FixedToInt(video.yscale);
-        R_DrawFuzzColumn = R_DrawFuzzColumn_block;
+        I_Error("R_DrawFuzzColumn: %i to %i at %i", dc_yl, dc_yh, dc_x);
     }
-    else
+#endif
+
+    byte *dest = ylookup[dc_yl] + columnofs[dc_x];
+
+    count++; // killough 1/99: minor tuning
+
+    do
     {
-        R_DrawFuzzColumn = R_DrawFuzzColumn_orig;
+        *dest = fullcolormap[8 * 256 + *dest];
+
+        dest += linesize; // killough 11/98
+    } while (--count);
+}
+
+fuzzmode_t fuzzmode;
+void (*R_DrawFuzzColumn)(void) = DrawFuzzColumnOriginal;
+
+void R_SetFuzzColumnMode(void)
+{
+    fuzzmode_t mode =
+        (strictmode || (netgame && !solonet)) ? FUZZ_BLOCKY : fuzzmode;
+
+    switch (mode)
+    {
+        case FUZZ_BLOCKY:
+            if (current_video_height > SCREENHEIGHT)
+            {
+                fuzzblocksize = FixedToInt(video.yscale);
+                R_DrawFuzzColumn = DrawFuzzColumnBlocky;
+            }
+            else
+            {
+                R_DrawFuzzColumn = DrawFuzzColumnOriginal;
+            }
+            break;
+        case FUZZ_REFRACTION:
+            fuzzblocksize = FixedToInt(video.yscale);
+            R_DrawFuzzColumn = DrawFuzzColumnRefraction;
+            break;
+        case FUZZ_SHADOW:
+            R_DrawFuzzColumn = DrawFuzzColumnShadow;
+            break;
     }
 }
 
diff --git a/src/r_draw.h b/src/r_draw.h
index b6602fb46..951d18f29 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -48,7 +48,14 @@ void R_SetFuzzPosTic(void);
 void R_SetFuzzPosDraw(void);
 
 // [FG] spectre drawing mode
-extern boolean fuzzcolumn_mode;
+typedef enum
+{
+    FUZZ_BLOCKY,
+    FUZZ_REFRACTION,
+    FUZZ_SHADOW,
+} fuzzmode_t;
+
+extern fuzzmode_t fuzzmode;
 void R_SetFuzzColumnMode(void);
 
 // [Nugget - ceski] Selective fuzz darkening
diff --git a/src/r_main.c b/src/r_main.c
index 7f3e136ad..bec9ad87f 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1037,12 +1037,9 @@ void R_ExecuteSetViewSize (void)
         }
     }
 
-  // [crispy] forcefully initialize the status bar backing screen
-  // [Nugget] Unless the alt. intermission background is enabled
+  // [Nugget] Alt. intermission background
   if (!WI_UsingAltInterpic())
     ST_refreshBackground(); // [Nugget] NUGHUD
-
-  /*pspr_interp = false;*/ // [Nugget] New weapon interpolation: don't need this
 }
 
 //
@@ -1879,22 +1876,19 @@ void R_BindRenderVariables(void)
   // [Nugget] (CFG-only)
   BIND_BOOL(no_killough_face, false, "Disable the Killough-face easter egg");
 
-  BIND_NUM(screenblocks, 10, 3, 12, "Size of game-world screen");
+  BIND_NUM(screenblocks, 10, 3, UL, "Size of game-world screen");
 
   M_BindBool("translucency", &translucency, NULL, true, ss_gen, wad_yes,
              "Translucency for some things");
   M_BindNum("tran_filter_pct", &tran_filter_pct, NULL,
-            66, 0, 100, ss_gen, wad_yes,
+            66, 0, 100, ss_none, wad_yes,
             "Percent of foreground/background translucency mix");
 
   M_BindBool("flipcorpses", &flipcorpses, NULL, false, ss_enem, wad_no,
              "Randomly mirrored death animations");
-  M_BindBool("fuzzcolumn_mode", &fuzzcolumn_mode, NULL, true, ss_enem, wad_no, // [Nugget] Restored menu item
-             "Fuzz rendering (0 = Resolution-dependent; 1 = Blocky)");
-
-  // [Nugget - ceski] Selective fuzz darkening
-  M_BindBool("fuzzdark_mode", &fuzzdark_mode, NULL, false, ss_enem, wad_no,
-             "Selective fuzz darkening");
+  M_BindNum("fuzzmode", &fuzzmode, NULL,
+            FUZZ_BLOCKY, FUZZ_BLOCKY, FUZZ_SHADOW, ss_none, wad_no,
+            "Partial Invisibility (0 = Vanilla; 1 = Refraction; 2 = Shadow)");
 
   BIND_BOOL(draw_nearby_sprites, true,
     "Draw sprites overlapping into visible sectors");
diff --git a/src/r_plane.c b/src/r_plane.c
index bdc14a63e..8008192cf 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -39,6 +39,7 @@
 #include "doomstat.h"
 #include "doomtype.h"
 #include "i_system.h"
+#include "i_video.h"
 #include "m_fixed.h"
 #include "r_bmaps.h" // [crispy] R_BrightmapForTexName()
 #include "r_data.h"
@@ -419,9 +420,21 @@ static void DrawSkyTex(visplane_t *pl, skytex_t *skytex)
     dc_texheight = textureheight[texture] >> FRACBITS;
     dc_iscale = FixedMul(skyiscale, skytex->scaley);
 
-    dc_texturemid += skytex->curry;
+    fixed_t deltax, deltay;
+    if (uncapped && leveltime > oldleveltime)
+    {
+        deltax = LerpFixed(skytex->prevx, skytex->currx);
+        deltay = LerpFixed(skytex->prevy, skytex->curry);
+    }
+    else
+    {
+        deltax = skytex->currx;
+        deltay = skytex->curry;
+    }
+
+    dc_texturemid += deltay;
 
-    angle_t an = viewangle + FixedToAngle(skytex->currx);
+    angle_t an = viewangle + (deltax << (ANGLETOSKYSHIFT - FRACBITS));
 
     for (int x = pl->minx; x <= pl->maxx; x++)
     {
diff --git a/src/r_sky.c b/src/r_sky.c
index 8aaf4b920..b190ae558 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -178,12 +178,16 @@ void R_UpdateSky(void)
     }
 
     skytex_t *background = &sky->skytex;
+    background->prevx = background->currx;
+    background->prevy = background->curry;
     background->currx += background->scrollx;
     background->curry += background->scrolly;
 
     if (sky->type == SkyType_WithForeground)
     {
         skytex_t *foreground = &sky->foreground;
+        foreground->prevx = foreground->currx;
+        foreground->prevy = foreground->curry;
         foreground->currx += foreground->scrollx;
         foreground->curry += foreground->scrolly;
     }
diff --git a/src/r_skydefs.c b/src/r_skydefs.c
index 5f00ff622..e075c5c18 100644
--- a/src/r_skydefs.c
+++ b/src/r_skydefs.c
@@ -50,6 +50,7 @@ static boolean ParseSkyTex(json_t *json, skytex_t *out)
     json_t *name = JS_GetObject(json, "name");
     if (!JS_IsString(name))
     {
+        out->name = "-"; // no texture
         return false;
     }
     out->name = M_StringDuplicate(JS_GetString(name));
diff --git a/src/r_skydefs.h b/src/r_skydefs.h
index 0f20b3cff..c4d548108 100644
--- a/src/r_skydefs.h
+++ b/src/r_skydefs.h
@@ -37,8 +37,10 @@ typedef struct
     double mid;
     fixed_t scrollx;
     fixed_t currx;
+    fixed_t prevx;
     fixed_t scrolly;
     fixed_t curry;
+    fixed_t prevy;
     fixed_t scalex;
     fixed_t scaley;
 } skytex_t;
diff --git a/src/r_things.c b/src/r_things.c
index cb6b9bb25..ced6e2848 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -143,6 +143,11 @@ void R_InitSpritesRes(void)
 static void R_InstallSpriteLump(int lump, unsigned frame,
                                 unsigned rotation, boolean flipped)
 {
+  if (frame == '^' - 'A')
+  {
+    frame = '\\' - 'A';
+  }
+
   if (frame >= MAX_SPRITE_FRAMES || rotation > 8)
     I_Error("R_InstallSpriteLump: Bad frame characters in lump %i", lump);
 
@@ -835,8 +840,6 @@ void R_NearbySprites (void)
 // R_DrawPSprite
 //
 
-boolean pspr_interp = true; // weapon bobbing interpolation
-
 void R_DrawPSprite (pspdef_t *psp, boolean translucent) // [Nugget] Translucent flashes
 {
   fixed_t       tx;
@@ -869,30 +872,29 @@ void R_DrawPSprite (pspdef_t *psp, boolean translucent) // [Nugget] Translucent
   lump = sprframe->lump[0];
   flip = (boolean) sprframe->flip[0];
 
-  // [Nugget] New interpolation /---------------------------------------------
+  fixed_t sx2, sy2;
 
-  fixed_t sx2, sy2, wix, wiy;
+  fixed_t wix, wiy; // [Nugget]
 
-  if (uncapped && oldleveltime < leveltime && pspr_interp)
+  if (uncapped && oldleveltime < leveltime)
   {
     sx2 = LerpFixed(psp->oldsx2, psp->sx2);
     sy2 = LerpFixed(psp->oldsy2, psp->sy2);
+
     wix = LerpFixed(psp->oldwix, psp->wix);
     wiy = LerpFixed(psp->oldwiy, psp->wiy);
   }
-  else {
-    pspr_interp = true;
-
+  else
+  {
     sx2 = psp->sx2;
     sy2 = psp->sy2;
+
     wix = psp->wix;
     wiy = psp->wiy;
   }
 
-  // [Nugget] ---------------------------------------------------------------/
-
   // calculate edges of the shape
-  tx = sx2-160*FRACUNIT; // [FG] centered weapon sprite
+  tx = sx2 - 160*FRACUNIT; // [FG] centered weapon sprite
 
   // [Nugget] Weapon inertia | Flip levels
   if (STRICTMODE(weapon_inertia))
@@ -919,10 +921,11 @@ void R_DrawPSprite (pspdef_t *psp, boolean translucent) // [Nugget] Translucent
 
   // killough 12/98: fix psprite positioning problem
   vis->texturemid = (BASEYCENTER<<FRACBITS) /* + FRACUNIT/2 */ -
-                    (sy2-spritetopoffset[lump]) // [FG] centered weapon sprite
-                    // [Nugget]
-                    - (STRICTMODE(weapon_inertia) ? wiy : 0) // Weapon inertia
-                    + MIN(0, R_GetFOVFX(FOVFX_ZOOM) * FRACUNIT/2); // Lower weapon based on zoom
+                    (sy2 - spritetopoffset[lump]); // [FG] centered weapon sprite
+
+  // [Nugget]
+  vis->texturemid += (STRICTMODE(weapon_inertia) ? -wiy : 0) // Weapon inertia
+                   + MIN(0, R_GetFOVFX(FOVFX_ZOOM) * FRACUNIT/2); // Lower weapon based on zoom
 
   vis->x1 = x1 < 0 ? 0 : x1;
   vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
@@ -965,8 +968,6 @@ void R_DrawPSprite (pspdef_t *psp, boolean translucent) // [Nugget] Translucent
   // [Nugget] Translucent flashes
   vis->tranmap = translucent ? R_GetGenericTranMap(pspr_translucency_pct) : NULL;
 
-  // [Nugget] Removed old interpolation code
-
   // [crispy] free look
   vis->texturemid += (centery - viewheight/2) * pspriteiscale
                    - (STRICTMODE(ST_GetNughudOn()) ? nughud.weapheight*FRACUNIT : 0); // [Nugget] NUGHUD
diff --git a/src/r_things.h b/src/r_things.h
index f85d45a02..40321c554 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -39,7 +39,6 @@ extern int64_t sprtopscreen; // [FG] 64-bit integer math
 extern fixed_t pspritescale;
 extern fixed_t pspriteiscale;
 
-extern boolean pspr_interp; // weapon bobbing interpolation
 extern boolean flipcorpses;
 
 extern lighttable_t **spritelights;
diff --git a/src/s_musinfo.c b/src/s_musinfo.c
index d3cea70f1..01cdcacd5 100644
--- a/src/s_musinfo.c
+++ b/src/s_musinfo.c
@@ -23,11 +23,11 @@
 #include "s_musinfo.h"
 
 #include "doomtype.h"
-#include "g_game.h"
+#include "g_umapinfo.h"
 #include "i_printf.h"
 #include "p_mobj.h"
 #include "s_sound.h"
-#include "u_scanner.h"
+#include "m_scanner.h"
 #include "w_wad.h"
 #include "z_zone.h"
 
@@ -40,51 +40,47 @@ musinfo_t musinfo = {0};
 
 void S_ParseMusInfo(const char *mapid)
 {
-    u_scanner_t *s;
-    int num, lumpnum;
-
-    lumpnum = W_CheckNumForName("MUSINFO");
-
+    int lumpnum = W_CheckNumForName("MUSINFO");
     if (lumpnum < 0)
     {
         return;
     }
 
-    s = U_ScanOpen(W_CacheLumpNum(lumpnum, PU_CACHE), W_LumpLength(lumpnum),
-                   "MUSINFO");
+    scanner_t *s = SC_Open("MUSINFO", W_CacheLumpNum(lumpnum, PU_CACHE),
+                           W_LumpLength(lumpnum));
 
-    while (U_HasTokensLeft(s))
+    while (SC_TokensLeft(s))
     {
-        if (U_CheckToken(s, TK_Identifier))
+        if (SC_CheckToken(s, TK_Identifier))
         {
-            if (!strcasecmp(s->string, mapid))
+            if (!strcasecmp(SC_GetString(s), mapid))
             {
                 break;
             }
         }
         else
         {
-            U_GetNextLineToken(s);
+            SC_GetNextLineToken(s);
         }
     }
 
-    while (U_HasTokensLeft(s))
+    while (SC_TokensLeft(s))
     {
-        if (U_CheckToken(s, TK_Identifier))
+        if (SC_CheckToken(s, TK_Identifier))
         {
-            if (G_ValidateMapName(s->string, NULL, NULL))
+            if (G_ValidateMapName(SC_GetString(s), NULL, NULL))
             {
                 break;
             }
         }
-        else if (U_CheckInteger(s))
+        else if (SC_CheckToken(s, TK_IntConst))
         {
-            num = s->number;
+            int num = SC_GetNumber(s);
             // Check number in range
             if (num > 0 && num < MAX_MUS_ENTRIES)
             {
-                U_GetString(s);
-                lumpnum = W_CheckNumForName(s->string);
+                SC_GetNextTokenLumpName(s);
+                lumpnum = W_CheckNumForName(SC_GetString(s));
                 if (lumpnum > 0)
                 {
                     musinfo.items[num] = lumpnum;
@@ -92,7 +88,7 @@ void S_ParseMusInfo(const char *mapid)
                 else
                 {
                     I_Printf(VB_WARNING, "S_ParseMusInfo: Unknown MUS lump %s",
-                             s->string);
+                             SC_GetString(s));
                 }
             }
             else
@@ -104,11 +100,11 @@ void S_ParseMusInfo(const char *mapid)
         }
         else
         {
-            U_GetNextLineToken(s);
+            SC_GetNextLineToken(s);
         }
     }
 
-    U_ScanClose(s);
+    SC_Close(s);
 }
 
 void T_MusInfo(void)
diff --git a/src/s_sound.c b/src/s_sound.c
index e36982b0f..e15c8c085 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -24,6 +24,7 @@
 
 #include "doomdef.h"
 #include "doomstat.h"
+#include "g_umapinfo.h"
 #include "i_printf.h"
 #include "i_rumble.h"
 #include "i_sound.h"
@@ -36,7 +37,6 @@
 #include "s_sound.h"
 #include "s_trakinfo.h"
 #include "sounds.h"
-#include "u_mapinfo.h"
 #include "w_wad.h"
 #include "z_zone.h"
 
diff --git a/src/s_trakinfo.c b/src/s_trakinfo.c
index 903b81ecb..bb9c14b95 100644
--- a/src/s_trakinfo.c
+++ b/src/s_trakinfo.c
@@ -36,6 +36,10 @@ static trakinfo_t *trakinfo;
 void S_ParseTrakInfo(int lumpnum)
 {
     json_t *json = JS_OpenOptions(lumpnum, true);
+    if (!json)
+    {
+        return;
+    }
 
     json_obj_iter_t *iter = JS_ObjectIterator(json);
 
diff --git a/src/sounds.c b/src/sounds.c
index 1e7f71eb0..2c8365581 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -155,7 +155,7 @@ sfxinfo_t original_S_sfx[NUMSFX] = {
   SOUND("stnmov", sg_none,  119),
   SOUND("swtchn", sg_none,   78),
   SOUND("swtchx", sg_none,   78),
-  SOUND("plpain", sg_none,   96),
+  SOUND("plpain", sg_oof,    96),
   SOUND("dmpain", sg_none,   96),
   SOUND("popain", sg_none,   96),
   SOUND("vipain", sg_none,   96),
diff --git a/src/st_carousel.c b/src/st_carousel.c
index 30a7ff86e..cbcaa6c37 100644
--- a/src/st_carousel.c
+++ b/src/st_carousel.c
@@ -13,6 +13,7 @@
 
 #include <math.h>
 
+#include "d_items.h"
 #include "d_player.h"
 #include "doomdef.h"
 #include "doomtype.h"
@@ -176,8 +177,17 @@ void ST_UpdateCarousel(player_t *player)
 static void DrawIcon(int x, int y, sbarelem_t *elem, weapon_icon_t icon)
 {
     char lump[9] = {0};
-    M_snprintf(lump, sizeof(lump), "%s%d", names[icon.weapon],
-        icon.state == wpi_selected);
+    const char *name;
+    if (weaponinfo[icon.weapon].carouselicon)
+    {
+        name = weaponinfo[icon.weapon].carouselicon;
+    }
+    else
+    {
+        name = names[icon.weapon];
+    }
+
+    M_snprintf(lump, sizeof(lump), "%s%d", name, icon.state == wpi_selected);
 
     patch_t *patch = V_CachePatchName(lump, PU_CACHE);
 
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 2ae3b6e21..cfdc1d3cc 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -59,11 +59,15 @@
 #include "z_zone.h"
 
 // [Nugget]
+#include "mn_internal.h"
 #include "m_nughud.h"
 #include "sounds.h"
 
 // [Nugget] /=================================================================
 
+static sbardef_t *normal_sbardef = NULL,
+                 *nughud_sbardef = NULL;  // NUGHUD
+
 static patch_t *stbersrk;
 static int lu_berserk;
 
@@ -84,14 +88,7 @@ boolean no_radsuit_tint;
 boolean comp_godface;
 boolean comp_unusedpals;
 
-typedef enum hudtype_s
-{
-  HUDTYPE_SBARDEF,
-  HUDTYPE_NUGHUD
-} hudtype_t;
-
-static hudtype_t hud_type;
-
+static boolean use_nughud;
 static boolean hud_blink_keys;
 static boolean sts_show_berserk;
 int force_carousel;
@@ -141,7 +138,11 @@ boolean ST_GetNughudOn(void)
 
 static void UpdateNughudOn(void)
 {
-  st_nughud = screenblocks == 11 && hud_type == HUDTYPE_NUGHUD && automap_off;
+  // If we have no proper status bars (e.g. SBARDEF is empty),
+  // use NUGHUD when `screenblocks == 11`
+  st_nughud = automap_off
+              && (   ( normal_sbardef && screenblocks == maxscreenblocks - 1 && use_nughud)
+                  || (!normal_sbardef && screenblocks == 11));
 }
 
 static sbarelem_t *nughud_health_elem = NULL,
@@ -1189,13 +1190,23 @@ static void UpdateStatusBar(player_t *player)
 
     int barindex = MAX(screenblocks - 10, 0);
 
+    // [Nugget] NUGHUD
+    if (st_nughud)
+    {
+        barindex = -2;
+    }
+    else
+
+    if (barindex >= array_size(sbardef->statusbars))
+    {
+        barindex = array_size(sbardef->statusbars) - 1;
+    }
+
     if (automapactive == AM_FULL && automapoverlay == AM_OVERLAY_OFF)
     {
         barindex = 0;
     }
 
-    if (st_nughud) { barindex = 3; } // [Nugget] NUGHUD
-
     if (oldbarindex != barindex)
     {
         st_ammo_elem = NULL; // [Nugget]
@@ -2208,11 +2219,6 @@ void ST_Drawer(void)
     }
 
     DrawStatusBar();
-
-    if (hud_crosshair_on) // [Nugget] Crosshair toggle
-    {
-        HU_DrawCrosshair();
-    }
 }
 
 void ST_Start(void)
@@ -2241,13 +2247,12 @@ void ST_Init(void)
 
     static boolean firsttime = true;
 
-    static sbardef_t *normal_sbardef = NULL, *nughud_sbardef = NULL;
-
     if (firsttime)
     {
         normal_sbardef = ST_ParseSbarDef();
         nughud_sbardef = CreateNughudSbarDef();
 
+        ST_StatusbarList(); // Calculate `maxscreenblocks`
         UpdateNughudOn();
     }
 
@@ -2379,6 +2384,44 @@ void ST_InitRes(void)
   st_bar = Z_Malloc((video.pitch * V_ScaleY(stbar_height)) * sizeof(*st_bar), PU_RENDERER, 0);
 }
 
+const char **ST_StatusbarList(void)
+{
+    // [Nugget] Use `normal_sbardef`; calculate `maxscreenblocks` here
+
+    if (!normal_sbardef)
+    {
+        maxscreenblocks = 11; // [Nugget] NUGHUD
+        return NULL;
+    }
+
+    maxscreenblocks = 10;
+
+    static const char **strings;
+
+    if (array_size(strings))
+    {
+        maxscreenblocks += array_size(strings) - 1;
+        return strings;
+    }
+
+    statusbar_t *item;
+    array_foreach(item, normal_sbardef->statusbars)
+    {
+        if (item->fullscreenrender)
+        {
+            array_push(strings, "Fullscreen");
+        }
+        else
+        {
+            array_push(strings, "Status Bar");
+        }
+    }
+
+    maxscreenblocks += array_size(strings) - 1;
+
+    return strings;
+}
+
 void ST_ResetPalette(void)
 {
     I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE));
@@ -3701,16 +3744,15 @@ static sbardef_t *CreateNughudSbarDef(void)
 void ST_BindSTSVariables(void)
 {
   // [Nugget] NUGHUD
-  M_BindNum("fullscreen_hud_type", &hud_type, NULL,
-            HUDTYPE_SBARDEF, HUDTYPE_SBARDEF, HUDTYPE_NUGHUD,
-            ss_stat, wad_no, "Fullscreen HUD type (0 = SBARDEF; 1 = NUGHUD)");
+  M_BindBool("use_nughud", &use_nughud, NULL,
+             true, ss_stat, wad_no, "Replace second-to-last HUD with NUGHUD");
 
   M_BindNum("st_layout", &st_layout, NULL,  st_wide, st_original, st_wide,
              ss_stat, wad_no, "HUD layout");
   M_BindBool("sts_colored_numbers", &sts_colored_numbers, NULL,
              false, ss_stat, wad_yes, "Colored numbers on the status bar");
   M_BindBool("sts_pct_always_gray", &sts_pct_always_gray, NULL,
-             false, ss_stat, wad_yes,
+             false, ss_none, wad_yes,
              "Percent signs on the status bar are always gray");
   M_BindBool("st_solidbackground", &st_solidbackground, NULL,
              false, ss_stat, wad_no,
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 23db5439e..b91fe6f90 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -111,6 +111,8 @@ extern struct patch_s **hu_font;
 void WI_UpdateWidgets(void);
 void WI_DrawWidgets(void);
 
+const char **ST_StatusbarList(void);
+
 void ST_BindSTSVariables(void);
 
 #endif
diff --git a/src/st_widgets.c b/src/st_widgets.c
index 9dc57854b..b3a4b8960 100644
--- a/src/st_widgets.c
+++ b/src/st_widgets.c
@@ -24,6 +24,7 @@
 #include "doomstat.h"
 #include "doomtype.h"
 #include "dstrings.h"
+#include "g_umapinfo.h"
 #include "hu_command.h"
 #include "hu_coordinates.h"
 #include "hu_obituary.h"
@@ -41,7 +42,6 @@
 #include "sounds.h"
 #include "st_sbardef.h"
 #include "st_stuff.h"
-#include "u_mapinfo.h"
 #include "v_video.h"
 
 // [Nugget]
@@ -850,10 +850,11 @@ void ST_ResetTitle(void)
             s = gamemapinfo->mapname;
         }
 
-        if (s == gamemapinfo->mapname || U_CheckField(s))
+        if (!(gamemapinfo->flags & MapInfo_LabelClear))
         {
             M_snprintf(string, sizeof(string), "%s: ", s);
         }
+
         s = gamemapinfo->levelname;
     }
     else if (gamestate == GS_LEVEL)
@@ -902,7 +903,7 @@ void ST_ResetTitle(void)
     announce_string[0] = '\0';
     if (hud_map_announce && leveltime == 0)
     {
-        if (gamemapinfo && U_CheckField(gamemapinfo->author))
+        if (gamemapinfo && gamemapinfo->author)
         {
             M_snprintf(announce_string, sizeof(announce_string), "%s by %s",
                        string, gamemapinfo->author);
diff --git a/src/u_mapinfo.c b/src/u_mapinfo.c
deleted file mode 100644
index 78c892450..000000000
--- a/src/u_mapinfo.c
+++ /dev/null
@@ -1,888 +0,0 @@
-//-----------------------------------------------------------------------------
-//
-// Copyright 2017 Christoph Oelckers
-// Copyright 2019 Fernando Carmona Varo
-//
-// This program 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 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/
-//
-//-----------------------------------------------------------------------------
-
-#include <stdlib.h>
-#include <string.h>
-
-#include "doomdef.h"
-#include "doomstat.h"
-#include "doomtype.h"
-#include "i_system.h"
-#include "m_array.h"
-#include "m_misc.h"
-#include "u_mapinfo.h"
-#include "u_scanner.h"
-#include "w_wad.h"
-#include "z_zone.h"
-
-void M_AddEpisode(const char *map, const char *gfx, const char *txt,
-                  const char *alpha);
-void M_ClearEpisodes(void);
-
-int G_ValidateMapName(const char *mapname, int *pEpi, int *pMap);
-
-umapinfo_t U_mapinfo;
-
-umapinfo_t default_mapinfo;
-
-static level_t *secretlevels;
-
-static const char *const ActorNames[] =
-{
-    "DoomPlayer", "ZombieMan", "ShotgunGuy", "Archvile", "ArchvileFire",
-    "Revenant", "RevenantTracer", "RevenantTracerSmoke", "Fatso", "FatShot",
-    "ChaingunGuy", "DoomImp", "Demon", "Spectre", "Cacodemon", "BaronOfHell",
-    "BaronBall", "HellKnight", "LostSoul", "SpiderMastermind", "Arachnotron",
-    "Cyberdemon", "PainElemental", "WolfensteinSS", "CommanderKeen",
-    "BossBrain", "BossEye", "BossTarget", "SpawnShot", "SpawnFire",
-    "ExplosiveBarrel", "DoomImpBall", "CacodemonBall", "Rocket", "PlasmaBall",
-    "BFGBall", "ArachnotronPlasma", "BulletPuff", "Blood", "TeleportFog",
-    "ItemFog", "TeleportDest", "BFGExtra", "GreenArmor", "BlueArmor",
-    "HealthBonus", "ArmorBonus", "BlueCard", "RedCard", "YellowCard",
-    "YellowSkull", "RedSkull", "BlueSkull", "Stimpack", "Medikit", "Soulsphere",
-    "InvulnerabilitySphere", "Berserk", "BlurSphere", "RadSuit", "Allmap",
-    "Infrared", "Megasphere", "Clip", "ClipBox", "RocketAmmo", "RocketBox",
-    "Cell", "CellPack", "Shell", "ShellBox", "Backpack", "BFG9000", "Chaingun",
-    "Chainsaw", "RocketLauncher", "PlasmaRifle", "Shotgun", "SuperShotgun",
-    "TechLamp", "TechLamp2", "Column", "TallGreenColumn", "ShortGreenColumn",
-    "TallRedColumn", "ShortRedColumn", "SkullColumn", "HeartColumn", "EvilEye",
-    "FloatingSkull", "TorchTree", "BlueTorch", "GreenTorch", "RedTorch",
-    "ShortBlueTorch", "ShortGreenTorch", "ShortRedTorch", "Stalagtite",
-    "TechPillar", "CandleStick", "Candelabra", "BloodyTwitch", "Meat2", "Meat3",
-    "Meat4", "Meat5", "NonsolidMeat2", "NonsolidMeat4", "NonsolidMeat3",
-    "NonsolidMeat5", "NonsolidTwitch", "DeadCacodemon", "DeadMarine",
-    "DeadZombieMan", "DeadDemon", "DeadLostSoul", "DeadDoomImp",
-    "DeadShotgunGuy", "GibbedMarine", "GibbedMarineExtra", "HeadsOnAStick",
-    "Gibs", "HeadOnAStick", "HeadCandles", "DeadStick", "LiveStick", "BigTree",
-    "BurningBarrel", "HangNoGuts", "HangBNoBrain", "HangTLookingDown",
-    "HangTSkull", "HangTLookingUp", "HangTNoBrain", "ColonGibs",
-    "SmallBloodPool", "BrainStem",
-    // Boom/MBF additions
-    "PointPusher", "PointPuller", "MBFHelperDog", "PlasmaBall1", "PlasmaBall2",
-    "EvilSceptre", "UnholyBible", "MusicChanger", "Deh_Actor_145",
-    "Deh_Actor_146", "Deh_Actor_147", "Deh_Actor_148", "Deh_Actor_149",
-    // DEHEXTRA Actors start here
-    "Deh_Actor_150", // Extra thing 0
-    "Deh_Actor_151", // Extra thing 1
-    "Deh_Actor_152", // Extra thing 2
-    "Deh_Actor_153", // Extra thing 3
-    "Deh_Actor_154", // Extra thing 4
-    "Deh_Actor_155", // Extra thing 5
-    "Deh_Actor_156", // Extra thing 6
-    "Deh_Actor_157", // Extra thing 7
-    "Deh_Actor_158", // Extra thing 8
-    "Deh_Actor_159", // Extra thing 9
-    "Deh_Actor_160", // Extra thing 10
-    "Deh_Actor_161", // Extra thing 11
-    "Deh_Actor_162", // Extra thing 12
-    "Deh_Actor_163", // Extra thing 13
-    "Deh_Actor_164", // Extra thing 14
-    "Deh_Actor_165", // Extra thing 15
-    "Deh_Actor_166", // Extra thing 16
-    "Deh_Actor_167", // Extra thing 17
-    "Deh_Actor_168", // Extra thing 18
-    "Deh_Actor_169", // Extra thing 19
-    "Deh_Actor_170", // Extra thing 20
-    "Deh_Actor_171", // Extra thing 21
-    "Deh_Actor_172", // Extra thing 22
-    "Deh_Actor_173", // Extra thing 23
-    "Deh_Actor_174", // Extra thing 24
-    "Deh_Actor_175", // Extra thing 25
-    "Deh_Actor_176", // Extra thing 26
-    "Deh_Actor_177", // Extra thing 27
-    "Deh_Actor_178", // Extra thing 28
-    "Deh_Actor_179", // Extra thing 29
-    "Deh_Actor_180", // Extra thing 30
-    "Deh_Actor_181", // Extra thing 31
-    "Deh_Actor_182", // Extra thing 32
-    "Deh_Actor_183", // Extra thing 33
-    "Deh_Actor_184", // Extra thing 34
-    "Deh_Actor_185", // Extra thing 35
-    "Deh_Actor_186", // Extra thing 36
-    "Deh_Actor_187", // Extra thing 37
-    "Deh_Actor_188", // Extra thing 38
-    "Deh_Actor_189", // Extra thing 39
-    "Deh_Actor_190", // Extra thing 40
-    "Deh_Actor_191", // Extra thing 41
-    "Deh_Actor_192", // Extra thing 42
-    "Deh_Actor_193", // Extra thing 43
-    "Deh_Actor_194", // Extra thing 44
-    "Deh_Actor_195", // Extra thing 45
-    "Deh_Actor_196", // Extra thing 46
-    "Deh_Actor_197", // Extra thing 47
-    "Deh_Actor_198", // Extra thing 48
-    "Deh_Actor_199", // Extra thing 49
-    "Deh_Actor_200", // Extra thing 50
-    "Deh_Actor_201", // Extra thing 51
-    "Deh_Actor_202", // Extra thing 52
-    "Deh_Actor_203", // Extra thing 53
-    "Deh_Actor_204", // Extra thing 54
-    "Deh_Actor_205", // Extra thing 55
-    "Deh_Actor_206", // Extra thing 56
-    "Deh_Actor_207", // Extra thing 57
-    "Deh_Actor_208", // Extra thing 58
-    "Deh_Actor_209", // Extra thing 59
-    "Deh_Actor_210", // Extra thing 60
-    "Deh_Actor_211", // Extra thing 61
-    "Deh_Actor_212", // Extra thing 62
-    "Deh_Actor_213", // Extra thing 63
-    "Deh_Actor_214", // Extra thing 64
-    "Deh_Actor_215", // Extra thing 65
-    "Deh_Actor_216", // Extra thing 66
-    "Deh_Actor_217", // Extra thing 67
-    "Deh_Actor_218", // Extra thing 68
-    "Deh_Actor_219", // Extra thing 69
-    "Deh_Actor_220", // Extra thing 70
-    "Deh_Actor_221", // Extra thing 71
-    "Deh_Actor_222", // Extra thing 72
-    "Deh_Actor_223", // Extra thing 73
-    "Deh_Actor_224", // Extra thing 74
-    "Deh_Actor_225", // Extra thing 75
-    "Deh_Actor_226", // Extra thing 76
-    "Deh_Actor_227", // Extra thing 77
-    "Deh_Actor_228", // Extra thing 78
-    "Deh_Actor_229", // Extra thing 79
-    "Deh_Actor_230", // Extra thing 80
-    "Deh_Actor_231", // Extra thing 81
-    "Deh_Actor_232", // Extra thing 82
-    "Deh_Actor_233", // Extra thing 83
-    "Deh_Actor_234", // Extra thing 84
-    "Deh_Actor_235", // Extra thing 85
-    "Deh_Actor_236", // Extra thing 86
-    "Deh_Actor_237", // Extra thing 87
-    "Deh_Actor_238", // Extra thing 88
-    "Deh_Actor_239", // Extra thing 89
-    "Deh_Actor_240", // Extra thing 90
-    "Deh_Actor_241", // Extra thing 91
-    "Deh_Actor_242", // Extra thing 92
-    "Deh_Actor_243", // Extra thing 93
-    "Deh_Actor_244", // Extra thing 94
-    "Deh_Actor_245", // Extra thing 95
-    "Deh_Actor_246", // Extra thing 96
-    "Deh_Actor_247", // Extra thing 97
-    "Deh_Actor_248", // Extra thing 98
-    "Deh_Actor_249", // Extra thing 99
-    NULL};
-
-static void FreeMap(mapentry_t *mape)
-{
-    if (mape->mapname)
-    {
-        free(mape->mapname);
-    }
-    if (mape->levelname)
-    {
-        free(mape->levelname);
-    }
-    if (mape->label)
-    {
-        free(mape->label);
-    }
-    if (mape->intertext)
-    {
-        free(mape->intertext);
-    }
-    if (mape->intertextsecret)
-    {
-        free(mape->intertextsecret);
-    }
-    if (mape->author)
-    {
-        free(mape->author);
-    }
-    mape->mapname = NULL;
-}
-
-static void ReplaceString(char **pptr, const char *newstring)
-{
-    if (*pptr != NULL)
-    {
-        free(*pptr);
-    }
-    *pptr = strdup(newstring);
-}
-
-static void UpdateMapEntry(mapentry_t *mape, mapentry_t *newe)
-{
-    if (newe->mapname)
-    {
-        ReplaceString(&mape->mapname, newe->mapname);
-    }
-    if (newe->levelname)
-    {
-        ReplaceString(&mape->levelname, newe->levelname);
-    }
-    if (newe->label)
-    {
-        ReplaceString(&mape->label, newe->label);
-    }
-    if (newe->author)
-    {
-        ReplaceString(&mape->author, newe->author);
-    }
-    if (newe->intertext)
-    {
-        ReplaceString(&mape->intertext, newe->intertext);
-    }
-    if (newe->intertextsecret)
-    {
-        ReplaceString(&mape->intertextsecret, newe->intertextsecret);
-    }
-    if (newe->levelpic[0])
-    {
-        strcpy(mape->levelpic, newe->levelpic);
-    }
-    if (newe->nextmap[0])
-    {
-        strcpy(mape->nextmap, newe->nextmap);
-    }
-    if (newe->nextsecret[0])
-    {
-        strcpy(mape->nextsecret, newe->nextsecret);
-    }
-    if (newe->music[0])
-    {
-        strcpy(mape->music, newe->music);
-    }
-    if (newe->skytexture[0])
-    {
-        strcpy(mape->skytexture, newe->skytexture);
-    }
-    if (newe->endpic[0])
-    {
-        strcpy(mape->endpic, newe->endpic);
-    }
-    if (newe->exitpic[0])
-    {
-        strcpy(mape->exitpic, newe->exitpic);
-    }
-    if (newe->enterpic[0])
-    {
-        strcpy(mape->enterpic, newe->enterpic);
-    }
-    if (newe->exitanim[0])
-    {
-        strcpy(mape->exitanim, newe->exitanim);
-    }
-    if (newe->enteranim[0])
-    {
-        strcpy(mape->enteranim, newe->enteranim);
-    }
-    if (newe->interbackdrop[0])
-    {
-        strcpy(mape->interbackdrop, newe->interbackdrop);
-    }
-    if (newe->intermusic[0])
-    {
-        strcpy(mape->intermusic, newe->intermusic);
-    }
-    if (newe->partime)
-    {
-        mape->partime = newe->partime;
-    }
-    if (newe->nointermission)
-    {
-        mape->nointermission = newe->nointermission;
-    }
-    if (newe->numbossactions)
-    {
-        mape->numbossactions = newe->numbossactions;
-        if (mape->numbossactions == -1)
-        {
-            if (mape->bossactions)
-            {
-                free(mape->bossactions);
-            }
-            mape->bossactions = NULL;
-        }
-        else
-        {
-            mape->bossactions = realloc(mape->bossactions,
-                                        sizeof(bossaction_t) * mape->numbossactions);
-            memcpy(mape->bossactions, newe->bossactions,
-                   sizeof(bossaction_t) * mape->numbossactions);
-        }
-    }
-}
-
-// -----------------------------------------------
-// Parses a set of string and concatenates them
-// Returns a pointer to the string (must be freed)
-// -----------------------------------------------
-static char *ParseMultiString(u_scanner_t *s, int error)
-{
-    char *build = NULL;
-
-    if (U_CheckToken(s, TK_Identifier))
-    {
-        if (!strcasecmp(s->string, "clear"))
-        {
-            // this was explicitly deleted to override the default.
-            return strdup("-");
-        }
-        else
-        {
-            U_Error(s, "Either 'clear' or string constant expected");
-        }
-    }
-
-    do
-    {
-        U_MustGetToken(s, TK_StringConst);
-        if (build == NULL)
-        {
-            build = strdup(s->string);
-        }
-        else
-        {
-            // plus room for one \n and one \0
-            size_t newlen = strlen(build) + strlen(s->string) + 2;
-            build = I_Realloc(build, newlen);
-            // Replace the existing text's \0 terminator with a \n
-            strcat(build, "\n");
-            // Concatenate the new line onto the existing text
-            strcat(build, s->string);
-        }
-    } while (U_CheckToken(s, ','));
-    return build;
-}
-
-// -----------------------------------------------
-// Parses a lump name. The buffer must be at least 9 characters.
-// If parsed name is longer than 8 chars, sets NULL pointer.
-//
-// returns 1 on successfully parsing an element
-//         0 on parse error in last read token
-// -----------------------------------------------
-static int ParseLumpName(u_scanner_t *s, char *buffer)
-{
-    if (!U_MustGetToken(s, TK_StringConst))
-    {
-        return 0;
-    }
-
-    if (strlen(s->string) > 8)
-    {
-        U_Error(s, "String too long. Maximum size is 8 characters.");
-        return 1; // not a parse error
-    }
-    strncpy(buffer, s->string, 8);
-    buffer[8] = 0;
-    M_StringToUpper(buffer);
-    return 1;
-}
-
-// -----------------------------------------------
-// Parses a standard property that is already known
-// These do not get stored in the property list
-// but in dedicated struct member variables.
-//
-// returns 1 on successfully parsing an element
-//         0 on parse error in last read token
-// -----------------------------------------------
-static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape)
-{
-    char *pname;
-    int status = 1; // 1 for success, 0 for parse error
-    if (!U_MustGetToken(s, TK_Identifier))
-    {
-        return 0;
-    }
-
-    pname = strdup(s->string);
-    U_MustGetToken(s, '=');
-
-    if (!strcasecmp(pname, "levelname"))
-    {
-        if (U_MustGetToken(s, TK_StringConst))
-        {
-            ReplaceString(&mape->levelname, s->string);
-        }
-    }
-    else if (!strcasecmp(pname, "label"))
-    {
-        if (U_CheckToken(s, TK_Identifier))
-        {
-            if (!strcasecmp(s->string, "clear"))
-            {
-                ReplaceString(&mape->label, "-");
-            }
-            else
-            {
-                U_Error(s, "Either 'clear' or string constant expected");
-            }
-        }
-        else if (U_MustGetToken(s, TK_StringConst))
-        {
-            ReplaceString(&mape->label, s->string);
-        }
-    }
-    else if (!strcasecmp(pname, "author"))
-    {
-        if (U_MustGetToken(s, TK_StringConst))
-        {
-            ReplaceString(&mape->author, s->string);
-        }
-    }
-    else if (!strcasecmp(pname, "episode"))
-    {
-        if (U_CheckToken(s, TK_Identifier))
-        {
-            if (!strcasecmp(s->string, "clear"))
-            {
-                M_ClearEpisodes();
-            }
-            else
-            {
-                U_Error(s, "Either 'clear' or string constant expected");
-            }
-        }
-        else
-        {
-            char lumpname[9] = {0};
-            char *alttext = NULL;
-            char *key = NULL;
-
-            ParseLumpName(s, lumpname);
-            if (U_CheckToken(s, ','))
-            {
-                if (U_MustGetToken(s, TK_StringConst))
-                {
-                    alttext = strdup(s->string);
-                }
-                if (U_CheckToken(s, ','))
-                {
-                    if (U_MustGetToken(s, TK_StringConst))
-                    {
-                        key = strdup(s->string);
-                        key[0] = M_ToLower(key[0]);
-                    }
-                }
-            }
-
-            M_AddEpisode(mape->mapname, lumpname, alttext, key);
-            mape->flags |= MapInfo_Episode;
-
-            if (alttext)
-            {
-                free(alttext);
-            }
-            if (key)
-            {
-                free(key);
-            }
-        }
-    }
-    else if (!strcasecmp(pname, "next"))
-    {
-        status = ParseLumpName(s, mape->nextmap);
-        if (!G_ValidateMapName(mape->nextmap, NULL, NULL))
-        {
-            U_Error(s, "Invalid map name %s.", mape->nextmap);
-            status = 0;
-        }
-    }
-    else if (!strcasecmp(pname, "nextsecret"))
-    {
-        status = ParseLumpName(s, mape->nextsecret);
-        level_t level = {0};
-        if (!G_ValidateMapName(mape->nextsecret, &level.episode, &level.map))
-        {
-            U_Error(s, "Invalid map name %s", mape->nextsecret);
-            status = 0;
-        }
-        array_push(secretlevels, level);
-    }
-    else if (!strcasecmp(pname, "levelpic"))
-    {
-        status = ParseLumpName(s, mape->levelpic);
-    }
-    else if (!strcasecmp(pname, "skytexture"))
-    {
-        status = ParseLumpName(s, mape->skytexture);
-    }
-    else if (!strcasecmp(pname, "music"))
-    {
-        status = ParseLumpName(s, mape->music);
-    }
-    else if (!strcasecmp(pname, "endpic"))
-    {
-        status = ParseLumpName(s, mape->endpic);
-    }
-    else if (!strcasecmp(pname, "endcast"))
-    {
-        U_MustGetToken(s, TK_BoolConst);
-        if (s->sc_boolean)
-        {
-            strcpy(mape->endpic, "$CAST");
-            mape->flags |= MapInfo_Endgame;
-        }
-        else
-        {
-            strcpy(mape->endpic, "-");
-        }
-    }
-    else if (!strcasecmp(pname, "endbunny"))
-    {
-        U_MustGetToken(s, TK_BoolConst);
-        if (s->sc_boolean)
-        {
-            strcpy(mape->endpic, "$BUNNY");
-            mape->flags |= MapInfo_Endgame;
-        }
-        else
-        {
-            strcpy(mape->endpic, "-");
-        }
-    }
-    else if (!strcasecmp(pname, "endgame"))
-    {
-        U_MustGetToken(s, TK_BoolConst);
-        if (s->sc_boolean)
-        {
-            strcpy(mape->endpic, "!");
-            mape->flags |= MapInfo_Endgame;
-        }
-        else
-        {
-            strcpy(mape->endpic, "-");
-        }
-    }
-    else if (!strcasecmp(pname, "exitpic"))
-    {
-        status = ParseLumpName(s, mape->exitpic);
-    }
-    else if (!strcasecmp(pname, "enterpic"))
-    {
-        status = ParseLumpName(s, mape->enterpic);
-    }
-    else if (!strcasecmp(pname, "exitanim"))
-    {
-        status = ParseLumpName(s, mape->exitanim);
-    }
-    else if (!strcasecmp(pname, "enteranim"))
-    {
-        status = ParseLumpName(s, mape->enteranim);
-    }
-    else if (!strcasecmp(pname, "nointermission"))
-    {
-        if (U_MustGetToken(s, TK_BoolConst))
-        {
-            mape->nointermission = s->sc_boolean;
-        }
-    }
-    else if (!strcasecmp(pname, "partime"))
-    {
-        if (U_MustGetInteger(s))
-        {
-            mape->partime = s->number;
-        }
-    }
-    else if (!strcasecmp(pname, "intertext"))
-    {
-        char *lname = ParseMultiString(s, 1);
-        if (!lname)
-        {
-            return 0;
-        }
-        if (mape->intertext != NULL)
-        {
-            free(mape->intertext);
-        }
-        mape->intertext = lname;
-    }
-    else if (!strcasecmp(pname, "intertextsecret"))
-    {
-        char *lname = ParseMultiString(s, 1);
-        if (!lname)
-        {
-            return 0;
-        }
-        if (mape->intertextsecret != NULL)
-        {
-            free(mape->intertextsecret);
-        }
-        mape->intertextsecret = lname;
-    }
-    else if (!strcasecmp(pname, "interbackdrop"))
-    {
-        status = ParseLumpName(s, mape->interbackdrop);
-    }
-    else if (!strcasecmp(pname, "intermusic"))
-    {
-        status = ParseLumpName(s, mape->intermusic);
-    }
-    else if (!strcasecmp(pname, "bossaction"))
-    {
-        if (U_MustGetToken(s, TK_Identifier))
-        {
-            if (!strcasecmp(s->string, "clear"))
-            {
-                // mark level free of boss actions
-                if (mape->bossactions)
-                {
-                    free(mape->bossactions);
-                }
-                mape->bossactions = NULL;
-                mape->numbossactions = -1;
-            }
-            else
-            {
-                int i, special, tag;
-                for (i = 0; ActorNames[i]; i++)
-                {
-                    if (!strcasecmp(s->string, ActorNames[i]))
-                    {
-                        break;
-                    }
-                }
-                if (ActorNames[i] == NULL)
-                {
-                    U_Error(s, "bossaction: unknown thing type '%s'",
-                            s->string);
-                    return 0;
-                }
-                U_MustGetToken(s, ',');
-                U_MustGetInteger(s);
-                special = s->number;
-                U_MustGetToken(s, ',');
-                U_MustGetInteger(s);
-                tag = s->number;
-                // allow no 0-tag specials here, unless a level exit.
-                if (tag != 0 || special == 11 || special == 51 || special == 52
-                    || special == 124)
-                {
-                    if (mape->numbossactions == -1)
-                    {
-                        mape->numbossactions = 1;
-                    }
-                    else
-                    {
-                        mape->numbossactions++;
-                    }
-                    mape->bossactions = realloc(mape->bossactions,
-                                                sizeof(bossaction_t) * mape->numbossactions);
-                    bossaction_t *bossaction = &mape->bossactions[mape->numbossactions - 1];
-                    bossaction->type = i;
-                    bossaction->special = special;
-                    bossaction->tag = tag;
-                }
-            }
-        }
-    }
-    // If no known property name was given, skip all comma-separated values
-    // after the = sign
-    else if (s->token == '=')
-    {
-        do
-        {
-            U_GetNextToken(s, true);
-        } while (U_CheckToken(s, ','));
-    }
-
-    free(pname);
-    return status;
-}
-
-// -----------------------------------------------
-//
-// Parses a complete map entry
-//
-// -----------------------------------------------
-
-static int ParseMapEntry(u_scanner_t *s, mapentry_t *val)
-{
-    val->mapname = NULL;
-
-    if (!U_MustGetIdentifier(s, "map"))
-    {
-        return 0;
-    }
-
-    U_MustGetToken(s, TK_Identifier);
-    if (!G_ValidateMapName(s->string, NULL, NULL))
-    {
-        U_Error(s, "Invalid map name %s", s->string);
-        return 0;
-    }
-
-    ReplaceString(&val->mapname, s->string);
-    U_MustGetToken(s, '{');
-    while (!U_CheckToken(s, '}'))
-    {
-        if (!ParseStandardProperty(s, val))
-        {
-            // If there was an error parsing, skip to next token
-            U_GetNextToken(s, true);
-        }
-    }
-    return 1;
-}
-
-// -----------------------------------------------
-//
-// Parses a complete UMAPINFO lump
-//
-// -----------------------------------------------
-
-static boolean UpdateDefaultMapEntry(mapentry_t *val, int num)
-{
-    int i;
-
-    for (i = 0; i < default_mapinfo.mapcount; ++i)
-    {
-        if (!strcmp(val->mapname, default_mapinfo.maps[i].mapname))
-        {
-            memset(&U_mapinfo.maps[num], 0, sizeof(mapentry_t));
-            UpdateMapEntry(&U_mapinfo.maps[num], &default_mapinfo.maps[i]);
-            UpdateMapEntry(&U_mapinfo.maps[num], val);
-            FreeMap(val);
-            return true;
-        }
-    }
-
-    return false;
-}
-
-void U_ParseMapDefInfo(int lumpnum)
-{
-    const char *buffer = W_CacheLumpNum(lumpnum, PU_CACHE);
-    size_t length = W_LumpLength(lumpnum);
-    u_scanner_t *s = U_ScanOpen(buffer, length, "UMAPDEF");
-
-    while (U_HasTokensLeft(s))
-    {
-        mapentry_t parsed = {0};
-        if (!ParseMapEntry(s, &parsed))
-        {
-            U_Error(s, "Skipping entry: %s", s->string);
-            continue;
-        }
-
-        default_mapinfo.mapcount++;
-        default_mapinfo.maps = realloc(default_mapinfo.maps,
-                                       sizeof(mapentry_t) * default_mapinfo.mapcount);
-        default_mapinfo.maps[default_mapinfo.mapcount - 1] = parsed;
-    }
-    U_ScanClose(s);
-}
-
-void U_ParseMapInfo(int lumpnum)
-{
-    unsigned int i;
-    const char *buffer = W_CacheLumpNum(lumpnum, PU_CACHE);
-    size_t length = W_LumpLength(lumpnum);
-    u_scanner_t *s = U_ScanOpen(buffer, length, "UMAPINFO");
-
-    while (U_HasTokensLeft(s))
-    {
-        mapentry_t parsed = {0};
-        if (!ParseMapEntry(s, &parsed))
-        {
-            U_Error(s, "Skipping entry: %s", s->string);
-            continue;
-        }
-
-        // Set default level progression here to simplify the checks elsewhere.
-        // Doing this lets us skip all normal code for this if nothing has been
-        // defined.
-        if (parsed.endpic[0] && (strcmp(parsed.endpic, "-") != 0))
-        {
-            parsed.nextmap[0] = 0;
-        }
-        else if (!parsed.nextmap[0] && !parsed.endpic[0])
-        {
-            if (!strcasecmp(parsed.mapname, "MAP30"))
-            {
-                strcpy(parsed.endpic, "$CAST");
-            }
-            else if (!strcasecmp(parsed.mapname, "E1M8"))
-            {
-                strcpy(parsed.endpic, gamemode == retail ? "CREDIT" : "HELP2");
-            }
-            else if (!strcasecmp(parsed.mapname, "E2M8"))
-            {
-                strcpy(parsed.endpic, "VICTORY2");
-            }
-            else if (!strcasecmp(parsed.mapname, "E3M8"))
-            {
-                strcpy(parsed.endpic, "$BUNNY");
-            }
-            else if (!strcasecmp(parsed.mapname, "E4M8"))
-            {
-                strcpy(parsed.endpic, "ENDPIC");
-            }
-            else
-            {
-                int ep, map;
-
-                G_ValidateMapName(parsed.mapname, &ep, &map);
-
-                strcpy(parsed.nextmap, MapName(ep, map + 1));
-            }
-        }
-
-        // Does this property already exist? If yes, replace it.
-        for (i = 0; i < U_mapinfo.mapcount; i++)
-        {
-            if (!strcmp(parsed.mapname, U_mapinfo.maps[i].mapname))
-            {
-                FreeMap(&U_mapinfo.maps[i]);
-
-                if (!UpdateDefaultMapEntry(&parsed, i))
-                {
-                    U_mapinfo.maps[i] = parsed;
-                }
-                break;
-            }
-        }
-        // Not found so create a new one.
-        if (i == U_mapinfo.mapcount)
-        {
-            U_mapinfo.mapcount++;
-            U_mapinfo.maps = realloc(U_mapinfo.maps,
-                                     sizeof(mapentry_t) * U_mapinfo.mapcount);
-
-            if (!UpdateDefaultMapEntry(&parsed, i))
-            {
-                U_mapinfo.maps[U_mapinfo.mapcount - 1] = parsed;
-            }
-        }
-    }
-    U_ScanClose(s);
-}
-
-boolean U_CheckField(char *str)
-{
-    return str && str[0] && strcmp(str, "-");
-}
-
-boolean U_IsSecretMap(int episode, int map)
-{
-    level_t *level;
-    array_foreach(level, secretlevels)
-    {
-        if (level->episode == episode && level->map == map)
-        {
-            return true;
-        }
-    }
-    return false;
-}
diff --git a/src/u_mapinfo.h b/src/u_mapinfo.h
deleted file mode 100644
index ea6d6a019..000000000
--- a/src/u_mapinfo.h
+++ /dev/null
@@ -1,87 +0,0 @@
-//-----------------------------------------------------------------------------
-//
-// Copyright 2017 Christoph Oelckers
-// Copyright 2019 Fernando Carmona Varo
-//
-// This program 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 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/
-//
-//-----------------------------------------------------------------------------
-
-#ifndef __UMAPINFO_H
-#define __UMAPINFO_H
-
-#include "doomtype.h"
-
-typedef struct
-{
-    int type;
-    int special;
-    int tag;
-} bossaction_t;
-
-typedef enum
-{
-    MapInfo_Episode    = 0x0001,
-    MapInfo_Endgame    = 0x0002,
-} mapinfo_flags_t;
-
-typedef struct mapentry_s
-{
-    char *mapname;
-    char *levelname;
-    char *label;
-    char *intertext;
-    char *intertextsecret;
-    char *author;
-    char levelpic[9];
-    char nextmap[9];
-    char nextsecret[9];
-    char music[9];
-    char skytexture[9];
-    char endpic[9];
-    char exitpic[9];
-    char enterpic[9];
-    char exitanim[9];
-    char enteranim[9];
-    char interbackdrop[9];
-    char intermusic[9];
-    int partime;
-    mapinfo_flags_t flags;
-    boolean nointermission;
-    int numbossactions;
-    bossaction_t *bossactions;
-} mapentry_t;
-
-typedef struct
-{
-    unsigned int mapcount;
-    mapentry_t *maps;
-} umapinfo_t;
-
-extern umapinfo_t U_mapinfo;
-extern umapinfo_t default_mapinfo;
-
-extern boolean EpiCustom;
-
-mapentry_t *G_LookupMapinfo(int episode, int map);
-
-boolean U_CheckField(char *str);
-
-void U_ParseMapDefInfo(int lumpnum);
-
-void U_ParseMapInfo(int lumpnum);
-
-boolean U_IsSecretMap(int episode, int map);
-
-#endif
diff --git a/src/u_scanner.c b/src/u_scanner.c
deleted file mode 100644
index 5d308484d..000000000
--- a/src/u_scanner.c
+++ /dev/null
@@ -1,909 +0,0 @@
-// Copyright (c) 2019, Fernando Carmona Varo  <ferkiwi@gmail.com>
-// Copyright (c) 2010, Braden "Blzut3" Obrzut <admin@maniacsvault.net>
-// 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 <organization> 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 <COPYRIGHT HOLDER> 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 <ctype.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "doomtype.h"
-#include "i_system.h"
-#include "m_misc.h"
-#include "u_scanner.h"
-
-static const char *U_TokenNames[TK_NumSpecialTokens] =
-{
-    "Identifier",  // case insensitive identifier, beginning with a letter and
-                   // may contain [a-z0-9_]
-    "String Constant",
-    "Integer Constant",
-    "Float Constant",
-    "Boolean Constant",
-    "Logical And",
-    "Logical Or",
-    "Equals",
-    "Not Equals",
-    "Greater Than or Equals",
-    "Less Than or Equals",
-    "Left Shift",
-    "Right Shift"
-};
-
-static void U_CheckForWhitespace(u_scanner_t *scanner);
-static void U_ExpandState(u_scanner_t *scanner);
-static void U_Unescape(char *str);
-static void U_SetString(char **ptr, const char *start, int length);
-
-u_scanner_t *U_ScanOpen(const char *data, int length, const char *name)
-{
-    u_scanner_t *s = calloc(1, sizeof(*s));
-    s->line = s->tokenLine = 1;
-    s->needNext = true;
-    s->string = NULL;
-    s->nextState.string = NULL;
-    s->name = name;
-
-    if (length == -1)
-    {
-        length = strlen(data);
-    }
-    s->length = length;
-    s->data = malloc(length);
-    memcpy(s->data, data, length);
-
-    U_CheckForWhitespace(s);
-
-    return s;
-}
-
-void U_ScanClose(u_scanner_t *s)
-{
-    if (s->nextState.string != NULL)
-    {
-        free(s->nextState.string);
-    }
-    if (s->data != NULL)
-    {
-        free(s->data);
-    }
-    free(s);
-}
-
-static void U_IncrementLine(u_scanner_t *s)
-{
-    s->line++;
-    s->lineStart = s->scanPos;
-}
-
-static void U_CheckForWhitespace(u_scanner_t *s)
-{
-    int comment = 0; // 1 = till next new line, 2 = till end block
-    while (s->scanPos < s->length)
-    {
-        char cur = s->data[s->scanPos];
-        char next = (s->scanPos + 1 < s->length) ? s->data[s->scanPos + 1] : 0;
-        if (comment == 2)
-        {
-            if (cur != '*' || next != '/')
-            {
-                if (cur == '\n' || cur == '\r')
-                {
-                    s->scanPos++;
-
-                    // Do a quick check for Windows style new line
-                    if (cur == '\r' && next == '\n')
-                    {
-                        s->scanPos++;
-                    }
-                    U_IncrementLine(s);
-                }
-                else
-                {
-                    s->scanPos++;
-                }
-            }
-            else
-            {
-                comment = 0;
-                s->scanPos += 2;
-            }
-            continue;
-        }
-
-        if (cur == ' ' || cur == '\t' || cur == 0)
-        {
-            s->scanPos++;
-        }
-        else if (cur == '\n' || cur == '\r')
-        {
-            s->scanPos++;
-            if (comment == 1)
-            {
-                comment = 0;
-            }
-
-            // Do a quick check for Windows style new line
-            if (cur == '\r' && next == '\n')
-            {
-                s->scanPos++;
-            }
-            U_IncrementLine(s);
-        }
-        else if (cur == '/' && comment == 0)
-        {
-            switch (next)
-            {
-                case '/':
-                    comment = 1;
-                    break;
-                case '*':
-                    comment = 2;
-                    break;
-                default:
-                    return;
-            }
-            s->scanPos += 2;
-        }
-        else
-        {
-            if (comment == 0)
-            {
-                return;
-            }
-            else
-            {
-                s->scanPos++;
-            }
-        }
-    }
-}
-
-boolean U_CheckToken(u_scanner_t *s, char token)
-{
-    if (s->needNext)
-    {
-        if (!U_GetNextToken(s, false))
-        {
-            return false;
-        }
-    }
-
-    // An int can also be a float.
-    if ((s->nextState).token == token
-        || ((s->nextState).token == TK_IntConst && s->token == TK_FloatConst))
-    {
-        s->needNext = true;
-        U_ExpandState(s);
-        return true;
-    }
-    s->needNext = false;
-    return false;
-}
-
-static void U_ExpandState(u_scanner_t *s)
-{
-    s->logicalPosition = s->scanPos;
-    U_CheckForWhitespace(s);
-
-    U_SetString(&(s->string), s->nextState.string, -1);
-    s->number = s->nextState.number;
-    s->decimal = s->nextState.decimal;
-    s->sc_boolean = s->nextState.sc_boolean;
-    s->token = s->nextState.token;
-    s->tokenLine = s->nextState.tokenLine;
-    s->tokenLinePosition = s->nextState.tokenLinePosition;
-}
-
- // NOLINTBEGIN(clang-analyzer-unix.Malloc)
-static void U_SaveState(u_scanner_t *s, u_scanner_t savedstate)
-{
-    // This saves the entire parser state except for the data pointer.
-    if (savedstate.string != NULL)
-    {
-        free(savedstate.string);
-    }
-    if (savedstate.nextState.string != NULL)
-    {
-        free(savedstate.nextState.string);
-    }
-
-    memcpy(&savedstate, s, sizeof(*s));
-    savedstate.string = strdup(s->string);
-    savedstate.nextState.string = strdup(s->nextState.string);
-    savedstate.data = NULL;
-}
-// NOLINTEND(clang-analyzer-unix.Malloc)
-
-static void U_RestoreState(u_scanner_t *s, u_scanner_t savedstate)
-{
-    if (savedstate.data == NULL)
-    {
-        char *saveddata = s->data;
-        U_SaveState(&savedstate, *s);
-        s->data = saveddata;
-    }
-}
-
-boolean U_GetString(u_scanner_t *s)
-{
-    unsigned int start;
-    char cur;
-    u_parserstate_t *nextState = &s->nextState;
-
-    if (!s->needNext)
-    {
-        s->needNext = true;
-        U_ExpandState(s);
-        return true;
-    }
-
-    nextState->tokenLine = s->line;
-    nextState->tokenLinePosition = s->scanPos - s->lineStart;
-    nextState->token = TK_NoToken;
-    if (s->scanPos >= s->length)
-    {
-        U_ExpandState(s);
-        return false;
-    }
-
-    start = s->scanPos;
-    s->scanPos++;
-
-    while (s->scanPos < s->length)
-    {
-        cur = s->data[s->scanPos];
-
-        if (cur == ' ' || cur == '\t' || cur == '\n' || cur == '\r' || cur == 0)
-        {
-            break;
-        }
-        else
-        {
-            s->scanPos++;
-        }
-    }
-
-    U_SetString(&(nextState->string), s->data + start, s->scanPos - start);
-    U_ExpandState(s);
-    return true;
-}
-
-boolean U_GetNextToken(u_scanner_t *s, boolean expandState)
-{
-    unsigned int start;
-    unsigned int end;
-    char cur;
-    int integerBase = 10;
-    boolean floatHasDecimal = false;
-    boolean floatHasExponent = false;
-    // Strings are the only things that can have 0 length tokens.
-    boolean stringFinished = false;
-    u_parserstate_t *nextState = &s->nextState;
-
-    if (!s->needNext)
-    {
-        s->needNext = true;
-        if (expandState)
-        {
-            U_ExpandState(s);
-        }
-        return true;
-    }
-
-    nextState->tokenLine = s->line;
-    nextState->tokenLinePosition = s->scanPos - s->lineStart;
-    nextState->token = TK_NoToken;
-    if (s->scanPos >= s->length)
-    {
-        if (expandState)
-        {
-            U_ExpandState(s);
-        }
-        return false;
-    }
-
-    start = s->scanPos;
-    end = s->scanPos;
-    cur = s->data[s->scanPos++];
-
-    // Determine by first character
-    if (cur == '_' || (cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z'))
-    {
-        nextState->token = TK_Identifier;
-    }
-    else if (cur >= '0' && cur <= '9')
-    {
-        if (cur == '0')
-        {
-            integerBase = 8;
-        }
-        nextState->token = TK_IntConst;
-    }
-    else if (cur == '.')
-    {
-        floatHasDecimal = true;
-        nextState->token = TK_FloatConst;
-    }
-    else if (cur == '"')
-    {
-        end = ++start; // Move the start up one character so we don't have to
-                       // trim it later.
-        nextState->token = TK_StringConst;
-    }
-    else
-    {
-        end = s->scanPos;
-        nextState->token = cur;
-
-        // Now check for operator tokens
-        if (s->scanPos < s->length)
-        {
-            char next = s->data[s->scanPos];
-            if (cur == '&' && next == '&')
-            {
-                nextState->token = TK_AndAnd;
-            }
-            else if (cur == '|' && next == '|')
-            {
-                nextState->token = TK_OrOr;
-            }
-            else if (cur == '<' && next == '<')
-            {
-                nextState->token = TK_ShiftLeft;
-            }
-            else if (cur == '>' && next == '>')
-            {
-                nextState->token = TK_ShiftRight;
-            }
-            // else if(cur == '#' && next == '#')
-            //   nextState.token = TK_MacroConcat;
-            else if (next == '=')
-            {
-                switch (cur)
-                {
-                    case '=':
-                        nextState->token = TK_EqEq;
-                        break;
-                    case '!':
-                        nextState->token = TK_NotEq;
-                        break;
-                    case '>':
-                        nextState->token = TK_GtrEq;
-                        break;
-                    case '<':
-                        nextState->token = TK_LessEq;
-                        break;
-                    default:
-                        break;
-                }
-            }
-            if (nextState->token != cur)
-            {
-                s->scanPos++;
-                end = s->scanPos;
-            }
-        }
-    }
-
-    if (start == end)
-    {
-        while (s->scanPos < s->length)
-        {
-            cur = s->data[s->scanPos];
-            switch (nextState->token)
-            {
-                default:
-                    break;
-                case TK_Identifier:
-                    if (cur != '_' && (cur < 'A' || cur > 'Z')
-                        && (cur < 'a' || cur > 'z') && (cur < '0' || cur > '9'))
-                    {
-                        end = s->scanPos;
-                    }
-                    break;
-                case TK_IntConst:
-                    if (cur == '.' || (s->scanPos - 1 != start && cur == 'e'))
-                    {
-                        nextState->token = TK_FloatConst;
-                    }
-                    else if ((cur == 'x' || cur == 'X')
-                             && s->scanPos - 1 == start)
-                    {
-                        integerBase = 16;
-                        break;
-                    }
-                    else
-                    {
-                        switch (integerBase)
-                        {
-                            default:
-                                if (cur < '0' || cur > '9')
-                                {
-                                    end = s->scanPos;
-                                }
-                                break;
-                            case 8:
-                                if (cur < '0' || cur > '7')
-                                {
-                                    end = s->scanPos;
-                                }
-                                break;
-                            case 16:
-                                if ((cur < '0' || cur > '9')
-                                    && (cur < 'A' || cur > 'F')
-                                    && (cur < 'a' || cur > 'f'))
-                                {
-                                    end = s->scanPos;
-                                }
-                                break;
-                        }
-                        break;
-                    }
-                case TK_FloatConst:
-                    if (cur < '0' || cur > '9')
-                    {
-                        if (!floatHasDecimal && cur == '.')
-                        {
-                            floatHasDecimal = true;
-                            break;
-                        }
-                        else if (!floatHasExponent && cur == 'e')
-                        {
-                            floatHasDecimal = true;
-                            floatHasExponent = true;
-                            if (s->scanPos + 1 < s->length)
-                            {
-                                char next = s->data[s->scanPos + 1];
-                                if ((next < '0' || next > '9') && next != '+'
-                                    && next != '-')
-                                {
-                                    end = s->scanPos;
-                                }
-                                else
-                                {
-                                    s->scanPos++;
-                                }
-                            }
-                            break;
-                        }
-                        end = s->scanPos;
-                    }
-                    break;
-                case TK_StringConst:
-                    if (cur == '"')
-                    {
-                        stringFinished = true;
-                        end = s->scanPos;
-                        s->scanPos++;
-                    }
-                    else if (cur == '\\')
-                    {
-                        s->scanPos++; // Will add two since the loop
-                                      // automatically adds one
-                    }
-                    break;
-            }
-            if (start == end && !stringFinished)
-            {
-                s->scanPos++;
-            }
-            else
-            {
-                break;
-            }
-        }
-        // If we reached end of input while reading, set it as the end of token
-        if (s->scanPos == s->length && start == end)
-        {
-            end = s->length;
-        }
-    }
-
-    if (end - start > 0 || stringFinished)
-    {
-        U_SetString(&(nextState->string), s->data + start, end - start);
-        if (nextState->token == TK_FloatConst)
-        {
-            nextState->decimal = atof(nextState->string);
-            nextState->number = (int)(nextState->decimal);
-            nextState->sc_boolean = (nextState->number != 0);
-        }
-        else if (nextState->token == TK_IntConst)
-        {
-            nextState->number = strtol(nextState->string, NULL, integerBase);
-            nextState->decimal = nextState->number;
-            nextState->sc_boolean = (nextState->number != 0);
-        }
-        else if (nextState->token == TK_Identifier)
-        {
-            // Identifiers should be case insensitive.
-            char *p = nextState->string;
-            while (*p)
-            {
-                *p = M_ToLower(*p);
-                p++;
-            }
-            // Check for a boolean constant.
-            if (strcmp(nextState->string, "true") == 0)
-            {
-                nextState->token = TK_BoolConst;
-                nextState->sc_boolean = true;
-            }
-            else if (strcmp(nextState->string, "false") == 0)
-            {
-                nextState->token = TK_BoolConst;
-                nextState->sc_boolean = false;
-            }
-        }
-        else if (nextState->token == TK_StringConst)
-        {
-            U_Unescape(nextState->string);
-        }
-        if (expandState)
-        {
-            U_ExpandState(s);
-        }
-        return true;
-    }
-    nextState->token = TK_NoToken;
-    if (expandState)
-    {
-        U_ExpandState(s);
-    }
-    return false;
-}
-
-// Skips all Tokens in current line and parses the first token on the next
-// line.
-boolean U_GetNextLineToken(u_scanner_t *s)
-{
-    unsigned int line = s->line;
-    boolean retval = false;
-
-    do
-    {
-        retval = U_GetNextToken(s, true);
-    } while (retval && s->line == line);
-
-    return retval;
-}
-
-void U_ErrorToken(u_scanner_t *s, int token)
-{
-    if (token < TK_NumSpecialTokens && s->token >= TK_Identifier
-        && s->token < TK_NumSpecialTokens)
-    {
-        U_Error(s, "Expected %s but got %s '%s' instead.", U_TokenNames[token],
-                U_TokenNames[(int)s->token], s->string);
-    }
-    else if (token < TK_NumSpecialTokens && s->token >= TK_NumSpecialTokens)
-    {
-        U_Error(s, "Expected %s but got '%c' instead.", U_TokenNames[token],
-                s->token);
-    }
-    else if (token < TK_NumSpecialTokens && s->token == TK_NoToken)
-    {
-        U_Error(s, "Expected %s", U_TokenNames[token]);
-    }
-    else if (token >= TK_NumSpecialTokens && s->token >= TK_Identifier
-             && s->token < TK_NumSpecialTokens)
-    {
-        U_Error(s, "Expected '%c' but got %s '%s' instead.", token,
-                U_TokenNames[(int)s->token], s->string);
-    }
-    else
-    {
-        U_Error(s, "Expected '%c' but got '%c' instead.", token, s->token);
-    }
-}
-
-void U_ErrorString(u_scanner_t *s, const char *mustget)
-{
-    if (s->token < TK_NumSpecialTokens)
-    {
-        U_Error(s, "Expected '%s' but got %s '%s' instead.", mustget,
-                U_TokenNames[(int)s->token], s->string);
-    }
-    else
-    {
-        U_Error(s, "Expected '%s' but got '%c' instead.", mustget, s->token);
-    }
-}
-
-void PRINTF_ATTR(2, 0) U_Error(u_scanner_t *s, const char *msg, ...)
-{
-    char buffer[1024];
-    va_list ap;
-    va_start(ap, msg);
-    M_vsnprintf(buffer, 1024, msg, ap);
-    va_end(ap);
-    I_Error("%s:%d:%d:%s", s->name, s->tokenLine, s->tokenLinePosition, buffer);
-}
-
-boolean U_MustGetToken(u_scanner_t *s, char token)
-{
-    if (!U_CheckToken(s, token))
-    {
-        U_ExpandState(s);
-        U_ErrorToken(s, token);
-        return false;
-    }
-    return true;
-}
-
-boolean U_MustGetIdentifier(u_scanner_t *s, const char *ident)
-{
-    if (!U_CheckToken(s, TK_Identifier) || strcasecmp(s->string, ident))
-    {
-        U_ErrorString(s, ident);
-        return false;
-    }
-    return true;
-}
-
-void U_Unget(u_scanner_t *s)
-{
-    s->needNext = false;
-}
-
-// Convenience helpers that parse an entire number including a leading minus or
-// plus sign
-static boolean U_ScanInteger(u_scanner_t *s)
-{
-    boolean neg = false;
-    if (!U_GetNextToken(s, true))
-    {
-        return false;
-    }
-    if (s->token == '-')
-    {
-        if (!U_GetNextToken(s, true))
-        {
-            return false;
-        }
-        neg = true;
-    }
-    else if (s->token == '+')
-    {
-        if (!U_GetNextToken(s, true))
-        {
-            return false;
-        }
-    }
-    if (s->token != TK_IntConst)
-    {
-        return false;
-    }
-    if (neg)
-    {
-        s->number = -(s->number);
-        s->decimal = -(s->decimal);
-    }
-    return true;
-}
-
-static boolean U_ScanFloat(u_scanner_t *s)
-{
-    boolean neg = false;
-    if (!U_GetNextToken(s, true))
-    {
-        return false;
-    }
-    if (s->token == '-')
-    {
-        if (!U_GetNextToken(s, true))
-        {
-            return false;
-        }
-        neg = true;
-    }
-    else if (s->token == '+')
-    {
-        if (!U_GetNextToken(s, true))
-        {
-            return false;
-        }
-    }
-    if (s->token != TK_IntConst && s->token != TK_FloatConst)
-    {
-        return false;
-    }
-    if (neg)
-    {
-        s->number = -(s->number);
-        s->decimal = -(s->decimal);
-    }
-    return true;
-}
-
-boolean U_CheckInteger(u_scanner_t *s)
-{
-    boolean res;
-    u_scanner_t savedstate = {0};
-    U_SaveState(s, savedstate);
-    res = U_ScanInteger(s);
-    if (!res)
-    {
-        U_RestoreState(s, savedstate);
-    }
-    return res;
-}
-
-boolean U_CheckFloat(u_scanner_t *s)
-{
-    boolean res;
-    u_scanner_t savedstate = {0};
-    U_SaveState(s, savedstate);
-    res = U_ScanFloat(s);
-    if (!res)
-    {
-        U_RestoreState(s, savedstate);
-    }
-    return res;
-}
-
-boolean U_MustGetInteger(u_scanner_t *s)
-{
-    if (!U_ScanInteger(s))
-    {
-        U_ErrorToken(s, TK_IntConst);
-        return false;
-    }
-    return true;
-}
-
-boolean U_MustGetFloat(u_scanner_t *s)
-{
-    if (!U_ScanFloat(s))
-    {
-        U_ErrorToken(s, TK_FloatConst);
-        return false;
-    }
-    return true;
-}
-
-boolean U_HasTokensLeft(u_scanner_t *s)
-{
-    return (s->scanPos < s->length);
-}
-
-// This is taken from ZDoom's strbin function which can do a lot more than just
-// unescaping backslashes and quotation marks.
-void U_Unescape(char *str)
-{
-    char *p = str, c;
-    int i;
-
-    while ((c = *p++))
-    {
-        if (c != '\\')
-        {
-            *str++ = c;
-        }
-        else
-        {
-            switch (*p)
-            {
-                case 'a':
-                    *str++ = '\a';
-                    break;
-                case 'b':
-                    *str++ = '\b';
-                    break;
-                case 'f':
-                    *str++ = '\f';
-                    break;
-                case 'n':
-                    *str++ = '\n';
-                    break;
-                case 't':
-                    *str++ = '\t';
-                    break;
-                case 'r':
-                    *str++ = '\r';
-                    break;
-                case 'v':
-                    *str++ = '\v';
-                    break;
-                case '?':
-                    *str++ = '\?';
-                    break;
-                case '\n':
-                    break;
-                case 'x':
-                case 'X':
-                    c = 0;
-                    for (i = 0; i < 2; i++)
-                    {
-                        p++;
-                        if (*p >= '0' && *p <= '9')
-                        {
-                            c = (c << 4) + *p - '0';
-                        }
-                        else if (*p >= 'a' && *p <= 'f')
-                        {
-                            c = (c << 4) + 10 + *p - 'a';
-                        }
-                        else if (*p >= 'A' && *p <= 'F')
-                        {
-                            c = (c << 4) + 10 + *p - 'A';
-                        }
-                        else
-                        {
-                            p--;
-                            break;
-                        }
-                    }
-                    *str++ = c;
-                    break;
-                case '0':
-                case '1':
-                case '2':
-                case '3':
-                case '4':
-                case '5':
-                case '6':
-                case '7':
-                    c = *p - '0';
-                    for (i = 0; i < 2; i++)
-                    {
-                        p++;
-                        if (*p >= '0' && *p <= '7')
-                        {
-                            c = (c << 3) + *p - '0';
-                        }
-                        else
-                        {
-                            p--;
-                            break;
-                        }
-                    }
-                    *str++ = c;
-                    break;
-                default:
-                    *str++ = *p;
-                    break;
-            }
-            p++;
-        }
-    }
-    *str = 0;
-}
-
-static void U_SetString(char **ptr, const char *start, int length)
-{
-    if (length == -1)
-    {
-        length = strlen(start);
-    }
-    if (*ptr != NULL)
-    {
-        free(*ptr);
-    }
-    *ptr = (char *)malloc(length + 1);
-    memcpy(*ptr, start, length);
-    (*ptr)[length] = '\0';
-}
diff --git a/src/u_scanner.h b/src/u_scanner.h
deleted file mode 100644
index bdbef6247..000000000
--- a/src/u_scanner.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2010, Braden "Blzut3" Obrzut <admin@maniacsvault.net>
-// Copyright (c) 2019, Fernando Carmona Varo  <ferkiwi@gmail.com>
-// 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 <organization> 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 <COPYRIGHT HOLDER> 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 __U_SCANNER_H__
-#define __U_SCANNER_H__
-
-#include "doomtype.h"
-
-enum
-{
-    TK_Identifier,  // Ex: SomeIdentifier
-    TK_StringConst, // Ex: "Some String"
-    TK_IntConst,    // Ex: 27
-    TK_FloatConst,  // Ex: 1.5
-    TK_BoolConst,   // Ex: true
-    TK_AndAnd,      // &&
-    TK_OrOr,        // ||
-    TK_EqEq,        // ==
-    TK_NotEq,       // !=
-    TK_GtrEq,       // >=
-    TK_LessEq,      // <=
-    TK_ShiftLeft,   // <<
-    TK_ShiftRight,  // >>
-
-    TK_NumSpecialTokens,
-
-    TK_NoToken = -1
-};
-
-typedef struct
-{
-    char *string;
-    int number;
-    double decimal;
-    boolean sc_boolean;
-    char token;
-    unsigned int tokenLine;
-    unsigned int tokenLinePosition;
-} u_parserstate_t;
-
-typedef struct
-{
-    const char *name;
-
-    u_parserstate_t nextState;
-
-    char *data;
-    unsigned int length;
-
-    unsigned int line;
-    unsigned int lineStart;
-    unsigned int logicalPosition;
-    unsigned int tokenLine;
-    unsigned int tokenLinePosition;
-    unsigned int scanPos;
-
-    boolean needNext; // If checkToken returns false this will be false.
-
-    char *string;
-    int number;
-    double decimal;
-    boolean sc_boolean;
-    char token;
-
-} u_scanner_t;
-
-u_scanner_t *U_ScanOpen(const char *data, int length, const char *name);
-void U_ScanClose(u_scanner_t *s);
-boolean U_GetNextToken(u_scanner_t *s, boolean expandState);
-boolean U_GetNextLineToken(u_scanner_t *s);
-boolean U_HasTokensLeft(u_scanner_t *s);
-boolean U_MustGetToken(u_scanner_t *s, char token);
-boolean U_MustGetIdentifier(u_scanner_t *s, const char *ident);
-boolean U_MustGetInteger(u_scanner_t *s);
-boolean U_MustGetFloat(u_scanner_t *s);
-boolean U_CheckToken(u_scanner_t *s, char token);
-boolean U_CheckInteger(u_scanner_t *s);
-boolean U_CheckFloat(u_scanner_t *s);
-void U_Unget(u_scanner_t *s);
-boolean U_GetString(u_scanner_t *s);
-
-void PRINTF_ATTR(2, 0) U_Error(u_scanner_t *s, const char *msg, ...);
-void U_ErrorToken(u_scanner_t *s, int token);
-void U_ErrorString(u_scanner_t *s, const char *mustget);
-
-#endif /* __U_SCANNER_H__ */
diff --git a/src/v_video.c b/src/v_video.c
index a6b499300..bee247164 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -480,7 +480,7 @@ static void DrawPatchInternal(int x, int y, patch_t *patch, boolean flipped)
     }
     else // too far off-screen
     {
-        x1 = (x1 * video.xscale) >> FRACBITS;
+        x1 = -(DIV_ROUND_FLOOR(video.width * (-x1 - 1), video.unscaledw));
     }
 
     if (x2 < video.unscaledw)
diff --git a/src/w_wad.h b/src/w_wad.h
index 0b78632b0..1a55781ed 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -65,11 +65,13 @@ typedef enum
   ns_hires // [Woof!] namespace to avoid conflicts with high-resolution textures
 } namespace_t;
 
+typedef struct archive_s archive_t; 
+
 typedef struct
 {
     union
     {
-        void *zip;
+        archive_t *archive;
         const char *base_path;
         int descriptor;
     } p1;
diff --git a/src/w_zip.c b/src/w_zip.c
index ba415af23..2befb39e4 100644
--- a/src/w_zip.c
+++ b/src/w_zip.c
@@ -11,6 +11,8 @@
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 // GNU General Public License for more details.
 
+#include <stdlib.h>
+
 #include "doomtype.h"
 #include "i_printf.h"
 #include "m_array.h"
@@ -21,6 +23,20 @@
 
 #include "miniz.h"
 
+typedef struct
+{
+    int index;
+    const char *filename;
+} record_t;
+
+struct archive_s
+{
+    mz_zip_archive *zip;
+    record_t *directory;
+};
+
+static archive_t *archives;
+
 static void ConvertSlashes(char *path)
 {
     for (char *p = path; *p; ++p)
@@ -37,7 +53,7 @@ static void AddWadInMem(w_handle_t handle, const char *name, int index,
 {
     I_Printf(VB_INFO, " - adding %s", name);
 
-    mz_zip_archive *zip = handle.p1.zip;
+    mz_zip_archive *zip = handle.p1.archive->zip;
 
     byte *data = malloc(data_size);
 
@@ -108,7 +124,9 @@ static void AddWadInMem(w_handle_t handle, const char *name, int index,
 static boolean W_ZIP_AddDir(w_handle_t handle, const char *path,
                             const char *start_marker, const char *end_marker)
 {
-    mz_zip_archive *zip = handle.p1.zip;
+    archive_t *archive = handle.p1.archive;
+
+    mz_zip_archive *zip = archive->zip;
 
     boolean is_root = (path[0] == '.');
 
@@ -117,32 +135,34 @@ static boolean W_ZIP_AddDir(w_handle_t handle, const char *path,
 
     int startlump = numlumps;
 
-    for (int index = 0 ; index < mz_zip_reader_get_num_files(zip); ++index)
+    for (int i = 0; i < mz_zip_reader_get_num_files(zip); ++i)
     {
+        const record_t record = archive->directory[i];
+
         mz_zip_archive_file_stat stat;
-        mz_zip_reader_file_stat(zip, index, &stat);
+        mz_zip_reader_file_stat(zip, record.index, &stat);
 
         if (stat.m_is_directory)
         {
             continue;
         }
 
-        if (is_root && M_StringCaseEndsWith(stat.m_filename, ".wad"))
+        char *name = M_DirName(record.filename);
+        if (strcasecmp(name, dir))
         {
-            AddWadInMem(handle, M_BaseName(stat.m_filename), index,
-                        stat.m_uncomp_size);
+            free(name);
             continue;
         }
+        free(name);
 
-        char *name = M_DirName(stat.m_filename);
-        if (strcasecmp(name, dir))
+        if (is_root && M_StringCaseEndsWith(record.filename, ".wad"))
         {
-            free(name);
+            AddWadInMem(handle, M_BaseName(record.filename), record.index,
+                        stat.m_uncomp_size);
             continue;
         }
-        free(name);
 
-        if (W_SkipFile(stat.m_filename))
+        if (W_SkipFile(record.filename))
         {
             continue;
         }
@@ -158,7 +178,8 @@ static boolean W_ZIP_AddDir(w_handle_t handle, const char *path,
         item.size = stat.m_uncomp_size;
 
         item.module = &w_zip_module;
-        w_handle_t local_handle = {.p1.zip = zip, .p2.index = index,
+        w_handle_t local_handle = {.p1.archive = archive,
+                                   .p2.index = record.index,
                                    .priority = handle.priority};
         item.handle = local_handle;
 
@@ -175,29 +196,49 @@ static boolean W_ZIP_AddDir(w_handle_t handle, const char *path,
     return true;
 }
 
-static mz_zip_archive **zips = NULL;
+static int compare_records(const void *a, const void *b)
+{
+    const record_t *arg1 = a;
+    const record_t *arg2 = b;
+
+    return strcasecmp(arg1->filename, arg2->filename);
+}
 
 static w_type_t W_ZIP_Open(const char *path, w_handle_t *handle)
 {
     mz_zip_archive *zip = calloc(1, sizeof(*zip));
 
-    if (!mz_zip_reader_init_file(zip, path, 0))
+    if (!mz_zip_reader_init_file(zip, path, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
     {
         free(zip);
         return W_NONE;
     }
 
+    const int num_files = mz_zip_reader_get_num_files(zip);
+    record_t *directory = malloc(num_files * sizeof(*directory));
+    for (int i = 0; i < num_files; ++i)
+    {
+        directory[i].index = i;
+        int size = mz_zip_reader_get_filename(zip, i, NULL, 0);
+        char *filename = malloc(size);
+        mz_zip_reader_get_filename(zip, i, filename, size);
+        directory[i].filename = filename;
+    }
+    qsort(directory, num_files, sizeof(*directory), compare_records);
+
     I_Printf(VB_INFO, " adding %s", path);
 
-    handle->p1.zip = zip;
-    array_push(zips, zip);
+    archive_t archive = {zip, directory};
+    array_push(archives, archive);
+    handle->p1.archive = array_end(archives) - 1;
+
     return W_DIR;
 }
 
 static void W_ZIP_Read(w_handle_t handle, void *dest, int size)
 {
     boolean result = mz_zip_reader_extract_to_mem(
-        handle.p1.zip, handle.p2.index, dest, size, 0);
+        handle.p1.archive->zip, handle.p2.index, dest, size, 0);
 
     if (!result)
     {
@@ -207,9 +248,9 @@ static void W_ZIP_Read(w_handle_t handle, void *dest, int size)
 
 static void W_ZIP_Close(void)
 {
-    for (int i = 0; i < array_size(zips); ++i)
+    for (int i = 0; i < array_size(archives); ++i)
     {
-        mz_zip_reader_end(zips[i]);
+        mz_zip_reader_end(archives[i].zip);
     }
 }
 
diff --git a/src/wi_stuff.c b/src/wi_stuff.c
index 6d9c3eb1c..e23fb03de 100644
--- a/src/wi_stuff.c
+++ b/src/wi_stuff.c
@@ -26,6 +26,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "doomtype.h"
+#include "g_umapinfo.h"
 #include "g_game.h"
 #include "i_printf.h"
 #include "m_misc.h"
@@ -37,7 +38,6 @@
 #include "st_sbardef.h"
 #include "st_stuff.h"
 #include "sounds.h"
-#include "u_mapinfo.h"
 #include "v_fmt.h"
 #include "v_video.h"
 #include "w_wad.h"
@@ -485,7 +485,7 @@ static boolean CheckConditions(interlevelcond_t *conditions,
                 break;
 
             case AnimCondition_MapNotSecret:
-                conditionsmet &= !U_IsSecretMap(episode, map);
+                conditionsmet &= !G_IsSecretMap(episode, map);
                 break;
 
             case AnimCondition_SecretVisited:
@@ -717,26 +717,38 @@ static boolean SetupMusic(boolean enteringcondition)
 //
 void WI_slamBackground(void)
 {
-  char  name[32];
+    // [Nugget] Alt. intermission background
+    if (alt_interpic_on)
+    {
+        R_RenderPlayerView(&players[displayplayer]);
+        V_ShadeScreen(17);
+        return;
+    }
 
-  // [Nugget] Alt. intermission background
-  if (alt_interpic_on) {
-    R_RenderPlayerView(&players[displayplayer]);
-    V_ShadeScreen(17);
-    return;
-  }
+    const char *name;
 
-  if (state != StatCount && enterpic)
-    strcpy(name, enterpic);
-  else if (exitpic)
-    strcpy(name, exitpic);
-  // with UMAPINFO it is possible that wbs->epsd > 3
-  else if (gamemode == commercial || wbs->epsd >= 3)
-    strcpy(name, "INTERPIC");
-  else
-    M_snprintf(name, sizeof(name), "WIMAP%d", wbs->epsd);
+    char lump[9] = {0};
+
+    if (state != StatCount && enterpic)
+    {
+        name = enterpic;
+    }
+    else if (exitpic)
+    {
+        name = exitpic;
+    }
+    // with UMAPINFO it is possible that wbs->epsd > 3
+    else if (gamemode == commercial || wbs->epsd >= 3)
+    {
+        name = "INTERPIC";
+    }
+    else
+    {
+        M_snprintf(lump, sizeof(lump), "WIMAP%d", wbs->epsd);
+        name = lump;
+    }
 
-  V_DrawPatchFullScreen(V_CachePatchName(name, PU_CACHE));
+    V_DrawPatchFullScreen(V_CachePatchName(name, PU_CACHE));
 }
 
 // ====================================================================
@@ -766,50 +778,45 @@ static void WI_DrawString(int y, const char* str)
 //
 static void WI_drawLF(void)
 {
-  int y = WI_TITLEY;
-
-  // The level defines a new name but no texture for the name.
-  if (wbs->lastmapinfo && wbs->lastmapinfo->levelname && wbs->lastmapinfo->levelpic[0] == 0)
-  {
-    WI_DrawString(y, wbs->lastmapinfo->levelname);
+    int y = WI_TITLEY;
 
-    y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+    const mapentry_t *mapinfo = wbs->lastmapinfo;
 
-    if (wbs->lastmapinfo->author)
+    // The level defines a new name but no texture for the name.
+    if (mapinfo && mapinfo->levelname && !mapinfo->levelpic[0])
     {
-      WI_DrawString(y, wbs->lastmapinfo->author);
+        WI_DrawString(y, mapinfo->levelname);
+
+        y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+
+        if (mapinfo->author)
+        {
+            WI_DrawString(y, mapinfo->author);
 
-      y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+            y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+        }
     }
-  }
-  else if (wbs->lastmapinfo && wbs->lastmapinfo->levelpic[0])
-  {
-    patch_t* lpic = V_CachePatchName(wbs->lastmapinfo->levelpic, PU_CACHE);
+    else if (mapinfo && mapinfo->levelpic[0])
+    {
+        patch_t *patch = V_CachePatchName(mapinfo->levelpic, PU_CACHE);
 
-     // [Nugget] HUD/menu shadows
-    V_DrawPatchSH((SCREENWIDTH - SHORT(lpic->width))/2,
-                  y, lpic);
+        V_DrawPatchSH((SCREENWIDTH - SHORT(patch->width)) / 2, y, patch);
 
-    y += (5 * SHORT(lpic->height)) / 4;
-  }
-  else
-  // [FG] prevent crashes for levels without name graphics
-  if (wbs->last >= 0 && wbs->last < num_lnames && lnames[wbs->last] != NULL )
-  {
-  // draw <LevelName> 
-  // [Nugget] HUD/menu shadows
-  V_DrawPatchSH((SCREENWIDTH - SHORT(lnames[wbs->last]->width))/2,
-                y, lnames[wbs->last]);
+        y += (5 * SHORT(patch->height)) / 4;
+    }
+    // [FG] prevent crashes for levels without name graphics
+    else if (wbs->last >= 0 && wbs->last < num_lnames && lnames[wbs->last])
+    {
+        // draw <LevelName>
+        V_DrawPatchSH((SCREENWIDTH - SHORT(lnames[wbs->last]->width)) / 2, y,
+                    lnames[wbs->last]);
 
-  // draw "Finished!"
-  y += (5*SHORT(lnames[wbs->last]->height))/4;
-  }
- 
-  // [Nugget] HUD/menu shadows
-  V_DrawPatchSH((SCREENWIDTH - SHORT(finished->width))/2,
-                y, finished);
-}
+        // draw "Finished!"
+        y += (5 * SHORT(lnames[wbs->last]->height)) / 4;
+    }
 
+    V_DrawPatchSH((SCREENWIDTH - SHORT(finished->width)) / 2, y, finished);
+}
 
 // ====================================================================
 // WI_drawEL
@@ -819,52 +826,53 @@ static void WI_drawLF(void)
 //
 static void WI_drawEL(void)
 {
-  int y = WI_TITLEY;
-
-  // draw "Entering"
-  // [Nugget] HUD/menu shadows
-  V_DrawPatchSH((SCREENWIDTH - SHORT(entering->width))/2,
-                y, entering);
+    int y = WI_TITLEY;
 
-  // The level defines a new name but no texture for the name
-  if (wbs->nextmapinfo && wbs->nextmapinfo->levelname && wbs->nextmapinfo->levelpic[0] == 0)
-  {
-    y += (5 * SHORT(entering->height)) / 4;
+    // draw "Entering"
+    V_DrawPatchSH((SCREENWIDTH - SHORT(entering->width)) / 2, y, entering);
 
-    WI_DrawString(y, wbs->nextmapinfo->levelname);
+    const mapentry_t *mapinfo = wbs->nextmapinfo;
 
-    if (wbs->nextmapinfo->author)
+    // The level defines a new name but no texture for the name
+    if (mapinfo && mapinfo->levelname && !mapinfo->levelpic[0])
     {
-      y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+        y += (5 * SHORT(entering->height)) / 4;
+
+        WI_DrawString(y, mapinfo->levelname);
 
-      WI_DrawString(y, wbs->nextmapinfo->author);
+        if (mapinfo->author)
+        {
+            y += (5 * SHORT(hu_font['A' - HU_FONTSTART]->height) / 4);
+
+            WI_DrawString(y, mapinfo->author);
+        }
     }
-  }
-  else if (wbs->nextmapinfo && wbs->nextmapinfo->levelpic[0])
-  {
-    patch_t* lpic = V_CachePatchName(wbs->nextmapinfo->levelpic, PU_CACHE);
+    else if (mapinfo && mapinfo->levelpic[0])
+    {
+        patch_t *patch = V_CachePatchName(mapinfo->levelpic, PU_CACHE);
 
-    if (SHORT(lpic->height) < SCREENHEIGHT)
-      y += (5 * SHORT(lpic->height)) / 4;
+        if (SHORT(patch->height) < SCREENHEIGHT)
+        {
+            y += (5 * SHORT(patch->height)) / 4;
+        }
 
-    // [Nugget] HUD/menu shadows
-    V_DrawPatchSH((SCREENWIDTH - SHORT(lpic->width))/2, y, lpic);
-  }
-  // [FG] prevent crashes for levels without name graphics
-  else if (wbs->next >= 0 && wbs->next < num_lnames && lnames[wbs->next] != NULL)
-  {
-  // draw level
-  // haleyjd: corrected to use height of entering, not map name
-  if (SHORT(lnames[wbs->next]->height) < SCREENHEIGHT)
-    y += (5 * SHORT(entering->height)) / 4;
+        V_DrawPatchSH((SCREENWIDTH - SHORT(patch->width)) / 2, y, patch);
+    }
+    // [FG] prevent crashes for levels without name graphics
+    else if (wbs->next >= 0 && wbs->next < num_lnames && lnames[wbs->next])
+    {
+        // draw level
+        // haleyjd: corrected to use height of entering, not map name
+        if (SHORT(lnames[wbs->next]->height) < SCREENHEIGHT)
+        {
+            y += (5 * SHORT(entering->height)) / 4;
+        }
 
-  // [Nugget] HUD/menu shadows
-  V_DrawPatchSH((SCREENWIDTH - SHORT(lnames[wbs->next]->width))/2,
-                y, lnames[wbs->next]);
-  }
+        V_DrawPatchSH((SCREENWIDTH - SHORT(lnames[wbs->next]->width)) / 2, y,
+                    lnames[wbs->next]);
+    }
 }
 
-
 // ====================================================================
 // WI_drawOnLnode
 // Purpose: Draw patches at a location based on episode/map
@@ -934,9 +942,13 @@ static void WI_initAnimatedBack(boolean firstcall)
   }
 
   if (exitpic)
-    return;
-  if (enterpic && entering)
-    return;
+  {
+      return;
+  }
+  if (enterpic && state != StatCount)
+  {
+      return;
+  }
 
   if (gamemode == commercial)  // no animation for DOOM2
     return;
@@ -985,9 +997,13 @@ static void WI_updateAnimatedBack(void)
   }
 
   if (exitpic)
-    return;
+  {
+      return;
+  }
   if (enterpic && state != StatCount)
-    return;
+  {
+      return;
+  }
 
   if (gamemode == commercial)
     return;
@@ -1054,9 +1070,13 @@ static void WI_drawAnimatedBack(void)
   }
 
   if (exitpic)
-    return;
+  {
+      return;
+  }
   if (enterpic && state != StatCount)
-    return;
+  {
+      return;
+  }
 
   if (gamemode==commercial) //jff 4/25/98 Someone forgot commercial an enum
     return;
@@ -1352,22 +1372,23 @@ static void WI_initShowNextLoc(void)
 
   if (gamemapinfo)
   {
-    if (gamemapinfo->endpic[0])
-    {
-      G_WorldDone();
-      return;
-    }
-    state = ShowNextLoc;
+      if (gamemapinfo->flags & MapInfo_EndGame)
+      {
+          G_WorldDone();
+          return;
+      }
 
-    // episode change
-    if (wbs->epsd != wbs->nextep)
-    {
-      void WI_loadData(void);
+      state = ShowNextLoc;
 
-      wbs->epsd = wbs->nextep;
-      wbs->last = wbs->next - 1;
-      WI_loadData();
-    }
+      // episode change
+      if (wbs->epsd != wbs->nextep)
+      {
+          void WI_loadData(void);
+
+          wbs->epsd = wbs->nextep;
+          wbs->last = wbs->next - 1;
+          WI_loadData();
+      }
   }
 
   state = ShowNextLoc;
@@ -1406,9 +1427,9 @@ static void WI_drawShowNextLoc(void)
   int   i;
   int   last;
 
-  if (gamemapinfo && U_CheckField(gamemapinfo->endpic))
+  if (gamemapinfo && gamemapinfo->flags & MapInfo_EndGame)
   {
-    return;
+      return;
   }
 
   WI_slamBackground();
@@ -2212,29 +2233,32 @@ static void WI_drawStats(void)
   V_DrawPatchSH(SP_STATSX, SP_STATSY+2*lh, sp_secret);
   WI_drawPercent(SCREENWIDTH - SP_STATSX, SP_STATSY+2*lh, cnt_secret[0]);
 
+  const boolean draw_partime = (W_IsIWADLump(maplump) || deh_pars || um_pars) &&
+                               (wbs->epsd < 3 || um_pars);
+  // [FG] choose x-position depending on width of time string
+  const boolean wide_total = (wbs->totaltimes / TICRATE > 61*59) ||
+                             (SP_TIMEX + SHORT(total->width) >= SCREENWIDTH/4);
+  const boolean wide_time = (wide_total && !draw_partime);
+
   V_DrawPatchSH(SP_TIMEX, SP_TIMEY, witime);
-  WI_drawTime(SCREENWIDTH/2 - SP_TIMEX, SP_TIMEY, cnt_time, true);
+  WI_drawTime((wide_time ? SCREENWIDTH : SCREENWIDTH/2) - SP_TIMEX,
+              SP_TIMEY, cnt_time, true);
 
   // Ty 04/11/98: redid logic: should skip only if with pwad but
   // without deh patch
   // killough 2/22/98: skip drawing par times on pwads
   // Ty 03/17/98: unless pars changed with deh patch
 
-  if (W_IsIWADLump(maplump) || deh_pars || um_pars)
-    if (wbs->epsd < 3 || um_pars)
-      {
-	V_DrawPatchSH(SCREENWIDTH/2 + SP_TIMEX, SP_TIMEY, par);
-	WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par, true);
-      }
-
-  // [FG] draw total time alongside level time and par time
+  if (draw_partime)
   {
-    const boolean wide = (wbs->totaltimes / TICRATE > 61*59) || (SP_TIMEX + SHORT(total->width) >= SCREENWIDTH/4);
-
-    V_DrawPatchSH(SP_TIMEX, SP_TIMEY + 16, total);
-    // [FG] choose x-position depending on width of time string
-    WI_drawTime((wide ? SCREENWIDTH : SCREENWIDTH/2) - SP_TIMEX, SP_TIMEY + 16, cnt_total_time, false);
+    V_DrawPatchSH(SCREENWIDTH/2 + SP_TIMEX, SP_TIMEY, par);
+    WI_drawTime(SCREENWIDTH - SP_TIMEX, SP_TIMEY, cnt_par, true);
   }
+
+  // [FG] draw total time alongside level time and par time
+  V_DrawPatchSH(SP_TIMEX, SP_TIMEY + 16, total);
+  WI_drawTime((wide_total ? SCREENWIDTH : SCREENWIDTH/2) - SP_TIMEX,
+              SP_TIMEY + 16, cnt_total_time, false);
 }
 
 // ====================================================================