diff --git a/doc/manual/1up.png b/doc/manual/1up.png
deleted file mode 100644
index d8617cb8bccfa11165a14ac82f70494e8ee83a50..0000000000000000000000000000000000000000
Binary files a/doc/manual/1up.png and /dev/null differ
diff --git a/doc/manual/RNGA0000.png b/doc/manual/RNGA0000.png
deleted file mode 100644
index 78f98625ce7f803a40544e00ec4bbb0536fae266..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGA0000.png and /dev/null differ
diff --git a/doc/manual/RNGB0000.png b/doc/manual/RNGB0000.png
deleted file mode 100644
index 71a9c51be612475283523e913328e4f5acf97296..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGB0000.png and /dev/null differ
diff --git a/doc/manual/RNGE0000.png b/doc/manual/RNGE0000.png
deleted file mode 100644
index 960b9c3bae1f065bb24bbd173a44d9e0d22f2647..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGE0000.png and /dev/null differ
diff --git a/doc/manual/RNGG0000.png b/doc/manual/RNGG0000.png
deleted file mode 100644
index 38c552fcab5a5549bcfc1e8edac9c809c6884a47..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGG0000.png and /dev/null differ
diff --git a/doc/manual/RNGR0000.png b/doc/manual/RNGR0000.png
deleted file mode 100644
index ad9c5c073282040361a6a092a8ec1d17eb1f5d62..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGR0000.png and /dev/null differ
diff --git a/doc/manual/RNGS0000.png b/doc/manual/RNGS0000.png
deleted file mode 100644
index bbcb6636a59011311da1535b1edf8f8eb1d503ce..0000000000000000000000000000000000000000
Binary files a/doc/manual/RNGS0000.png and /dev/null differ
diff --git a/doc/manual/Ring0000.png b/doc/manual/Ring0000.png
deleted file mode 100644
index 730f6f5908aea9be0f0f91bac7f172330c604d25..0000000000000000000000000000000000000000
Binary files a/doc/manual/Ring0000.png and /dev/null differ
diff --git a/doc/manual/acz.png b/doc/manual/acz.png
deleted file mode 100644
index f8d98a6537b7fa0ccc155f9e5a1b9d53fe8f1ac7..0000000000000000000000000000000000000000
Binary files a/doc/manual/acz.png and /dev/null differ
diff --git a/doc/manual/airspin.png b/doc/manual/airspin.png
deleted file mode 100644
index 943ee546a6e9b2e17247cbea4b01e83af05b6346..0000000000000000000000000000000000000000
Binary files a/doc/manual/airspin.png and /dev/null differ
diff --git a/doc/manual/attack.png b/doc/manual/attack.png
deleted file mode 100644
index 3f3e48fddff34bc22766e48a84cea30a891d5a44..0000000000000000000000000000000000000000
Binary files a/doc/manual/attack.png and /dev/null differ
diff --git a/doc/manual/blue_monitor.png b/doc/manual/blue_monitor.png
deleted file mode 100644
index f6d01f802f702d6a4621b240814f717dbb86c4d2..0000000000000000000000000000000000000000
Binary files a/doc/manual/blue_monitor.png and /dev/null differ
diff --git a/doc/manual/blue_ring.png b/doc/manual/blue_ring.png
deleted file mode 100644
index 8ffd4b38de62bbc79cdf7285eba16b19348e9f89..0000000000000000000000000000000000000000
Binary files a/doc/manual/blue_ring.png and /dev/null differ
diff --git a/doc/manual/bshield.png b/doc/manual/bshield.png
deleted file mode 100644
index 01f16a3af592165cc6594ede9fa186116980b157..0000000000000000000000000000000000000000
Binary files a/doc/manual/bshield.png and /dev/null differ
diff --git a/doc/manual/cez.png b/doc/manual/cez.png
deleted file mode 100644
index 34848023d1b1c74df57f326801fcdbdbf8ac7643..0000000000000000000000000000000000000000
Binary files a/doc/manual/cez.png and /dev/null differ
diff --git a/doc/manual/controls.png b/doc/manual/controls.png
deleted file mode 100644
index 8422625a1088a70806882a7a7722b3f395abba8a..0000000000000000000000000000000000000000
Binary files a/doc/manual/controls.png and /dev/null differ
diff --git a/doc/manual/conveyor.png b/doc/manual/conveyor.png
deleted file mode 100644
index 871549aa910b1078b6c7755feb914a388e7682d5..0000000000000000000000000000000000000000
Binary files a/doc/manual/conveyor.png and /dev/null differ
diff --git a/doc/manual/coop.png b/doc/manual/coop.png
deleted file mode 100644
index b1ed48b053fcb5803c4701d9467ecd688bdd0201..0000000000000000000000000000000000000000
Binary files a/doc/manual/coop.png and /dev/null differ
diff --git a/doc/manual/crusher.png b/doc/manual/crusher.png
deleted file mode 100644
index 42859e61ddf04b2152b9cd4eaab2c85649c15005..0000000000000000000000000000000000000000
Binary files a/doc/manual/crusher.png and /dev/null differ
diff --git a/doc/manual/ctf.png b/doc/manual/ctf.png
deleted file mode 100644
index 24712f6534d713b2effa5f4ad60a40f013abf2d5..0000000000000000000000000000000000000000
Binary files a/doc/manual/ctf.png and /dev/null differ
diff --git a/doc/manual/dsz.png b/doc/manual/dsz.png
deleted file mode 100644
index cc062bf8ee36f959939da7b619899a44c03700fa..0000000000000000000000000000000000000000
Binary files a/doc/manual/dsz.png and /dev/null differ
diff --git a/doc/manual/eggbox.png b/doc/manual/eggbox.png
deleted file mode 100644
index a45ead1bfdd6b54aeb8a7fbeeda8de106e5bd881..0000000000000000000000000000000000000000
Binary files a/doc/manual/eggbox.png and /dev/null differ
diff --git a/doc/manual/eggman.png b/doc/manual/eggman.png
deleted file mode 100644
index 3ee0a0d20dc69030c193c4623bdba68576a18a5c..0000000000000000000000000000000000000000
Binary files a/doc/manual/eggman.png and /dev/null differ
diff --git a/doc/manual/emblem.png b/doc/manual/emblem.png
deleted file mode 100644
index 0aa63b651bf0fa88f342f4bc74350d2f1f4f69b7..0000000000000000000000000000000000000000
Binary files a/doc/manual/emblem.png and /dev/null differ
diff --git a/doc/manual/fan.png b/doc/manual/fan.png
deleted file mode 100644
index 74e4a17d06fd5b6e9f6eec83706390b2e04b1b7d..0000000000000000000000000000000000000000
Binary files a/doc/manual/fan.png and /dev/null differ
diff --git a/doc/manual/gfz.png b/doc/manual/gfz.png
deleted file mode 100644
index 6530f1a994130ee11a893a26040fe0d56d9098ee..0000000000000000000000000000000000000000
Binary files a/doc/manual/gfz.png and /dev/null differ
diff --git a/doc/manual/gshield.png b/doc/manual/gshield.png
deleted file mode 100644
index 225f1a76d6e6db475ef4b15f405532b381c510a5..0000000000000000000000000000000000000000
Binary files a/doc/manual/gshield.png and /dev/null differ
diff --git a/doc/manual/invc.png b/doc/manual/invc.png
deleted file mode 100644
index f0e7a63409c3c43752bbeb820e6eb00b3ce7a267..0000000000000000000000000000000000000000
Binary files a/doc/manual/invc.png and /dev/null differ
diff --git a/doc/manual/knuckles.png b/doc/manual/knuckles.png
deleted file mode 100644
index 4fef38b59e57b211a10ca1b6eea2051596156510..0000000000000000000000000000000000000000
Binary files a/doc/manual/knuckles.png and /dev/null differ
diff --git a/doc/manual/koneup.png b/doc/manual/koneup.png
deleted file mode 100644
index cda5b1b84a864cd4aaf883554997139fd63cc0f4..0000000000000000000000000000000000000000
Binary files a/doc/manual/koneup.png and /dev/null differ
diff --git a/doc/manual/manual.htm b/doc/manual/manual.htm
index bdf4d2a98f2c6110d6d0f9cf51cdf4ac2be209a7..3fea1b66f18b694468c9103064e8c6fd1f33efd3 100644
--- a/doc/manual/manual.htm
+++ b/doc/manual/manual.htm
@@ -13,7 +13,7 @@
 		doc = doc || document;
 		// from http://stackoverflow.com/questions/1145850/get-height-of-entire-document-with-javascript
 		var body = doc.body, html = doc.documentElement;
-		var height = Math.max( body.scrollHeight, body.offsetHeight, 
+		var height = Math.max( body.scrollHeight, body.offsetHeight,
 			html.clientHeight, html.scrollHeight, html.offsetHeight );
 		return height;
 }
@@ -65,4 +65,4 @@
 		<iframe id="ifrm" name="ifrm" src="intro.htm" onload="setIframeHeight(this.id)"> </iframe>
 	</p>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/doc/manual/match.png b/doc/manual/match.png
deleted file mode 100644
index a89bf8d5d4461eea400f41e18dd49b9acfc298c8..0000000000000000000000000000000000000000
Binary files a/doc/manual/match.png and /dev/null differ
diff --git a/doc/manual/metal.png b/doc/manual/metal.png
deleted file mode 100644
index 6fc385c42183b7a4fbbf678e1e37e84759295e6e..0000000000000000000000000000000000000000
Binary files a/doc/manual/metal.png and /dev/null differ
diff --git a/doc/manual/mixup.png b/doc/manual/mixup.png
deleted file mode 100644
index fc26fed1701d3679025d799899689dc40dd117b2..0000000000000000000000000000000000000000
Binary files a/doc/manual/mixup.png and /dev/null differ
diff --git a/doc/manual/pit.png b/doc/manual/pit.png
deleted file mode 100644
index 5accda689aebec6dd52b872d7593d09e32d9c2ce..0000000000000000000000000000000000000000
Binary files a/doc/manual/pit.png and /dev/null differ
diff --git a/doc/manual/platform.png b/doc/manual/platform.png
deleted file mode 100644
index c0e4c4993b82b71cf4d3a58551dce569ad85b810..0000000000000000000000000000000000000000
Binary files a/doc/manual/platform.png and /dev/null differ
diff --git a/doc/manual/race.png b/doc/manual/race.png
deleted file mode 100644
index 66ebf9c6e135a51ecad73bd7ea2486addda92185..0000000000000000000000000000000000000000
Binary files a/doc/manual/race.png and /dev/null differ
diff --git a/doc/manual/random.png b/doc/manual/random.png
deleted file mode 100644
index 303ad5b5370865fe47a50d787bc3419a24f38d2f..0000000000000000000000000000000000000000
Binary files a/doc/manual/random.png and /dev/null differ
diff --git a/doc/manual/recycler.png b/doc/manual/recycler.png
deleted file mode 100644
index 12e0343f63330fde4da5a2924ea0b2d16edc2a31..0000000000000000000000000000000000000000
Binary files a/doc/manual/recycler.png and /dev/null differ
diff --git a/doc/manual/red_monitor.png b/doc/manual/red_monitor.png
deleted file mode 100644
index 8ceae6ef59c893939d807703950d58d8e36577d5..0000000000000000000000000000000000000000
Binary files a/doc/manual/red_monitor.png and /dev/null differ
diff --git a/doc/manual/red_ring.png b/doc/manual/red_ring.png
deleted file mode 100644
index c3dcdd4c38c3b253d8807d4d58ca0dd2e8652cd2..0000000000000000000000000000000000000000
Binary files a/doc/manual/red_ring.png and /dev/null differ
diff --git a/doc/manual/ring.png b/doc/manual/ring.png
deleted file mode 100644
index 6f7c54f0d77a9fa15399ac39e74c50b0ce4fab96..0000000000000000000000000000000000000000
Binary files a/doc/manual/ring.png and /dev/null differ
diff --git a/doc/manual/rshield.png b/doc/manual/rshield.png
deleted file mode 100644
index 7a346cab58d54e25f69005819f5d895bb2ac167b..0000000000000000000000000000000000000000
Binary files a/doc/manual/rshield.png and /dev/null differ
diff --git a/doc/manual/rvz.png b/doc/manual/rvz.png
deleted file mode 100644
index e20c5dc1ff0f3d0390a93e39750648b742dffe3d..0000000000000000000000000000000000000000
Binary files a/doc/manual/rvz.png and /dev/null differ
diff --git a/doc/manual/shoes.png b/doc/manual/shoes.png
deleted file mode 100644
index 5667f5462dd0bbd64dc0c8c999efbefae0ce9e13..0000000000000000000000000000000000000000
Binary files a/doc/manual/shoes.png and /dev/null differ
diff --git a/doc/manual/slime.png b/doc/manual/slime.png
deleted file mode 100644
index 857f98fd3878e89bdc5e4cce500988b0651edd20..0000000000000000000000000000000000000000
Binary files a/doc/manual/slime.png and /dev/null differ
diff --git a/doc/manual/soneup.png b/doc/manual/soneup.png
deleted file mode 100644
index f9f869f0c33f06c8b5bf0e2332deb0f32116856e..0000000000000000000000000000000000000000
Binary files a/doc/manual/soneup.png and /dev/null differ
diff --git a/doc/manual/spring1.png b/doc/manual/spring1.png
deleted file mode 100644
index 8a222f378de25c9d0f2cb9cddbc3ae1a88460a2c..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring1.png and /dev/null differ
diff --git a/doc/manual/spring2.png b/doc/manual/spring2.png
deleted file mode 100644
index 4e7b1a05a4fa20a8e16272ec0f529358694142c1..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring2.png and /dev/null differ
diff --git a/doc/manual/spring3.png b/doc/manual/spring3.png
deleted file mode 100644
index c7b88b88af5c19c35fb11a0118655e793ad819e6..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring3.png and /dev/null differ
diff --git a/doc/manual/spring4.png b/doc/manual/spring4.png
deleted file mode 100644
index d16919266b9cae87bd09e1fbc6df909f78754606..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring4.png and /dev/null differ
diff --git a/doc/manual/spring5.png b/doc/manual/spring5.png
deleted file mode 100644
index 0eedf4a6fd0b2bdf117fddd71266533d22990b43..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring5.png and /dev/null differ
diff --git a/doc/manual/spring6.png b/doc/manual/spring6.png
deleted file mode 100644
index 012b7bc2a6a5df8b1def0ae704b99265474c4119..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring6.png and /dev/null differ
diff --git a/doc/manual/spring7.png b/doc/manual/spring7.png
deleted file mode 100644
index 98c88bcd6a6a66e9f53313c8a361825abcd0dbbb..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring7.png and /dev/null differ
diff --git a/doc/manual/spring8.png b/doc/manual/spring8.png
deleted file mode 100644
index ec552eebfe18578a7b21343ff6e27e563aec1312..0000000000000000000000000000000000000000
Binary files a/doc/manual/spring8.png and /dev/null differ
diff --git a/doc/manual/stats_lives.png b/doc/manual/stats_lives.png
deleted file mode 100644
index ec1371398d9c96465e813191d5326b7e69eadaf0..0000000000000000000000000000000000000000
Binary files a/doc/manual/stats_lives.png and /dev/null differ
diff --git a/doc/manual/stats_rings.png b/doc/manual/stats_rings.png
deleted file mode 100644
index 5b807403e9ee874a364988848314bc37779086e2..0000000000000000000000000000000000000000
Binary files a/doc/manual/stats_rings.png and /dev/null differ
diff --git a/doc/manual/tag.png b/doc/manual/tag.png
deleted file mode 100644
index 72954b2b78795d80a37a0a893b56f8f128356c85..0000000000000000000000000000000000000000
Binary files a/doc/manual/tag.png and /dev/null differ
diff --git a/doc/manual/tailsfly.png b/doc/manual/tailsfly.png
deleted file mode 100644
index 7628128de2f36be33f6b2174ebff946d32bd01ef..0000000000000000000000000000000000000000
Binary files a/doc/manual/tailsfly.png and /dev/null differ
diff --git a/doc/manual/thanks.png b/doc/manual/thanks.png
deleted file mode 100644
index 8cb0ad1159b40e4f1fca83255106e529a0c44699..0000000000000000000000000000000000000000
Binary files a/doc/manual/thanks.png and /dev/null differ
diff --git a/doc/manual/thz.png b/doc/manual/thz.png
deleted file mode 100644
index f4db3c59ce66c64eab6b0d2d2dfc33b6272e265c..0000000000000000000000000000000000000000
Binary files a/doc/manual/thz.png and /dev/null differ
diff --git a/doc/manual/title.png b/doc/manual/title.png
deleted file mode 100644
index e4ea963a95bc7af8d6d0491c526ea2a4c33c7055..0000000000000000000000000000000000000000
Binary files a/doc/manual/title.png and /dev/null differ
diff --git a/doc/manual/token.png b/doc/manual/token.png
deleted file mode 100644
index 0e821ce37ed93f1955a35b053cda2e3b765203e8..0000000000000000000000000000000000000000
Binary files a/doc/manual/token.png and /dev/null differ
diff --git a/doc/manual/toneup.png b/doc/manual/toneup.png
deleted file mode 100644
index 425973294424925218d840cc94086c58b4a1172a..0000000000000000000000000000000000000000
Binary files a/doc/manual/toneup.png and /dev/null differ
diff --git a/doc/manual/unknown.png b/doc/manual/unknown.png
deleted file mode 100644
index a59a6c7645d451003ee3a71548e0af10b17a0409..0000000000000000000000000000000000000000
Binary files a/doc/manual/unknown.png and /dev/null differ
diff --git a/doc/manual/water.png b/doc/manual/water.png
deleted file mode 100644
index 2d5d607e06c19ba59dbb8eb1245dde1772e40533..0000000000000000000000000000000000000000
Binary files a/doc/manual/water.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_auto.png b/doc/manual/wpnpanel_auto.png
deleted file mode 100644
index af71f98bff842bd32831c97c07e148b602a4cea1..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_auto.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_bounce.png b/doc/manual/wpnpanel_bounce.png
deleted file mode 100644
index 606b3598ee535b80ea1a6c9edc9b5289ff0471c4..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_bounce.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_explosion.png b/doc/manual/wpnpanel_explosion.png
deleted file mode 100644
index f3bd45decb7f51d119f8cc5a6e940ae596efeeb8..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_explosion.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_grenade.png b/doc/manual/wpnpanel_grenade.png
deleted file mode 100644
index f0e18fa08bfa338c5524def0b0de8ebb1e966398..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_grenade.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_rail.png b/doc/manual/wpnpanel_rail.png
deleted file mode 100644
index bf0822360903e25dbf24d6bc40d06275ef83c22f..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_rail.png and /dev/null differ
diff --git a/doc/manual/wpnpanel_scatter.png b/doc/manual/wpnpanel_scatter.png
deleted file mode 100644
index 497f1abe1421f6d0cde6916548a939345e8b1d03..0000000000000000000000000000000000000000
Binary files a/doc/manual/wpnpanel_scatter.png and /dev/null differ
diff --git a/doc/manual/wshield.png b/doc/manual/wshield.png
deleted file mode 100644
index 20e3cf3445255adc7d97facc1b0f4c745aabab5d..0000000000000000000000000000000000000000
Binary files a/doc/manual/wshield.png and /dev/null differ
diff --git a/doc/manual/yshield.png b/doc/manual/yshield.png
deleted file mode 100644
index 6271eab9a5b8021abe3d5872cb96fda69b03789e..0000000000000000000000000000000000000000
Binary files a/doc/manual/yshield.png and /dev/null differ
diff --git a/readme.txt b/readme.txt
index f96d3823c759a46c9c3a86a9e98d6c71a9a7ad82..8a09f0bc5e54edf4b92518742cd5223114d9c3be 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-Here it is! SRB2 v2.1.11 source code!
+Here it is! SRB2 v2.1.12 source code!
 (why do we keep the version number up to date
 	when everything else in this file is hilariously old?
 	- Inuyasha)
diff --git a/src/blua/lapi.c b/src/blua/lapi.c
index 2d837057cab82c3e4a9394e505a33b0bd2b8de3c..73fbbf5618ed4ce8ca94d23fe775cf7f4507762a 100644
--- a/src/blua/lapi.c
+++ b/src/blua/lapi.c
@@ -319,21 +319,6 @@ LUA_API lua_Number lua_tonumber (lua_State *L, int idx) {
     return 0;
 }
 
-
-LUA_API lua_Integer lua_tointeger (lua_State *L, int idx) {
-  TValue n;
-  const TValue *o = index2adr(L, idx);
-  if (tonumber(o, &n)) {
-    lua_Integer res;
-    lua_Number num = nvalue(o);
-    lua_number2integer(res, num);
-    return res;
-  }
-  else
-    return 0;
-}
-
-
 LUA_API int lua_toboolean (lua_State *L, int idx) {
   const TValue *o = index2adr(L, idx);
   return !l_isfalse(o);
@@ -446,14 +431,6 @@ LUA_API void lua_pushnumber (lua_State *L, lua_Number n) {
 }
 
 
-LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) {
-  lua_lock(L);
-  setnvalue(L->top, cast_num(n));
-  api_incr_top(L);
-  lua_unlock(L);
-}
-
-
 LUA_API void lua_pushlstring (lua_State *L, const char *s, size_t len) {
   lua_lock(L);
   luaC_checkGC(L);
diff --git a/src/blua/lauxlib.c b/src/blua/lauxlib.c
index c43cefd1e328f3e52bdb8a10a3af97072f1af92d..db7d9d0d542ea83f1532de9aaef7603507b7c7f1 100644
--- a/src/blua/lauxlib.c
+++ b/src/blua/lauxlib.c
@@ -186,20 +186,6 @@ LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) {
 }
 
 
-LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) {
-  lua_Integer d = lua_tointeger(L, narg);
-  if (d == 0 && !lua_isnumber(L, narg))  /* avoid extra test when d is not 0 */
-    tag_error(L, narg, LUA_TNUMBER);
-  return d;
-}
-
-
-LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg,
-                                                      lua_Integer def) {
-  return luaL_opt(L, luaL_checkinteger, narg, def);
-}
-
-
 LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) {
   if (!lua_getmetatable(L, obj))  /* no metatable? */
     return 0;
diff --git a/src/blua/lauxlib.h b/src/blua/lauxlib.h
index 0320410520457cac56acddd2ba33e2b6ad0be099..c5ea45a1cbc4c4612909107b4e3fdd5d8f88de0d 100644
--- a/src/blua/lauxlib.h
+++ b/src/blua/lauxlib.h
@@ -54,9 +54,8 @@ LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg,
 LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg);
 LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
 
-LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg);
-LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg,
-                                          lua_Integer def);
+#define luaL_checkinteger luaL_checknumber
+#define luaL_optinteger luaL_optnumber
 
 LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
 LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t);
diff --git a/src/blua/lua.h b/src/blua/lua.h
index a945fcb5cab364831cb2e5d1e2ada20c41264c9a..4e26c2a79fa5ff2370f2fd87a8b547f6eb7305dd 100644
--- a/src/blua/lua.h
+++ b/src/blua/lua.h
@@ -100,7 +100,7 @@ typedef LUA_NUMBER lua_Number;
 
 
 /* type for integer functions */
-typedef LUA_INTEGER lua_Integer;
+#define lua_Integer lua_Number
 
 
 
@@ -144,7 +144,7 @@ LUA_API int            (lua_rawequal) (lua_State *L, int idx1, int idx2);
 LUA_API int            (lua_lessthan) (lua_State *L, int idx1, int idx2);
 
 LUA_API lua_Number      (lua_tonumber) (lua_State *L, int idx);
-LUA_API lua_Integer     (lua_tointeger) (lua_State *L, int idx);
+#define lua_tointeger lua_tonumber
 LUA_API int             (lua_toboolean) (lua_State *L, int idx);
 LUA_API const char     *(lua_tolstring) (lua_State *L, int idx, size_t *len);
 LUA_API size_t          (lua_objlen) (lua_State *L, int idx);
@@ -159,7 +159,7 @@ LUA_API const void     *(lua_topointer) (lua_State *L, int idx);
 */
 LUA_API void  (lua_pushnil) (lua_State *L);
 LUA_API void  (lua_pushnumber) (lua_State *L, lua_Number n);
-LUA_API void  (lua_pushinteger) (lua_State *L, lua_Integer n);
+#define lua_pushinteger lua_pushnumber
 LUA_API void  (lua_pushlstring) (lua_State *L, const char *s, size_t l);
 LUA_API void  (lua_pushstring) (lua_State *L, const char *s);
 LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
diff --git a/src/blua/lvm.c b/src/blua/lvm.c
index 17764846e5a0b3e0bf9d0c5810403ed27e2239d1..a921d443773b90a83bc741f6b0b54646f85f6a1a 100644
--- a/src/blua/lvm.c
+++ b/src/blua/lvm.c
@@ -323,7 +323,7 @@ static void Arith (lua_State *L, StkId ra, TValue *rb,
       case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
       case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
       case TM_DIV: if (nc == 0) { lua_pushliteral(L, "divide by zero error"); lua_error(L); } else setnvalue(ra, luai_numdiv(nb, nc)); break;
-      case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break;
+      case TM_MOD: if (nc == 0) { lua_pushliteral(L, "modulo by zero error"); lua_error(L); } else setnvalue(ra, luai_nummod(nb, nc)); break;
       case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
       case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
       case TM_AND: setnvalue(ra, luai_numand(nb, nc)); break;
@@ -494,7 +494,7 @@ void luaV_execute (lua_State *L, int nexeccalls) {
           if (nc == 0) {
             lua_pushliteral(L, "divide by zero error");
             lua_error(L);
-					}
+          }
           else
             setnvalue(ra, luai_numdiv(nb, nc));
         }
@@ -503,7 +503,19 @@ void luaV_execute (lua_State *L, int nexeccalls) {
         continue;
       }
       case OP_MOD: {
-        arith_op(luai_nummod, TM_MOD);
+        TValue *rb = RKB(i);
+        TValue *rc = RKC(i);
+        if (ttisnumber(rb) && ttisnumber(rc)) {
+          lua_Number nb = nvalue(rb), nc = nvalue(rc);
+          if (nc == 0) {
+            lua_pushliteral(L, "modulo by zero error");
+            lua_error(L);
+          }
+          else
+            setnvalue(ra, luai_nummod(nb, nc));
+        }
+        else
+          Protect(Arith(L, ra, rb, rc, TM_MOD));
         continue;
       }
       case OP_POW: {
diff --git a/src/command.c b/src/command.c
index baf97cbd188d5c27ea2bf72ea0139aa74aec9b72..09588eb84b9762a8b814ff8b7e25cd243596d5a4 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1055,9 +1055,22 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 
 	if (var->PossibleValue)
 	{
-		INT32 v = atoi(valstr);
-		if (!v && valstr[0] != '0')
-			v = INT32_MIN; // Invalid integer trigger
+		INT32 v;
+
+		if (var->flags & CV_FLOAT)
+		{
+			double d = atof(valstr);
+			if (!d && valstr[0] != '0')
+				v = INT32_MIN;
+			else
+				v = (INT32)(d * FRACUNIT);
+		}
+		else
+		{
+			v = atoi(valstr);
+			if (!v && valstr[0] != '0')
+				v = INT32_MIN; // Invalid integer trigger
+		}
 
 		if (var->PossibleValue[0].strvalue && !stricmp(var->PossibleValue[0].strvalue, "MIN")) // bounded cvar
 		{
@@ -1134,13 +1147,13 @@ found:
 
 	var->string = var->zstring = Z_StrDup(valstr);
 
-	if (var->flags & CV_FLOAT)
+	if (override)
+		var->value = overrideval;
+	else if (var->flags & CV_FLOAT)
 	{
 		double d = atof(var->string);
 		var->value = (INT32)(d * FRACUNIT);
 	}
-	else if (override)
-		var->value = overrideval;
 	else
 		var->value = atoi(var->string);
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 659dac1d05c8a5d478004dc367b090735d8a13cb..e24a1426ab5929fc716116101492a4a76eca197c 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2921,6 +2921,12 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			displayplayer = newplayernum;
 			secondarydisplayplayer = newplayernum;
 			DEBFILE("spawning me\n");
+			// Apply player flags as soon as possible!
+			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+			if (cv_flipcam.value)
+				players[newplayernum].pflags |= PF_FLIPCAM;
+			if (cv_analog.value)
+				players[newplayernum].pflags |= PF_ANALOGMODE;
 		}
 		else
 		{
@@ -2928,6 +2934,12 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			DEBFILE("spawning my brother\n");
 			if (botingame)
 				players[newplayernum].bot = 1;
+			// Same goes for player 2 when relevant
+			players[newplayernum].pflags &= ~(/*PF_FLIPCAM|*/PF_ANALOGMODE);
+			//if (cv_flipcam2.value)
+				//players[newplayernum].pflags |= PF_FLIPCAM;
+			if (cv_analog2.value)
+				players[newplayernum].pflags |= PF_ANALOGMODE;
 		}
 		D_SendPlayerConfig();
 		addedtogame = true;
diff --git a/src/d_main.c b/src/d_main.c
index bf1bc7330e147be1f37ede103e01100b5e1d800d..4cea94a8178c4f34a35a9622bcaf704660f48210 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1087,14 +1087,14 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#if 1 // md5s last updated 8/05/14
+#if 1 // md5s last updated 11/10/14
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, "ac309fb3c7d4b5b685e2cd26beccf0e8"); // srb2.srb/srb2.wad
 	W_VerifyFileMD5(1, "f39b6c849295e3c81875726e8cc0e2c7"); // zones.dta
 	W_VerifyFileMD5(2, "cfca0f1c73023cbbd8f844f45480f799"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "3d6cfc185fd7c195eb934ce593b0248f"); // patch.dta
+	W_VerifyFileMD5(4, "a45cc59d13dce924f2112b3e4201d0ae"); // patch.dta
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
 #endif
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 5e81a5a55379b41392798d9f42eac8052d88209a..377da84338a737d101bb71fb90e0b2e6e36b6b5f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -164,6 +164,7 @@ static void Command_Archivetest_f(void);
 // =========================================================================
 
 void SendWeaponPref(void);
+void SendWeaponPref2(void);
 
 static CV_PossibleValue_t usemouse_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Force"}, {0, NULL}};
 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
@@ -1345,26 +1346,34 @@ void SendWeaponPref(void)
 	XBOXSTATIC UINT8 buf[1];
 
 	buf[0] = 0;
-	if (cv_flipcam.value)
+	if (players[consoleplayer].pflags & PF_FLIPCAM)
 		buf[0] |= 1;
+	if (players[consoleplayer].pflags & PF_ANALOGMODE)
+		buf[0] |= 2;
 	SendNetXCmd(XD_WEAPONPREF, buf, 1);
+}
 
-	if (splitscreen)
-	{
-		buf[0] = 0;
-		if (cv_flipcam2.value)
-			buf[0] |= 1;
-		SendNetXCmd2(XD_WEAPONPREF, buf, 1);
-	}
+void SendWeaponPref2(void)
+{
+	XBOXSTATIC UINT8 buf[1];
+
+	buf[0] = 0;
+	if (players[secondarydisplayplayer].pflags & PF_FLIPCAM)
+		buf[0] |= 1;
+	if (players[secondarydisplayplayer].pflags & PF_ANALOGMODE)
+		buf[0] |= 2;
+	SendNetXCmd2(XD_WEAPONPREF, buf, 1);
 }
 
 static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
 {
 	UINT8 prefs = READUINT8(*cp);
+
+	players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
 	if (prefs & 1)
 		players[playernum].pflags |= PF_FLIPCAM;
-	else
-		players[playernum].pflags &= ~PF_FLIPCAM;
+	if (prefs & 2)
+		players[playernum].pflags |= PF_ANALOGMODE;
 }
 
 void D_SendPlayerConfig(void)
@@ -1373,6 +1382,8 @@ void D_SendPlayerConfig(void)
 	if (splitscreen || botingame)
 		SendNameAndColor2();
 	SendWeaponPref();
+	if (splitscreen)
+		SendWeaponPref2();
 }
 
 // Only works for displayplayer, sorry!
@@ -1798,7 +1809,6 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	if (demorecording) // Okay, level loaded, character spawned and skinned,
 		G_BeginRecording(); // I AM NOW READY TO RECORD.
 	demo_start = true;
-	metal_start = true;
 }
 
 static void Command_Pause(void)
diff --git a/src/d_player.h b/src/d_player.h
index b0484f0a1b896534ca4a8d07d55c335e81a55c42..b9fcdef75f34af7e533f263d8d5126cfd501683f 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -151,6 +151,7 @@ typedef enum
 
 	/*** misc ***/
 	PF_FORCESTRAFE       = 1<<29, // Turning inputs are translated into strafing inputs
+	PF_ANALOGMODE        = 1<<30, // Analog mode?
 
 	// free: 1<<30 and 1<<31
 } pflags_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index 334179ace2c1314f70aeb2f795c7bd1fa07a1a46..ccb7248e3fd1533f0289eb62c4163cf5e555b8b9 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7273,6 +7273,7 @@ static const char *const PLAYERFLAG_LIST[] = {
 
 	/*** misc ***/
 	"FORCESTRAFE", // Translate turn inputs into strafe inputs
+	"ANALOGMODE", // Analog mode?
 
 	NULL // stop loop here.
 };
diff --git a/src/doomdef.h b/src/doomdef.h
index a978e047ba3ecad97cbebbbfd44cb57273b158f6..a9e18eaf6a79159b0c871fac6cf2b3186691851c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -144,8 +144,8 @@ extern FILE *logstream;
 #define VERSIONSTRING "Trunk"
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 11  // more precise version number
-#define VERSIONSTRING "v2.1.11"
+#define SUBVERSION 12  // more precise version number
+#define VERSIONSTRING "v2.1.12"
 #endif
 
 // Modification options
@@ -428,9 +428,8 @@ extern const char *compdate, *comptime, *comprevision;
 // Compile them at your own risk!
 
 ///	Max recursive portal renders
-///	\note	sadly some additional work will need to be done
-///	     	before anything > 1 will function correctly
-#define PORTAL_LIMIT 1
+///	\note	obsoleted by cv_maxportals
+//#define PORTAL_LIMIT 8
 
 ///	Fun experimental slope stuff!
 //#define SLOPENESS
@@ -453,7 +452,7 @@ extern const char *compdate, *comptime, *comprevision;
 //#define CHAOSISNOTDEADYET
 
 ///	Polyobject fake flat code
-//#define POLYOBJECTS_PLANES
+#define POLYOBJECTS_PLANES
 
 ///	Blue spheres for future use.
 ///	\todo	Remove this define.
@@ -493,4 +492,7 @@ extern const char *compdate, *comptime, *comprevision;
 #define CLIENT_LOADINGSCREEN
 #endif
 
+/// Experimental tweaks to analog mode. (Needs a lot of work before it's ready for primetime.)
+//#define REDSANALOG
+
 #endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index 3cf70e463f90960705d955673b530e0c27e58ca0..b05b3833c61767cfd16c32c2d7b5ec7011a1fed4 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -165,7 +165,6 @@ extern cutscene_t *cutscenes[128];
 
 // For the Custom Exit linedef.
 extern INT16 nextmapoverride;
-extern INT32 nextmapgametype;
 extern boolean skipstats;
 
 extern UINT32 totalrings; //  Total # of rings in a level
diff --git a/src/f_finale.c b/src/f_finale.c
index 103c7df3159e79b2b14011641183b482df7890bd..f541995d49977d3e54cc229a657922006b0a3f81 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -958,29 +958,30 @@ boolean F_IntroResponder(event_t *event)
 //  CREDITS
 // =========
 static const char *credits[] = {
-	"\1Sonic Team Junior",
-	"\1Staff",
+	"\1Sonic Robo Blast II",
+	"\1Credits",
 	"",
 	"\1Game Design",
-	"\"SSNTails\"",
 	"Ben \"Mystic\" Geyer",
+	"\"SSNTails\"",
 	"Johnny \"Sonikku\" Wallbank",
 	"",
 	"\1Programming",
-	"\"SSNTails\"",
 	"Alam \"GBC\" Arias",
 	"Logan \"GBA\" Arias",
+	"Tim \"RedEnchilada\" Bordelon",
 	"Callum Dickinson",
 	"Scott \"Graue\" Feeney",
 	"Nathan \"Jazz\" Giroux",
 	"Thomas \"Shadow Hog\" Igoe",
 	"\"Monster\" Iestyn Jealous",
+	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
 	"John \"JTE\" Muniz",
+	"\"SSNTails\"",
 	"Matthew \"Inuyasha\" Walsh",
 	"",
 	"\1Programming",
 	"\1Assistance",
-	"Tim \"RedEnchilada\" Bordelon",
 	"Andrew \"orospakr\" Clunis",
 	"Gregor \"Oogaland\" Dick",
 	"Julio \"Chaos Zero 64\" Guir",
@@ -993,7 +994,6 @@ static const char *credits[] = {
 	"Ben \"Cue\" Woodford",
 	"",
 	"\1Sprite Artists",
-	"\"SSNTails\"",
 	"Odi \"Iceman404\" Atunzu",
 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
 	"Jim \"MotorRoach\" DeMello",
@@ -1001,6 +1001,7 @@ static const char *credits[] = {
 	"Sherman \"CoatRack\" DesJardins",
 	"Andrew \"Senku Niola\" Moran",
 	"David \"Instant Sonic\" Spencer Jr.",
+	"\"SSNTails\"",
 	"",
 	"\1Texture Artists",
 	"Ryan \"Blaze Hedgehog\" Bloom",
@@ -1010,8 +1011,6 @@ static const char *credits[] = {
 	"",
 	"\1Music and Sound",
 	"\1Production",
-	"\"SSNTails\"",
-	"Michael \"Spazzo\" Antonakes",
 	"Malcolm \"RedXVI\" Brown",
 	"David \"Bulmybag\" Bulmer",
 	"Paul \"Boinciel\" Clempson",
@@ -1021,12 +1020,12 @@ static const char *credits[] = {
 	"Jarel \"Arrow\" Jones",
 	"Stefan \"Stuf\" Rimalia",
 	"Shane Strife",
+	"\"Spazzo\"",
 	"David \"Big Wave Dave\" Spencer Sr.",
 	"David \"Instant Sonic\" Spencer Jr.",
+	"\"SSNTails\"",
 	"",
 	"\1Level Design",
-	"\"SSNTails\"",
-	"Michael \"Spazzo\" Antonakes",
 	"Matthew \"Fawfulfan\" Chapman",
 	"Paul \"Boinciel\" Clempson",
 	"Desmond \"Blade\" DesJardins",
@@ -1038,12 +1037,22 @@ static const char *credits[] = {
 	"Thomas \"Shadow Hog\" Igoe",
 	"Erik \"Torgo\" Nielsen",
 	"Wessel \"Spherallic\" Smit",
+	"\"Spazzo\"",
+	"\"SSNTails\"",
 	"Rob Tisdell",
 	"Jarrett \"JEV3\" Voight",
 	"Johnny \"Sonikku\" Wallbank",
 	"Matthew \"Inuyasha\" Walsh",
 	"Marco \"Digiku\" Zafra",
 	"",
+	"\1Boss Design",
+	"Ben \"Mystic\" Geyer",
+	"Thomas \"Shadow Hog\" Igoe",
+	"John \"JTE\" Muniz",
+	"Samuel \"Prime 2.0\" Peters",
+	"\"SSNTails\"",
+	"Johnny \"Sonikku\" Wallbank",
+	"",
 	"\1Testing",
 	"Hank \"FuriousFox\" Brannock",
 	"Cody \"SRB2 Playah\" Koester",
@@ -1060,9 +1069,12 @@ static const char *credits[] = {
 	"Alex \"MistaED\" Fuller",
 	"FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak
 	"Randy Heit (<!>)", // For his MSPaint <!> sprite that we nicked
-#if 0 // (don't take your anger out on me anymore, ok, JTE...?)
-	"Abigail \"Raspberry\" Fox", // (Inuyasha's girlfriend. >_> <_< >_>)
-#endif
+	"",
+	"\1Produced By",
+	"Sonic Team Junior",
+	"",
+	"\1Published By",
+	"A 28K dialup modem",
 	"",
 	"\1Thank you",
 	"\1for playing!",
diff --git a/src/g_game.c b/src/g_game.c
index 21112ca3cbc4686edd9d6782ecf8b46946dc4a50..50f30dfcab60091dadfed6b18197df9b33e4f253 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -131,7 +131,6 @@ boolean countdowntimeup = false;
 cutscene_t *cutscenes[128];
 
 INT16 nextmapoverride;
-INT32 nextmapgametype;
 boolean skipstats;
 
 // Pointers to each CTF flag
@@ -244,7 +243,6 @@ mobj_t *metalplayback;
 static UINT8 *metalbuffer = NULL;
 static UINT8 *metal_p;
 static UINT16 metalversion;
-boolean metal_start;
 
 // extra data stuff (events registered this frame while recording)
 static struct {
@@ -283,6 +281,8 @@ static void UserAnalog_OnChange(void);
 static void UserAnalog2_OnChange(void);
 static void Analog_OnChange(void);
 static void Analog2_OnChange(void);
+void SendWeaponPref(void);
+void SendWeaponPref2(void);
 
 static CV_PossibleValue_t crosshair_cons_t[] = {{0, "Off"}, {1, "Cross"}, {2, "Angle"}, {3, "Point"}, {0, NULL}};
 static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"},
@@ -595,14 +595,18 @@ void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare)
 void G_SetNightsRecords(void)
 {
 	INT32 i;
+	UINT32 totalscore = 0;
+	tic_t totaltime = 0;
+
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	char *gpath;
+	char lastdemo[256], bestdemo[256];
 
 	if (!ntemprecords.nummares)
 		return;
 
 	// Set overall
 	{
-		UINT32 totalscore = 0;
-		tic_t totaltime = 0;
 		UINT8 totalrank = 0, realrank = 0;
 
 		for (i = 1; i <= ntemprecords.nummares; ++i)
@@ -648,6 +652,50 @@ void G_SetNightsRecords(void)
 
 	memset(&ntemprecords, 0, sizeof(nightsdata_t));
 
+	// Save demo!
+	bestdemo[255] = '\0';
+	lastdemo[255] = '\0';
+	G_SetDemoTime(totaltime, totalscore, 0);
+	G_CheckDemoStatus();
+
+	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
+	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
+
+	if ((gpath = malloc(glen)) == NULL)
+		I_Error("Out of memory for replay filepath\n");
+
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+	snprintf(lastdemo, 255, "%s-last.lmp", gpath);
+
+	if (FIL_FileExists(lastdemo))
+	{
+		UINT8 *buf;
+		size_t len = FIL_ReadFile(lastdemo, &buf);
+
+		snprintf(bestdemo, 255, "%s-time-best.lmp", gpath);
+		if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
+		{ // Better time, save this demo.
+			if (FIL_FileExists(bestdemo))
+				remove(bestdemo);
+			FIL_WriteFile(bestdemo, buf, len);
+			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
+		}
+
+		snprintf(bestdemo, 255, "%s-score-best.lmp", gpath);
+		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
+		{ // Better score, save this demo.
+			if (FIL_FileExists(bestdemo))
+				remove(bestdemo);
+			FIL_WriteFile(bestdemo, buf, len);
+			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
+		}
+
+		//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
+
+		Z_Free(buf);
+	}
+	free(gpath);
+
 	// If the mare count changed, this will update the score display
 	CV_AddValue(&cv_nextmap, 1);
 	CV_AddValue(&cv_nextmap, -1);
@@ -909,6 +957,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	// these ones used for multiple conditions
 	boolean turnleft, turnright, mouseaiming, analogjoystickmove, gamepadjoystickmove;
 	player_t *player = &players[consoleplayer];
+	camera_t *thiscam = &camera;
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
@@ -1172,8 +1221,16 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	cmd->forwardmove = (SINT8)(cmd->forwardmove + forward);
 	cmd->sidemove = (SINT8)(cmd->sidemove + side);
 
-	localangle += (cmd->angleturn<<16);
-	cmd->angleturn = (INT16)(localangle >> 16);
+	if (cv_analog.value) {
+		cmd->angleturn = (INT16)(thiscam->angle >> 16);
+		if (player->awayviewtics)
+			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+	}
+	else
+	{
+		localangle += (cmd->angleturn<<16);
+		cmd->angleturn = (INT16)(localangle >> 16);
+	}
 
 	//Reset away view if a command is given.
 	if ((cmd->forwardmove || cmd->sidemove || cmd->buttons)
@@ -1190,6 +1247,7 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	// these ones used for multiple conditions
 	boolean turnleft, turnright, mouseaiming, analogjoystickmove, gamepadjoystickmove;
 	player_t *player = &players[secondarydisplayplayer];
+	camera_t *thiscam = (player->bot == 2 ? &camera : &camera2);
 
 	static INT32 turnheld; // for accelerative turning
 	static boolean keyboard_look; // true if lookup/down using keyboard
@@ -1463,8 +1521,16 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		}
 	}
 
-	localangle2 += (cmd->angleturn<<16);
-	cmd->angleturn = (INT16)(localangle2 >> 16);
+	if (cv_analog2.value) {
+		cmd->angleturn = (INT16)(thiscam->angle >> 16);
+		if (player->awayviewtics)
+			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+	}
+	else
+	{
+		localangle2 += (cmd->angleturn<<16);
+		cmd->angleturn = (INT16)(localangle2 >> 16);
+	}
 }
 
 // User has designated that they want
@@ -1497,25 +1563,45 @@ static void Analog_OnChange(void)
 
 	if (leveltime > 1)
 		CV_SetValue(&cv_cam_dist, 128);
-	if (netgame)
-		CV_StealthSetValue(&cv_analog, 0);
-	else if (cv_analog.value || demoplayback)
+	if (cv_analog.value || demoplayback)
 		CV_SetValue(&cv_cam_dist, 192);
+
+	if (!cv_chasecam.value && cv_analog.value) {
+		CV_SetValue(&cv_analog, 0);
+		return;
+	}
+
+	if (cv_analog.value)
+		players[consoleplayer].pflags |= PF_ANALOGMODE;
+	else
+		players[consoleplayer].pflags &= ~PF_ANALOGMODE;
+
+	SendWeaponPref();
 }
 
 static void Analog2_OnChange(void)
 {
-	if (!splitscreen || !cv_cam2_dist.string)
+	if (!(splitscreen || botingame) || !cv_cam2_dist.string)
 		return;
 
 	// cameras are not initialized at this point
 
 	if (leveltime > 1)
 		CV_SetValue(&cv_cam2_dist, 128);
-	if (netgame)
-		CV_StealthSetValue(&cv_analog2, 0);
-	else if (cv_analog2.value)
+	if (cv_analog2.value)
 		CV_SetValue(&cv_cam2_dist, 192);
+
+	if (!cv_chasecam2.value && cv_analog2.value) {
+		CV_SetValue(&cv_analog2, 0);
+		return;
+	}
+
+	if (cv_analog2.value)
+		players[secondarydisplayplayer].pflags |= PF_ANALOGMODE;
+	else
+		players[secondarydisplayplayer].pflags &= ~PF_ANALOGMODE;
+
+	SendWeaponPref2();
 }
 
 //
@@ -1999,7 +2085,7 @@ void G_PlayerReborn(INT32 player)
 	exiting = players[player].exiting;
 	jointime = players[player].jointime;
 	spectator = players[player].spectator;
-	pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED));
+	pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE));
 
 	// As long as we're not in multiplayer, carry over cheatcodes from map to map
 	if (!(netgame || multiplayer))
@@ -2838,23 +2924,12 @@ static void G_DoWorldDone(void)
 {
 	if (server)
 	{
-		INT32 nextgametype;
-
-		// for custom exit (linetype 2) that changes gametype
-		if (nextmapgametype != -1)
-			nextgametype = nextmapgametype;
-		else
-		{
-			// use current gametype by default
-			nextgametype = gametype;
-		}
-
-		if (gametype == GT_COOP && nextgametype == GT_COOP)
+		if (gametype == GT_COOP)
 			// don't reset player between maps
-			D_MapChange(nextmap+1, nextgametype, ultimatemode, false, 0, false, false);
+			D_MapChange(nextmap+1, gametype, ultimatemode, false, 0, false, false);
 		else
 			// resetplayer in match/chaos/tag/CTF/race for more equality
-			D_MapChange(nextmap+1, nextgametype, ultimatemode, true, 0, false, false);
+			D_MapChange(nextmap+1, gametype, ultimatemode, true, 0, false, false);
 	}
 
 	gameaction = ga_nothing;
@@ -3618,6 +3693,7 @@ static ticcmd_t oldcmd;
 // Not used for Metal Sonic
 #define GZT_SPRITE 0x10 // Animation frame
 #define GZT_EXTRA  0x20
+#define GZT_NIGHTS 0x40 // NiGHTS Mode stuff!
 
 // GZT_EXTRA flags
 #define EZT_THOK   0x01 // Spawned a thok object
@@ -3632,6 +3708,21 @@ static ticcmd_t oldcmd;
 
 static mobj_t oldmetal, oldghost;
 
+void G_SaveMetal(UINT8 **buffer)
+{
+	I_Assert(buffer != NULL && *buffer != NULL);
+
+	WRITEUINT32(*buffer, metal_p - metalbuffer);
+}
+
+void G_LoadMetal(UINT8 **buffer)
+{
+	I_Assert(buffer != NULL && *buffer != NULL);
+
+	G_DoPlayMetal();
+	metal_p = metalbuffer + READUINT32(*buffer);
+}
+
 ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n)
 {
 	return M_Memcpy(dest, src, n*sizeof(*src));
@@ -3814,6 +3905,13 @@ void G_WriteGhostTic(mobj_t *ghost)
 	if (!(demoflags & DF_GHOST))
 		return; // No ghost data to write.
 
+	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE && ghost->tracer)
+	{
+		// We're talking about the NiGHTS thing, not the normal platforming thing!
+		ziptic |= GZT_NIGHTS;
+		ghost = ghost->tracer;
+	}
+
 	ziptic_p = demo_p++; // the ziptic, written at the end of this function
 
 	#define MAXMOM (0xFFFF<<8)
@@ -3875,10 +3973,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 	}
 
 	// Store the sprite frame.
-	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE && ghost->tracer)
-		frame = ghost->tracer->frame & 0xFF; // get frame from NiGHTS tracer
-	else
-		frame = ghost->frame & 0xFF; // get frame from player
+	frame = ghost->frame & 0xFF;
 	if (frame != oldghost.frame)
 	{
 		oldghost.frame = frame;
@@ -3887,10 +3982,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 	}
 
 	// Check for sprite set changes
-	if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE && ghost->tracer)
-		sprite = ghost->tracer->sprite; // get sprite from NiGHTS tracer
-	else
-		sprite = ghost->sprite; // get sprite from player
+	sprite = ghost->sprite;
 	if (sprite != oldghost.sprite)
 	{
 		oldghost.sprite = sprite;
@@ -3957,12 +4049,16 @@ void G_ConsGhostTic(void)
 {
 	UINT8 ziptic;
 	UINT16 px,py,pz,gx,gy,gz;
+	mobj_t *testmo;
+	boolean nightsfail = false;
 
 	if (!demo_p || !demo_start)
 		return;
 	if (!(demoflags & DF_GHOST))
 		return; // No ghost data to use.
 
+	testmo = players[0].mo;
+
 	// Grab ghost data.
 	ziptic = READUINT8(demo_p);
 	if (ziptic & GZT_XYZ)
@@ -3988,6 +4084,12 @@ void G_ConsGhostTic(void)
 		demo_p++;
 	if (ziptic & GZT_SPRITE)
 		demo_p++;
+	if(ziptic & GZT_NIGHTS) {
+		if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE) || !testmo->tracer)
+			nightsfail = true;
+		else
+			testmo = testmo->tracer;
+	}
 
 	if (ziptic & GZT_EXTRA)
 	{ // But wait, there's more!
@@ -4029,7 +4131,12 @@ void G_ConsGhostTic(void)
 					mobj = NULL; // wasn't this one, keep searching.
 				}
 				if (mobj && mobj->health != health) // Wasn't damaged?! This is desync! Fix it!
+				{
+					if (demosynced)
+						CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
+					demosynced = false;
 					P_DamageMobj(mobj, players[0].mo, players[0].mo, 1);
+				}
 			}
 		}
 		if (ziptic & EZT_SPRITE)
@@ -4037,24 +4144,24 @@ void G_ConsGhostTic(void)
 	}
 
 	// Re-synchronise
-	px = players[0].mo->x>>FRACBITS;
-	py = players[0].mo->y>>FRACBITS;
-	pz = players[0].mo->z>>FRACBITS;
+	px = testmo->x>>FRACBITS;
+	py = testmo->y>>FRACBITS;
+	pz = testmo->z>>FRACBITS;
 	gx = oldghost.x>>FRACBITS;
 	gy = oldghost.y>>FRACBITS;
 	gz = oldghost.z>>FRACBITS;
 
-	if (px != gx || py != gy || pz != gz)
+	if (nightsfail || px != gx || py != gy || pz != gz)
 	{
 		if (demosynced)
 			CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
 		demosynced = false;
 
-		P_UnsetThingPosition(players[0].mo);
-		players[0].mo->x = oldghost.x;
-		players[0].mo->y = oldghost.y;
-		P_SetThingPosition(players[0].mo);
-		players[0].mo->z = oldghost.z;
+		P_UnsetThingPosition(testmo);
+		testmo->x = oldghost.x;
+		testmo->y = oldghost.y;
+		P_SetThingPosition(testmo);
+		testmo->z = oldghost.z;
 	}
 
 	if (*demo_p == DEMOMARKER)
@@ -4272,7 +4379,7 @@ void G_ReadMetalTic(mobj_t *metal)
 	UINT16 speed;
 	UINT8 statetype;
 
-	if (!metal_p || !metal_start)
+	if (!metal_p)
 		return;
 	ziptic = READUINT8(metal_p);
 
@@ -4517,11 +4624,7 @@ void G_BeginRecording(void)
 	memset(name,0,sizeof(name));
 
 	demo_p = demobuffer;
-	demoflags = DF_GHOST;
-	if (modeattacking == ATTACKING_RECORD)
-		demoflags |= DF_RECORDATTACK;
-	else if (modeattacking == ATTACKING_NIGHTS)
-		demoflags |= DF_NIGHTSATTACK;
+	demoflags = DF_GHOST|(modeattacking<<DF_ATTACKSHIFT);
 
 	// Setup header.
 	M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12;
@@ -4651,12 +4754,21 @@ void G_BeginMetal(void)
 
 void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings)
 {
-	if (!(demorecording && demoflags & DF_RECORDATTACK && demotime_p))
-		return; // Can't record a time. :(
-	WRITEUINT32(demotime_p, ptime);
-	WRITEUINT32(demotime_p, pscore);
-	WRITEUINT16(demotime_p, prings);
-	demotime_p = NULL;
+	if (!demorecording || !demotime_p)
+		return;
+	if (demoflags & DF_RECORDATTACK)
+	{
+		WRITEUINT32(demotime_p, ptime);
+		WRITEUINT32(demotime_p, pscore);
+		WRITEUINT16(demotime_p, prings);
+		demotime_p = NULL;
+	}
+	else if (demoflags & DF_NIGHTSATTACK)
+	{
+		WRITEUINT32(demotime_p, ptime);
+		WRITEUINT32(demotime_p, pscore);
+		demotime_p = NULL;
+	}
 }
 
 // Returns bitfield:
@@ -4672,6 +4784,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	size_t bufsize ATTRUNUSED;
 	UINT8 c;
 	UINT16 s ATTRUNUSED;
+	UINT8 aflags = 0;
 
 	// load the new file
 	FIL_DefaultExtension(newname, ".lmp");
@@ -4694,10 +4807,23 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	p += 2; // gamemap
 	p += 16; // map md5
 	flags = READUINT8(p); // demoflags
-	I_Assert(flags & DF_RECORDATTACK);
-	newtime = READUINT32(p);
-	newscore = READUINT32(p);
-	newrings = READUINT16(p);
+
+	aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK);
+	I_Assert(aflags);
+	if (flags & DF_RECORDATTACK)
+	{
+		newtime = READUINT32(p);
+		newscore = READUINT32(p);
+		newrings = READUINT16(p);
+	}
+	else if (flags & DF_NIGHTSATTACK)
+	{
+		newtime = READUINT32(p);
+		newscore = READUINT32(p);
+		newrings = 0;
+	}
+	else // appease compiler
+		return 0;
 
 	Z_Free(buffer);
 
@@ -4745,15 +4871,26 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 		p += 2; // gamemap
 	p += 16; // mapmd5
 	flags = READUINT8(p);
-	if (!(flags & DF_RECORDATTACK))
+	if (!(flags & aflags))
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from timeattack. It will be overwritten.\n"), oldname);
+		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname);
 		Z_Free(buffer);
 		return UINT8_MAX;
 	}
-	oldtime = READUINT32(p);
-	oldscore = READUINT32(p);
-	oldrings = READUINT16(p);
+	if (flags & DF_RECORDATTACK)
+	{
+		oldtime = READUINT32(p);
+		oldscore = READUINT32(p);
+		oldrings = READUINT16(p);
+	}
+	else if (flags & DF_NIGHTSATTACK)
+	{
+		oldtime = READUINT32(p);
+		oldscore = READUINT32(p);
+		oldrings = 0;
+	}
+	else // appease compiler
+		return UINT8_MAX;
 
 	Z_Free(buffer);
 
@@ -4967,7 +5104,6 @@ void G_DoPlayDemo(char *defdemoname)
 
 	// didn't start recording right away.
 	demo_start = false;
-	metal_start = false;
 
 #ifdef HAVE_BLUA
 	LUAh_MapChange();
@@ -5013,7 +5149,6 @@ void G_DoPlayDemo(char *defdemoname)
 	players[0].jumpfactor = jumpfactor;
 
 	demo_start = true;
-	metal_start = true;
 }
 
 void G_AddGhost(char *defdemoname)
@@ -5433,7 +5568,7 @@ boolean G_CheckDemoStatus(void)
 			I_Quit();
 		G_StopDemo();
 
-		if (modeattacking == ATTACKING_RECORD)
+		if (modeattacking)
 			M_EndModeAttackRun();
 		else
 			D_AdvanceDemo();
diff --git a/src/g_game.h b/src/g_game.h
index 03bbce60638ba6ea50b584c1e6e5400defcb0fc1..0d1720d186a2db048ce60f53c68561a7ca52a36a 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -43,7 +43,6 @@ extern boolean singledemo;
 extern boolean demo_start;
 
 extern mobj_t *metalplayback;
-extern boolean metal_start;
 
 // gametic at level start
 extern tic_t levelstarttic;
@@ -147,6 +146,8 @@ void G_ConsGhostTic(void);
 void G_GhostTicker(void);
 void G_ReadMetalTic(mobj_t *metal);
 void G_WriteMetalTic(mobj_t *metal);
+void G_SaveMetal(UINT8 **buffer);
+void G_LoadMetal(UINT8 **buffer);
 
 void G_DoPlayDemo(char *defdemoname);
 void G_TimeDemo(const char *name);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 3e5ee377b2ad209beeca643871ac50fa2155c647..0ae245855c84e56771d605644b3563c8fff00006 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -3487,6 +3487,46 @@ static fixed_t HWR_OpaqueFloorAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t hei
 	return floorz;
 }
 
+//
+// HWR_DoCulling
+// Hardware version of R_DoCulling
+// (see r_main.c)
+static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float vz, float bottomh, float toph)
+{
+	float cullplane;
+
+	if (!cullheight)
+		return false;
+
+	cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight);
+	if (cullheight->flags & ML_NOCLIMB) // Group culling
+	{
+		if (!viewcullheight)
+			return false;
+
+		// Make sure this is part of the same group
+		if (viewcullheight->frontsector == cullheight->frontsector)
+		{
+			// OK, we can cull
+			if (vz > cullplane && toph < cullplane) // Cull if below plane
+				return true;
+
+			if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+				return true;
+		}
+	}
+	else // Quick culling
+	{
+		if (vz > cullplane && toph < cullplane) // Cull if below plane
+			return true;
+
+		if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+			return true;
+	}
+
+	return false;
+}
+
 // -----------------+
 // HWR_DrawSprite   : Draw flat sprites
 //                  : (monsters, bonuses, weapons, lights, ...)
@@ -4607,29 +4647,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (thing->subsector->sector->cullheight)
 	{
-		float cullplane = FIXED_TO_FLOAT(thing->subsector->sector->cullheight->frontsector->floorheight);
-		if (thing->subsector->sector->cullheight->flags & ML_NOCLIMB) // Group culling
-		{
-			// Make sure this is part of the same group
-			if (viewsector->cullheight && viewsector->cullheight->frontsector
-				== thing->subsector->sector->cullheight->frontsector)
-			{
-				// OK, we can cull
-				if (gr_viewz > cullplane && gzt < cullplane) // Cull if below plane
-					return;
-
-				if (gz > cullplane && gr_viewz <= cullplane) // Cull if above plane
-					return;
-			}
-		}
-		else // Quick culling
-		{
-			if (gr_viewz > cullplane && gzt < cullplane) // Cull if below plane
-				return;
-
-			if (gz > cullplane && gr_viewz <= cullplane) // Cull if above plane
-				return;
-		}
+		if (HWR_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, gr_viewz, gz, gzt))
+			return;
 	}
 
 	heightsec = thing->subsector->sector->heightsec;
@@ -4795,6 +4814,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 	FOutVector v[4];
 	angle_t angle;
 	float dimensionmultiply;
+	float aspectratio;
+	float angleturn;
 
 //  3--2
 //  | /|
@@ -4830,9 +4851,9 @@ static void HWR_DrawSkyBackground(player_t *player)
 	// Y
 	angle = aimingangle;
 
-	float aspectratio = (float)vid.width/(float)vid.height;
+	aspectratio = (float)vid.width/(float)vid.height;
 	dimensionmultiply = ((float)textures[skytexture]->height/(128.0f*aspectratio));
-	float angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
+	angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
 
 	// Middle of the sky should always be at angle 0
 	// need to keep correct aspect ratio with X
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 0f0f7e0148c166404bc80375d7b58cbea695602a..631bb66586fed9106a50d043de1bae43009f100f 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -399,10 +399,10 @@ static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
 
 #ifndef MINI_GL_COMPATIBILITY
 /* 1.3 functions for multitexturing */
-typedef void (APIENTRY *PFNGLACTIVETEXTUREPROC) (GLenum);
-static PFNGLACTIVETEXTUREPROC pglActiveTexture;
-typedef void (APIENTRY *PFNGLMULTITEXCOORD2FPROC) (GLenum, GLfloat, GLfloat);
-static PFNGLMULTITEXCOORD2FPROC pglMultiTexCoord2f;
+typedef void (APIENTRY *PFNglActiveTexture) (GLenum);
+static PFNglActiveTexture pglActiveTexture;
+typedef void (APIENTRY *PFNglMultiTexCoord2f) (GLenum, GLfloat, GLfloat);
+static PFNglMultiTexCoord2f pglMultiTexCoord2f;
 #endif
 #endif
 
@@ -526,21 +526,18 @@ boolean SetupGLFunc13(void)
 #ifdef STATIC_OPENGL
 	gl13 = true;
 #else
-	const char *glversion = (const char *)pglGetString(GL_VERSION);
-	UINT32 majorversion = 0, minorversion = 0;
-
-	if (glversion != NULL && sscanf(glversion, "%u.%u", &majorversion, &minorversion) == 2) // There is a version number I can identify
+	if (isExtAvailable("GL_ARB_multitexture", gl_extensions))
 	{
-		if (majorversion > 1 || (majorversion == 1 && minorversion >= 3)) // Version of OpenGL is equal to or greater than 1.3
-		{
-			// Get the functions
-			pglActiveTexture  = GetGLFunc("glActiveTexture");
-			pglMultiTexCoord2f  = GetGLFunc("glMultiTexCoord2f");
+		// Get the functions
+		pglActiveTexture  = GetGLFunc("glActiveTextureARB");
+		pglMultiTexCoord2f  = GetGLFunc("glMultiTexCoord2fARB");
+
+		gl13 = true; // This is now true, so the new fade mask stuff can be done, if OpenGL version is less than 1.3, it still uses the old fade stuff.
+		DBG_Printf("GL_ARB_multitexture support: enabled\n");
 
-			if (pglMultiTexCoord2f)
-				gl13 = true; // This is now true, so the new fade mask stuff can be done, if OpenGL version is less than 1.3, it still uses the old fade stuff.
-		}
 	}
+	else
+		DBG_Printf("GL_ARB_multitexture support: disabled\n");
 #undef GETOPENGLFUNC
 
 #endif
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index a6c6dbe0a843e64c47558a54fd38f3a0b06ec799..80e30beb5d54e0e7530604624f0a9a53c7657405 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1028,20 +1028,26 @@ UINT16 hu_demorings;
 static void HU_DrawDemoInfo(void)
 {
 	V_DrawString(4, 188-24, V_YELLOWMAP, va(M_GetText("%s's replay"), player_names[0]));
-	V_DrawString(4, 188-16, V_YELLOWMAP|V_MONOSPACE, "SCORE:");
-	V_DrawRightAlignedString(120, 188-16, V_MONOSPACE, va("%d", hu_demoscore));
-
-	V_DrawString(4, 188- 8, V_YELLOWMAP|V_MONOSPACE, "TIME:");
-	if (hu_demotime != UINT32_MAX)
-		V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, va("%i:%02i.%02i",
-			G_TicsToMinutes(hu_demotime,true),
-			G_TicsToSeconds(hu_demotime),
-			G_TicsToCentiseconds(hu_demotime)));
-	else
-		V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, "--:--.--");
+	if (modeattacking)
+	{
+		V_DrawString(4, 188-16, V_YELLOWMAP|V_MONOSPACE, "SCORE:");
+		V_DrawRightAlignedString(120, 188-16, V_MONOSPACE, va("%d", hu_demoscore));
+
+		V_DrawString(4, 188- 8, V_YELLOWMAP|V_MONOSPACE, "TIME:");
+		if (hu_demotime != UINT32_MAX)
+			V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, va("%i:%02i.%02i",
+				G_TicsToMinutes(hu_demotime,true),
+				G_TicsToSeconds(hu_demotime),
+				G_TicsToCentiseconds(hu_demotime)));
+		else
+			V_DrawRightAlignedString(120, 188- 8, V_MONOSPACE, "--:--.--");
 
-	V_DrawString(4, 188   , V_YELLOWMAP|V_MONOSPACE, "RINGS:");
-	V_DrawRightAlignedString(120, 188   , V_MONOSPACE, va("%d", hu_demorings));
+		if (modeattacking == ATTACKING_RECORD)
+		{
+			V_DrawString(4, 188   , V_YELLOWMAP|V_MONOSPACE, "RINGS:");
+			V_DrawRightAlignedString(120, 188   , V_MONOSPACE, va("%d", hu_demorings));
+		}
+	}
 }
 
 // Heads up displays drawer, call each frame
diff --git a/src/info.c b/src/info.c
index a15646f01a95c6aaa3e9b1cb07a0fbc23564020d..c8f754b54d44acefbbe4ce0e90c73a1983fe8d51 100644
--- a/src/info.c
+++ b/src/info.c
@@ -570,7 +570,7 @@ state_t states[NUMSTATES] =
 	{SPR_EGGO,  9,   8, {A_BossScream},            0, 0, S_EGGMOBILE3_DIE14},   // S_EGGMOBILE3_DIE13
 	{SPR_EGGO,  9,  -1, {A_BossDeath},             0, 0, S_NULL},               // S_EGGMOBILE3_DIE14
 	{SPR_EGGO, 10,   5, {NULL},                    0, 0, S_EGGMOBILE3_FLEE2},   // S_EGGMOBILE3_FLEE1
-	{SPR_EGGO, 11,   5, {A_BossScream},            0, 0, S_EGGMOBILE3_FLEE1},   // S_EGGMOBILE3_FLEE2
+	{SPR_EGGO, 11,   5, {NULL},                    0, 0, S_EGGMOBILE3_FLEE1},   // S_EGGMOBILE3_FLEE2
 
 	// Boss 3 Propeller
 	{SPR_PRPL, 0, 1, {NULL}, 0, 0, S_PROPELLER2}, // S_PROPELLER1
@@ -3349,7 +3349,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // seesound
 		2*TICRATE,      // reactiontime
 		sfx_None,       // attacksound
-		S_CCOMMAND1,    // painstate
+		S_CCOMMAND3,    // painstate
 		200,            // painchance
 		sfx_dmpain,     // painsound
 		S_NULL,         // meleestate
@@ -13452,7 +13452,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13479,7 +13479,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13506,7 +13506,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13533,7 +13533,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13560,7 +13560,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13587,7 +13587,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13614,7 +13614,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13641,7 +13641,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13668,7 +13668,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13695,7 +13695,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13722,7 +13722,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13749,7 +13749,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13776,7 +13776,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13803,7 +13803,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13830,7 +13830,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -13857,7 +13857,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		1000,           // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 6124b3498ce63033bfd1dd002fca596195c48ca8..ad5d740f8edbb1da5521971fc2844c57436f4e90 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1744,7 +1744,26 @@ static int lib_gDoReborn(lua_State *L)
 
 static int lib_gExitLevel(lua_State *L)
 {
+	int n = lua_gettop(L); // Num arguments
 	NOHUD
+
+	// LUA EXTENSION: Custom exit like support
+	// Supported:
+	//	G_ExitLevel();			[no modifications]
+	//	G_ExitLevel(int)		[nextmap override only]
+	//	G_ExitLevel(bool)		[skipstats only]
+	//	G_ExitLevel(int, bool)	[both of the above]
+	if (n >= 1)
+	{
+		if (lua_isnumber(L, 1) || n >= 2)
+		{
+			nextmapoverride = (INT16)luaL_checknumber(L, 1);
+			lua_pop(L, 1); // pop nextmapoverride; skipstats now 1 if available
+		}
+		skipstats = lua_optboolean(L, 1);
+	}
+	// ---
+
 	G_ExitLevel();
 	return 0;
 }
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 85d00533bf4500b2069ffb86b5a1198bad12f273..fae3bb7e6d1672a50f72d7d5e58c524bb65317c1 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -71,7 +71,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source); // Ho
 #define LUAh_JumpSpinSpecial(player) LUAh_PlayerHook(player, hook_JumpSpinSpecial) // Hook for P_DoJumpStuff (Spin button effect (mid-air))
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name
-boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo); // Hook for linedef executors
+boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors
 boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg); // Hook for chat messages
 boolean LUAh_DeathMsg(player_t *player, mobj_t *inflictor, mobj_t *source); // Hook for hurt messages
 
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 4e9328e1caefe3ef161be910c123ead8f0026d76..532726ac223afa4d686029cc0917731e405c0cfc 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -873,7 +873,7 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 }
 
 // Hook for linedef executors
-boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo)
+boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 {
 	if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8))))
 		return false;
@@ -898,7 +898,8 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo)
 
 	LUA_PushUserdata(gL, line, META_LINE);
 	LUA_PushUserdata(gL, mo, META_MOBJ);
-	LUA_Call(gL, 2); // pops hook function, line, mo
+	LUA_PushUserdata(gL, sector, META_SECTOR);
+	LUA_Call(gL, 3); // pops hook function, line, mo, sector
 
 	lua_pop(gL, -1);
 	lua_gc(gL, LUA_GCSTEP, 1);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 6800a2f984588d7a9b111a99e2e6c97dfca34d42..19390d50df00d5b811b330b349949726399ac884 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -13,6 +13,7 @@
 #include "doomdef.h"
 #ifdef HAVE_BLUA
 #include "r_defs.h"
+#include "r_local.h"
 #include "st_stuff.h" // hudinfo[]
 #include "g_game.h"
 #include "p_local.h" // camera_t
@@ -222,6 +223,11 @@ static int hudinfo_num(lua_State *L)
 	return 1;
 }
 
+static int colormap_get(lua_State *L)
+{
+	return luaL_error(L, "colormap is not a struct.");
+}
+
 static int patch_get(lua_State *L)
 {
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
@@ -352,7 +358,7 @@ static int libd_draw(lua_State *L)
 	patch = *((patch_t **)luaL_checkudata(L, 3, META_PATCH));
 	flags = luaL_optinteger(L, 4, 0);
 	if (!lua_isnoneornil(L, 5))
-		colormap = luaL_checkudata(L, 5, META_COLORMAP);
+		colormap = *((UINT8 **)luaL_checkudata(L, 5, META_COLORMAP));
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
@@ -374,7 +380,7 @@ static int libd_drawScaled(lua_State *L)
 	patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
 	flags = luaL_optinteger(L, 5, 0);
 	if (!lua_isnoneornil(L, 6))
-		colormap = luaL_checkudata(L, 6, META_COLORMAP);
+		colormap = *((UINT8 **)luaL_checkudata(L, 6, META_COLORMAP));
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
@@ -490,6 +496,35 @@ static int libd_stringWidth(lua_State *L)
 	return 1;
 }
 
+static int libd_getColormap(lua_State *L)
+{
+	INT32 skinnum = TC_DEFAULT;
+	skincolors_t color = luaL_optinteger(L, 2, 0);
+	UINT8* colormap = NULL;
+	//HUDSAFE
+	if (lua_isnoneornil(L, 1))
+		; // defaults to TC_DEFAULT
+	else if (lua_type(L, 1) == LUA_TNUMBER) // skin number
+	{
+		skinnum = (INT32)luaL_checkinteger(L, 1);
+		if (skinnum < TC_ALLWHITE || skinnum >= MAXSKINS)
+			return luaL_error(L, "argument #1 is out of range");
+	}
+	else // skin name
+	{
+		const char *skinname = luaL_checkstring(L, 1);
+		INT32 i = R_SkinAvailable(skinname);
+		if (i != -1) // if -1, just default to TC_DEFAULT as above
+			skinnum = i;
+	}
+
+	// all was successful above, now we generate the colormap at last!
+
+	colormap = R_GetTranslationColormap(skinnum, color, GTC_CACHE);
+	LUA_PushUserdata(L, colormap, META_COLORMAP); // push as META_COLORMAP userdata, specifically for patches to use!
+	return 1;
+}
+
 static luaL_Reg lib_draw[] = {
 	{"patchExists", libd_patchExists},
 	{"cachePatch", libd_cachePatch},
@@ -500,6 +535,7 @@ static luaL_Reg lib_draw[] = {
 	{"drawFill", libd_drawFill},
 	{"drawString", libd_drawString},
 	{"stringWidth", libd_stringWidth},
+	{"getColormap", libd_getColormap},
 	{NULL, NULL}
 };
 
@@ -592,6 +628,11 @@ int LUA_HudLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "hudinfo");
 
+	luaL_newmetatable(L, META_COLORMAP);
+		lua_pushcfunction(L, colormap_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L,1);
+
 	luaL_newmetatable(L, META_PATCH);
 		lua_pushcfunction(L, patch_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 3209fc5329fb0cabb068ef1619f388e5136a6eba..80f66ed60543c3607e184594c25b705c82d5a30a 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -89,7 +89,8 @@ enum line_e {
 	line_validcount,
 	line_firsttag,
 	line_nexttag,
-	line_text
+	line_text,
+	line_callcount
 };
 
 static const char *const line_opt[] = {
@@ -111,6 +112,7 @@ static const char *const line_opt[] = {
 	"firsttag",
 	"nexttag",
 	"text",
+	"callcount",
 	NULL};
 
 enum side_e {
@@ -574,6 +576,9 @@ static int line_get(lua_State *L)
 	case line_text:
 		lua_pushstring(L, line->text);
 		return 1;
+	case line_callcount:
+		lua_pushinteger(L, line->callcount);
+		return 1;
 	}
 	return 0;
 }
@@ -1033,7 +1038,7 @@ static int ffloor_set(lua_State *L)
 		boolean flag;
 		fixed_t lastpos = *ffloor->topheight;
 		sector_t *sector = &sectors[ffloor->secnum];
-		sector->floorheight = (fixed_t)luaL_checkinteger(L, 3);
+		sector->ceilingheight = (fixed_t)luaL_checkinteger(L, 3);
 		flag = P_CheckSector(sector, true);
 		if (flag && sector->numattached)
 		{
@@ -1052,7 +1057,7 @@ static int ffloor_set(lua_State *L)
 		boolean flag;
 		fixed_t lastpos = *ffloor->bottomheight;
 		sector_t *sector = &sectors[ffloor->secnum];
-		sector->ceilingheight = (fixed_t)luaL_checkinteger(L, 3);
+		sector->floorheight = (fixed_t)luaL_checkinteger(L, 3);
 		flag = P_CheckSector(sector, true);
 		if (flag && sector->numattached)
 		{
diff --git a/src/m_menu.c b/src/m_menu.c
index 830b264432b6b874f654511e032be99135940482..b8be38e825dc6816c487e4f7a0278ab46719df56 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -250,7 +250,8 @@ static void M_ModeAttackEndGame(INT32 choice);
 static void M_SetGuestReplay(INT32 choice);
 static void M_ChoosePlayer(INT32 choice);
 menu_t SP_GameStatsDef, SP_LevelStatsDef;
-static menu_t SP_TimeAttackDef, SP_NightsAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
+static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
+static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef;
 
 // Multiplayer
 #ifndef NONET
@@ -733,6 +734,17 @@ static menuitem_t SP_ReplayMenu[] =
 	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",           &SP_TimeAttackDef, 50}
 };
 
+static menuitem_t SP_NightsReplayMenu[] =
+{
+	{IT_WHITESTRING|IT_CALL, NULL, "Replay Best Score", M_ReplayTimeAttack, 8},
+	{IT_WHITESTRING|IT_CALL, NULL, "Replay Best Time",  M_ReplayTimeAttack,16},
+
+	{IT_WHITESTRING|IT_CALL, NULL, "Replay Last",       M_ReplayTimeAttack,29},
+	{IT_WHITESTRING|IT_CALL, NULL, "Replay Guest",      M_ReplayTimeAttack,37},
+
+	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",           &SP_NightsAttackDef, 50}
+};
+
 static menuitem_t SP_GuestReplayMenu[] =
 {
 	{IT_WHITESTRING|IT_CALL, NULL, "Save Best Score as Guest", M_SetGuestReplay, 0},
@@ -745,6 +757,17 @@ static menuitem_t SP_GuestReplayMenu[] =
 	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",                &SP_TimeAttackDef, 50}
 };
 
+static menuitem_t SP_NightsGuestReplayMenu[] =
+{
+	{IT_WHITESTRING|IT_CALL, NULL, "Save Best Score as Guest", M_SetGuestReplay, 8},
+	{IT_WHITESTRING|IT_CALL, NULL, "Save Best Time as Guest",  M_SetGuestReplay,16},
+	{IT_WHITESTRING|IT_CALL, NULL, "Save Last as Guest",       M_SetGuestReplay,24},
+
+	{IT_WHITESTRING|IT_CALL, NULL, "Delete Guest Replay",      M_SetGuestReplay,37},
+
+	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",                &SP_NightsAttackDef, 50}
+};
+
 static menuitem_t SP_GhostMenu[] =
 {
 	{IT_STRING|IT_CVAR,         NULL, "Best Score", &cv_ghost_bestscore, 0},
@@ -757,18 +780,37 @@ static menuitem_t SP_GhostMenu[] =
 	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",       &SP_TimeAttackDef,  50}
 };
 
+static menuitem_t SP_NightsGhostMenu[] =
+{
+	{IT_STRING|IT_CVAR,         NULL, "Best Score", &cv_ghost_bestscore, 8},
+	{IT_STRING|IT_CVAR,         NULL, "Best Time",  &cv_ghost_besttime, 16},
+	{IT_STRING|IT_CVAR,         NULL, "Last",       &cv_ghost_last,     24},
+
+	{IT_STRING|IT_CVAR,         NULL, "Guest",      &cv_ghost_guest,    37},
+
+	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",       &SP_NightsAttackDef,  50}
+};
+
 // Single Player Nights Attack
 static menuitem_t SP_NightsAttackMenu[] =
 {
 	{IT_STRING|IT_CVAR,        NULL, "Level",            &cv_nextmap,          44},
-	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,       60},
-	{IT_WHITESTRING|IT_CALL,   NULL, "Start",            M_ChooseNightsAttack, 130},
+	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,       54},
+
+	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,   108},
+	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,        118},
+	{IT_DISABLED,              NULL, "Ghosts...",        &SP_NightsGhostDef,         128},
+	{IT_WHITESTRING|IT_CALL,   NULL, "Start",            M_ChooseNightsAttack, 138},
 };
 
 enum
 {
 	nalevel,
 	narecords,
+
+	naguest,
+	nareplay,
+	naghost,
 	nastart
 };
 
@@ -1515,6 +1557,40 @@ static menu_t SP_NightsAttackDef =
 	0,
 	NULL
 };
+static menu_t SP_NightsReplayDef =
+{
+	"M_NIGHTS",
+	sizeof(SP_NightsReplayMenu)/sizeof(menuitem_t),
+	&SP_NightsAttackDef,
+	SP_NightsReplayMenu,
+	M_DrawNightsAttackMenu,
+	32, 120,
+	0,
+	NULL
+};
+static menu_t SP_NightsGuestReplayDef =
+{
+	"M_NIGHTS",
+	sizeof(SP_NightsGuestReplayMenu)/sizeof(menuitem_t),
+	&SP_NightsAttackDef,
+	SP_NightsGuestReplayMenu,
+	M_DrawNightsAttackMenu,
+	32, 120,
+	0,
+	NULL
+};
+static menu_t SP_NightsGhostDef =
+{
+	"M_NIGHTS",
+	sizeof(SP_NightsGhostMenu)/sizeof(menuitem_t),
+	&SP_NightsAttackDef,
+	SP_NightsGhostMenu,
+	M_DrawNightsAttackMenu,
+	32, 120,
+	0,
+	NULL
+};
+
 
 menu_t SP_PlayerDef =
 {
@@ -1682,7 +1758,7 @@ static void Nextmap_OnChange(void)
 	char *leveltitle;
 	char tabase[256];
 	short i;
-	boolean active = false;
+	boolean active;
 
 	// Update the string in the consvar.
 	Z_Free(cv_nextmap.zstring);
@@ -1697,9 +1773,53 @@ static void Nextmap_OnChange(void)
 			SP_NightsAttackMenu[narecords].status = IT_DISABLED;
 		else
 			SP_NightsAttackMenu[narecords].status = IT_STRING|IT_CVAR;
+
+		// Do the replay things.
+		active = false;
+		SP_NightsAttackMenu[naguest].status = IT_DISABLED;
+		SP_NightsAttackMenu[nareplay].status = IT_DISABLED;
+		SP_NightsAttackMenu[naghost].status = IT_DISABLED;
+
+		// Check if file exists, if not, disable REPLAY option
+		sprintf(tabase,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s",srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value));
+		for (i = 0; i < 4; i++) {
+			SP_NightsReplayMenu[i].status = IT_DISABLED;
+			SP_NightsGuestReplayMenu[i].status = IT_DISABLED;
+		}
+		if (FIL_FileExists(va("%s-score-best.lmp", tabase))) {
+			SP_NightsReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
+			SP_NightsGuestReplayMenu[0].status = IT_WHITESTRING|IT_CALL;
+			active = true;
+		}
+		if (FIL_FileExists(va("%s-time-best.lmp", tabase))) {
+			SP_NightsReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
+			SP_NightsGuestReplayMenu[1].status = IT_WHITESTRING|IT_CALL;
+			active = true;
+		}
+		if (FIL_FileExists(va("%s-last.lmp", tabase))) {
+			SP_NightsReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
+			SP_NightsGuestReplayMenu[2].status = IT_WHITESTRING|IT_CALL;
+			active = true;
+		}
+		if (FIL_FileExists(va("%s-guest.lmp", tabase))) {
+			SP_NightsReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
+			SP_NightsGuestReplayMenu[3].status = IT_WHITESTRING|IT_CALL;
+			active = true;
+		}
+		if (active) {
+			SP_NightsAttackMenu[naguest].status = IT_WHITESTRING|IT_SUBMENU;
+			SP_NightsAttackMenu[nareplay].status = IT_WHITESTRING|IT_SUBMENU;
+			SP_NightsAttackMenu[naghost].status = IT_WHITESTRING|IT_SUBMENU;
+		}
+		else if(itemOn == nareplay) // Reset lastOn so replay isn't still selected when not available.
+		{
+			currentMenu->lastOn = itemOn;
+			itemOn = nastart;
+		}
 	}
 	else if (currentMenu == &SP_TimeAttackDef)
 	{
+		active = false;
 		SP_TimeAttackMenu[taguest].status = IT_DISABLED;
 		SP_TimeAttackMenu[tareplay].status = IT_DISABLED;
 		SP_TimeAttackMenu[taghost].status = IT_DISABLED;
@@ -5287,55 +5407,70 @@ void M_DrawNightsAttackMenu(void)
 		if (P_HasGrades(cv_nextmap.value, 0))
 			V_DrawScaledPatch(200, 28 + 8, 0, ngradeletters[bestoverall]);
 
-		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
+		if (currentMenu == &SP_NightsAttackDef)
 		{
-			V_DrawString(160-88, 130, V_YELLOWMAP, "BEST GRADE:");
-			V_DrawSmallScaledPatch(160 + 86 - (ngradeletters[bestgrade]->width/2),
-				130 + 8 - (ngradeletters[bestgrade]->height/2),
-				0, ngradeletters[bestgrade]);
-		}
+			if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
+			{
+				V_DrawString(160-88, 112, V_YELLOWMAP, "BEST GRADE:");
+				V_DrawSmallScaledPatch(160 + 86 - (ngradeletters[bestgrade]->width/2),
+					112 + 8 - (ngradeletters[bestgrade]->height/2),
+					0, ngradeletters[bestgrade]);
+			}
 
-		if (!bestscore)
-			sprintf(beststr, "(none)");
-		else
-			sprintf(beststr, "%u", bestscore);
+			if (!bestscore)
+				sprintf(beststr, "(none)");
+			else
+				sprintf(beststr, "%u", bestscore);
 
-		V_DrawString(160 - 88, 140, V_YELLOWMAP, "BEST SCORE:");
-		V_DrawRightAlignedString(160 + 88, 140, V_ALLOWLOWERCASE, beststr);
+			V_DrawString(160 - 88, 122, V_YELLOWMAP, "BEST SCORE:");
+			V_DrawRightAlignedString(160 + 88, 122, V_ALLOWLOWERCASE, beststr);
 
-		if (besttime == UINT32_MAX)
-			sprintf(beststr, "(none)");
-		else
-			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
-			                                 G_TicsToSeconds(besttime),
-			                                 G_TicsToCentiseconds(besttime));
+			if (besttime == UINT32_MAX)
+				sprintf(beststr, "(none)");
+			else
+				sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
+																				 G_TicsToSeconds(besttime),
+																				 G_TicsToCentiseconds(besttime));
 
-		V_DrawString(160-88, 150, V_YELLOWMAP, "BEST TIME:");
-		V_DrawRightAlignedString(160+88, 150, V_ALLOWLOWERCASE, beststr);
+			V_DrawString(160-88, 132, V_YELLOWMAP, "BEST TIME:");
+			V_DrawRightAlignedString(160+88, 132, V_ALLOWLOWERCASE, beststr);
 
-		if (cv_dummymares.value == 0) {
-			// Draw record emblems.
-			em = M_GetLevelEmblems(cv_nextmap.value);
-			while (em)
-			{
-				switch (em->type)
+			if (cv_dummymares.value == 0) {
+				// Draw record emblems.
+				em = M_GetLevelEmblems(cv_nextmap.value);
+				while (em)
 				{
-					case ET_NGRADE: yHeight = 130; break;
-					case ET_NTIME:  yHeight = 150; break;
-					default:
-						goto skipThisOne;
-				}
+					switch (em->type)
+					{
+						case ET_NGRADE: yHeight = 112; break;
+						case ET_NTIME:  yHeight = 132; break;
+						default:
+							goto skipThisOne;
+					}
 
-				if (em->collected)
-					V_DrawSmallMappedPatch(160+88, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
-					                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
-				else
-					V_DrawSmallScaledPatch(160+88, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+					if (em->collected)
+						V_DrawSmallMappedPatch(160+88, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+																	 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
+					else
+						V_DrawSmallScaledPatch(160+88, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
 
-				skipThisOne:
-				em = M_GetLevelEmblems(-1);
+					skipThisOne:
+					em = M_GetLevelEmblems(-1);
+				}
 			}
 		}
+		// ALWAYS DRAW level name even when not on this menu!
+		else
+		{
+			consvar_t *ncv;
+			INT32 x = SP_NightsAttackDef.x;
+			INT32 y = SP_NightsAttackDef.y;
+
+			ncv = (consvar_t *)SP_NightsAttackMenu[0].itemaction;
+			V_DrawString(x, y + SP_NightsAttackMenu[0].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[0].text);
+			V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0),
+									 y + SP_NightsAttackMenu[0].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
+		}
 	}
 }
 
@@ -5368,16 +5503,25 @@ static void M_NightsAttack(INT32 choice)
 }
 
 // Player has selected the "START" from the nights attack screen
-// (no demo recording yet)
 static void M_ChooseNightsAttack(INT32 choice)
 {
+	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_NIGHTS;
 
-	// Demos and NiGHTS don't play well together
-	G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), (UINT8)(cv_chooseskin.value-1), false, false);
+	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
+	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
+
+	snprintf(nameofdemo, sizeof nameofdemo, "replay"PATHSEP"%s"PATHSEP"%s-last", timeattackfolder, G_BuildMapName(cv_nextmap.value));
+
+	if (!cv_autorecord.value)
+		remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo));
+	else
+		G_RecordDemo(nameofdemo);
+
+	G_DeferedInitNew(false, G_BuildMapName(cv_nextmap.value), 0, false, false);
 }
 
 // Player has selected the "START" from the time attack screen
@@ -5415,28 +5559,50 @@ static void M_ReplayTimeAttack(INT32 choice)
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_RECORD; // set modeattacking before G_DoPlayDemo so the map loader knows
 
-	switch(choice) {
-	default:
-	case 0: // best score
-		which = "score-best";
-		break;
-	case 1: // best time
-		which = "time-best";
-		break;
-	case 2: // best rings
-		which = "rings-best";
-		break;
-	case 3: // last
-		which = "last";
-		break;
-	case 4: // guest
-		// srb2/replay/main/map01-guest.lmp
-		G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
-		return;
+	if (currentMenu == &SP_ReplayDef)
+	{
+		switch(choice) {
+		default:
+		case 0: // best score
+			which = "score-best";
+			break;
+		case 1: // best time
+			which = "time-best";
+			break;
+		case 2: // best rings
+			which = "rings-best";
+			break;
+		case 3: // last
+			which = "last";
+			break;
+		case 4: // guest
+			// srb2/replay/main/map01-guest.lmp
+			G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
+			return;
+		}
+		// srb2/replay/main/map01-sonic-time-best.lmp
+		G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which));
+	}
+	else if (currentMenu == &SP_NightsReplayDef)
+	{
+		switch(choice) {
+		default:
+		case 0: // best score
+			which = "score-best";
+			break;
+		case 1: // best time
+			which = "time-best";
+			break;
+		case 2: // last
+			which = "last";
+			break;
+		case 3: // guest
+			which = "guest";
+			break;
+		}
+		// srb2/replay/main/map01-score-best.lmp
+		G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which));
 	}
-
-	// srb2/replay/main/map01-sonic-time-best.lmp
-	G_DoPlayDemo(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which));
 }
 
 static void M_EraseGuest(INT32 choice)
@@ -5445,17 +5611,24 @@ static void M_EraseGuest(INT32 choice)
 	(void)choice;
 	if (FIL_FileExists(rguest))
 		remove(rguest);
-	M_SetupNextMenu(&SP_TimeAttackDef);
+	if (currentMenu == &SP_NightsGuestReplayDef)
+		M_SetupNextMenu(&SP_NightsAttackDef);
+	else
+		M_SetupNextMenu(&SP_TimeAttackDef);
 	CV_AddValue(&cv_nextmap, -1);
 	CV_AddValue(&cv_nextmap, 1);
 	M_StartMessage(M_GetText("Guest replay data erased.\n"),NULL,MM_NOTHING);
 }
 
-static void M_OverwriteGuest(const char *which)
+static void M_OverwriteGuest(const char *which, boolean nights)
 {
 	char *rguest = Z_StrDup(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-guest.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value)));
 	UINT8 *buf;
-	size_t len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which), &buf);
+	size_t len;
+	if (!nights)
+		len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), cv_chooseskin.string, which), &buf);
+	else
+		len = FIL_ReadFile(va("%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s-%s.lmp", srb2home, timeattackfolder, G_BuildMapName(cv_nextmap.value), which), &buf);
 	if (!len) {
 		return;
 	}
@@ -5465,7 +5638,10 @@ static void M_OverwriteGuest(const char *which)
 	}
 	FIL_WriteFile(rguest, buf, len);
 	Z_Free(rguest);
-	M_SetupNextMenu(&SP_TimeAttackDef);
+	if (currentMenu == &SP_NightsGuestReplayDef)
+		M_SetupNextMenu(&SP_NightsAttackDef);
+	else
+		M_SetupNextMenu(&SP_TimeAttackDef);
 	CV_AddValue(&cv_nextmap, -1);
 	CV_AddValue(&cv_nextmap, 1);
 	M_StartMessage(M_GetText("Guest replay data saved.\n"),NULL,MM_NOTHING);
@@ -5474,30 +5650,32 @@ static void M_OverwriteGuest(const char *which)
 static void M_OverwriteGuest_Time(INT32 choice)
 {
 	(void)choice;
-	M_OverwriteGuest("time-best");
+	M_OverwriteGuest("time-best", currentMenu == &SP_NightsGuestReplayDef);
 }
 
 static void M_OverwriteGuest_Score(INT32 choice)
 {
 	(void)choice;
-	M_OverwriteGuest("score-best");
+	M_OverwriteGuest("score-best", currentMenu == &SP_NightsGuestReplayDef);
 }
 
 static void M_OverwriteGuest_Rings(INT32 choice)
 {
 	(void)choice;
-	M_OverwriteGuest("rings-best");
+	M_OverwriteGuest("rings-best", false);
 }
 
 static void M_OverwriteGuest_Last(INT32 choice)
 {
 	(void)choice;
-	M_OverwriteGuest("last");
+	M_OverwriteGuest("last", currentMenu == &SP_NightsGuestReplayDef);
 }
 
 static void M_SetGuestReplay(INT32 choice)
 {
 	void (*which)(INT32);
+	if (currentMenu == &SP_NightsGuestReplayDef && choice >= 2)
+		choice++; // skip best rings
 	switch(choice)
 	{
 	case 0: // best score
@@ -5526,51 +5704,39 @@ static void M_SetGuestReplay(INT32 choice)
 static void M_ModeAttackRetry(INT32 choice)
 {
 	(void)choice;
-	if (modeattacking == ATTACKING_RECORD) // Cancel recording
-	{
-		G_CheckDemoStatus();
+	G_CheckDemoStatus(); // Cancel recording
+	if (modeattacking == ATTACKING_RECORD)
 		M_ChooseTimeAttack(0);
-	}
 	else if (modeattacking == ATTACKING_NIGHTS)
-	{
-		// No demos to cancel
 		M_ChooseNightsAttack(0);
-	}
 }
 
 static void M_ModeAttackEndGame(INT32 choice)
 {
 	(void)choice;
-	if (modeattacking == ATTACKING_RECORD) // Cancel recording
-	{
-		G_CheckDemoStatus();
+	G_CheckDemoStatus(); // Cancel recording
 
-		if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
-			Command_ExitGame_f();
+	if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
+		Command_ExitGame_f();
 
-		M_StartControlPanel();
-		currentMenu = &SP_TimeAttackDef;
-		itemOn = currentMenu->lastOn;
-		modeattacking = ATTACKING_NONE;
-		G_SetGamestate(GS_TIMEATTACK);
-		S_ChangeMusic(mus_racent, true);
-		// Update replay availability.
-		CV_AddValue(&cv_nextmap, 1);
-		CV_AddValue(&cv_nextmap, -1);
-	}
-	else if (modeattacking == ATTACKING_NIGHTS)
+	M_StartControlPanel();
+	switch(modeattacking)
 	{
-		// No demos to cancel
-		if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
-			Command_ExitGame_f();
-
-		M_StartControlPanel();
+	default:
+	case ATTACKING_RECORD:
+		currentMenu = &SP_TimeAttackDef;
+		break;
+	case ATTACKING_NIGHTS:
 		currentMenu = &SP_NightsAttackDef;
-		itemOn = currentMenu->lastOn;
-		modeattacking = ATTACKING_NONE;
-		G_SetGamestate(GS_TIMEATTACK);
-		S_ChangeMusic(mus_racent, true);
+		break;
 	}
+	itemOn = currentMenu->lastOn;
+	G_SetGamestate(GS_TIMEATTACK);
+	modeattacking = ATTACKING_NONE;
+	S_ChangeMusic(mus_racent, true);
+	// Update replay availability.
+	CV_AddValue(&cv_nextmap, 1);
+	CV_AddValue(&cv_nextmap, -1);
 }
 
 // ========
diff --git a/src/m_random.c b/src/m_random.c
index ddf5e135b8a9a687019556c325a5415f5e572b25..fce65b88a468a2d429a2a9a1d62e8197fc3def43 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -131,7 +131,9 @@ INT32 P_RandomKeyD(const char *rfile, INT32 rline, INT32 a)
 {
 	CONS_Printf("P_RandomKey() at: %sp %d\n", rfile, rline);
 #endif
-	return (INT32)(((P_Random()|(P_Random() << 8))/65536.0f)*a);
+	INT32 prandom = P_Random(); // note: forcing explicit function call order
+	prandom |= P_Random() << 8; // (function call order is not strictly defined)
+	return (INT32)((prandom/65536.0f)*a);
 }
 
 /** Provides a random number in between a specific range.
@@ -148,7 +150,9 @@ INT32 P_RandomRangeD(const char *rfile, INT32 rline, INT32 a, INT32 b)
 {
 	CONS_Printf("P_RandomRange() at: %sp %d\n", rfile, rline);
 #endif
-	return (INT32)(((P_Random()|(P_Random() << 8))/65536.0f)*(b-a+1))+a;
+	INT32 prandom = P_Random(); // note: forcing explicit function call order
+	prandom |= P_Random() << 8; // (function call order is not strictly defined)
+	return (INT32)((prandom/65536.0f)*(b-a+1))+a;
 }
 
 /** Provides a random byte without saving what the seed would be.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 2a8280f14b7feb5a30b40d1ed0e69f21478dafb1..54e151123b041237a3bc38d78b12cb2d6ed02f41 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -2089,7 +2089,7 @@ void A_Boss1Laser(mobj_t *actor)
 		actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y);
 		if (mobjinfo[locvar1].seesound)
 			S_StartSound(actor, mobjinfo[locvar1].seesound);
-		if (!(actor->spawnpoint->options & MTF_AMBUSH))
+		if (!(actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH))
 		{
 			point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET);
 			point->fuse = actor->tics+1;
@@ -2097,10 +2097,10 @@ void A_Boss1Laser(mobj_t *actor)
 			P_SetTarget(&actor->target, point);
 		}
 	}
-	else if (actor->target && !(actor->spawnpoint->options & MTF_AMBUSH))
+	else if (actor->target && !(actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH))
 		actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y);
 
-	if (actor->spawnpoint->options & MTF_AMBUSH)
+	if (actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH)
 		angle = FixedAngle(FixedDiv(actor->tics*160*FRACUNIT, actor->state->tics*FRACUNIT) + 10*FRACUNIT);
 	else
 		angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y));
@@ -9608,10 +9608,6 @@ void A_TrapShot(mobj_t *actor)
 	missile->momz = FixedMul(FINESINE(vertang>>ANGLETOFINESHIFT), speed);
 }
 
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO: Test the HELL out of these, then remove these annoying lines that are only here to remind you of that -SH //
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
 // Function: A_VileTarget
 //
 // Description: Spawns an object directly on the target, and sets this object as the actor's tracer.
diff --git a/src/p_floor.c b/src/p_floor.c
index 6d28175eb83e783e576bb5593d497a29e857d507..f798174ad83850e9664e781bc43f94116234812d 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1242,11 +1242,8 @@ void T_FloatSector(levelspecthink_t *floater)
 
 	if (actionsector)
 	{
-		boolean tofloat = false;
-		boolean floatanyway = false; // Ignore the crumblestate setting.
-		fixed_t waterheight = actionsector->floorheight - 512*FRACUNIT;
-
-		waterheight = P_SectorCheckWater(actionsector, floater->sector); // find the highest suitable water block around
+		//boolean floatanyway = false; // Ignore the crumblestate setting.
+		fixed_t waterheight = P_SectorCheckWater(actionsector, floater->sector); // find the highest suitable water block around
 
 		if (waterheight == cheeseheight) // same height, no floating needed
 			;
@@ -1254,10 +1251,8 @@ void T_FloatSector(levelspecthink_t *floater)
 			;
 		else if (floater->sector->ceilingheight == actionsector->ceilingheight && waterheight > cheeseheight) // too high
 			;
-		else // we have something to float in! Or we're for some reason above the ground, let's fall anyway
-			tofloat = true;
-
-		if (tofloat && (floater->sector->crumblestate == 0 || floater->sector->crumblestate >= 3 || floatanyway))
+		// we have something to float in! Or we're for some reason above the ground, let's fall anyway
+		else if (floater->sector->crumblestate == 0 || floater->sector->crumblestate >= 3/* || floatanyway*/)
 			EV_BounceSector(floater->sector, FRACUNIT, floater->sourceline);
 
 		P_RecalcPrecipInSector(actionsector);
@@ -2252,6 +2247,9 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 		// This should now run ONLY the stuff for eachtime->sourceline itself, instead of all trigger linedefs sharing the same tag.
 		// Makes much more sense doing it this way, honestly.
 		P_RunTriggerLinedef(eachtime->sourceline, players[affectPlayer].mo, sec);
+
+		if (!eachtime->sourceline->special) // this happens only for "Trigger on X calls" linedefs
+			P_RemoveThinker(&eachtime->thinker);
 	}
 }
 
@@ -2852,30 +2850,33 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	fixed_t topy, bottomy;
 	fixed_t topz;
 	fixed_t a, b, c;
+	mobjtype_t type = MT_ROCKCRUMBLE1;
+
+	// If the control sector has a special
+	// of Section3:7-15, use the custom debris.
+	if (GETSECSPECIAL(rover->master->frontsector->special, 3) >= 8)
+		type = MT_ROCKCRUMBLE1+(GETSECSPECIAL(rover->master->frontsector->special, 3)-7);
 
 	// soundorg z height never gets set normally, so MEH.
 	sec->soundorg.z = sec->floorheight;
 	S_StartSound(&sec->soundorg, sfx_crumbl);
 
-	// Find the leftmost vertex in the subsector.
+	// Find the outermost vertexes in the subsector
 	for (i = 0; i < sec->linecount; i++)
+	{
+		// Find the leftmost vertex in the subsector.
 		if ((sec->lines[i]->v1->x < sec->lines[leftmostvertex]->v1->x))
 			leftmostvertex = i;
-
-	// Find the rightmost vertex in the subsector.
-	for (i = 0; i < sec->linecount; i++)
+		// Find the rightmost vertex in the subsector.
 		if ((sec->lines[i]->v1->x > sec->lines[rightmostvertex]->v1->x))
 			rightmostvertex = i;
-
-	// Find the topmost vertex in the subsector.
-	for (i = 0; i < sec->linecount; i++)
+		// Find the topmost vertex in the subsector.
 		if ((sec->lines[i]->v1->y > sec->lines[topmostvertex]->v1->y))
 			topmostvertex = i;
-
-	// Find the bottommost vertex in the subsector.
-	for (i = 0; i < sec->linecount; i++)
+		// Find the bottommost vertex in the subsector.
 		if ((sec->lines[i]->v1->y < sec->lines[bottommostvertex]->v1->y))
 			bottommostvertex = i;
+	}
 
 	leftx = sec->lines[leftmostvertex]->v1->x+(16<<FRACBITS);
 	rightx = sec->lines[rightmostvertex]->v1->x;
@@ -2892,13 +2893,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 				mobj_t *spawned = NULL;
 				for (c = topz; c > *rover->bottomheight; c -= (32<<FRACBITS))
 				{
-					// If the control sector has a special
-					// of Section3:7-15, use the custom debris.
-					if (GETSECSPECIAL(rover->master->frontsector->special, 3) >= 8)
-						spawned = P_SpawnMobj(a, b, c, MT_ROCKCRUMBLE1+(GETSECSPECIAL(rover->master->frontsector->special, 3)-7));
-					else
-						spawned = P_SpawnMobj(a, b, c, MT_ROCKCRUMBLE1);
-
+					spawned = P_SpawnMobj(a, b, c, type);
 					spawned->fuse = 3*TICRATE;
 				}
 			}
diff --git a/src/p_local.h b/src/p_local.h
index f5c765fe69d9120401e075a5219ae2b67265a769..9f8918cd81092327de4dd5fdef5deeb9151a47b3 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -373,6 +373,10 @@ void P_DoNightsScore(player_t *player);
 
 extern INT32 ceilmovesound;
 
+// Factor to scale scrolling effect into mobj-carrying properties = 3/32.
+// (This is so scrolling floors and objects on them can move at same speed.)
+#define CARRYFACTOR ((3*FRACUNIT)/32)
+
 void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle,
 			INT16 starpostx, INT16 starposty, INT16 starpostz,
 			INT32 starpostnum, tic_t starposttime, angle_t starpostangle,
diff --git a/src/p_map.c b/src/p_map.c
index be59af0a852a413c22071e20add3132e86489f1f..62cbf7b77635b8604cd7ecf0ad7f01628e5c976f 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -211,12 +211,160 @@ void P_DoSpring(mobj_t *spring, mobj_t *object)
 	}
 }
 
+static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
+{
+	player_t *p = object->player; // will be NULL if not a player
+	fixed_t zdist; // distance between bottoms
+	fixed_t speed = spring->info->mass; // conveniently, both fans and gas jets use this for the vertical thrust
+	SINT8 flipval = P_MobjFlip(spring); // virtually everything here centers around the thruster's gravity, not the object's!
+
+	if (p && object->state == &states[object->info->painstate]) // can't use fans and gas jets when player is in pain!
+		return;
+
+	// is object below thruster's position? if not, calculate distance between their bottoms
+	if (spring->eflags & MFE_VERTICALFLIP)
+	{
+		if (object->z + object->height > spring->z + spring->height)
+			return;
+		zdist = (spring->z + spring->height) - (object->z + object->height);
+	}
+	else
+	{
+		if (object->z < spring->z)
+			return;
+		zdist = object->z - spring->z;
+	}
+
+	switch (spring->type)
+	{
+		case MT_FAN: // fan
+			if (zdist > (spring->health << FRACBITS)) // max z distance determined by health (set by map thing angle)
+				break;
+			if (flipval*object->momz >= FixedMul(speed, spring->scale)) // if object's already moving faster than your best, don't bother
+				break;
+			if (p && (p->climbing || p->pflags & PF_GLIDING)) // doesn't affect Knux when he's using his abilities!
+				break;
+
+			object->momz += flipval*FixedMul(speed/4, spring->scale);
+
+			// limit the speed if too high
+			if (flipval*object->momz > FixedMul(speed, spring->scale))
+				object->momz = flipval*FixedMul(speed, spring->scale);
+
+			if (p && !p->powers[pw_tailsfly]) // doesn't reset anim for Tails' flight
+			{
+				P_ResetPlayer(p);
+				if (p->panim != PA_FALL)
+					P_SetPlayerMobjState(object, S_PLAY_FALL1);
+			}
+			break;
+		case MT_STEAM: // Steam
+			if (zdist > FixedMul(16*FRACUNIT, spring->scale))
+				break;
+			if (spring->state != &states[S_STEAM1]) // Only when it bursts
+				break;
+
+			object->momz = flipval*FixedMul(speed, FixedSqrt(FixedMul(spring->scale, object->scale))); // scale the speed with both objects' scales, just like with springs!
+
+			if (p)
+			{
+				P_ResetPlayer(p);
+				if (p->panim != PA_FALL)
+					P_SetPlayerMobjState(object, S_PLAY_FALL1);
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void P_DoTailsCarry(player_t *sonic, player_t *tails)
+{
+	INT32 p;
+	fixed_t zdist; // z distance between the two players' bottoms
+
+	if ((tails->pflags & PF_CARRIED) && tails->mo->tracer == sonic->mo)
+		return;
+	if ((sonic->pflags & PF_CARRIED) && sonic->mo->tracer == tails->mo)
+		return;
+
+	if (!tails->powers[pw_tailsfly] && !(tails->charability == CA_FLY && (tails->mo->state >= &states[S_PLAY_SPC1] && tails->mo->state <= &states[S_PLAY_SPC4])))
+		return;
+
+	if (tails->bot == 1)
+		return;
+
+	if (sonic->pflags & PF_NIGHTSMODE)
+		return;
+
+	if (sonic->mo->tracer && sonic->mo->tracer->type == MT_TUBEWAYPOINT
+	&& !(sonic->pflags & PF_ROPEHANG))
+		return; // don't steal players from zoomtubes!
+
+	if ((sonic->mo->eflags & MFE_VERTICALFLIP) != (tails->mo->eflags & MFE_VERTICALFLIP))
+		return; // Both should be in same gravity
+
+	if (tails->mo->eflags & MFE_VERTICALFLIP)
+	{
+		if (tails->mo->ceilingz - (tails->mo->z + tails->mo->height) < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale))
+			return;
+	}
+	else if (tails->mo->z - tails->mo->floorz < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale))
+		return; // No room to pick up this guy!
+
+	// Search in case another player is already being carried by this fox.
+	for (p = 0; p < MAXPLAYERS; p++)
+		if (playeringame[p] && players[p].mo
+		&& players[p].pflags & PF_CARRIED && players[p].mo->tracer == tails->mo)
+			return;
+
+	if (tails->mo->eflags & MFE_VERTICALFLIP)
+		zdist = (sonic->mo->z + sonic->mo->height) - (tails->mo->z + tails->mo->height);
+	else
+		zdist = tails->mo->z - sonic->mo->z;
+
+	if (zdist <= sonic->mo->height + FixedMul(FRACUNIT, sonic->mo->scale)
+		&& zdist > sonic->mo->height*2/3
+		&& P_MobjFlip(tails->mo)*sonic->mo->momz <= 0)
+	{
+	// Why block opposing teams from tailsflying each other?
+		// Sneaking into the hands of a flying tails player in Race might be a viable strategy, who knows.
+		/*
+		if (gametype == GT_RACE || gametype == GT_COMPETITION
+			|| (netgame && (tails->spectator || sonic->spectator))
+			|| (G_TagGametype() && (!(tails->pflags & PF_TAGIT) != !(sonic->pflags & PF_TAGIT)))
+			|| (gametype == GT_MATCH)
+			|| (G_GametypeHasTeams() && tails->ctfteam != sonic->ctfteam))
+			sonic->pflags &= ~PF_CARRIED; */
+		if (tails->spectator || sonic->spectator)
+			sonic->pflags &= ~PF_CARRIED;
+		else
+		{
+			if (sonic-players == consoleplayer && botingame)
+				CV_SetValue(&cv_analog2, false);
+			P_ResetPlayer(sonic);
+			P_SetTarget(&sonic->mo->tracer, tails->mo);
+			sonic->pflags |= PF_CARRIED;
+			S_StartSound(sonic->mo, sfx_s3k4a);
+			P_UnsetThingPosition(sonic->mo);
+			sonic->mo->x = tails->mo->x;
+			sonic->mo->y = tails->mo->y;
+			P_SetThingPosition(sonic->mo);
+		}
+	}
+	else {
+		if (sonic-players == consoleplayer && botingame)
+			CV_SetValue(&cv_analog2, true);
+		sonic->pflags &= ~PF_CARRIED;
+	}
+}
+
 //
 // PIT_CheckThing
 //
 static boolean PIT_CheckThing(mobj_t *thing)
 {
-	fixed_t blockdist, topz, tmtopz;
+	fixed_t blockdist;
 
 	// don't clip against self
 	tmsprung = false;
@@ -662,103 +810,17 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->flags & MF_PUSHABLE)
 	{
-		if (tmthing->eflags & MFE_VERTICALFLIP)
-		{
-			if (thing->z + thing->height <= tmthing->z + tmthing->height)
-			{
-				switch (tmthing->type)
-				{
-					case MT_FAN: // fan
-						if (thing->z + thing->height >= tmthing->z + tmthing->height - (tmthing->health << FRACBITS) && thing->momz > -FixedMul(tmthing->info->mass, tmthing->scale))
-						{
-							thing->momz -= FixedMul(tmthing->info->mass/4, tmthing->scale);
-
-							if (thing->momz < -FixedMul(tmthing->info->mass, tmthing->scale))
-								thing->momz = -FixedMul(tmthing->info->mass, tmthing->scale);
-						}
-						break;
-					case MT_STEAM: // Steam
-						if (tmthing->state == &states[S_STEAM1] && thing->z + thing->height >= tmthing->z + tmthing->height - FixedMul(16*FRACUNIT, tmthing->scale)) // Only when it bursts
-							thing->momz = -FixedMul(tmthing->info->mass, FixedSqrt(FixedMul(tmthing->scale, thing->scale)));
-						break;
-					default:
-						break;
-				}
-			}
-		}
-		else
-		{
-			if (thing->z >= tmthing->z)
-			{
-				switch (tmthing->type)
-				{
-					case MT_FAN: // fan
-						if (thing->z <= tmthing->z + (tmthing->health << FRACBITS) && thing->momz < FixedMul(tmthing->info->mass, tmthing->scale))
-						{
-							thing->momz += FixedMul(tmthing->info->mass/4, tmthing->scale);
-
-							if (thing->momz > FixedMul(tmthing->info->mass, tmthing->scale))
-								thing->momz = FixedMul(tmthing->info->mass, tmthing->scale);
-						}
-						break;
-					case MT_STEAM: // Steam
-						if (tmthing->state == &states[S_STEAM1] && thing->z <= tmthing->z + FixedMul(16*FRACUNIT, tmthing->scale)) // Only when it bursts
-							thing->momz = FixedMul(tmthing->info->mass, FixedSqrt(FixedMul(tmthing->scale, thing->scale)));
-						break;
-					default:
-						break;
-				}
-			}
-		}
+		if (tmthing->type == MT_FAN || tmthing->type == MT_STEAM)
+			P_DoFanAndGasJet(tmthing, thing);
 	}
 
 	if (tmthing->flags & MF_PUSHABLE)
 	{
-		if ((thing->eflags & MFE_VERTICALFLIP) && tmthing->z + tmthing->height <= thing->z + thing->height)
-		{
-			switch (thing->type)
-			{
-				case MT_FAN: // fan
-					if (tmthing->z + tmthing->height >= thing->z + thing->height - (thing->health << FRACBITS) && tmthing->momz > -FixedMul(thing->info->mass, thing->scale))
-					{
-						tmthing->momz -= FixedMul(thing->info->mass/4, thing->scale);
-
-						if (tmthing->momz < -FixedMul(thing->info->mass, thing->scale))
-							tmthing->momz = -FixedMul(thing->info->mass, thing->scale);
-					}
-					break;
-				case MT_STEAM: // Steam
-					if (thing->state == &states[S_STEAM1] && tmthing->z + tmthing->height >= thing->z + thing->height - FixedMul(16*FRACUNIT, thing->scale)) // Only when it bursts
-						tmthing->momz = -FixedMul(thing->info->mass, FixedSqrt(FixedMul(thing->scale, tmthing->scale)));
-					break;
-				default:
-					break;
-			}
-		}
-		else if (!(thing->eflags & MFE_VERTICALFLIP) && tmthing->z >= thing->z)
-		{
-			switch (thing->type)
-			{
-				case MT_FAN: // fan
-					if (tmthing->z <= thing->z + (thing->health << FRACBITS) && tmthing->momz < FixedMul(thing->info->mass, thing->scale))
-					{
-						tmthing->momz += FixedMul(thing->info->mass/4, thing->scale);
-
-						if (tmthing->momz > FixedMul(thing->info->mass, thing->scale))
-							tmthing->momz = FixedMul(thing->info->mass, thing->scale);
-					}
-					break;
-				case MT_STEAM: // Steam
-					if (thing->state == &states[S_STEAM1] && tmthing->z <= thing->z + FixedMul(16*FRACUNIT, thing->scale)) // Only when it bursts
-						tmthing->momz = FixedMul(thing->info->mass, FixedSqrt(FixedMul(thing->scale, tmthing->scale)));
-					break;
-				default:
-					break;
-			}
-		}
+		if (thing->type == MT_FAN || thing->type == MT_STEAM)
+			P_DoFanAndGasJet(thing, tmthing);
 
 		if ((!(thing->eflags & MFE_VERTICALFLIP) && (tmthing->z <= (thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)) && (tmthing->z + tmthing->height) >= thing->z))
-		|| ((thing->eflags & MFE_VERTICALFLIP) && (tmthing->z + tmthing->height >= (thing->z - FixedMul(FRACUNIT, thing->scale)) && tmthing->z <= (thing->z + thing->height))))
+		  || ((thing->eflags & MFE_VERTICALFLIP) && (tmthing->z + tmthing->height >= (thing->z - FixedMul(FRACUNIT, thing->scale)) && tmthing->z <= (thing->z + thing->height))))
 		{
 			if (thing->flags & MF_SPRING)
 			{
@@ -799,78 +861,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	{
 		if (tmthing->player && thing->player)
 		{
-			if ((tmthing->player->pflags & PF_CARRIED) && tmthing->tracer == thing)
-				return true;
-			else if ((thing->player->pflags & PF_CARRIED) && thing->tracer == tmthing)
-				return true;
-			else if (tmthing->player->powers[pw_tailsfly]
-				|| (tmthing->player->charability == CA_FLY && (tmthing->state >= &states[S_PLAY_SPC1] && tmthing->state <= &states[S_PLAY_SPC4])))
-			{
-				INT32 p;
-
-				if (tmthing->player->bot == 1)
-					return true;
-
-				if (thing->player->pflags & PF_NIGHTSMODE)
-					return true;
-
-				if (thing->tracer && thing->tracer->type == MT_TUBEWAYPOINT
-				&& !(thing->player->pflags & PF_ROPEHANG))
-					return true; // don't steal players from zoomtubes!
-
-				if ((thing->eflags & MFE_VERTICALFLIP) != (tmthing->eflags & MFE_VERTICALFLIP))
-					return true; // Both should be in same gravity
-
-				if ((tmthing->eflags & MFE_VERTICALFLIP)
-					&& tmthing->ceilingz - (tmthing->z + tmthing->height) < thing->height-FixedMul(2*FRACUNIT, thing->scale))
-					return true;
-				else if (tmthing->z - tmthing->floorz < thing->height-FixedMul(2*FRACUNIT, thing->scale))
-					return true; // No room to pick up this guy!
-
-				// Search in case another player is already being carried by this fox.
-				for (p = 0; p < MAXPLAYERS; p++)
-					if (playeringame[p] && players[p].mo
-					&& players[p].pflags & PF_CARRIED && players[p].mo->tracer == tmthing)
-						return true;
-
-				if ((!(tmthing->eflags & MFE_VERTICALFLIP) && (tmthing->z <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale))
-					&& tmthing->z > thing->z + thing->height*2/3
-					&& thing->momz <= 0)
-					|| ((tmthing->eflags & MFE_VERTICALFLIP) && (tmthing->z + tmthing->height >= thing->z - FixedMul(FRACUNIT, thing->scale))
-					&& tmthing->z + tmthing->height < thing->z + thing->height - thing->height*2/3
-					&& thing->momz >= 0))
-				{
-					// Why block opposing teams from tailsflying each other?
-					// Sneaking into the hands of a flying tails player in Race might be a viable strategy, who knows.
-					/*
-					if (gametype == GT_RACE || gametype == GT_COMPETITION
-						|| (netgame && (tmthing->player->spectator || thing->player->spectator))
-						|| (G_TagGametype() && (!(tmthing->player->pflags & PF_TAGIT) != !(thing->player->pflags & PF_TAGIT)))
-						|| (gametype == GT_MATCH)
-						|| (G_GametypeHasTeams() && tmthing->player->ctfteam != thing->player->ctfteam))
-						thing->player->pflags &= ~PF_CARRIED; */
-					if (tmthing->player->spectator || thing->player->spectator)
-						thing->player->pflags &= ~PF_CARRIED;
-					else
-					{
-						if (thing->player-players == consoleplayer && botingame)
-							CV_SetValue(&cv_analog2, false);
-						P_ResetPlayer(thing->player);
-						P_SetTarget(&thing->tracer, tmthing);
-						thing->player->pflags |= PF_CARRIED;
-						S_StartSound(thing, sfx_s3k4a);
-						P_UnsetThingPosition(thing);
-						thing->x = tmthing->x;
-						thing->y = tmthing->y;
-						P_SetThingPosition(thing);
-					}
-				}
-				else {
-					if (thing->player-players == consoleplayer && botingame)
-						CV_SetValue(&cv_analog2, true);
-					thing->player->pflags &= ~PF_CARRIED;
-				}
-			}
+			P_DoTailsCarry(thing->player, tmthing->player);
 			return true;
 		}
 	}
@@ -882,114 +873,33 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->player)
 	{
-		if (tmthing->eflags & MFE_VERTICALFLIP) //doesn't matter what gravity player's following! Just do your stuff in YOUR direction only
+		// Doesn't matter what gravity player's following! Just do your stuff in YOUR direction only
+		if (tmthing->eflags & MFE_VERTICALFLIP
+		&& (tmthing->z + tmthing->height + tmthing->momz > thing->z
+		 || tmthing->z + tmthing->height + tmthing->momz >= thing->z + thing->height))
+			;
+		else if (!(tmthing->eflags & MFE_VERTICALFLIP)
+		&& (tmthing->z + tmthing->momz > thing->z + thing->height
+		 || tmthing->z + tmthing->momz <= thing->z))
+			;
+		else if  (P_IsObjectOnGround(thing)
+			&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
+			&& (tmthing->flags & MF_SOLID))
 		{
-			// Objects kill you if it falls from above.
-			if (tmthing->z + tmthing->height + tmthing->momz >= thing->z
-				&& tmthing->z + tmthing->height + tmthing->momz < thing->z + thing->height
-				&& P_IsObjectOnGround(thing)
-				&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
-				&& (tmthing->flags & MF_SOLID))
-			{
-				if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
-				{
-					if (thing != tmthing->target)
-						P_DamageMobj(thing, tmthing, tmthing->target, 10000);
-
-					tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
-					// The tmthing->target allows the pusher of the object
-					// to get the point if he topples it on an opponent.
-				}
-			}
-
-			if (thing->z + thing->height <= tmthing->z + tmthing->height && !(thing->state == &states[thing->info->painstate])) // Stuff where da player don't gotta move
+			if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
 			{
-				switch (tmthing->type)
-				{
-					case MT_FAN: // fan
-						if (thing->z + thing->height >= tmthing->z + tmthing->height - (tmthing->health << FRACBITS) && thing->momz > -FixedMul(tmthing->info->mass, tmthing->scale) && !(thing->player->climbing || (thing->player->pflags & PF_GLIDING)))
-						{
-							thing->momz -= FixedMul(tmthing->info->mass/4, tmthing->scale);
-
-							if (thing->momz < -FixedMul(tmthing->info->mass, tmthing->scale))
-								thing->momz = -FixedMul(tmthing->info->mass, tmthing->scale);
+				// Objects kill you if it falls from above.
+				if (thing != tmthing->target)
+					P_DamageMobj(thing, tmthing, tmthing->target, 10000);
 
-							if (!thing->player->powers[pw_tailsfly])
-							{
-								P_ResetPlayer(thing->player);
-								if (thing->player->panim != PA_FALL)
-									P_SetPlayerMobjState(thing, S_PLAY_FALL1);
-							}
-						}
-						break;
-					case MT_STEAM: // Steam
-						if (tmthing->state == &states[S_STEAM1] && thing->z + thing->height >= tmthing->z + tmthing->height - FixedMul(16*FRACUNIT, tmthing->scale)) // Only when it bursts
-						{
-							thing->momz = -FixedMul(tmthing->info->mass, FixedSqrt(FixedMul(tmthing->scale, thing->scale)));
-							P_ResetPlayer(thing->player);
-							if (thing->player->panim != PA_FALL)
-								P_SetPlayerMobjState(thing, S_PLAY_FALL1);
-						}
-						break;
-					default:
-						break;
-				}
+				tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
+				// The tmthing->target allows the pusher of the object
+				// to get the point if he topples it on an opponent.
 			}
 		}
-		else
-		{
-			// Objects kill you if it falls from above.
-			if (tmthing->z + tmthing->momz <= thing->z + thing->height
-				&& tmthing->z + tmthing->momz > thing->z
-				&& P_IsObjectOnGround(thing)
-				&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
-				&& (tmthing->flags & MF_SOLID))
-			{
-				if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
-				{
-					if (thing != tmthing->target)
-						P_DamageMobj(thing, tmthing, tmthing->target, 10000);
-
-					tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
-					// The tmthing->target allows the pusher of the object
-					// to get the point if he topples it on an opponent.
-				}
-			}
-
-			if (thing->z >= tmthing->z && !(thing->state == &states[thing->info->painstate])) // Stuff where da player don't gotta move
-			{
-				switch (tmthing->type)
-				{
-					case MT_FAN: // fan
-						if (thing->z <= tmthing->z + (tmthing->health << FRACBITS) && thing->momz < FixedMul(tmthing->info->mass, tmthing->scale) && !(thing->player->climbing || (thing->player->pflags & PF_GLIDING)))
-						{
-							thing->momz += FixedMul(tmthing->info->mass/4, tmthing->scale);
 
-							if (thing->momz > FixedMul(tmthing->info->mass, tmthing->scale))
-								thing->momz = FixedMul(tmthing->info->mass, tmthing->scale);
-
-							if (!thing->player->powers[pw_tailsfly])
-							{
-								P_ResetPlayer(thing->player);
-								if (thing->player->panim != PA_FALL)
-									P_SetPlayerMobjState(thing, S_PLAY_FALL1);
-							}
-						}
-						break;
-					case MT_STEAM: // Steam
-						if (tmthing->state == &states[S_STEAM1] && thing->z <= tmthing->z + FixedMul(16*FRACUNIT, tmthing->scale)) // Only when it bursts
-						{
-							thing->momz = FixedMul(tmthing->info->mass, FixedSqrt(FixedMul(tmthing->scale, thing->scale)));
-							P_ResetPlayer(thing->player);
-							if (thing->player->panim != PA_FALL)
-								P_SetPlayerMobjState(thing, S_PLAY_FALL1);
-						}
-						break;
-					default:
-						break;
-				}
-			}
-		}
+		if (tmthing->type == MT_FAN || tmthing->type == MT_STEAM)
+			P_DoFanAndGasJet(tmthing, thing);
 	}
 
 	if (tmthing->player) // Is the moving/interacting object the player?
@@ -997,72 +907,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (!tmthing->health)
 			return true;
 
-		if ((thing->eflags & MFE_VERTICALFLIP) && tmthing->z + tmthing->height <= thing->z + thing->height && !(tmthing->state == &states[tmthing->info->painstate]))
-		{
-			switch (thing->type)
-			{
-				case MT_FAN: // fan
-					if (tmthing->z + tmthing->height >= thing->z + thing->height - (thing->health << FRACBITS) && tmthing->momz > -FixedMul(thing->info->mass, thing->scale) && !(tmthing->player->climbing || (tmthing->player->pflags & PF_GLIDING)))
-					{
-						tmthing->momz -= FixedMul(thing->info->mass/4, thing->scale);
-
-						if (tmthing->momz < -FixedMul(thing->info->mass, thing->scale))
-							tmthing->momz = -FixedMul(thing->info->mass, thing->scale);
-
-						if (!tmthing->player->powers[pw_tailsfly])
-						{
-							P_ResetPlayer(tmthing->player);
-							if (tmthing->player->panim != PA_FALL)
-								P_SetPlayerMobjState(tmthing, S_PLAY_FALL1);
-						}
-					}
-					break;
-				case MT_STEAM: // Steam
-					if (thing->state == &states[S_STEAM1] && tmthing->z + tmthing->height >= thing->z + thing->height - FixedMul(16*FRACUNIT, thing->scale)) // Only when it bursts
-					{
-						tmthing->momz = -FixedMul(thing->info->mass, FixedSqrt(FixedMul(thing->scale, tmthing->scale)));
-						P_ResetPlayer(tmthing->player);
-						if (tmthing->player->panim != PA_FALL)
-							P_SetPlayerMobjState(tmthing, S_PLAY_FALL1);
-					}
-					break;
-				default:
-					break;
-			}
-		}
-		else if (!(thing->eflags & MFE_VERTICALFLIP) && tmthing->z >= thing->z && !(tmthing->state == &states[tmthing->info->painstate]))
-		{
-			switch (thing->type)
-			{
-				case MT_FAN: // fan
-					if (tmthing->z <= thing->z + (thing->health << FRACBITS) && tmthing->momz < FixedMul(thing->info->mass, thing->scale) && !(tmthing->player->climbing || (tmthing->player->pflags & PF_GLIDING)))
-					{
-						tmthing->momz += FixedMul(thing->info->mass/4, thing->scale);
-
-						if (tmthing->momz > FixedMul(thing->info->mass, thing->scale))
-							tmthing->momz = FixedMul(thing->info->mass, thing->scale);
-
-						if (!tmthing->player->powers[pw_tailsfly])
-						{
-							P_ResetPlayer(tmthing->player);
-							if (tmthing->player->panim != PA_FALL)
-								P_SetPlayerMobjState(tmthing, S_PLAY_FALL1);
-						}
-					}
-					break;
-				case MT_STEAM: // Steam
-					if (thing->state == &states[S_STEAM1] && tmthing->z <= thing->z + FixedMul(16*FRACUNIT, thing->scale)) // Only when it bursts
-					{
-						tmthing->momz = FixedMul(thing->info->mass, FixedSqrt(FixedMul(thing->scale, tmthing->scale)));
-						P_ResetPlayer(tmthing->player);
-						if (tmthing->player->panim != PA_FALL)
-							P_SetPlayerMobjState(tmthing, S_PLAY_FALL1);
-					}
-					break;
-				default:
-					break;
-			}
-		}
+		if (thing->type == MT_FAN || thing->type == MT_STEAM)
+			P_DoFanAndGasJet(thing, tmthing);
 
 		// Are you touching the side of the object you're interacting with?
 		if (thing->z - FixedMul(FRACUNIT, thing->scale) <= tmthing->z + tmthing->height
@@ -1104,6 +950,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
 		&& (tmthing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID)
 	{
+		fixed_t topz, tmtopz;
+
 		if (tmthing->eflags & MFE_VERTICALFLIP)
 		{
 			// pass under
@@ -1415,8 +1263,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				;
 			else if (thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE))
 				;
-			else if (!((((rover->flags & FF_BLOCKPLAYER) && thing->player)
-				|| ((rover->flags & FF_BLOCKOTHERS) && !thing->player))
+			else if (!((rover->flags & FF_BLOCKPLAYER && thing->player)
+				|| (rover->flags & FF_BLOCKOTHERS && !thing->player)
 				|| rover->flags & FF_QUICKSAND))
 				continue;
 
@@ -1450,16 +1298,21 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 		}
 	}
 
+	// The bounding box is extended by MAXRADIUS
+	// because mobj_ts are grouped into mapblocks
+	// based on their origin point, and can overlap
+	// into adjacent blocks by up to MAXRADIUS units.
+
+	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
+
 #ifdef POLYOBJECTS
 	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
 	{
 		validcount++;
 
-		xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
-		xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
-		yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
-		yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
-
 		for (by = yl; by <= yh; by++)
 			for (bx = xl; bx <= xh; bx++)
 			{
@@ -1533,15 +1386,6 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 		return true;
 
 	// Check things first, possibly picking things up.
-	// The bounding box is extended by MAXRADIUS
-	// because mobj_ts are grouped into mapblocks
-	// based on their origin point, and can overlap
-	// into adjacent blocks by up to MAXRADIUS units.
-
-	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
-	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
-	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
-	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
 
 	// MF_NOCLIPTHING: used by camera to not be blocked by things
 	if (!(thing->flags & MF_NOCLIPTHING))
@@ -1671,16 +1515,21 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 		}
 	}
 
+	// The bounding box is extended by MAXRADIUS
+	// because mobj_ts are grouped into mapblocks
+	// based on their origin point, and can overlap
+	// into adjacent blocks by up to MAXRADIUS units.
+
+	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
+
 #ifdef POLYOBJECTS
 	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
 	{
 		validcount++;
 
-		xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
-		xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
-		yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
-		yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
-
 		for (by = yl; by <= yh; by++)
 			for (bx = xl; bx <= xh; bx++)
 			{
@@ -1749,18 +1598,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 	}
 #endif
 
-	// Check things.
-	// The bounding box is extended by MAXRADIUS
-	// because mobj_ts are grouped into mapblocks
-	// based on their origin point, and can overlap
-	// into adjacent blocks by up to MAXRADIUS units.
-
 	// check lines
-	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
-	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
-	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
-	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
-
 	for (bx = xl; bx <= xh; bx++)
 		for (by = yl; by <= yh; by++)
 			if (!P_BlockLinesIterator(bx, by, PIT_CheckCameraLine))
diff --git a/src/p_maputl.c b/src/p_maputl.c
index d46080abbf535c6c58883647a7402536e929e451..48dd54e8da8efbcebfc6183d9fa817698a2f54e8 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -538,8 +538,14 @@ void P_LineOpening(line_t *linedef)
 			// Check for frontsector's fake floors
 			for (rover = front->ffloors; rover; rover = rover->next)
 			{
-				if (!(rover->flags & FF_EXISTS) || !(((rover->flags & FF_BLOCKPLAYER) && tmthing->player)
-				|| ((rover->flags & FF_BLOCKOTHERS) && !tmthing->player))) continue;
+				if (!(rover->flags & FF_EXISTS))
+					continue;
+
+				if (tmthing->player && (P_CheckSolidLava(tmthing, rover) || P_CanRunOnWater(tmthing->player, rover)))
+					;
+				else if (!((rover->flags & FF_BLOCKPLAYER && tmthing->player)
+					|| (rover->flags & FF_BLOCKOTHERS && !tmthing->player)))
+					continue;
 
 				delta1 = abs(tmthing->z - (*rover->bottomheight + ((*rover->topheight - *rover->bottomheight)/2)));
 				delta2 = abs(thingtop - (*rover->bottomheight + ((*rover->topheight - *rover->bottomheight)/2)));
@@ -564,8 +570,14 @@ void P_LineOpening(line_t *linedef)
 			// Check for backsectors fake floors
 			for (rover = back->ffloors; rover; rover = rover->next)
 			{
-				if (!(rover->flags & FF_EXISTS) || !(((rover->flags & FF_BLOCKPLAYER) && tmthing->player)
-				|| ((rover->flags & FF_BLOCKOTHERS) && !tmthing->player))) continue;
+				if (!(rover->flags & FF_EXISTS))
+					continue;
+
+				if (tmthing->player && (P_CheckSolidLava(tmthing, rover) || P_CanRunOnWater(tmthing->player, rover)))
+					;
+				else if (!((rover->flags & FF_BLOCKPLAYER && tmthing->player)
+					|| (rover->flags & FF_BLOCKOTHERS && !tmthing->player)))
+					continue;
 
 				delta1 = abs(tmthing->z - (*rover->bottomheight + ((*rover->topheight - *rover->bottomheight)/2)));
 				delta2 = abs(thingtop - (*rover->bottomheight + ((*rover->topheight - *rover->bottomheight)/2)));
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 1ee30fbc7aea66528635255d7c8d26dc0ac8ff2e..e85e25b052948f6b882c3d769f1aa73b428855ba 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2030,6 +2030,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 					// Check if we're on a polyobject
 					// that triggers a linedef executor.
 					msecnode_t *node;
+					boolean stopmovecut = false;
 
 					for (node = mo->touching_sectorlist; node; node = node->m_snext)
 					{
@@ -2051,21 +2052,26 @@ static void P_PlayerZMovement(mobj_t *mo)
 
 								while(po)
 								{
-									if (!(po->flags & POF_LDEXEC)
-										|| !(po->flags & POF_SOLID))
+									if (!P_MobjInsidePolyobj(po, mo))
 									{
 										po = (polyobj_t *)(po->link.next);
 										continue;
 									}
 
-									if (!P_MobjInsidePolyobj(po, mo))
+									polysec = po->lines[0]->backsector;
+
+									// Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red
+									if ((mo->z == polysec->ceilingheight || mo->z+mo->height == polysec->floorheight) && (po->flags & POF_SOLID) && po->thinker)
+										stopmovecut = true;
+
+									if (!(po->flags & POF_LDEXEC)
+										|| !(po->flags & POF_SOLID))
 									{
 										po = (polyobj_t *)(po->link.next);
 										continue;
 									}
 
 									// We're inside it! Yess...
-									polysec = po->lines[0]->backsector;
 
 									if (mo->z == polysec->ceilingheight)
 									{
@@ -2080,6 +2086,8 @@ static void P_PlayerZMovement(mobj_t *mo)
 							}
 						}
 					}
+
+					if (!stopmovecut)
 #endif
 
 					// Cut momentum in half when you hit the ground and
@@ -5761,6 +5769,29 @@ void P_MobjThinker(mobj_t *mobj)
 			case MT_SEED:
 				mobj->momz = mobj->info->speed;
 				break;
+			case MT_ROCKCRUMBLE1:
+			case MT_ROCKCRUMBLE2:
+			case MT_ROCKCRUMBLE3:
+			case MT_ROCKCRUMBLE4:
+			case MT_ROCKCRUMBLE5:
+			case MT_ROCKCRUMBLE6:
+			case MT_ROCKCRUMBLE7:
+			case MT_ROCKCRUMBLE8:
+			case MT_ROCKCRUMBLE9:
+			case MT_ROCKCRUMBLE10:
+			case MT_ROCKCRUMBLE11:
+			case MT_ROCKCRUMBLE12:
+			case MT_ROCKCRUMBLE13:
+			case MT_ROCKCRUMBLE14:
+			case MT_ROCKCRUMBLE15:
+			case MT_ROCKCRUMBLE16:
+				if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
+					&& mobj->state != &states[mobj->info->deathstate])
+				{
+					P_SetMobjState(mobj, mobj->info->deathstate);
+					return;
+				}
+				break;
 			default:
 				if (mobj->fuse)
 				{ // Scenery object fuse! Very basic!
@@ -5951,29 +5982,6 @@ void P_MobjThinker(mobj_t *mobj)
 		}
 	else switch (mobj->type)
 	{
-		case MT_ROCKCRUMBLE1:
-		case MT_ROCKCRUMBLE2:
-		case MT_ROCKCRUMBLE3:
-		case MT_ROCKCRUMBLE4:
-		case MT_ROCKCRUMBLE5:
-		case MT_ROCKCRUMBLE6:
-		case MT_ROCKCRUMBLE7:
-		case MT_ROCKCRUMBLE8:
-		case MT_ROCKCRUMBLE9:
-		case MT_ROCKCRUMBLE10:
-		case MT_ROCKCRUMBLE11:
-		case MT_ROCKCRUMBLE12:
-		case MT_ROCKCRUMBLE13:
-		case MT_ROCKCRUMBLE14:
-		case MT_ROCKCRUMBLE15:
-		case MT_ROCKCRUMBLE16:
-			if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
-				&& mobj->state != &states[mobj->info->deathstate])
-			{
-				P_SetMobjState(mobj, mobj->info->deathstate);
-				return;
-			}
-			break;
 		case MT_EMERALDSPAWN:
 			if (mobj->threshold)
 			{
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index b6d7caa9cb8b0dd33c185b787320a4b89dddd4cb..9c955c97beeb30af6f0a341bf46fc998869c5c29 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -142,6 +142,16 @@ FUNCINLINE static ATTRINLINE void Polyobj_vecSub2(vertex_t *dst, vertex_t *v1, v
 	dst->y = v1->y - v2->y;
 }
 
+// Add the polyobject's thinker to the thinker list
+// Unlike P_AddThinker, this adds it to the front of the list instead of the back, so that carrying physics can work right. -Red
+FUNCINLINE static ATTRINLINE void PolyObj_AddThinker(thinker_t *th)
+{
+	thinkercap.next->prev = th;
+	th->next = thinkercap.next;
+	th->prev = &thinkercap;
+	thinkercap.next = th;
+}
+
 //
 // P_PointInsidePolyobj
 //
@@ -573,7 +583,7 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 	// set to default thrust; may be modified by attached thinkers
 	// TODO: support customized thrust?
 	po->thrust = FRACUNIT;
-	po->flags = 0;
+	po->spawnflags = po->flags = 0;
 
 	// 1. Search segs for "line start" special with tag matching this
 	//    polyobject's id number. If found, iterate through segs which
@@ -613,6 +623,8 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 			if (seg->linedef->flags & ML_NOCLIMB) // Has a linedef executor
 				po->flags |= POF_LDEXEC;
 
+			po->spawnflags = po->flags; // save original flags to reference later for netgames!
+
 			Polyobj_findSegs(po, seg);
 			po->parent = parentID;
 			if (po->parent == po->id) // do not allow a self-reference
@@ -966,6 +978,38 @@ static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 	}
 }
 
+//
+// Polyobj_slideThing
+//
+// Moves an object resting on top of a polyobject by (x, y). Template function to make alteration easier.
+//
+static void Polyobj_slideThing(mobj_t *mo, fixed_t dx, fixed_t dy)
+{
+	if (mo->player) { // Do something similar to conveyor movement. -Red
+		mo->player->cmomx += dx;
+		mo->player->cmomy += dy;
+
+		dx = FixedMul(dx, CARRYFACTOR);
+		dy = FixedMul(dy, CARRYFACTOR);
+
+		mo->player->cmomx -= dx;
+		mo->player->cmomy -= dy;
+
+		if (mo->player->pflags & PF_SPINNING && (mo->player->rmomx || mo->player->rmomy) && !(mo->player->pflags & PF_STARTDASH)) {
+#define SPINMULT 5184 // Consider this a substitute for properly calculating FRACUNIT-friction. I'm tired. -Red
+			dx = FixedMul(dx, SPINMULT);
+			dy = FixedMul(dy, SPINMULT);
+#undef SPINMULT
+		}
+
+		mo->momx += dx;
+		mo->momy += dy;
+
+		mo->player->onconveyor = 1;
+	} else
+		P_TryMove(mo, mo->x+dx, mo->y+dy, true);
+}
+
 //
 // Polyobj_carryThings
 //
@@ -1015,7 +1059,7 @@ static void Polyobj_carryThings(polyobj_t *po, fixed_t dx, fixed_t dy)
 				if (!P_MobjInsidePolyobj(po, mo))
 					continue;
 
-				P_TryMove(mo, mo->x+dx, mo->y+dy, true);
+				Polyobj_slideThing(mo, dx, dy);
 			}
 		}
 	}
@@ -1206,12 +1250,91 @@ static void Polyobj_rotateLine(line_t *ld)
 	}
 }
 
+//
+// Polyobj_rotateThings
+//
+// Causes objects resting on top of the rotating polyobject to 'ride' with its movement.
+//
+static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta, UINT8 turnthings)
+{
+	static INT32 pomovecount = 10000;
+	INT32 x, y;
+
+	pomovecount++;
+
+	if (!(po->flags & POF_SOLID))
+		return;
+
+	for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
+	{
+		for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
+		{
+			mobj_t *mo;
+
+			if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+				continue;
+
+			mo = blocklinks[y * bmapwidth + x];
+
+			for (; mo; mo = mo->bnext)
+			{
+				if (mo->lastlook == pomovecount)
+					continue;
+
+				mo->lastlook = pomovecount;
+
+				// always push players even if not solid
+				if (!((mo->flags & MF_SOLID) || mo->player))
+					continue;
+
+				if (mo->flags & MF_NOCLIP)
+					continue;
+
+				if ((mo->eflags & MFE_VERTICALFLIP) && mo->z + mo->height != po->lines[0]->backsector->floorheight)
+					continue;
+
+				if (!(mo->eflags & MFE_VERTICALFLIP) && mo->z != po->lines[0]->backsector->ceilingheight)
+					continue;
+
+				if (!P_MobjInsidePolyobj(po, mo))
+					continue;
+
+				{
+					fixed_t newxoff, newyoff;
+					angle_t angletoobj = R_PointToAngle2(origin.x, origin.y, mo->x, mo->y);
+					fixed_t disttoobj = R_PointToDist2(origin.x, origin.y, mo->x, mo->y);
+
+					if (mo->player) // Hack to fix players sliding off of spinning polys -Red
+					{
+						disttoobj = FixedMul(disttoobj, 0xfe40);
+					}
+
+					angletoobj += delta;
+					angletoobj >>= ANGLETOFINESHIFT;
+					newxoff = FixedMul(FINECOSINE(angletoobj), disttoobj);
+					newyoff = FixedMul(FINESINE(angletoobj), disttoobj);
+
+					Polyobj_slideThing(mo, origin.x+newxoff-mo->x, origin.y+newyoff-mo->y);
+
+					if (turnthings == 2 || (turnthings == 1 && !mo->player)) {
+						mo->angle += delta;
+						if (mo->player == &players[consoleplayer])
+							localangle = mo->angle;
+						else if (mo->player == &players[secondarydisplayplayer])
+							localangle2 = mo->angle;
+					}
+				}
+			}
+		}
+	}
+}
+
 //
 // Polyobj_rotate
 //
 // Rotates a polyobject around its start point.
 //
-static boolean Polyobj_rotate(polyobj_t *po, angle_t delta)
+static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings)
 {
 	size_t i;
 	angle_t angle;
@@ -1247,6 +1370,8 @@ static boolean Polyobj_rotate(polyobj_t *po, angle_t delta)
 	for (i = 0; i < po->numLines; ++i)
 		hitflags |= Polyobj_clipThings(po, po->lines[i]);
 
+	Polyobj_rotateThings(po, origin, delta, turnthings);
+
 	if (hitflags & 2)
 	{
 		// reset vertices to previous positions
@@ -1466,7 +1591,7 @@ void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y)
 	fixed_t dx, dy;
 
 	// first, rotate to the saved angle
-	Polyobj_rotate(po, angle);
+	Polyobj_rotate(po, angle, false);
 
 	// determine component distances to translate
 	dx = x - po->spawnSpot.x;
@@ -1513,7 +1638,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, th->turnobjs) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -1551,8 +1676,21 @@ void T_PolyObjRotate(polyrotate_t *th)
 FUNCINLINE static ATTRINLINE void Polyobj_componentSpeed(INT32 resVel, INT32 angle,
                                             fixed_t *xVel, fixed_t *yVel)
 {
-	*xVel = FixedMul(resVel, FINECOSINE(angle));
-	*yVel = FixedMul(resVel,   FINESINE(angle));
+	if (angle == 0)
+	{
+		*xVel = resVel;
+		*yVel = 0;
+	}
+	else if (angle == (INT32)(ANGLE_90>>ANGLETOFINESHIFT))
+	{
+		*xVel = 0;
+		*yVel = resVel;
+	}
+	else
+	{
+		*xVel = FixedMul(resVel, FINECOSINE(angle));
+		*yVel = FixedMul(resVel,   FINESINE(angle));
+	}
 }
 
 void T_PolyObjMove(polymove_t *th)
@@ -1694,15 +1832,16 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 	if (dist>>FRACBITS <= P_AproxDistance(P_AproxDistance(target->x - adjustx - momx, target->y - adjusty - momy), target->z - adjustz - momz)>>FRACBITS)
 	{
 		// If further away, set XYZ of polyobject to waypoint location
-		fixed_t amtx, amty;
+		fixed_t amtx, amty, amtz;
+		fixed_t diffz;
 		amtx = (target->x - th->diffx) - po->centerPt.x;
 		amty = (target->y - th->diffy) - po->centerPt.y;
 		Polyobj_moveXY(po, amtx, amty);
 		// TODO: use T_MovePlane
-		amtx = (po->lines[0]->backsector->ceilingheight - po->lines[0]->backsector->floorheight)/2;
-		po->lines[0]->backsector->floorheight = target->z - amtx;
-		po->lines[0]->backsector->ceilingheight = target->z + amtx;
-
+		amtz = (po->lines[0]->backsector->ceilingheight - po->lines[0]->backsector->floorheight)/2;
+		diffz = po->lines[0]->backsector->floorheight - (target->z - amtz);
+		po->lines[0]->backsector->floorheight = target->z - amtz;
+		po->lines[0]->backsector->ceilingheight = target->z + amtz;
 		// Apply action to mirroring polyobjects as well
 		start = 0;
 		while ((po = Polyobj_GetChild(oldpo, &start)))
@@ -1712,9 +1851,8 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 
 			Polyobj_moveXY(po, amtx, amty);
 			// TODO: use T_MovePlane
-			amtx = (po->lines[0]->backsector->ceilingheight - po->lines[0]->backsector->floorheight)/2;
-			po->lines[0]->backsector->floorheight = target->z - amtx;
-			po->lines[0]->backsector->ceilingheight = target->z + amtx;
+			po->lines[0]->backsector->floorheight += diffz; // move up/down by same amount as the parent did
+			po->lines[0]->backsector->ceilingheight += diffz;
 		}
 
 		po = oldpo;
@@ -2035,7 +2173,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, false) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -2091,6 +2229,46 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 	}
 }
 
+// T_PolyObjDisplace: shift a polyobject based on a control sector's heights. -Red
+void T_PolyObjDisplace(polydisplace_t *th)
+{
+	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
+	fixed_t newheights, delta;
+	fixed_t dx, dy;
+
+	if (!po)
+#ifdef RANGECHECK
+		I_Error("T_PolyDoorSwing: thinker has invalid id %d\n", th->polyObjNum);
+#else
+	{
+		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSwing: thinker with invalid id %d removed.\n", th->polyObjNum);
+		P_RemoveThinkerDelayed(&th->thinker);
+		return;
+	}
+#endif
+
+	// check for displacement due to override and reattach when possible
+	if (po->thinker == NULL)
+	{
+		po->thinker = &th->thinker;
+
+		// reset polyobject's thrust
+		po->thrust = FRACUNIT;
+	}
+
+	newheights = th->controlSector->floorheight+th->controlSector->ceilingheight;
+	delta = newheights-th->oldHeights;
+
+	if (!delta)
+		return;
+
+	dx = FixedMul(th->dx, delta);
+	dy = FixedMul(th->dy, delta);
+
+	if (Polyobj_moveXY(po, dx, dy))
+		th->oldHeights = newheights;
+}
+
 static inline INT32 Polyobj_AngSpeed(INT32 speed)
 {
 	return (speed*ANG1)>>3; // no FixedAngle()
@@ -2122,7 +2300,7 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotate;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2149,10 +2327,15 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 
 	oldpo = po;
 
+	th->turnobjs = prdata->turnobjs;
+
 	// apply action to mirroring polyobjects as well
 	start = 0;
 	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		prdata->polyObjNum = po->id; // change id to match child polyobject's
 		EV_DoPolyObjRotate(prdata);
+	}
 
 	// action was successful
 	return 1;
@@ -2182,7 +2365,7 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjMove;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2208,7 +2391,10 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	// apply action to mirroring polyobjects as well
 	start = 0;
 	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		pmdata->polyObjNum = po->id; // change id to match child polyobject's
 		EV_DoPolyObjMove(pmdata);
+	}
 
 	// action was successful
 	return 1;
@@ -2240,7 +2426,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjWaypoint;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2382,7 +2568,7 @@ static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new slide door thinker
 	th = Z_Malloc(sizeof(polyslidedoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSlide;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2430,7 +2616,7 @@ static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new swing door thinker
 	th = Z_Malloc(sizeof(polyswingdoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSwing;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2492,6 +2678,52 @@ INT32 EV_DoPolyDoor(polydoordata_t *doordata)
 	return 1;
 }
 
+INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
+{
+	polyobj_t *po;
+	polyobj_t *oldpo;
+	polydisplace_t *th;
+	INT32 start;
+
+	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
+	{
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
+		return 0;
+	}
+
+	// don't allow line actions to affect bad polyobjects
+	if (po->isBad)
+		return 0;
+
+	// create a new thinker
+	th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL);
+	th->thinker.function.acp1 = (actionf_p1)T_PolyObjDisplace;
+	PolyObj_AddThinker(&th->thinker);
+	po->thinker = &th->thinker;
+
+	// set fields
+	th->polyObjNum = prdata->polyObjNum;
+
+	th->controlSector = prdata->controlSector;
+	th->oldHeights = th->controlSector->floorheight+th->controlSector->ceilingheight;
+
+	th->dx = prdata->dx;
+	th->dy = prdata->dy;
+
+	oldpo = po;
+
+	// apply action to mirroring polyobjects as well
+	start = 0;
+	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		prdata->polyObjNum = po->id; // change id to match child polyobject's
+		EV_DoPolyObjDisplace(prdata);
+	}
+
+	// action was successful
+	return 1;
+}
+
 void T_PolyObjFlag(polymove_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -2567,7 +2799,7 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFlag;
-	P_AddThinker(&th->thinker);
+	PolyObj_AddThinker(&th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2586,7 +2818,10 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	// apply action to mirroring polyobjects as well
 	start = 0;
 	while ((po = Polyobj_GetChild(oldpo, &start)))
+	{
+		pfdata->tag = po->id;
 		EV_DoPolyObjFlag(pfdata);
+	}
 
 	// action was successful
 	return 1;
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index 4e21f4a8c4651ad128eb892158e6c7ae08dab760..71cf965e3b7c990ca937331da5f2c32bb8e6bc31 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -99,6 +99,9 @@ typedef struct polyobj_s
 
 	UINT8 isBad;         // a bad polyobject: should not be rendered/manipulated
 	INT32 translucency; // index to translucency tables
+
+	// these are saved for netgames, so do not let Lua touch these!
+	INT32 spawnflags; // Flags the polyobject originally spawned with
 } polyobj_t;
 
 //
@@ -122,6 +125,7 @@ typedef struct polyrotate_s
 	INT32 polyObjNum;    // numeric id of polyobject (avoid C pointers here)
 	INT32 speed;         // speed of movement per frame
 	INT32 distance;      // distance to move
+	UINT8 turnobjs;      // turn objects? 0=no, 1=everything but players, 2=everything
 } polyrotate_t;
 
 typedef struct polymove_s
@@ -189,6 +193,17 @@ typedef struct polyswingdoor_s
 	UINT8 closing;        // if true, is closing
 } polyswingdoor_t;
 
+typedef struct polydisplace_s
+{
+	thinker_t thinker; // must be first
+
+	INT32 polyObjNum;
+	struct sector_s *controlSector;
+	fixed_t dx;
+	fixed_t dy;
+	fixed_t oldHeights;
+} polydisplace_t;
+
 //
 // Line Activation Data Structures
 //
@@ -199,7 +214,8 @@ typedef struct polyrotdata_s
 	INT32 direction;    // direction of rotation
 	INT32 speed;        // angular speed
 	INT32 distance;     // distance to move
-	UINT8 overRide;      // if true, will override any action on the object
+	UINT8 turnobjs;     // rotate objects being carried?
+	UINT8 overRide;     // if true, will override any action on the object
 } polyrotdata_t;
 
 typedef struct polymovedata_s
@@ -208,7 +224,7 @@ typedef struct polymovedata_s
 	fixed_t distance;   // distance to move
 	fixed_t speed;      // linear speed
 	angle_t angle;      // angle of movement
-	UINT8 overRide;   // if true, will override any action on the object
+	UINT8 overRide;     // if true, will override any action on the object
 } polymovedata_t;
 
 typedef struct polywaypointdata_s
@@ -239,6 +255,14 @@ typedef struct polydoordata_s
 	INT32 delay;          // delay time after opening
 } polydoordata_t;
 
+typedef struct polydisplacedata_s
+{
+	INT32 polyObjNum;
+	struct sector_s *controlSector;
+	fixed_t dx;
+	fixed_t dy;
+} polydisplacedata_t;
+
 //
 // Functions
 //
@@ -258,12 +282,14 @@ void T_PolyObjMove  (polymove_t *);
 void T_PolyObjWaypoint (polywaypoint_t *);
 void T_PolyDoorSlide(polyslidedoor_t *);
 void T_PolyDoorSwing(polyswingdoor_t *);
+void T_PolyObjDisplace  (polydisplace_t *);
 void T_PolyObjFlag  (polymove_t *);
 
 INT32 EV_DoPolyDoor(polydoordata_t *);
 INT32 EV_DoPolyObjMove(polymovedata_t *);
 INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *);
 INT32 EV_DoPolyObjRotate(polyrotdata_t *);
+INT32 EV_DoPolyObjDisplace(polydisplacedata_t *);
 INT32 EV_DoPolyObjFlag(struct line_s *);
 
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 3392d1e241fb5961407c42f48993142a85cd400a..bf64d9a2b2bf11d49df16b7899a18ca70df850a0 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -143,13 +143,13 @@ static inline void P_NetArchivePlayers(void)
 		WRITEUINT16(save_p, players[i].flashcount);
 
 		WRITEUINT32(save_p, players[i].score);
-		WRITEINT32(save_p, players[i].dashspeed);
+		WRITEFIXED(save_p, players[i].dashspeed);
 		WRITEINT32(save_p, players[i].dashtime);
 		WRITESINT8(save_p, players[i].lives);
 		WRITESINT8(save_p, players[i].continues);
 		WRITESINT8(save_p, players[i].xtralife);
 		WRITEUINT8(save_p, players[i].gotcontinue);
-		WRITEINT32(save_p, players[i].speed);
+		WRITEFIXED(save_p, players[i].speed);
 		WRITEUINT8(save_p, players[i].jumping);
 		WRITEUINT8(save_p, players[i].secondjump);
 		WRITEUINT8(save_p, players[i].fly1);
@@ -172,8 +172,8 @@ static inline void P_NetArchivePlayers(void)
 		/////////////////////
 		// Race Mode Stuff //
 		/////////////////////
-		WRITEINT32(save_p, players[i].numboxes);
-		WRITEINT32(save_p, players[i].totalring);
+		WRITEINT16(save_p, players[i].numboxes);
+		WRITEINT16(save_p, players[i].totalring);
 		WRITEUINT32(save_p, players[i].realtime);
 		WRITEUINT8(save_p, players[i].laps);
 
@@ -211,7 +211,7 @@ static inline void P_NetArchivePlayers(void)
 		WRITEUINT32(save_p, players[i].marebegunat);
 		WRITEUINT32(save_p, players[i].startedtime);
 		WRITEUINT32(save_p, players[i].finishedtime);
-		WRITEUINT16(save_p, players[i].finishedrings);
+		WRITEINT16(save_p, players[i].finishedrings);
 		WRITEUINT32(save_p, players[i].marescore);
 		WRITEUINT32(save_p, players[i].lastmarescore);
 		WRITEUINT8(save_p, players[i].lastmare);
@@ -318,13 +318,13 @@ static inline void P_NetUnArchivePlayers(void)
 		players[i].flashcount = READUINT16(save_p);
 
 		players[i].score = READUINT32(save_p);
-		players[i].dashspeed = READINT32(save_p); // dashing speed
+		players[i].dashspeed = READFIXED(save_p); // dashing speed
 		players[i].dashtime = READINT32(save_p); // dashing speed
 		players[i].lives = READSINT8(save_p);
 		players[i].continues = READSINT8(save_p); // continues that player has acquired
 		players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter
 		players[i].gotcontinue = READUINT8(save_p); // got continue from stage
-		players[i].speed = READINT32(save_p); // Player's speed (distance formula of MOMX and MOMY values)
+		players[i].speed = READFIXED(save_p); // Player's speed (distance formula of MOMX and MOMY values)
 		players[i].jumping = READUINT8(save_p); // Jump counter
 		players[i].secondjump = READUINT8(save_p);
 		players[i].fly1 = READUINT8(save_p); // Tails flying
@@ -347,8 +347,8 @@ static inline void P_NetUnArchivePlayers(void)
 		/////////////////////
 		// Race Mode Stuff //
 		/////////////////////
-		players[i].numboxes = READINT32(save_p); // Number of item boxes obtained for Race Mode
-		players[i].totalring = READINT32(save_p); // Total number of rings obtained for Race Mode
+		players[i].numboxes = READINT16(save_p); // Number of item boxes obtained for Race Mode
+		players[i].totalring = READINT16(save_p); // Total number of rings obtained for Race Mode
 		players[i].realtime = READUINT32(save_p); // integer replacement for leveltime
 		players[i].laps = READUINT8(save_p); // Number of laps (optional)
 
@@ -386,7 +386,7 @@ static inline void P_NetUnArchivePlayers(void)
 		players[i].marebegunat = READUINT32(save_p);
 		players[i].startedtime = READUINT32(save_p);
 		players[i].finishedtime = READUINT32(save_p);
-		players[i].finishedrings = READUINT16(save_p);
+		players[i].finishedrings = READINT16(save_p);
 		players[i].marescore = READUINT32(save_p);
 		players[i].lastmarescore = READUINT32(save_p);
 		players[i].lastmare = READUINT8(save_p);
@@ -447,6 +447,7 @@ static inline void P_NetUnArchivePlayers(void)
 #define SD_LIGHT    0x10
 #define SD_SPECIAL  0x20
 #define SD_DIFF2    0x40
+#define SD_FFLOORS  0x80
 
 // diff2 flags
 #define SD_FXOFFS    0x01
@@ -459,6 +460,7 @@ static inline void P_NetUnArchivePlayers(void)
 
 #define LD_FLAG     0x01
 #define LD_SPECIAL  0x02
+#define LD_CLLCOUNT 0x04
 #define LD_S1TEXOFF 0x08
 #define LD_S1TOPTEX 0x10
 #define LD_S1BOTTEX 0x20
@@ -515,23 +517,37 @@ static void P_NetArchiveWorld(void)
 		if (ss->special != SHORT(ms->special))
 			diff |= SD_SPECIAL;
 
-		/// \todo this makes Flat Alignment (linetype 7) increase the savegame size!
-		if (ss->floor_xoffs != 0)
+		if (ss->floor_xoffs != ss->spawn_flr_xoffs)
 			diff2 |= SD_FXOFFS;
-		if (ss->floor_yoffs != 0)
+		if (ss->floor_yoffs != ss->spawn_flr_yoffs)
 			diff2 |= SD_FYOFFS;
-		if (ss->ceiling_xoffs != 0)
+		if (ss->ceiling_xoffs != ss->spawn_ceil_xoffs)
 			diff2 |= SD_CXOFFS;
-		if (ss->ceiling_yoffs != 0)
+		if (ss->ceiling_yoffs != ss->spawn_ceil_yoffs)
 			diff2 |= SD_CYOFFS;
-		if (ss->floorpic_angle != 0)
+		if (ss->floorpic_angle != ss->spawn_flrpic_angle)
 			diff2 |= SD_FLOORANG;
-		if (ss->ceilingpic_angle != 0)
+		if (ss->ceilingpic_angle != ss->spawn_flrpic_angle)
 			diff2 |= SD_CEILANG;
 
 		if (ss->tag != SHORT(ms->tag))
 			diff2 |= SD_TAG;
 
+		// Check if any of the sector's FOFs differ from how they spawned
+		if (ss->ffloors)
+		{
+			ffloor_t *rover;
+			for (rover = ss->ffloors; rover; rover = rover->next)
+			{
+				if (rover->flags != rover->spawnflags
+				|| rover->alpha != rover->spawnalpha)
+					{
+						diff |= SD_FFLOORS; // we found an FOF that changed!
+						break; // don't bother checking for more, we do that later
+					}
+			}
+		}
+
 		if (diff2)
 			diff |= SD_DIFF2;
 
@@ -573,6 +589,35 @@ static void P_NetArchiveWorld(void)
 				WRITEANGLE(put, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
 				WRITEANGLE(put, ss->ceilingpic_angle);
+
+			// Special case: save the stats of all modified ffloors along with their ffloor "number"s
+			// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
+			if (diff & SD_FFLOORS)
+			{
+				size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
+				ffloor_t *rover;
+				UINT8 fflr_diff;
+				for (rover = ss->ffloors; rover; rover = rover->next)
+				{
+					fflr_diff = 0; // reset diff flags
+					if (rover->flags != rover->spawnflags)
+						fflr_diff |= 1;
+					if (rover->alpha != rover->spawnalpha)
+						fflr_diff |= 2;
+
+					if (fflr_diff)
+					{
+						WRITEUINT16(put, j); // save ffloor "number"
+						WRITEUINT8(put, fflr_diff);
+						if (fflr_diff & 1)
+							WRITEUINT16(put, rover->flags);
+						if (fflr_diff & 2)
+							WRITEINT16(put, rover->alpha);
+					}
+					j++;
+				}
+				WRITEUINT16(put, 0xffff);
+			}
 		}
 	}
 
@@ -588,6 +633,9 @@ static void P_NetArchiveWorld(void)
 		if (li->special != SHORT(mld->special))
 			diff |= LD_SPECIAL;
 
+		if (mld->special == 321 || mld->special == 322) // only reason li->callcount would be non-zero is if either of these are involved
+			diff |= LD_CLLCOUNT;
+
 		if (li->sidenum[0] != 0xffff)
 		{
 			si = &sides[li->sidenum[0]];
@@ -633,6 +681,8 @@ static void P_NetArchiveWorld(void)
 				WRITEINT16(put, li->flags);
 			if (diff & LD_SPECIAL)
 				WRITEINT16(put, li->special);
+			if (diff & LD_CLLCOUNT)
+				WRITEINT16(put, li->callcount);
 
 			si = &sides[li->sidenum[0]];
 			if (diff & LD_S1TEXOFF)
@@ -732,6 +782,46 @@ static void P_NetUnArchiveWorld(void)
 			sectors[i].floorpic_angle  = READANGLE(get);
 		if (diff2 & SD_CEILANG)
 			sectors[i].ceilingpic_angle = READANGLE(get);
+
+		if (diff & SD_FFLOORS)
+		{
+			UINT16 j = 0; // number of current ffloor in loop
+			UINT16 fflr_i; // saved ffloor "number" of next modified ffloor
+			UINT16 fflr_diff; // saved ffloor diff
+			ffloor_t *rover;
+
+			rover = sectors[i].ffloors;
+			if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case...
+				I_Error("Sector does not have any ffloors!");
+
+			fflr_i = READUINT16(get); // get first modified ffloor's number ready
+			for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here?
+			{
+				if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already
+					break;
+				// should NEVER need to be checked
+				//if (rover == NULL)
+					//break;
+				if (j != fflr_i) // this ffloor was not modified
+				{
+					j++;
+					rover = rover->next;
+					continue;
+				}
+
+				fflr_diff = READUINT8(get);
+
+				if (fflr_diff & 1)
+					rover->flags = READUINT16(get);
+				if (fflr_diff & 2)
+					rover->alpha = READINT16(get);
+
+				fflr_i = READUINT16(get); // get next ffloor "number" ready
+
+				j++;
+				rover = rover->next;
+			}
+		}
 	}
 
 	for (;;)
@@ -754,6 +844,8 @@ static void P_NetUnArchiveWorld(void)
 			li->flags = READINT16(get);
 		if (diff & LD_SPECIAL)
 			li->special = READINT16(get);
+		if (diff & LD_CLLCOUNT)
+			li->callcount = READINT16(get);
 
 		si = &sides[li->sidenum[0]];
 		if (diff & LD_S1TEXOFF)
@@ -869,6 +961,7 @@ typedef enum
 	tc_polyslidedoor,
 	tc_polyswingdoor,
 	tc_polyflag,
+	tc_polydisplace,
 #endif
 	tc_end
 } specials_e;
@@ -1149,6 +1242,7 @@ static void SaveExecutorThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, SaveLine(ht->line));
 	WRITEUINT32(save_p, SaveMobjnum(ht->caller));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEINT32(save_p, ht->timer);
 }
 
@@ -1269,6 +1363,17 @@ static void SavePolyswingdoorThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->closing);
 }
 
+static void SavePolydisplaceThinker(const thinker_t *th, const UINT8 type)
+{
+	const polydisplace_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEINT32(save_p, ht->polyObjNum);
+	WRITEUINT32(save_p, SaveSector(ht->controlSector));
+	WRITEFIXED(save_p, ht->dx);
+	WRITEFIXED(save_p, ht->dy);
+	WRITEFIXED(save_p, ht->oldHeights);
+}
+
 #endif
 /*
 //
@@ -1696,6 +1801,11 @@ static void P_NetArchiveThinkers(void)
 			SavePolymoveThinker(th, tc_polyflag);
 			continue;
 		}
+		else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace)
+		{
+			SavePolydisplaceThinker(th, tc_polydisplace);
+			continue;
+		}
 #endif
 #ifdef PARANOIA
 		else if (th->function.acv != P_RemoveThinkerDelayed) // wait garbage collection
@@ -2057,6 +2167,7 @@ static inline void LoadExecutorThinker(actionf_p1 thinker)
 	ht->thinker.function.acp1 = thinker;
 	ht->line = LoadLine(READUINT32(save_p));
 	ht->caller = LoadMobj(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
 	ht->timer = READINT32(save_p);
 	P_AddThinker(&ht->thinker);
 }
@@ -2183,6 +2294,23 @@ static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
 	ht->closing = READUINT8(save_p);
 	P_AddThinker(&ht->thinker);
 }
+
+//
+// LoadPolydisplaceThinker
+//
+// Loads a polydisplace_t thinker
+//
+static inline void LoadPolydisplaceThinker(actionf_p1 thinker)
+{
+	polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->polyObjNum = READINT32(save_p);
+	ht->controlSector = LoadSector(READUINT32(save_p));
+	ht->dx = READFIXED(save_p);
+	ht->dy = READFIXED(save_p);
+	ht->oldHeights = READFIXED(save_p);
+	P_AddThinker(&ht->thinker);
+}
 #endif
 
 /*
@@ -2574,9 +2702,14 @@ static void P_NetUnArchiveThinkers(void)
 			case tc_polyswingdoor:
 				LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing);
 				break;
+
 			case tc_polyflag:
 				LoadPolymoveThinker((actionf_p1)T_PolyObjFlag);
 				break;
+
+			case tc_polydisplace:
+				LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace);
+				break;
 #endif
 			case tc_scroll:
 				LoadScrollThinker((actionf_p1)T_Scroll);
@@ -2617,13 +2750,29 @@ static void P_NetUnArchiveThinkers(void)
 // haleyjd 03/26/06: PolyObject saving code
 //
 #ifdef POLYOBJECTS
+#define PD_FLAGS  0x01
+#define PD_TRANS   0x02
+
 static inline void P_ArchivePolyObj(polyobj_t *po)
 {
+	UINT8 diff = 0;
 	WRITEINT32(save_p, po->id);
 	WRITEANGLE(save_p, po->angle);
 
 	WRITEFIXED(save_p, po->spawnSpot.x);
 	WRITEFIXED(save_p, po->spawnSpot.y);
+
+	if (po->flags != po->spawnflags)
+		diff |= PD_FLAGS;
+	if (po->translucency != 0)
+		diff |= PD_TRANS;
+
+	WRITEUINT8(save_p, diff);
+
+	if (diff & PD_FLAGS)
+		WRITEINT32(save_p, po->flags);
+	if (diff & PD_TRANS)
+		WRITEINT32(save_p, po->translucency);
 }
 
 static inline void P_UnArchivePolyObj(polyobj_t *po)
@@ -2631,6 +2780,7 @@ static inline void P_UnArchivePolyObj(polyobj_t *po)
 	INT32 id;
 	UINT32 angle;
 	fixed_t x, y;
+	UINT8 diff;
 
 	// nullify all polyobject thinker pointers;
 	// the thinkers themselves will fight over who gets the field
@@ -2644,6 +2794,13 @@ static inline void P_UnArchivePolyObj(polyobj_t *po)
 	x = READFIXED(save_p);
 	y = READFIXED(save_p);
 
+	diff = READUINT8(save_p);
+
+	if (diff & PD_FLAGS)
+		po->flags = READINT32(save_p);
+	if (diff & PD_TRANS)
+		po->translucency = READINT32(save_p);
+
 	// if the object is bad or isn't in the id hash, we can do nothing more
 	// with it, so return now
 	if (po->isBad || po != Polyobj_GetForNum(id))
@@ -2812,6 +2969,14 @@ static inline void P_NetArchiveSpecials(void)
 
 	// Current global weather type
 	WRITEUINT8(save_p, globalweather);
+
+	if (metalplayback) // Is metal sonic running?
+	{
+		WRITEUINT8(save_p, 0x01);
+		G_SaveMetal(&save_p);
+	}
+	else
+		WRITEUINT8(save_p, 0x00);
 }
 
 //
@@ -2851,6 +3016,9 @@ static void P_NetUnArchiveSpecials(void)
 		if (curWeather != PRECIP_NONE)
 			P_SwitchWeather(globalweather);
 	}
+
+	if (READUINT8(save_p) == 0x01) // metal sonic
+		G_LoadMetal(&save_p);
 }
 
 // =======================================================================
diff --git a/src/p_setup.c b/src/p_setup.c
index b3e85966aad52516c133bd6d8da1d58ee50da060..ca78db05a61f003a693ac83a92109991f3c856de 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -642,7 +642,9 @@ static void P_LoadSectors(lumpnum_t lumpnum)
 		ss->extra_colormap = NULL;
 
 		ss->floor_xoffs = ss->ceiling_xoffs = ss->floor_yoffs = ss->ceiling_yoffs = 0;
+		ss->spawn_flr_xoffs = ss->spawn_ceil_xoffs = ss->spawn_flr_yoffs = ss->spawn_ceil_yoffs = 0;
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
+		ss->spawn_flrpic_angle = ss->spawn_ceilpic_angle = 0;
 		ss->bottommap = ss->midmap = ss->topmap = -1;
 		ss->gravity = NULL;
 		ss->cullheight = NULL;
@@ -1139,6 +1141,7 @@ static void P_LoadLineDefs(lumpnum_t lumpnum)
 		ld->frontsector = ld->backsector = NULL;
 		ld->validcount = 0;
 		ld->firsttag = ld->nexttag = -1;
+		ld->callcount = 0;
 		// killough 11/98: fix common wad errors (missing sidedefs):
 
 		if (ld->sidenum[0] == 0xffff)
@@ -1380,7 +1383,6 @@ static void P_LoadSideDefs2(lumpnum_t lumpnum)
 						{
 							col = msd->bottomtexture;
 
-
 							sec->extra_colormap->fadergba =
 								(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
 								(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
@@ -1393,7 +1395,7 @@ static void P_LoadSideDefs2(lumpnum_t lumpnum)
 								sec->extra_colormap->fadergba += (25 << 24);
 						}
 						else
-							sec->extra_colormap->fadergba = 0x19000000;
+							sec->extra_colormap->fadergba = 0x19000000; // default alpha, (25 << 24)
 #undef ALPHA2INT
 #undef HEX2INT
 					}
@@ -2341,6 +2343,35 @@ static void P_LoadRecordGhosts(void)
 	free(gpath);
 }
 
+static void P_LoadNightsGhosts(void)
+{
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	char *gpath = malloc(glen);
+
+	if (!gpath)
+		return;
+
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+
+	// Best Score ghost
+	if (cv_ghost_bestscore.value && FIL_FileExists(va("%s-score-best.lmp", gpath)))
+			G_AddGhost(va("%s-score-best.lmp", gpath));
+
+	// Best Time ghost
+	if (cv_ghost_besttime.value && FIL_FileExists(va("%s-time-best.lmp", gpath)))
+			G_AddGhost(va("%s-time-best.lmp", gpath));
+
+	// Last ghost
+	if (cv_ghost_last.value && FIL_FileExists(va("%s-last.lmp", gpath)))
+		G_AddGhost(va("%s-last.lmp", gpath));
+
+	// Guest ghost
+	if (cv_ghost_guest.value && FIL_FileExists(va("%s-guest.lmp", gpath)))
+		G_AddGhost(va("%s-guest.lmp", gpath));
+
+	free(gpath);
+}
+
 /** Loads a level from a lump or external wad.
   *
   * \param skipprecip If true, don't spawn precipitation.
@@ -2602,6 +2633,8 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	if (modeattacking == ATTACKING_RECORD && !demoplayback)
 		P_LoadRecordGhosts();
+	else if (modeattacking == ATTACKING_NIGHTS && !demoplayback)
+		P_LoadNightsGhosts();
 
 	if (G_TagGametype())
 	{
@@ -2750,7 +2783,6 @@ boolean P_SetupLevel(boolean skipprecip)
 		R_PrecacheLevel();
 
 	nextmapoverride = 0;
-	nextmapgametype = -1;
 	skipstats = false;
 
 	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
diff --git a/src/p_spec.c b/src/p_spec.c
index 3c9af246df9785f7e61ec781730533e0ab09f058..323b93c6d8d18e2271c3d3316cc2405bf7c6a987 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -50,10 +50,6 @@ mobj_t *skyboxmo[2];
 // Amount (dx, dy) vector linedef is shifted right to get scroll amount
 #define SCROLL_SHIFT 5
 
-// Factor to scale scrolling effect into mobj-carrying properties = 3/32.
-// (This is so scrolling floors and objects on them can move at same speed.)
-#define CARRYFACTOR ((3*FRACUNIT)/32)
-
 /** Animated texture descriptor
   * This keeps track of an animated texture or an animated flat.
   * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
@@ -107,7 +103,7 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
 static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline);
 static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
-static void P_ProcessLineSpecial(line_t *line, mobj_t *mo);
+static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
 static void P_AddSpikeThinker(sector_t *sec, INT32 referrer);
 
@@ -1388,9 +1384,34 @@ static boolean PolyRotate(line_t *line)
 	// Polyobj_OR types have override set to true
 	prd.overRide  = (line->special == 485 || line->special == 487);
 
+	if (line->flags & ML_NOCLIMB)
+		prd.turnobjs = 0;
+	else if (line->flags & ML_EFFECT4)
+		prd.turnobjs = 2;
+	else
+		prd.turnobjs = 1;
+
 	return EV_DoPolyObjRotate(&prd);
 }
 
+//
+// PolyDisplace
+//
+// Parses arguments for parameterized polyobject move-by-sector-heights specials
+//
+static boolean PolyDisplace(line_t *line)
+{
+	polydisplacedata_t pdd;
+
+	pdd.polyObjNum = line->tag;
+
+	pdd.controlSector = line->frontsector;
+	pdd.dx = line->dx>>8;
+	pdd.dy = line->dy>>8;
+
+	return EV_DoPolyObjDisplace(&pdd);
+}
+
 #endif // ifdef POLYOBJECTS
 
 /** Changes a sector's tag.
@@ -1512,13 +1533,13 @@ void T_ExecutorDelay(executor_t *e)
 	{
 		if (e->caller && P_MobjWasRemoved(e->caller)) // If the mobj died while we were delaying
 			P_SetTarget(&e->caller, NULL); // Call with no mobj!
-		P_ProcessLineSpecial(e->line, e->caller);
+		P_ProcessLineSpecial(e->line, e->caller, e->sector);
 		P_SetTarget(&e->caller, NULL); // Let the mobj know it can be removed now.
 		P_RemoveThinker(&e->thinker);
 	}
 }
 
-static void P_AddExecutorDelay(line_t *line, mobj_t *mobj)
+static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
 {
 	executor_t *e;
 
@@ -1529,13 +1550,12 @@ static void P_AddExecutorDelay(line_t *line, mobj_t *mobj)
 
 	e->thinker.function.acp1 = (actionf_p1)T_ExecutorDelay;
 	e->line = line;
+	e->sector = sector;
 	e->timer = (line->backsector->ceilingheight>>FRACBITS)+(line->backsector->floorheight>>FRACBITS);
 	P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
 	P_AddThinker(&e->thinker);
 }
 
-static sector_t *triplinecaller;
-
 /** Used by P_LinedefExecute to check a trigger linedef's conditions
   * The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
   * Return false cancels P_LinedefExecute, this happens if a condition is not met.
@@ -1672,7 +1692,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		// If we were not triggered by a sector type especially for the purpose,
 		// a Linedef Executor linedef trigger is not handling sector triggers properly, return.
 
-		else if ((!GETSECSPECIAL(caller->special, 2) || GETSECSPECIAL(caller->special, 2) > 7) && (specialtype > 320))
+		else if ((!GETSECSPECIAL(caller->special, 2) || GETSECSPECIAL(caller->special, 2) > 7) && (specialtype > 322))
 		{
 			CONS_Alert(CONS_WARNING,
 				M_GetText("Linedef executor trigger isn't handling sector triggers properly!\nspecialtype = %d, if you are not a dev, report this warning instance\nalong with the wad that caused it!\n"),
@@ -1737,6 +1757,15 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 					return false;
 			}
 			break;
+		case 321: // continuous
+		case 322: // each time
+			// decrement calls left before triggering
+			if (triggerline->callcount > 0)
+			{
+				if (--triggerline->callcount > 0)
+					return false;
+			}
+			break;
 		default:
 			break;
 	}
@@ -1745,7 +1774,6 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	// Processing linedef specials //
 	/////////////////////////////////
 
-	triplinecaller = caller;
 	ctlsector = triggerline->frontsector;
 	sectori = (size_t)(ctlsector - sectors);
 	linecnt = ctlsector->linecount;
@@ -1757,9 +1785,9 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 				&& ctlsector->lines[i]->special < 500)
 			{
 				if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
-					P_AddExecutorDelay(ctlsector->lines[i], actor);
+					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
 				else
-					P_ProcessLineSpecial(ctlsector->lines[i], actor);
+					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
 			}
 	}
 	else // walk around the sector in a defined order
@@ -1845,13 +1873,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 				&& ctlsector->lines[i]->special < 500)
 			{
 				if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
-					P_AddExecutorDelay(ctlsector->lines[i], actor);
+					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
 				else
-					P_ProcessLineSpecial(ctlsector->lines[i], actor);
+					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
 			}
 		}
 	}
 
+	// "Trigger on X calls" linedefs reset if noclimb is set
+	if ((specialtype == 321 || specialtype == 322) && triggerline->flags & ML_NOCLIMB)
+		triggerline->callcount = sides[triggerline->sidenum[0]].textureoffset>>FRACBITS;
+	else
 	// These special types work only once
 	if (specialtype == 302  // Once
 	 || specialtype == 304  // Ring count - Once
@@ -1860,6 +1892,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	 || specialtype == 315  // No of pushables - Once
 	 || specialtype == 318  // Unlockable trigger - Once
 	 || specialtype == 320  // Unlockable - Once
+	 || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
 	 || specialtype == 399) // Level Load
 		triggerline->special = 0; // Clear it out
 
@@ -1899,7 +1932,8 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		 || lines[masterline].special == 301 // Each time
 		 || lines[masterline].special == 306 // Character ability - Each time
 		 || lines[masterline].special == 310 // CTF Red team - Each time
-		 || lines[masterline].special == 312) // CTF Blue team - Each time
+		 || lines[masterline].special == 312 // CTF Blue team - Each time
+		 || lines[masterline].special == 322) // Trigger on X calls - Each Time
 			continue;
 
 		if (lines[masterline].special < 300
@@ -2149,22 +2183,20 @@ static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s)
 }
 
 /** Processes the line special triggered by an object.
-  * The external variable ::triplinecaller points to the sector in which the
-  * action was initiated; it can be NULL. Because of the A_LinedefExecute()
-  * action, even if non-NULL, this sector might not have the same tag as the
-  * linedef executor, and it might not have the linedef executor sector type.
   *
   * \param line Line with the special command on it.
   * \param mo   mobj that triggered the line. Must be valid and non-NULL.
-  * \todo Get rid of the secret parameter and make ::triplinecaller actually get
-  *       passed to the function.
+  * \param callsec sector in which action was initiated; this can be NULL.
+  *        Because of the A_LinedefExecute() action, even if non-NULL,
+  *        this sector might not have the same tag as the linedef executor,
+  *        and it might not have the linedef executor sector type.
   * \todo Handle mo being NULL gracefully. T_MoveFloor() and T_MoveCeiling()
   *       don't have an object to pass.
   * \todo Split up into multiple functions.
   * \sa P_LinedefExecute
   * \author Graue <graue@oceanbase.org>
   */
-static void P_ProcessLineSpecial(line_t *line, mobj_t *mo)
+static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 {
 	INT32 secnum = -1;
 	mobj_t *bot = NULL;
@@ -2365,7 +2397,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo)
 				if (musicnum >= NUMMUSIC || musicnum == mus_None)
 					S_StopMusic();
 				else
-					S_ChangeMusic(mapmusic, !(line->flags & ML_NOCLIMB));
+					S_ChangeMusic(mapmusic, !(line->flags & ML_EFFECT4));
 
 				// Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
 				// if (mapmusic & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
@@ -2431,8 +2463,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo)
 					else if (line->flags & ML_BLOCKMONSTERS)
 					{
 						// play the sound from calling sector's soundorg
-						if (triplinecaller)
-							S_StartSound(&triplinecaller->soundorg, sfxnum);
+						if (callsec)
+							S_StartSound(&callsec->soundorg, sfxnum);
 						else if (mo)
 							S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
 					}
@@ -2976,7 +3008,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo)
 
 		case 443: // Calls a named Lua function
 #ifdef HAVE_BLUA
-			LUAh_LinedefExecute(line, mo);
+			LUAh_LinedefExecute(line, mo, callsec);
 #else
 			CONS_Alert(CONS_ERROR, "The map is trying to run a Lua script, but this exe was not compiled with Lua support!\n");
 #endif
@@ -3392,9 +3424,9 @@ static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *tar
 static inline boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
 {
 	if (mo->eflags & MFE_VERTICALFLIP)
-		return (mo->z+mo->height == sec->ceilingheight);
+		return (mo->z+mo->height == sec->ceilingheight && sec->flags & SF_FLIPSPECIAL_CEILING);
 	else
-		return (mo->z == sec->floorheight);
+		return (mo->z == sec->floorheight && sec->flags & SF_FLIPSPECIAL_FLOOR);
 }
 
 /** Applies a sector special to a player.
@@ -3715,15 +3747,6 @@ DoneSection2:
 
 					if (lines[lineindex].flags & ML_NOCLIMB)
 						skipstats = true;
-
-					// change the gametype using front x offset if passuse flag is given
-					// ...but not in single player!
-					if (multiplayer && lines[lineindex].flags & ML_EFFECT4)
-					{
-						INT32 xofs = sides[lines[lineindex].sidenum[0]].textureoffset;
-						if (xofs >= 0 && xofs < NUMGAMETYPES)
-							nextmapgametype = xofs;
-					}
 				}
 			}
 			break;
@@ -4861,7 +4884,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
 		flags &= ~FF_BLOCKPLAYER;
 
-	ffloor->flags = flags;
+	ffloor->spawnflags = ffloor->flags = flags;
 	ffloor->master = master;
 	ffloor->norender = INFTICS;
 
@@ -4927,6 +4950,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	else
 		ffloor->alpha = 0xff;
 
+	ffloor->spawnalpha = ffloor->alpha; // save for netgames
+
 	if (flags & FF_QUICKSAND)
 		CheckForQuicksand = true;
 
@@ -5607,13 +5632,13 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					if (!(lines[i].flags & ML_EFFECT5)) // Align floor unless ALLTRIGGER flag is set
 					{
 						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-							sectors[s].floorpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
+							sectors[s].spawn_flrpic_angle = sectors[s].floorpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
 					}
 
 					if (!(lines[i].flags & ML_BOUNCY)) // Align ceiling unless BOUNCY flag is set
 					{
 						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-							sectors[s].ceilingpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
+							sectors[s].spawn_ceilpic_angle = sectors[s].ceilingpic_angle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
 					}
 				}
 				else // Do offsets
@@ -5624,6 +5649,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
 						{
 							sectors[s].floor_xoffs += lines[i].dx;
 							sectors[s].floor_yoffs += lines[i].dy;
+							// saved for netgames
+							sectors[s].spawn_flr_xoffs = sectors[s].floor_xoffs;
+							sectors[s].spawn_flr_yoffs = sectors[s].floor_yoffs;
 						}
 					}
 
@@ -5633,6 +5661,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
 						{
 							sectors[s].ceiling_xoffs += lines[i].dx;
 							sectors[s].ceiling_yoffs += lines[i].dy;
+							// saved for netgames
+							sectors[s].spawn_ceil_xoffs = sectors[s].ceiling_xoffs;
+							sectors[s].spawn_ceil_yoffs = sectors[s].ceiling_yoffs;
 						}
 					}
 				}
@@ -6305,6 +6336,20 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 320:
 				break;
 
+			// Trigger on X calls
+			case 321:
+			case 322:
+				if (lines[i].flags & ML_NOCLIMB && sides[lines[i].sidenum[0]].rowoffset > 0) // optional "starting" count
+					lines[i].callcount = sides[lines[i].sidenum[0]].rowoffset>>FRACBITS;
+				else
+					lines[i].callcount = sides[lines[i].sidenum[0]].textureoffset>>FRACBITS;
+				if (lines[i].special == 322) // Each time
+				{
+					sec = sides[*lines[i].sidenum].sector - sectors;
+					P_AddEachTimeThinker(&sectors[sec], &lines[i]);
+				}
+				break;
+
 			case 399: // Linedef execute on map load
 				// This is handled in P_RunLevelLoadExecutors.
 				break;
@@ -6444,6 +6489,10 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 30: // Polyobj_Flag
 				EV_DoPolyObjFlag(&lines[i]);
 				break;
+
+			case 31: // Polyobj_Displace
+				PolyDisplace(&lines[i]);
+				break;
 		}
 	}
 #endif
@@ -6476,6 +6525,47 @@ static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinker
  P_SpawnScrollers
 */
 
+// helper function for T_Scroll
+static void P_DoScrollMove(mobj_t *thing, fixed_t dx, fixed_t dy, INT32 exclusive)
+{
+	fixed_t fuckaj = 0; // Nov 05 14:12:08 <+MonsterIestyn> I've heard of explicitly defined variables but this is ridiculous
+	if (thing->player)
+	{
+		if (!(dx | dy))
+		{
+			thing->player->cmomx = 0;
+			thing->player->cmomy = 0;
+		}
+		else
+		{
+			thing->player->cmomx += dx;
+			thing->player->cmomy += dy;
+			thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
+			thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
+		}
+	}
+
+	if (thing->player && (thing->player->pflags & PF_SPINNING) && (thing->player->rmomx || thing->player->rmomy) && !(thing->player->pflags & PF_STARTDASH))
+		fuckaj = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
+	else if (thing->friction != ORIG_FRICTION)
+		fuckaj = thing->friction;
+
+	if (fuckaj) {
+		// refactor thrust for new friction
+		dx = FixedDiv(dx, CARRYFACTOR);
+		dy = FixedDiv(dy, CARRYFACTOR);
+
+		dx = FixedMul(dx, FRACUNIT-fuckaj);
+		dy = FixedMul(dy, FRACUNIT-fuckaj);
+	}
+
+	thing->momx += dx;
+	thing->momy += dy;
+
+	if (exclusive)
+		thing->flags2 |= MF2_PUSHED;
+}
+
 /** Processes an active scroller.
   * This function, with the help of r_plane.c and r_bsp.c, supports generalized
   * scrolling floors and walls, with optional mobj-carrying properties, e.g.
@@ -6585,26 +6675,7 @@ void T_Scroll(scroll_t *s)
 						{
 							// Move objects only if on floor
 							// non-floating, and clipped.
-							thing->momx += dx;
-							thing->momy += dy;
-							if (thing->player)
-							{
-								if (!(dx | dy))
-								{
-									thing->player->cmomx = 0;
-									thing->player->cmomy = 0;
-								}
-								else
-								{
-									thing->player->cmomx += dx;
-									thing->player->cmomy += dy;
-									thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-									thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-								}
-							}
-
-							if (s->exclusive)
-								thing->flags2 |= MF2_PUSHED;
+							P_DoScrollMove(thing, dx, dy, s->exclusive);
 						}
 					} // end of for loop through touching_thinglist
 				} // end of loop through sectors
@@ -6619,31 +6690,12 @@ void T_Scroll(scroll_t *s)
 					if (thing->flags2 & MF2_PUSHED)
 						continue;
 
-					if (!((thing = node->m_thing)->flags & MF_NOCLIP) &&
+					if (!(thing->flags & MF_NOCLIP) &&
 						(!(thing->flags & MF_NOGRAVITY || thing->z > height)))
 					{
 						// Move objects only if on floor or underwater,
 						// non-floating, and clipped.
-						thing->momx += dx;
-						thing->momy += dy;
-						if (thing->player)
-						{
-							if (!(dx | dy))
-							{
-								thing->player->cmomx = 0;
-								thing->player->cmomy = 0;
-							}
-							else
-							{
-								thing->player->cmomx += dx;
-								thing->player->cmomy += dy;
-								thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-								thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-							}
-						}
-
-						if (s->exclusive)
-							thing->flags2 |= MF2_PUSHED;
+						P_DoScrollMove(thing, dx, dy, s->exclusive);
 					}
 				}
 			}
@@ -6682,26 +6734,7 @@ void T_Scroll(scroll_t *s)
 						{
 							// Move objects only if on floor or underwater,
 							// non-floating, and clipped.
-							thing->momx += dx;
-							thing->momy += dy;
-							if (thing->player)
-							{
-								if (!(dx | dy))
-								{
-									thing->player->cmomx = 0;
-									thing->player->cmomy = 0;
-								}
-								else
-								{
-									thing->player->cmomx += dx;
-									thing->player->cmomy += dy;
-									thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-									thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-								}
-							}
-
-							if (s->exclusive)
-								thing->flags2 |= MF2_PUSHED;
+							P_DoScrollMove(thing, dx, dy, s->exclusive);
 						}
 					} // end of for loop through touching_thinglist
 				} // end of loop through sectors
@@ -6716,31 +6749,12 @@ void T_Scroll(scroll_t *s)
 					if (thing->flags2 & MF2_PUSHED)
 						continue;
 
-					if (!((thing = node->m_thing)->flags & MF_NOCLIP) &&
+					if (!(thing->flags & MF_NOCLIP) &&
 						(!(thing->flags & MF_NOGRAVITY || thing->z+thing->height < height)))
 					{
 						// Move objects only if on floor or underwater,
 						// non-floating, and clipped.
-						thing->momx += dx;
-						thing->momy += dy;
-						if (thing->player)
-						{
-							if (!(dx | dy))
-							{
-								thing->player->cmomx = 0;
-								thing->player->cmomy = 0;
-							}
-							else
-							{
-								thing->player->cmomx += dx;
-								thing->player->cmomy += dy;
-								thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-								thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-							}
-						}
-
-						if (s->exclusive)
-							thing->flags2 |= MF2_PUSHED;
+						P_DoScrollMove(thing, dx, dy, s->exclusive);
 					}
 				}
 			}
@@ -7440,16 +7454,16 @@ void T_Pusher(pusher_t *p)
 		thing = node->m_thing;
 		if (thing->flags & (MF_NOGRAVITY | MF_NOCLIP)
 			&& !(thing->type == MT_SMALLBUBBLE
-											|| thing->type == MT_MEDIUMBUBBLE
-											|| thing->type == MT_EXTRALARGEBUBBLE))
+			|| thing->type == MT_MEDIUMBUBBLE
+			|| thing->type == MT_EXTRALARGEBUBBLE))
 			continue;
 
 		if (!(thing->flags & MF_PUSHABLE) && !(thing->type == MT_PLAYER
-											|| thing->type == MT_SMALLBUBBLE
-											|| thing->type == MT_MEDIUMBUBBLE
-											|| thing->type == MT_EXTRALARGEBUBBLE
-											|| thing->type == MT_LITTLETUMBLEWEED
-											|| thing->type == MT_BIGTUMBLEWEED))
+			|| thing->type == MT_SMALLBUBBLE
+			|| thing->type == MT_MEDIUMBUBBLE
+			|| thing->type == MT_EXTRALARGEBUBBLE
+			|| thing->type == MT_LITTLETUMBLEWEED
+			|| thing->type == MT_BIGTUMBLEWEED))
 			continue;
 
 		if (thing->flags2 & MF2_PUSHED)
diff --git a/src/p_spec.h b/src/p_spec.h
index 04152f68d75d48e3d601d0172ef9a3f2e1ed8d49..7b6a5655c47975972ff833bc9b20664d73a09f9c 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -347,6 +347,7 @@ typedef struct
 	thinker_t thinker; // Thinker for linedef executor delay
 	line_t *line;      // Pointer to line that is waiting.
 	mobj_t *caller;    // Pointer to calling mobj
+	sector_t *sector;  // Pointer to triggering sector
 	INT32 timer;       // Delay timer
 } executor_t;
 
diff --git a/src/p_tick.c b/src/p_tick.c
index 9a0f801650352fdc299d1bcc71b0e5aa94e06099..3b7d3683bfa70bc81ca7898d418a9416a42e40ad 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -523,19 +523,20 @@ static inline void P_DoTagStuff(void)
 	//increment survivor scores
 	if (leveltime % TICRATE == 0 && leveltime > (hidetime * TICRATE))
 	{
-		INT32 spectators = 0;
+		INT32 participants = 0;
 
-		for (i=0; i < MAXPLAYERS; i++) //count spectators to subtract from the player count.
+		for (i=0; i < MAXPLAYERS; i++)
 		{
-			if (players[i].spectator)
-				spectators++;
+			if (playeringame[i] && !players[i].spectator)
+				participants++;
 		}
 
 		for (i=0; i < MAXPLAYERS; i++)
 		{
-			if (!(players[i].pflags & PF_TAGIT) && !(players[i].pflags & PF_TAGGED)
-				&& !players[i].spectator && playeringame[i] && players[i].playerstate == PST_LIVE)
-				P_AddPlayerScore(&players[i], (D_NumPlayers() - spectators) / 2); //points given is the number of participating players divided by two.
+			if (playeringame[i] && !players[i].spectator && players[i].playerstate == PST_LIVE
+			&& !(players[i].pflags & (PF_TAGIT|PF_TAGGED)))
+				//points given is the number of participating players divided by two.
+				P_AddPlayerScore(&players[i], participants/2);
 		}
 	}
 }
@@ -691,7 +692,7 @@ void P_Ticker(boolean run)
 			G_WriteGhostTic(players[consoleplayer].mo);
 		if (demoplayback) // Use Ghost data for consistency checks.
 			G_ConsGhostTic();
-		if (modeattacking == ATTACKING_RECORD)
+		if (modeattacking)
 			G_GhostTicker();
 	}
 
diff --git a/src/p_user.c b/src/p_user.c
index a31666c9f19039355d52e908e092fa9261350f26..3c2d34a6ecc15e8258caf7a6a28ab762f6a28c1c 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -660,6 +660,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		player->mo->tracer->destscale = player->mo->scale;
 		P_SetScale(player->mo->tracer, player->mo->scale);
 		player->mo->tracer->eflags = (player->mo->tracer->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
+		player->mo->height = player->mo->tracer->height;
 	}
 
 	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_DRILLING);
@@ -3998,10 +3999,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 static boolean P_AnalogMove(player_t *player)
 {
-	if (netgame)
-		return false;
-	return ((player == &players[consoleplayer] && cv_analog.value)
-		|| (player == &players[secondarydisplayplayer] && cv_analog2.value));
+	return player->pflags & PF_ANALOGMODE;
 }
 
 //
@@ -4124,7 +4122,11 @@ static void P_2dMovement(player_t *player)
 	}
 	else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt
 		player->cmomx = player->cmomy = 0;
-	else if (player->onconveyor != 2 && player->onconveyor != 4)
+	else if (player->onconveyor != 2 && player->onconveyor != 4
+#ifdef POLYOBJECTS
+				&& player->onconveyor != 1
+#endif
+	)
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -4264,22 +4266,16 @@ static void P_3dMovement(player_t *player)
 	fixed_t movepushforward = 0, movepushside = 0;
 	INT32 mforward = 0, mbackward = 0;
 	angle_t dangle; // replaces old quadrants bits
-	camera_t *thiscam;
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
 	boolean analogmove = false;
 #ifndef OLD_MOVEMENT_CODE
 	fixed_t oldMagnitude, newMagnitude;
 
 	// Get the old momentum; this will be needed at the end of the function! -SH
-	oldMagnitude = R_PointToDist2(player->mo->momx, player->mo->momy, 0, 0);
+	oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
 #endif
 
-	if (splitscreen && player == &players[secondarydisplayplayer])
-		thiscam = &camera2;
-	else
-		thiscam = &camera;
-
-	analogmove = (P_AnalogMove(player) && thiscam->chase);
+	analogmove = P_AnalogMove(player);
 
 	cmd = &player->cmd;
 
@@ -4306,16 +4302,13 @@ static void P_3dMovement(player_t *player)
 
 	if (analogmove)
 	{
-		if (player->awayviewtics)
-			movepushangle = player->awayviewmobj->angle;
-		else
-			movepushangle = thiscam->angle;
+		movepushangle = (cmd->angleturn<<16 /* not FRACBITS */);
 	}
 	else
 	{
 		movepushangle = player->mo->angle;
 	}
-		movepushsideangle = movepushangle-ANGLE_90;
+	movepushsideangle = movepushangle-ANGLE_90;
 
 	// cmomx/cmomy stands for the conveyor belt speed.
 	if (player->onconveyor == 2) // Wind/Current
@@ -4326,7 +4319,11 @@ static void P_3dMovement(player_t *player)
 	}
 	else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt
 		player->cmomx = player->cmomy = 0;
-	else if (player->onconveyor != 2 && player->onconveyor != 4)
+	else if (player->onconveyor != 2 && player->onconveyor != 4
+#ifdef POLYOBJECTS
+				&& player->onconveyor != 1
+#endif
+	)
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -4482,8 +4479,6 @@ static void P_3dMovement(player_t *player)
 		if (!(player->pflags & PF_GLIDING || player->exiting || P_PlayerInPain(player)))
 		{
 			angle_t controldirection;
-			fixed_t tempx = 0, tempy = 0;
-			angle_t tempangle;
 #ifdef OLD_MOVEMENT_CODE
 			angle_t controlplayerdirection;
 			boolean cforward; // controls pointing forward from the player
@@ -4494,17 +4489,8 @@ static void P_3dMovement(player_t *player)
 #endif
 			// Calculate the angle at which the controls are pointing
 			// to figure out the proper mforward and mbackward.
-			tempangle = movepushangle;
-			tempangle >>= ANGLETOFINESHIFT;
-			tempx += FixedMul(cmd->forwardmove*FRACUNIT,FINECOSINE(tempangle));
-			tempy += FixedMul(cmd->forwardmove*FRACUNIT,FINESINE(tempangle));
-
-			tempangle = movepushsideangle;
-			tempangle >>= ANGLETOFINESHIFT;
-			tempx += FixedMul(cmd->sidemove*FRACUNIT,FINECOSINE(tempangle));
-			tempy += FixedMul(cmd->sidemove*FRACUNIT,FINESINE(tempangle));
-
-			controldirection = R_PointToAngle2(0, 0, tempx, tempy);
+			// (Why was it so complicated before? ~Red)
+			controldirection = R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT)+movepushangle;
 
 #ifdef OLD_MOVEMENT_CODE
 			controlplayerdirection = player->mo->angle;
@@ -4624,22 +4610,27 @@ static void P_3dMovement(player_t *player)
 	// If "no" to 2, normalize to topspeed, so we can't suddenly run faster than it of our own accord.
 	// If "no" to 1, we're not reaching any limits yet, so ignore this entirely!
 	// -Shadow Hog
-	newMagnitude = R_PointToDist2(player->mo->momx, player->mo->momy, 0, 0);
+	newMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
 	if (newMagnitude > topspeed)
 	{
+		fixed_t tempmomx, tempmomy;
 		if (oldMagnitude > topspeed)
 		{
 			if (newMagnitude > oldMagnitude)
 			{
-				player->mo->momx = FixedMul(FixedDiv(player->mo->momx, newMagnitude), oldMagnitude);
-				player->mo->momy = FixedMul(FixedDiv(player->mo->momy, newMagnitude), oldMagnitude);
+				tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), oldMagnitude);
+				tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), oldMagnitude);
+				player->mo->momx = tempmomx + player->cmomx;
+				player->mo->momy = tempmomy + player->cmomy;
 			}
 			// else do nothing
 		}
 		else
 		{
-			player->mo->momx = FixedMul(FixedDiv(player->mo->momx, newMagnitude), topspeed);
-			player->mo->momy = FixedMul(FixedDiv(player->mo->momy, newMagnitude), topspeed);
+			tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), topspeed);
+			tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), topspeed);
+			player->mo->momx = tempmomx + player->cmomx;
+			player->mo->momy = tempmomy + player->cmomy;
 		}
 	}
 #endif
@@ -6060,6 +6051,7 @@ static void P_SkidStuff(player_t *player)
 			particle->eflags |= player->mo->eflags & MFE_VERTICALFLIP;
 			P_SetScale(particle, player->mo->scale >> 2);
 			particle->destscale = player->mo->scale << 2;
+			particle->scalespeed = FixedMul(particle->scalespeed, player->mo->scale); // scale the scaling speed!
 			P_SetObjectMomZ(particle, FRACUNIT, false);
 			S_StartSound(player->mo, sfx_s3k7e); // the proper "Knuckles eats dirt" sfx.
 		}
@@ -6080,6 +6072,7 @@ static void P_SkidStuff(player_t *player)
 				particle->eflags |= player->mo->eflags & MFE_VERTICALFLIP;
 				P_SetScale(particle, player->mo->scale >> 2);
 				particle->destscale = player->mo->scale << 2;
+				particle->scalespeed = FixedMul(particle->scalespeed, player->mo->scale); // scale the scaling speed!
 				P_SetObjectMomZ(particle, FRACUNIT, false);
 			}
 		}
@@ -6120,17 +6113,11 @@ static void P_MovePlayer(player_t *player)
 	ticcmd_t *cmd;
 	INT32 i;
 
-	camera_t *thiscam;
 	fixed_t runspd;
 
 	if (countdowntimeup)
 		return;
 
-	if (splitscreen && player == &players[secondarydisplayplayer])
-		thiscam = &camera2;
-	else
-		thiscam = &camera;
-
 	if (player->mo->state >= &states[S_PLAY_SUPERTRANS1] && player->mo->state <= &states[S_PLAY_SUPERTRANS9])
 	{
 		player->mo->momx = player->mo->momy = player->mo->momz = 0;
@@ -6256,7 +6243,7 @@ static void P_MovePlayer(player_t *player)
 		P_2dMovement(player);
 	else
 	{
-		if (!player->climbing && (!P_AnalogMove(player) || player->pflags & PF_SPINNING))
+		if (!player->climbing && (!P_AnalogMove(player)))
 			player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
 
 		ticruned++;
@@ -6572,31 +6559,45 @@ static void P_MovePlayer(player_t *player)
 	//////////////////
 
 	// This really looks like it should be moved to P_3dMovement. -Red
-	if (P_AnalogMove(player) && thiscam->chase
+	if (P_AnalogMove(player)
 		&& (cmd->forwardmove != 0 || cmd->sidemove != 0) && !player->climbing && !twodlevel && !(player->mo->flags2 & MF2_TWOD))
 	{
 		// If travelling slow enough, face the way the controls
 		// point and not your direction of movement.
 		if (player->speed < FixedMul(5*FRACUNIT, player->mo->scale) || player->pflags & PF_GLIDING || !onground)
 		{
-			fixed_t tempx = 0, tempy = 0;
 			angle_t tempangle;
 
-			if (player->awayviewtics)
-				tempangle = player->awayviewmobj->angle;
-			else
-				tempangle = thiscam->angle;
-			tempangle >>= ANGLETOFINESHIFT;
-			tempx += FixedMul(cmd->forwardmove*FRACUNIT,FINECOSINE(tempangle));
-			tempy += FixedMul(cmd->forwardmove*FRACUNIT,FINESINE(tempangle));
+			tempangle = (cmd->angleturn << 16);
+
+#ifdef REDSANALOG // Ease to it. Chillax. ~Red
+			tempangle += R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT);
+			{
+				fixed_t tweenvalue = max(abs(cmd->forwardmove), abs(cmd->sidemove));
+
+				if (tweenvalue < 10 && (cmd->buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)) {
+					tempangle = (cmd->angleturn << 16);
+					tweenvalue = 16;
+				}
 
-			tempangle <<= ANGLETOFINESHIFT;
-			tempangle -= ANGLE_90;
-			tempangle >>= ANGLETOFINESHIFT;
-			tempx += FixedMul(cmd->sidemove*FRACUNIT,FINECOSINE(tempangle));
-			tempy += FixedMul(cmd->sidemove*FRACUNIT,FINESINE(tempangle));
+				tweenvalue *= tweenvalue*tweenvalue*1536;
 
-			player->mo->angle = R_PointToAngle2(0, 0, tempx, tempy);
+				//if (player->pflags & PF_GLIDING)
+					//tweenvalue >>= 1;
+
+				tempangle -= player->mo->angle;
+
+				if (tempangle < ANGLE_180 && tempangle > tweenvalue)
+					player->mo->angle += tweenvalue;
+				else if (tempangle >= ANGLE_180 && InvAngle(tempangle) > tweenvalue)
+					player->mo->angle -= tweenvalue;
+				else
+					player->mo->angle += tempangle;
+			}
+#else
+			// Less math this way ~Red
+			player->mo->angle = R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT)+tempangle;
+#endif
 		}
 		// Otherwise, face the direction you're travelling.
 		else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_ROLL
@@ -7559,19 +7560,20 @@ static void CV_CamRotate2_OnChange(void)
 		CV_SetValue(&cv_cam2_rotate, cv_cam2_rotate.value % 360);
 }
 
+static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {45, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
 
 consvar_t cv_cam_dist = {"cam_dist", "128", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_height = {"cam_height", "20", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_speed = {"cam_speed", "0.25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_speed = {"cam_speed", "0.25", CV_FLOAT, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", 0, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_dist = {"cam2_dist", "128", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_height = {"cam2_height", "20", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_speed = {"cam2_speed", "0.25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_speed = {"cam2_speed", "0.25", CV_FLOAT, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", 0, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -7724,6 +7726,15 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		camheight = FixedMul(cv_cam2_height.value, mo->scale);
 	}
 
+#ifdef REDSANALOG
+	if (P_AnalogMove(player) && (player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)) {
+		camstill = true;
+
+		if (camspeed < 4*FRACUNIT/5)
+			camspeed = 4*FRACUNIT/5;
+	}
+#endif // REDSANALOG
+
 	if (mo->eflags & MFE_VERTICALFLIP)
 		camheight += thiscam->height;
 
@@ -7772,6 +7783,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 
 	if (!objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && !(player->pflags & PF_NIGHTSMODE))
 	{
+#ifdef REDSANALOG
+		if ((player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)); else
+#endif
 		if (player->cmd.buttons & BT_CAMLEFT)
 		{
 			if (thiscam == &camera)
@@ -8569,6 +8583,9 @@ void P_PlayerThink(player_t *player)
 	// check water content, set stuff in mobj
 	P_MobjCheckWater(player->mo);
 
+#ifdef POLYOBJECTS
+	if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
+#endif
 	player->onconveyor = 0;
 	// check special sectors : damage & secrets
 
@@ -8707,6 +8724,11 @@ void P_PlayerThink(player_t *player)
 	if (!player->mo)
 		return; // P_MovePlayer removed player->mo.
 
+#ifdef POLYOBJECTS
+	if (player->onconveyor == 1)
+			player->cmomy = player->cmomx = 0;
+#endif
+
 	P_DoSuperStuff(player);
 	P_CheckSneakerAndLivesTimer(player);
 	P_DoBubbleBreath(player); // Spawn Sonic's bubbles
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 5a90f1fca7e6c1266b16f98ac5e1ad4e106ce278..e5e0942e3909f27c1a47b22ab473ecf95e04c149 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -192,6 +192,14 @@ void R_ClearClipSegs(void)
 	solidsegs[1].last = 0x7fffffff;
 	newend = solidsegs + 2;
 }
+void R_PortalClearClipSegs(INT32 start, INT32 end)
+{
+	solidsegs[0].first = -0x7fffffff;
+	solidsegs[0].last = start-1;
+	solidsegs[1].first = end;
+	solidsegs[1].last = 0x7fffffff;
+	newend = solidsegs + 2;
+}
 
 
 // R_DoorClosed
@@ -423,9 +431,9 @@ static void R_AddLine(seg_t *line)
 	backsector = line->backsector;
 
 	// Portal line
-	if (line->linedef->special == 40)
+	if (line->linedef->special == 40 && P_PointOnLineSide(viewx, viewy, line->linedef) == 0)
 	{
-		if (portalrender < PORTAL_LIMIT)
+		if (portalrender < cv_maxportals.value)
 		{
 			// Find the other side!
 			INT32 line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, -1);
@@ -433,8 +441,9 @@ static void R_AddLine(seg_t *line)
 				line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, line2);
 			if (line2 >= 0) // found it!
 			{
-				R_AddPortal(line->linedef-lines, line2); // Remember the lines for later rendering
-				return; // Don't fill in that space now!
+				R_AddPortal(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
+				//return; // Don't fill in that space now!
+				goto clipsolid;
 			}
 		}
 		// Recursed TOO FAR (viewing a portal within a portal)
@@ -683,6 +692,33 @@ void R_SortPolyObjects(subsector_t *sub)
 	}
 }
 
+//
+// R_PolysegCompare
+//
+// Callback for qsort to sort the segs of a polyobject. Returns such that the
+// closer one is sorted first. I sure hope this doesn't break anything. -Red
+//
+static int R_PolysegCompare(const void *p1, const void *p2)
+{
+	const seg_t *seg1 = *(const seg_t * const *)p1;
+	const seg_t *seg2 = *(const seg_t * const *)p2;
+	fixed_t dist1, dist2;
+
+	// TODO might be a better way to get distance?
+#define vxdist(v) FixedMul(R_PointToDist(v->x, v->y), FINECOSINE((R_PointToAngle(v->x, v->y)-viewangle)>>ANGLETOFINESHIFT))+0xFFFFFFF
+
+	dist1 = min(vxdist(seg1->v1), vxdist(seg1->v2));
+	dist2 = min(vxdist(seg2->v1), vxdist(seg2->v2));
+
+	if (dist1 == dist2) { // Segs connect toward the front, so use the back verts to determine order!
+		dist1 = max(vxdist(seg1->v1), vxdist(seg1->v2));
+		dist2 = max(vxdist(seg2->v1), vxdist(seg2->v2));
+	}
+#undef vxdist
+
+	return dist1-dist2;
+}
+
 //
 // R_AddPolyObjects
 //
@@ -709,6 +745,7 @@ static void R_AddPolyObjects(subsector_t *sub)
 	// render polyobjects
 	for (i = 0; i < numpolys; ++i)
 	{
+		qsort(po_ptrs[i]->segs, po_ptrs[i]->segCount, sizeof(seg_t *), R_PolysegCompare);
 		for (j = 0; j < po_ptrs[i]->segCount; ++j)
 			R_AddLine(po_ptrs[i]->segs[j]);
 	}
@@ -809,43 +846,10 @@ static void R_Subsector(size_t num)
 
 			if (frontsector->cullheight)
 			{
-				if (frontsector->cullheight->flags & ML_NOCLIMB) // Group culling
-				{
-					// Make sure this is part of the same group
-					if (viewsector->cullheight && viewsector->cullheight->frontsector
-						== frontsector->cullheight->frontsector)
-					{
-						// OK, we can cull
-						if (viewz > frontsector->cullheight->frontsector->floorheight
-							&& *rover->topheight < frontsector->cullheight->frontsector->floorheight) // Cull if below plane
-						{
-							rover->norender = leveltime;
-							continue;
-						}
-
-						if (*rover->bottomheight > frontsector->cullheight->frontsector->floorheight
-							&& viewz <= frontsector->cullheight->frontsector->floorheight) // Cull if above plane
-						{
-							rover->norender = leveltime;
-							continue;
-						}
-					}
-				}
-				else // Quick culling
+				if (R_DoCulling(frontsector->cullheight, viewsector->cullheight, viewz, *rover->bottomheight, *rover->topheight))
 				{
-					if (viewz > frontsector->cullheight->frontsector->floorheight
-						&& *rover->topheight < frontsector->cullheight->frontsector->floorheight) // Cull if below plane
-					{
-						rover->norender = leveltime;
-						continue;
-					}
-
-					if (*rover->bottomheight > frontsector->cullheight->frontsector->floorheight
-						&& viewz <= frontsector->cullheight->frontsector->floorheight) // Cull if above plane
-					{
-						rover->norender = leveltime;
-						continue;
-					}
+					rover->norender = leveltime;
+					continue;
 				}
 			}
 
@@ -909,15 +913,28 @@ static void R_Subsector(size_t num)
 				&& polysec->floorheight >= frontsector->floorheight
 				&& (viewz < polysec->floorheight))
 			{
+				fixed_t xoff, yoff;
+				xoff = polysec->floor_xoffs;
+				yoff = polysec->floor_yoffs;
+
+				if (po->angle != 0) {
+					angle_t fineshift = po->angle >> ANGLETOFINESHIFT;
+
+					xoff -= FixedMul(FINECOSINE(fineshift), po->centerPt.x)+FixedMul(FINESINE(fineshift), po->centerPt.y);
+					yoff -= FixedMul(FINESINE(fineshift), po->centerPt.x)-FixedMul(FINECOSINE(fineshift), po->centerPt.y);
+				} else {
+					xoff -= po->centerPt.x;
+					yoff += po->centerPt.y;
+				}
+
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
-						polysec->lightlevel, polysec->floor_xoffs,
-						polysec->floor_yoffs,
-						polysec->floorpic_angle,
+						polysec->lightlevel, xoff, yoff,
+						polysec->floorpic_angle-po->angle,
 						NULL,
 						NULL);
-				ffloor[numffloors].plane->polyobj = true;
+				ffloor[numffloors].plane->polyobj = po;
 
 				ffloor[numffloors].height = polysec->floorheight;
 				ffloor[numffloors].polyobj = po;
@@ -934,12 +951,27 @@ static void R_Subsector(size_t num)
 				&& polysec->ceilingheight <= frontsector->ceilingheight
 				&& (viewz > polysec->ceilingheight))
 			{
+				fixed_t xoff, yoff;
+				xoff = polysec->ceiling_xoffs;
+				yoff = polysec->ceiling_yoffs;
+
+				if (po->angle != 0) {
+					angle_t fineshift = po->angle >> ANGLETOFINESHIFT;
+
+					xoff -= FixedMul(FINECOSINE(fineshift), po->centerPt.x)+FixedMul(FINESINE(fineshift), po->centerPt.y);
+					yoff -= FixedMul(FINESINE(fineshift), po->centerPt.x)-FixedMul(FINECOSINE(fineshift), po->centerPt.y);
+				} else {
+					xoff -= po->centerPt.x;
+					yoff += po->centerPt.y;
+				}
+
 				light = R_GetPlaneLight(frontsector, polysec->ceilingheight, viewz < polysec->ceilingheight);
 				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
-					polysec->lightlevel, polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle,
+					polysec->lightlevel, xoff, yoff, polysec->ceilingpic_angle-po->angle,
 					NULL, NULL);
-				ffloor[numffloors].plane->polyobj = true;
+				ffloor[numffloors].plane->polyobj = po;
+
 				ffloor[numffloors].polyobj = po;
 				ffloor[numffloors].height = polysec->ceilingheight;
 //				ffloor[numffloors].ffloor = rover;
@@ -1147,5 +1179,14 @@ void R_RenderBSPNode(INT32 bspnum)
 
 		bspnum = bsp->children[side^1];
 	}
+
+	// PORTAL CULLING
+	if (portalcullsector) {
+		sector_t *sect = subsectors[bspnum & ~NF_SUBSECTOR].sector;
+		if (sect != portalcullsector)
+			return;
+		portalcullsector = NULL;
+	}
+
 	R_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR);
 }
diff --git a/src/r_bsp.h b/src/r_bsp.h
index a765b656ab4a1952c62dd61f958349270adae75e..20a80d89ab44a00a5125ce4f46d8cbe65ac38f91 100644
--- a/src/r_bsp.h
+++ b/src/r_bsp.h
@@ -37,9 +37,10 @@ typedef void (*drawfunc_t)(INT32 start, INT32 stop);
 
 // BSP?
 void R_ClearClipSegs(void);
+void R_PortalClearClipSegs(INT32 start, INT32 end);
 void R_ClearDrawSegs(void);
 void R_RenderBSPNode(INT32 bspnum);
-void R_AddPortal(INT32 line1, INT32 line2);
+void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2);
 
 #ifdef POLYOBJECTS
 void R_SortPolyObjects(subsector_t *sub);
diff --git a/src/r_defs.h b/src/r_defs.h
index 5a5eaf97a47b5f6bae9b486c10a3e1e76afdab97..7f8bd7e1d1ee6f99a7542adf8eb9aec392a7e3fe 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -167,6 +167,10 @@ typedef struct ffloor_s
 	INT32 lastlight;
 	INT32 alpha;
 	tic_t norender; // for culling
+
+	// these are saved for netgames, so do not let Lua touch these!
+	ffloortype_e spawnflags; // flags the 3D floor spawned with
+	INT32 spawnalpha; // alpha the 3D floor spawned with
 } ffloor_t;
 
 
@@ -332,6 +336,16 @@ typedef struct sector_s
 	// list of precipitation mobjs in sector
 	precipmobj_t *preciplist;
 	struct mprecipsecnode_s *touching_preciplist;
+
+	// these are saved for netgames, so do not let Lua touch these!
+
+	// offsets sector spawned with (via linedef type 7)
+	fixed_t spawn_flr_xoffs, spawn_flr_yoffs;
+	fixed_t spawn_ceil_xoffs, spawn_ceil_yoffs;
+
+	// flag angles sector spawned with (via linedef type 7)
+	angle_t spawn_flrpic_angle;
+	angle_t spawn_ceilpic_angle;
 } sector_t;
 
 //
@@ -381,6 +395,7 @@ typedef struct line_s
 #endif
 
 	char *text; // a concatination of all front and back texture names, for linedef specials that require a string.
+	INT16 callcount; // no. of calls left before triggering, for the "X calls" linedef specials, defaults to 0
 } line_t;
 
 //
diff --git a/src/r_main.c b/src/r_main.c
index 231dfe29b69e3da2eab9e59fa32afdc368f8314a..f2c641f6af914669e9a2d7b60a16760db90649d1 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -73,14 +73,29 @@ player_t *viewplayer;
 // PORTALS!
 // You can thank and/or curse JTE for these.
 UINT8 portalrender;
+sector_t *portalcullsector;
 typedef struct portal_pair
 {
 	INT32 line1;
 	INT32 line2;
 	UINT8 pass;
 	struct portal_pair *next;
+
+	fixed_t viewx;
+	fixed_t viewy;
+	fixed_t viewz;
+	angle_t viewangle;
+
+	INT32 start;
+	INT32 end;
+	INT16 *ceilingclip;
+	INT16 *floorclip;
+	fixed_t *frontscale;
+	size_t seg;
 } portal_pair;
 portal_pair *portal_base, *portal_cap;
+line_t *portalclipline;
+INT32 portalclipstart, portalclipend;
 
 //
 // precalculated math tables
@@ -123,17 +138,21 @@ static CV_PossibleValue_t drawdist_cons_t[] = {
 	{8192, "8192"},	{0, "Infinite"},	{0, NULL}};
 static CV_PossibleValue_t precipdensity_cons_t[] = {{0, "None"}, {1, "Light"}, {2, "Moderate"}, {4, "Heavy"}, {6, "Thick"}, {8, "V.Thick"}, {0, NULL}};
 static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card
 static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}};
 
 static void ChaseCam_OnChange(void);
 static void ChaseCam2_OnChange(void);
+static void FlipCam_OnChange(void);
+static void FlipCam2_OnChange(void);
 void SendWeaponPref(void);
+void SendWeaponPref2(void);
 
 consvar_t cv_tailspickup = {"tailspickup", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_chasecam = {"chasecam", "On", CV_CALL, CV_OnOff, ChaseCam_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_chasecam2 = {"chasecam2", "On", CV_CALL, CV_OnOff, ChaseCam2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_flipcam = {"flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, SendWeaponPref, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_flipcam2 = {"flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, SendWeaponPref, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_flipcam = {"flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_flipcam2 = {"flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_shadow = {"shadow", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_shadowoffs = {"offsetshadows", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -151,6 +170,8 @@ consvar_t cv_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensit
 // Okay, whoever said homremoval causes a performance hit should be shot.
 consvar_t cv_homremoval = {"homremoval", "No", CV_SAVE, homremoval_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_maxportals = {"maxportals", "2", CV_SAVE, maxportals_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 void SplitScreen_OnChange(void)
 {
 	if (!cv_debug && netgame)
@@ -207,6 +228,26 @@ static void ChaseCam2_OnChange(void)
 		CV_SetValue(&cv_analog2, 1);
 }
 
+static void FlipCam_OnChange(void)
+{
+	if (cv_flipcam.value)
+		players[consoleplayer].pflags |= PF_FLIPCAM;
+	else
+		players[consoleplayer].pflags &= ~PF_FLIPCAM;
+
+	SendWeaponPref();
+}
+
+static void FlipCam2_OnChange(void)
+{
+	if (cv_flipcam2.value)
+		players[secondarydisplayplayer].pflags |= PF_FLIPCAM;
+	else
+		players[secondarydisplayplayer].pflags &= ~PF_FLIPCAM;
+
+	SendWeaponPref2();
+}
+
 //
 // R_PointOnSide
 // Traverse BSP (sub) tree,
@@ -425,6 +466,47 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle)
 	return 64*FRACUNIT;
 }
 
+//
+// R_DoCulling
+// Checks viewz and top/bottom heights of an item against culling planes
+// Returns true if the item is to be culled, i.e it shouldn't be drawn!
+// if ML_NOCLIMB is set, the camera view is required to be in the same area for culling to occur
+boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph)
+{
+	fixed_t cullplane;
+
+	if (!cullheight)
+		return false;
+
+	cullplane = cullheight->frontsector->floorheight;
+	if (cullheight->flags & ML_NOCLIMB) // Group culling
+	{
+		if (!viewcullheight)
+			return false;
+
+		// Make sure this is part of the same group
+		if (viewcullheight->frontsector == cullheight->frontsector)
+		{
+			// OK, we can cull
+			if (vz > cullplane && toph < cullplane) // Cull if below plane
+				return true;
+
+			if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+				return true;
+		}
+	}
+	else // Quick culling
+	{
+		if (vz > cullplane && toph < cullplane) // Cull if below plane
+			return true;
+
+		if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+			return true;
+	}
+
+	return false;
+}
+
 //
 // R_InitTextureMapping
 //
@@ -1070,7 +1152,9 @@ void R_SetupFrame(player_t *player, boolean skybox)
 	centeryfrac = centery<<FRACBITS;
 }
 
-static void R_PortalFrame(player_t *player, line_t *start, line_t *dest)
+#define ANGLED_PORTALS
+
+static void R_PortalFrame(line_t *start, line_t *dest, portal_pair *portal)
 {
 	vertex_t dest_c, start_c;
 #ifdef ANGLED_PORTALS
@@ -1078,7 +1162,20 @@ static void R_PortalFrame(player_t *player, line_t *start, line_t *dest)
 	angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0);
 #endif
 
-	R_SetupFrame(player, false);
+	//R_SetupFrame(player, false);
+	viewx = portal->viewx;
+	viewy = portal->viewy;
+	viewz = portal->viewz;
+
+	viewangle = portal->viewangle;
+	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
+	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
+
+	portalcullsector = dest->frontsector;
+	viewsector = dest->frontsector;
+	portalclipline = dest;
+	portalclipstart = portal->start;
+	portalclipend = portal->end;
 
 	// Offset the portal view by the linedef centers
 
@@ -1090,6 +1187,9 @@ static void R_PortalFrame(player_t *player, line_t *start, line_t *dest)
 	dest_c.x = (dest->v1->x + dest->v2->x) / 2;
 	dest_c.y = (dest->v1->y + dest->v2->y) / 2;
 
+	// Heights!
+	viewz += dest->frontsector->floorheight - start->frontsector->floorheight;
+
 	// calculate the difference in position and rotation!
 #ifdef ANGLED_PORTALS
 	if (dangle == 0)
@@ -1104,22 +1204,51 @@ static void R_PortalFrame(player_t *player, line_t *start, line_t *dest)
 	viewangle += dangle;
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
-	CONS_Printf("dangle == %u\n", AngleFixed(dangle)>>FRACBITS);
+	//CONS_Printf("dangle == %u\n", AngleFixed(dangle)>>FRACBITS);
 
 	// ????
-	viewx = dest_c.x - viewx;
-	viewy = dest_c.y - viewy;
+	{
+		fixed_t disttopoint;
+		angle_t angtopoint;
+
+		disttopoint = R_PointToDist2(start_c.x, start_c.y, viewx, viewy);
+		angtopoint = R_PointToAngle2(start_c.x, start_c.y, viewx, viewy);
+		angtopoint += dangle;
+
+		viewx = dest_c.x+FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+		viewy = dest_c.y+FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+	}
 #endif
 }
 
-void R_AddPortal(INT32 line1, INT32 line2)
+void R_AddPortal(INT32 line1, INT32 line2, INT32 x1, INT32 x2)
 {
 	portal_pair *portal = Z_Malloc(sizeof(portal_pair), PU_LEVEL, NULL);
+	INT16 *ceilingclipsave = Z_Malloc(sizeof(INT16)*(x2-x1), PU_LEVEL, NULL);
+	INT16 *floorclipsave = Z_Malloc(sizeof(INT16)*(x2-x1), PU_LEVEL, NULL);
+	fixed_t *frontscalesave = Z_Malloc(sizeof(fixed_t)*(x2-x1), PU_LEVEL, NULL);
+
 	portal->line1 = line1;
 	portal->line2 = line2;
 	portal->pass = portalrender+1;
 	portal->next = NULL;
 
+	R_PortalStoreClipValues(x1, x2, ceilingclipsave, floorclipsave, frontscalesave);
+
+	portal->ceilingclip = ceilingclipsave;
+	portal->floorclip = floorclipsave;
+	portal->frontscale = frontscalesave;
+
+	portal->start = x1;
+	portal->end = x2;
+
+	portal->seg = ds_p-drawsegs;
+
+	portal->viewx = viewx;
+	portal->viewy = viewy;
+	portal->viewz = viewz;
+	portal->viewangle = viewangle;
+
 	if (!portal_base)
 	{
 		portal_base = portal;
@@ -1203,6 +1332,7 @@ void R_RenderPlayerView(player_t *player)
 	ProfZeroTimer();
 #endif
 	R_RenderBSPNode((INT32)numnodes - 1);
+	R_ClipSprites();
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
 	mytotal += mycount; // 64bit add
@@ -1211,32 +1341,49 @@ void R_RenderPlayerView(player_t *player)
 #endif
 //profile stuff ---------------------------------------------------------
 
-	R_DrawPlanes();
-#ifdef FLOORSPLATS
-	R_DrawVisibleFloorSplats();
-#endif
-	// draw mid texture and sprite
-	// And now 3D floors/sides!
-	R_DrawMasked();
-
 	// PORTAL RENDERING
 	for(portal = portal_base; portal; portal = portal_base)
 	{
 		// render the portal
 		CONS_Debug(DBG_RENDER, "Rendering portal from line %d to %d\n", portal->line1, portal->line2);
 		portalrender = portal->pass;
-		R_PortalFrame(player, &lines[portal->line1], &lines[portal->line2]);
+
+		R_PortalFrame(&lines[portal->line1], &lines[portal->line2], portal);
+
+		R_PortalClearClipSegs(portal->start, portal->end);
+
+		R_PortalRestoreClipValues(portal->start, portal->end, portal->ceilingclip, portal->floorclip, portal->frontscale);
+
 		validcount++;
+
+		if (portal->seg)
+		{
+			// Push the portal's old drawseg out of the way so it isn't interfering with sprite clipping. -Red
+			drawseg_t *seg = drawsegs+portal->seg;
+			seg->scale1 = 0;
+			seg->scale2 = 0;
+		}
+
 		R_RenderBSPNode((INT32)numnodes - 1);
-		R_DrawPlanes();
-		R_DrawMasked();
+		R_ClipSprites();
+		//R_DrawPlanes();
+		//R_DrawMasked();
 
 		// okay done. free it.
+		portalcullsector = NULL; // Just in case...
 		portal_base = portal->next;
 		Z_Free(portal);
 	}
 	// END PORTAL RENDERING
 
+	R_DrawPlanes();
+#ifdef FLOORSPLATS
+	R_DrawVisibleFloorSplats();
+#endif
+	// draw mid texture and sprite
+	// And now 3D floors/sides!
+	R_DrawMasked();
+
 	// Check for new console commands.
 	NetUpdate();
 }
@@ -1286,6 +1433,8 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_showhud);
 	CV_RegisterVar(&cv_translucenthud);
 
+	CV_RegisterVar(&cv_maxportals);
+
 	// Default viewheight is changeable,
 	// initialized to standard viewheight
 	CV_RegisterVar(&cv_viewheight);
diff --git a/src/r_main.h b/src/r_main.h
index a367960c7626484dc2dc8590b0c8328f29b7cba9..0d3f2def5222b8e60340ed6d8ab662ef0d3f2aae 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -77,6 +77,8 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle);
 subsector_t *R_PointInSubsector(fixed_t x, fixed_t y);
 subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y);
 
+boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph);
+
 //
 // REFRESH - the actual rendering functions.
 //
diff --git a/src/r_plane.c b/src/r_plane.c
index 7e1764fc549dfee930a2bc910209051e551e2ee6..dcff25c1304fd45deba11b9845cdaa40f23c7658 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -106,6 +106,50 @@ void R_InitPlanes(void)
 	// FIXME: unused
 }
 
+// R_PortalStoreClipValues
+// Saves clipping values for later. -Red
+void R_PortalStoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale)
+{
+	INT32 i;
+	for (i = 0; i < end-start; i++)
+	{
+		*ceil = ceilingclip[start+i];
+		ceil++;
+		*floor = floorclip[start+i];
+		floor++;
+		*scale = frontscale[start+i];
+		scale++;
+	}
+}
+
+// R_PortalRestoreClipValues
+// Inverse of the above. Restores the old value!
+void R_PortalRestoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale)
+{
+	INT32 i;
+	for (i = 0; i < end-start; i++)
+	{
+		ceilingclip[start+i] = *ceil;
+		ceil++;
+		floorclip[start+i] = *floor;
+		floor++;
+		frontscale[start+i] = *scale;
+		scale++;
+	}
+
+	// HACKS FOLLOW
+	for (i = 0; i < start; i++)
+	{
+		floorclip[i] = -1;
+		ceilingclip[i] = (INT16)viewheight;
+	}
+	for (i = end; i < vid.width; i++)
+	{
+		floorclip[i] = -1;
+		ceilingclip[i] = (INT16)viewheight;
+	}
+}
+
 
 //profile stuff ---------------------------------------------------------
 //#define TIMING
@@ -409,6 +453,10 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 
 	for (check = visplanes[hash]; check; check = check->next)
 	{
+#ifdef POLYOBJECTS_PLANES
+		if (check->polyobj && pfloor)
+			continue;
+#endif
 		if (height == check->height && picnum == check->picnum
 			&& lightlevel == check->lightlevel
 			&& xoff == check->xoffs && yoff == check->yoffs
@@ -434,6 +482,9 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 	check->viewz = viewz;
 	check->viewangle = viewangle + plangle;
 	check->plangle = plangle;
+#ifdef POLYOBJECTS_PLANES
+	check->polyobj = NULL;
+#endif
 
 	memset(check->top, 0xff, sizeof (check->top));
 	memset(check->bottom, 0x00, sizeof (check->bottom));
@@ -590,6 +641,7 @@ void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2)
 void R_DrawPlanes(void)
 {
 	visplane_t *pl;
+	angle_t skyviewangle = viewangle; // the flat angle itself can mess with viewangle, so do your own angle instead!
 	INT32 x;
 	INT32 angle;
 	INT32 i;
@@ -628,7 +680,7 @@ void R_DrawPlanes(void)
 
 					if (dc_yl <= dc_yh)
 					{
-						angle = (viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT;
+						angle = (skyviewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT;
 						dc_x = x;
 						dc_source =
 							R_GetColumn(skytexture,
@@ -666,6 +718,42 @@ void R_DrawSinglePlane(visplane_t *pl)
 	itswater = false;
 #endif
 	spanfunc = basespanfunc;
+
+#ifdef POLYOBJECTS_PLANES
+	if (pl->polyobj && pl->polyobj->translucency != 0) {
+		spanfunc = R_DrawTranslucentSpan_8;
+
+		// Hacked up support for alpha value in software mode Tails 09-24-2002 (sidenote: ported to polys 10-15-2014, there was no time travel involved -Red)
+		if (pl->polyobj->translucency >= 10)
+			return; // Don't even draw it
+		else if (pl->polyobj->translucency == 9)
+			ds_transmap = ((tr_trans90)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 8)
+			ds_transmap = ((tr_trans80)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 7)
+			ds_transmap = ((tr_trans70)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 6)
+			ds_transmap = ((tr_trans60)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 5)
+			ds_transmap = ((tr_trans50)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 4)
+			ds_transmap = ((tr_trans40)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 3)
+			ds_transmap = ((tr_trans30)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 2)
+			ds_transmap = ((tr_trans20)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else if (pl->polyobj->translucency == 1)
+			ds_transmap = ((tr_trans10)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		else // Opaque, but allow transparent flat pixels
+			spanfunc = splatfunc;
+
+		if (pl->extra_colormap && pl->extra_colormap->fog)
+			light = (pl->lightlevel >> LIGHTSEGSHIFT);
+		else
+		light = LIGHTLEVELS-1;
+
+	} else
+#endif
 	if (pl->ffloor)
 	{
 		// Don't draw planes that shouldn't be drawn.
diff --git a/src/r_plane.h b/src/r_plane.h
index 1f046588f3941748d6815484d02b4f5d4f1f19ee..f3a7f573fe90c0201f944430953327f29c999e17 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -59,7 +59,7 @@ typedef struct visplane_s
 
 	struct ffloor_s *ffloor;
 #ifdef POLYOBJECTS_PLANES
-	boolean polyobj;
+	polyobj_t *polyobj;
 #endif
 } visplane_t;
 
@@ -83,6 +83,8 @@ extern fixed_t *yslope;
 extern fixed_t distscale[MAXVIDWIDTH];
 
 void R_InitPlanes(void);
+void R_PortalStoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale);
+void R_PortalRestoreClipValues(INT32 start, INT32 end, INT16 *ceil, INT16 *floor, fixed_t *scale);
 void R_ClearPlanes(void);
 
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2);
diff --git a/src/r_segs.c b/src/r_segs.c
index 1070bff7c01e896d288f2b1f5bc554b060e32c76..c73cfdf9faeceb9445c22b4538e7798f00000056 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -589,7 +589,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				// draw the texture
 				col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);
 
-#ifdef POLYOBJECTS_PLANES
+//#ifdef POLYOBJECTS_PLANES
+#if 0 // Disabling this allows inside edges to render below the planes, for until the clipping is fixed to work right when POs are near the camera. -Red
 				if (curline->dontrenderme && curline->polyseg && (curline->polyseg->flags & POF_RENDERPLANES))
 				{
 					fixed_t my_topscreen;
@@ -611,7 +612,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 
 						for (i = 0; i < numffloors; i++)
 						{
-							if (!ffloor[i].polyobj)
+							if (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg)
 								continue;
 
 							if (ffloor[i].height < viewz)
@@ -650,6 +651,15 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	colfunc = wallcolfunc;
 }
 
+// Loop through R_DrawMaskedColumn calls
+static void R_DrawRepeatMaskedColumn(column_t *col)
+{
+	do {
+		R_DrawMaskedColumn(col);
+		sprtopscreen += dc_texheight*spryscale;
+	} while (sprtopscreen < sprbotscreen);
+}
+
 //
 // R_RenderThickSideRange
 // Renders all the thick sides in the given range.
@@ -836,7 +846,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	//faB: handle case where multipatch texture is drawn on a 2sided wall, multi-patch textures
 	//     are not stored per-column with post info anymore in Doom Legacy
 	if (textures[texnum]->holes)
-		colfunc_2s = R_DrawMaskedColumn;                    //render the usual 2sided single-patch packed texture
+		colfunc_2s = R_DrawRepeatMaskedColumn;                    //render the usual 2sided single-patch packed texture
 	else
 	{
 		colfunc_2s = R_Render2sidedMultiPatchColumn;        //render multipatch with no holes (no post_t info)
@@ -1120,8 +1130,16 @@ static void R_RenderSegLoop (void)
 			for (i = 0; i < numffloors; i++)
 			{
 #ifdef POLYOBJECTS_PLANES
-				if (curline->polyseg && !ffloor[i].polyobj)
-					continue;
+				//if (curline->polyseg && (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg))
+					//continue; // Causes issues with FOF planes in The Wall -Red
+
+				// FIXME hack to fix planes disappearing when a seg goes behind the camera. This NEEDS to be changed to be done properly. -Red
+				if (curline->polyseg) {
+					if (ffloor[i].plane->minx > rw_x)
+						ffloor[i].plane->minx = rw_x;
+					else if (ffloor[i].plane->maxx < rw_x)
+						ffloor[i].plane->maxx = rw_x;
+				}
 #endif
 
 				if (ffloor[i].height < viewz)
@@ -1135,6 +1153,13 @@ static void R_RenderSegLoop (void)
 					if (bottom_w > bottom)
 						bottom_w = bottom;
 
+#ifdef POLYOBJECTS_PLANES
+					// Polyobject-specific hack to fix plane leaking -Red
+					if (curline->polyseg && ffloor[i].polyobj && ffloor[i].polyobj == curline->polyseg && top_w >= bottom_w) {
+						ffloor[i].plane->top[rw_x] = ffloor[i].plane->bottom[rw_x] = 0xFFFF;
+					} else
+#endif
+
 					if (top_w <= bottom_w)
 					{
 						ffloor[i].plane->top[rw_x] = (INT16)top_w;
@@ -1152,6 +1177,13 @@ static void R_RenderSegLoop (void)
 					if (bottom_w > bottom)
 						bottom_w = bottom;
 
+#ifdef POLYOBJECTS_PLANES
+					// Polyobject-specific hack to fix plane leaking -Red
+					if (curline->polyseg && ffloor[i].polyobj && ffloor[i].polyobj == curline->polyseg && top_w >= bottom_w) {
+						ffloor[i].plane->top[rw_x] = ffloor[i].plane->bottom[rw_x] = 0xFFFF;
+					} else
+#endif
+
 					if (top_w <= bottom_w)
 					{
 						ffloor[i].plane->top[rw_x] = (INT16)top_w;
@@ -1327,9 +1359,9 @@ static void R_RenderSegLoop (void)
 
 		for (i = 0; i < numffloors; i++)
 		{
-#ifdef POLYOBJECTS_PLANES
-			if (curline->polyseg && !ffloor[i].polyobj)
-				continue;
+#if 0 //#ifdef POLYOBJECTS_PLANES
+			if (curline->polyseg && (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg))
+				continue; // Causes issues with FOF planes in The Wall -Red
 #endif
 
 			ffloor[i].f_frac += ffloor[i].f_step;
@@ -1339,9 +1371,9 @@ static void R_RenderSegLoop (void)
 		{
 			INT32 y_w;
 
-#ifdef POLYOBJECTS_PLANES
-			if (curline->polyseg && !ffloor[i].polyobj)
-				continue;
+#if 0 //#ifdef POLYOBJECTS_PLANES
+			if (curline->polyseg && (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg))
+				continue; // Causes issues with FOF planes in The Wall -Red
 #endif
 			y_w = ffloor[i].b_frac >> HEIGHTBITS;
 
@@ -1488,9 +1520,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	{
 		for (i = 0; i < numffloors; i++)
 		{
-#ifdef POLYOBJECTS_PLANES
-			if (ds_p->curline->polyseg && !ffloor[i].polyobj)
-					continue;
+#if 0 //#ifdef POLYOBJECTS_PLANES
+			if (ds_p->curline->polyseg && (!ffloor[i].polyobj || ffloor[i].polyobj != ds_p->curline->polyseg))
+				continue; // Causes issues with FOF planes in The Wall -Red
 #endif
 			ffloor[i].f_pos = ffloor[i].height - viewz;
 		}
@@ -1989,8 +2021,10 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	{
 		for (i = 0; i < numffloors; i++)
 		{
-//			if (curline->polyseg && !ffloor[i].polyobj)
-//					continue;
+#if 0 //#ifdef POLYOBJECTS_PLANES
+			if (curline->polyseg && (!ffloor[i].polyobj || ffloor[i].polyobj != curline->polyseg))
+				continue; // Causes issues with FOF planes in The Wall -Red
+#endif
 
 			ffloor[i].f_pos >>= 4;
 			ffloor[i].f_step = FixedMul(-rw_scalestep, ffloor[i].f_pos);
diff --git a/src/r_state.h b/src/r_state.h
index aec0a6485daf58f446869e455e50a22368786a12..dc1ed931b01341421a27bf55d46031fe5854fa9c 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -88,8 +88,12 @@ extern boolean viewsky, skyVisible;
 extern sector_t *viewsector;
 extern player_t *viewplayer;
 extern UINT8 portalrender;
+extern sector_t *portalcullsector;
+extern line_t *portalclipline;
+extern INT32 portalclipstart, portalclipend;
 
 extern consvar_t cv_allowmlook;
+extern consvar_t cv_maxportals;
 
 extern angle_t clipangle;
 extern angle_t doubleclipangle;
diff --git a/src/r_things.c b/src/r_things.c
index c0f33dafb7af18dd6af78491301b1579c92e97ab..6372ddb4c06f122b06499864d2ec39528aa98590 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -489,6 +489,7 @@ void R_DelSpriteDefs(UINT16 wadnum)
 // GAME FUNCTIONS
 //
 static UINT32 visspritecount;
+static UINT32 clippedvissprites;
 static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
 
@@ -546,7 +547,7 @@ void R_InitSprites(void)
 //
 void R_ClearSprites(void)
 {
-	visspritecount = 0;
+	visspritecount = clippedvissprites = 0;
 }
 
 static inline void R_ResetVisSpriteChunks(void)
@@ -608,8 +609,7 @@ void R_DrawMaskedColumn(column_t *column)
 			topdelta += prevdelta;
 		prevdelta = topdelta;
 		topscreen = sprtopscreen + spryscale*topdelta;
-		bottomscreen = sprbotscreen == INT32_MAX ? topscreen + spryscale*column->length
-		                                      : sprbotscreen + spryscale*column->length;
+		bottomscreen = topscreen + spryscale*column->length;
 
 		dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS;
 		dc_yh = (bottomscreen-1)>>FRACBITS;
@@ -1157,6 +1157,16 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (x2 < 0)
 		return;
 
+	// PORTAL SPRITE CLIPPING
+	if (portalrender)
+	{
+		if (x2 < portalclipstart || x1 > portalclipend)
+			return;
+
+		if (P_PointOnLineSide(thing->x, thing->y, portalclipline) != 0)
+			return;
+	}
+
 	//SoM: 3/17/2000: Disregard sprites that are out of view..
 	if (thing->eflags & MFE_VERTICALFLIP)
 	{
@@ -1174,29 +1184,8 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (thing->subsector->sector->cullheight)
 	{
-		fixed_t cullplane = thing->subsector->sector->cullheight->frontsector->floorheight;
-		if (thing->subsector->sector->cullheight->flags & ML_NOCLIMB) // Group culling
-		{
-			// Make sure this is part of the same group
-			if (viewsector->cullheight && viewsector->cullheight->frontsector
-				== thing->subsector->sector->cullheight->frontsector)
-			{
-				// OK, we can cull
-				if (viewz > cullplane && gzt < cullplane) // Cull if below plane
-					return;
-
-				if (gz > cullplane && viewz <= cullplane) // Cull if above plane
-					return;
-			}
-		}
-		else // Quick culling
-		{
-			if (viewz > cullplane && gzt < cullplane) // Cull if below plane
-				return;
-
-			if (gz > cullplane && viewz <= cullplane) // Cull if above plane
-				return;
-		}
+		if (R_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, viewz, gz, gzt))
+			return;
 	}
 
 	if (thing->subsector->sector->numlights)
@@ -1258,6 +1247,16 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	vis->x1 = x1 < 0 ? 0 : x1;
 	vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
+
+	// PORTAL SEMI-CLIPPING
+	if (portalrender)
+	{
+		if (vis->x1 < portalclipstart)
+			vis->x1 = portalclipstart;
+		if (vis->x2 > portalclipend)
+			vis->x2 = portalclipend;
+	}
+
 	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;
 	vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, yscale))>>FRACBITS);
@@ -1350,7 +1349,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	fixed_t iscale;
 
 	//SoM: 3/17/2000
-	fixed_t gzt;
+	fixed_t gz ,gzt;
 
 	// transform the origin point
 	tr_x = thing->x - viewx;
@@ -1417,33 +1416,24 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	if (x2 < 0)
 		return;
 
+	// PORTAL SPRITE CLIPPING
+	if (portalrender)
+	{
+		if (x2 < portalclipstart || x1 > portalclipend)
+			return;
+
+		if (P_PointOnLineSide(thing->x, thing->y, portalclipline) != 0)
+			return;
+	}
+
 	//SoM: 3/17/2000: Disregard sprites that are out of view..
 	gzt = thing->z + spritecachedinfo[lump].topoffset;
+	gz = gzt - spritecachedinfo[lump].height;
 
 	if (thing->subsector->sector->cullheight)
 	{
-		if (thing->subsector->sector->cullheight->flags & ML_NOCLIMB) // Group culling
-		{
-			// Make sure this is part of the same group
-			if (viewsector->cullheight && viewsector->cullheight->frontsector
-				== thing->subsector->sector->cullheight->frontsector)
-			{
-				// OK, we can cull
-				if (viewz > thing->subsector->sector->cullheight->frontsector->floorheight
-					&& gzt < thing->subsector->sector->cullheight->frontsector->floorheight) // Cull if below plane
-					return;
-				else if (gzt - spritecachedinfo[lump].height > thing->subsector->sector->cullheight->frontsector->floorheight) // Cull if above plane
-					return;
-			}
-		}
-		else // Quick culling
-		{
-			if (viewz > thing->subsector->sector->cullheight->frontsector->floorheight
-				&& gzt < thing->subsector->sector->cullheight->frontsector->floorheight) // Cull if below plane
-				return;
-			else if (gzt - spritecachedinfo[lump].height > thing->subsector->sector->cullheight->frontsector->floorheight) // Cull if above plane
-				return;
-		}
+		if (R_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, viewz, gz, gzt))
+			return;
 	}
 
 	// quick check for possible overflows
@@ -1454,13 +1444,12 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 		return;
 	}
 
-
 	// store information in a vissprite
 	vis = R_NewVisSprite();
 	vis->scale = yscale; //<<detailshift;
 	vis->gx = thing->x;
 	vis->gy = thing->y;
-	vis->gz = gzt - spritecachedinfo[lump].height;
+	vis->gz = gz;
 	vis->gzt = gzt;
 	vis->thingheight = 4*FRACUNIT;
 	vis->pz = thing->z;
@@ -1469,6 +1458,16 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	vis->x1 = x1 < 0 ? 0 : x1;
 	vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
+
+	// PORTAL SEMI-CLIPPING
+	if (portalrender)
+	{
+		if (vis->x1 < portalclipstart)
+			vis->x1 = portalclipstart;
+		if (vis->x2 > portalclipend)
+			vis->x2 = portalclipend;
+	}
+
 	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;
 	vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, yscale))>>FRACBITS);
@@ -1830,6 +1829,20 @@ static void R_CreateDrawNodes(void)
 			}
 			else if (r2->seg)
 			{
+#ifdef POLYOBJECTS_PLANES
+				if (r2->seg->curline->polyseg && rover->mobj && P_MobjInsidePolyobj(r2->seg->curline->polyseg, rover->mobj)) {
+					// Determine if we need to sort in front of the polyobj, based on the planes. This fixes the issue where
+					// polyobject planes render above the object standing on them. (A bit hacky... but it works.) -Red
+					mobj_t *mo = rover->mobj;
+					sector_t *po = r2->seg->curline->backsector;
+
+					if (po->ceilingheight < viewz && mo->z+mo->height > po->ceilingheight)
+						continue;
+
+					if (po->floorheight > viewz && mo->z < po->floorheight)
+						continue;
+				}
+#endif
 				if (rover->x1 > r2->seg->x2 || rover->x2 < r2->seg->x1)
 					continue;
 
@@ -1937,297 +1950,201 @@ void R_InitDrawNodes(void)
 //        don't draw the part of sprites hidden under the console
 static void R_DrawSprite(vissprite_t *spr)
 {
-	drawseg_t *ds;
-	INT16      clipbot[MAXVIDWIDTH];
-	INT16      cliptop[MAXVIDWIDTH];
-	INT32        x;
-	INT32        r1;
-	INT32        r2;
-	fixed_t    scale;
-	fixed_t    lowscale;
-	INT32        silhouette;
-
-	memset(clipbot,0x00,sizeof (clipbot));
-	memset(cliptop,0x00,sizeof (cliptop));
-	for (x = spr->x1; x <= spr->x2; x++)
-		clipbot[x] = cliptop[x] = -2;
-
-	// Scan drawsegs from end to start for obscuring segs.
-	// The first drawseg that has a greater scale
-	//  is the clip seg.
-	//SoM: 4/8/2000:
-	// Pointer check was originally nonportable
-	// and buggy, by going past LEFT end of array:
-
-	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-	for (ds = ds_p; ds-- > drawsegs ;)
-	{
-		// determine if the drawseg obscures the sprite
-		if (ds->x1 > spr->x2 ||
-		    ds->x2 < spr->x1 ||
-		    (!ds->silhouette
-		     && !ds->maskedtexturecol))
-		{
-			// does not cover sprite
-			continue;
-		}
-
-		r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
-		r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
+	mfloorclip = spr->clipbot;
+	mceilingclip = spr->cliptop;
+	R_DrawVisSprite(spr);
+}
 
-		if (ds->scale1 > ds->scale2)
-		{
-			lowscale = ds->scale2;
-			scale = ds->scale1;
-		}
-		else
-		{
-			lowscale = ds->scale1;
-			scale = ds->scale2;
-		}
+// Special drawer for precipitation sprites Tails 08-18-2002
+static void R_DrawPrecipitationSprite(vissprite_t *spr)
+{
+	mfloorclip = spr->clipbot;
+	mceilingclip = spr->cliptop;
+	R_DrawPrecipitationVisSprite(spr);
+}
 
-		if (scale < spr->scale ||
-		    (lowscale < spr->scale &&
-		     !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
-		{
-			// masked mid texture?
-			/*if (ds->maskedtexturecol)
-				R_RenderMaskedSegRange (ds, r1, r2);*/
-			// seg is behind sprite
-			continue;
-		}
+// R_ClipSprites
+// Clips vissprites without drawing, so that portals can work. -Red
+void R_ClipSprites(void)
+{
+	vissprite_t *spr;
+	for (;clippedvissprites < visspritecount; clippedvissprites++)
+	{
+		drawseg_t *ds;
+		INT32		x;
+		INT32		r1;
+		INT32		r2;
+		fixed_t		scale;
+		fixed_t		lowscale;
+		INT32		silhouette;
 
-		// clip this piece of the sprite
-		silhouette = ds->silhouette;
+		spr = R_GetVisSprite(clippedvissprites);
 
-		if (spr->gz >= ds->bsilheight)
-			silhouette &= ~SIL_BOTTOM;
+		for (x = spr->x1; x <= spr->x2; x++)
+			spr->clipbot[x] = spr->cliptop[x] = -2;
 
-		if (spr->gzt <= ds->tsilheight)
-			silhouette &= ~SIL_TOP;
+		// Scan drawsegs from end to start for obscuring segs.
+		// The first drawseg that has a greater scale
+		//  is the clip seg.
+		//SoM: 4/8/2000:
+		// Pointer check was originally nonportable
+		// and buggy, by going past LEFT end of array:
 
-		if (silhouette == 1)
+		//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
+		for (ds = ds_p; ds-- > drawsegs ;)
 		{
-			// bottom sil
-			for (x = r1; x <= r2; x++)
-				if (clipbot[x] == -2)
-					clipbot[x] = ds->sprbottomclip[x];
-		}
-		else if (silhouette == 2)
-		{
-			// top sil
-			for (x = r1; x <= r2; x++)
-				if (cliptop[x] == -2)
-					cliptop[x] = ds->sprtopclip[x];
-		}
-		else if (silhouette == 3)
-		{
-			// both
-			for (x = r1; x <= r2; x++)
+			// determine if the drawseg obscures the sprite
+			if (ds->x1 > spr->x2 ||
+			    ds->x2 < spr->x1 ||
+			    (!ds->silhouette
+			     && !ds->maskedtexturecol))
 			{
-				if (clipbot[x] == -2)
-					clipbot[x] = ds->sprbottomclip[x];
-				if (cliptop[x] == -2)
-					cliptop[x] = ds->sprtopclip[x];
+				// does not cover sprite
+				continue;
 			}
-		}
-	}
-	//SoM: 3/17/2000: Clip sprites in water.
-	if (spr->heightsec != -1)  // only things in specially marked sectors
-	{
-		fixed_t mh, h;
-		INT32 phs = viewplayer->mo->subsector->sector->heightsec;
-		if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
-		    (h = centeryfrac - FixedMul(mh -= viewz, spr->scale)) >= 0 &&
-		    (h >>= FRACBITS) < viewheight)
-		{
-			if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
-			{                          // clip bottom
-				for (x = spr->x1; x <= spr->x2; x++)
-					if (clipbot[x] == -2 || h < clipbot[x])
-						clipbot[x] = (INT16)h;
+
+			r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
+			r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
+
+			if (ds->scale1 > ds->scale2)
+			{
+				lowscale = ds->scale2;
+				scale = ds->scale1;
 			}
-			else                        // clip top
+			else
 			{
-				for (x = spr->x1; x <= spr->x2; x++)
-					if (cliptop[x] == -2 || h > cliptop[x])
-						cliptop[x] = (INT16)h;
+				lowscale = ds->scale1;
+				scale = ds->scale2;
 			}
-		}
 
-		if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
-		    (h = centeryfrac - FixedMul(mh-viewz, spr->scale)) >= 0 &&
-		    (h >>= FRACBITS) < viewheight)
-		{
-			if (phs != -1 && viewz >= sectors[phs].ceilingheight)
-			{                         // clip bottom
-				for (x = spr->x1; x <= spr->x2; x++)
-					if (clipbot[x] == -2 || h < clipbot[x])
-						clipbot[x] = (INT16)h;
-			}
-			else                       // clip top
+			if (scale < spr->scale ||
+			    (lowscale < spr->scale &&
+			     !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
 			{
-				for (x = spr->x1; x <= spr->x2; x++)
-					if (cliptop[x] == -2 || h > cliptop[x])
-						cliptop[x] = (INT16)h;
+				// masked mid texture?
+				/*if (ds->maskedtexturecol)
+					R_RenderMaskedSegRange (ds, r1, r2);*/
+				// seg is behind sprite
+				continue;
 			}
-		}
-	}
-	if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
-	{
-		for (x = spr->x1; x <= spr->x2; x++)
-		{
-			if (cliptop[x] == -2 || spr->szt > cliptop[x])
-				cliptop[x] = spr->szt;
-
-			if (clipbot[x] == -2 || spr->sz < clipbot[x])
-				clipbot[x] = spr->sz;
-		}
-	}
-	else if (spr->cut & SC_TOP)
-	{
-		for (x = spr->x1; x <= spr->x2; x++)
-		{
-			if (cliptop[x] == -2 || spr->szt > cliptop[x])
-				cliptop[x] = spr->szt;
-		}
-	}
-	else if (spr->cut & SC_BOTTOM)
-	{
-		for (x = spr->x1; x <= spr->x2; x++)
-		{
-			if (clipbot[x] == -2 || spr->sz < clipbot[x])
-				clipbot[x] = spr->sz;
-		}
-	}
 
-	// all clipping has been performed, so draw the sprite
+			// clip this piece of the sprite
+			silhouette = ds->silhouette;
 
-	// check for unclipped columns
-	for (x = spr->x1; x <= spr->x2; x++)
-	{
-		if (clipbot[x] == -2)
-			clipbot[x] = (INT16)viewheight;
+			if (spr->gz >= ds->bsilheight)
+				silhouette &= ~SIL_BOTTOM;
 
-		if (cliptop[x] == -2)
-			//Fab : 26-04-98: was -1, now clips against console bottom
-		cliptop[x] = (INT16)con_clipviewtop;
-	}
-
-	mfloorclip = clipbot;
-	mceilingclip = cliptop;
-	R_DrawVisSprite(spr);
-}
+			if (spr->gzt <= ds->tsilheight)
+				silhouette &= ~SIL_TOP;
 
-// Special drawer for precipitation sprites Tails 08-18-2002
-static void R_DrawPrecipitationSprite(vissprite_t *spr)
-{
-	drawseg_t *ds;
-	INT16      clipbot[MAXVIDWIDTH];
-	INT16      cliptop[MAXVIDWIDTH];
-	INT32        x;
-	INT32        r1;
-	INT32        r2;
-	fixed_t    scale;
-	fixed_t    lowscale;
-	INT32        silhouette;
-
-	memset(clipbot,0x00,sizeof (clipbot));
-	memset(cliptop,0x00,sizeof (cliptop));
-	for (x = spr->x1; x <= spr->x2; x++)
-		clipbot[x] = cliptop[x] = -2;
-
-	// Scan drawsegs from end to start for obscuring segs.
-	// The first drawseg that has a greater scale
-	//  is the clip seg.
-	//SoM: 4/8/2000:
-	// Pointer check was originally nonportable
-	// and buggy, by going past LEFT end of array:
-
-	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-	for (ds = ds_p; ds-- > drawsegs ;)
-	{
-		// determine if the drawseg obscures the sprite
-		if (ds->x1 > spr->x2 ||
-		    ds->x2 < spr->x1 ||
-		    (!ds->silhouette &&
-		     !ds->maskedtexturecol))
-		{
-			// does not cover sprite
-			continue;
-		}
-
-		r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
-		r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
-
-		if (ds->scale1 > ds->scale2)
-		{
-			lowscale = ds->scale2;
-			scale = ds->scale1;
+			if (silhouette == 1)
+			{
+				// bottom sil
+				for (x = r1; x <= r2; x++)
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+			}
+			else if (silhouette == 2)
+			{
+				// top sil
+				for (x = r1; x <= r2; x++)
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+			}
+			else if (silhouette == 3)
+			{
+				// both
+				for (x = r1; x <= r2; x++)
+				{
+					if (spr->clipbot[x] == -2)
+						spr->clipbot[x] = ds->sprbottomclip[x];
+					if (spr->cliptop[x] == -2)
+						spr->cliptop[x] = ds->sprtopclip[x];
+				}
+			}
 		}
-		else
+		//SoM: 3/17/2000: Clip sprites in water.
+		if (spr->heightsec != -1)  // only things in specially marked sectors
 		{
-			lowscale = ds->scale1;
-			scale = ds->scale2;
-		}
+			fixed_t mh, h;
+			INT32 phs = viewplayer->mo->subsector->sector->heightsec;
+			if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
+				(h = centeryfrac - FixedMul(mh -= viewz, spr->scale)) >= 0 &&
+				(h >>= FRACBITS) < viewheight)
+			{
+				if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
+				{                          // clip bottom
+					for (x = spr->x1; x <= spr->x2; x++)
+						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+							spr->clipbot[x] = (INT16)h;
+				}
+				else						// clip top
+				{
+					for (x = spr->x1; x <= spr->x2; x++)
+						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+							spr->cliptop[x] = (INT16)h;
+				}
+			}
 
-		if (scale < spr->scale ||
-		    (lowscale < spr->scale &&
-		     !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
-		{
-			// masked mid texture?
-			/*if (ds->maskedtexturecol)
-				R_RenderMaskedSegRange(ds, r1, r2);*/
-			// seg is behind sprite
-			continue;
+			if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
+			    (h = centeryfrac - FixedMul(mh-viewz, spr->scale)) >= 0 &&
+			    (h >>= FRACBITS) < viewheight)
+			{
+				if (phs != -1 && viewz >= sectors[phs].ceilingheight)
+				{                         // clip bottom
+					for (x = spr->x1; x <= spr->x2; x++)
+						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+							spr->clipbot[x] = (INT16)h;
+				}
+				else                       // clip top
+				{
+					for (x = spr->x1; x <= spr->x2; x++)
+						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+							spr->cliptop[x] = (INT16)h;
+				}
+			}
 		}
-
-		// clip this piece of the sprite
-		silhouette = ds->silhouette;
-
-		if (silhouette == 1)
+		if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
 		{
-			// bottom sil
-			for (x = r1; x <= r2; x++)
-				if (clipbot[x] == -2)
-					clipbot[x] = ds->sprbottomclip[x];
+			for (x = spr->x1; x <= spr->x2; x++)
+			{
+				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+					spr->cliptop[x] = spr->szt;
+
+				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+					spr->clipbot[x] = spr->sz;
+			}
 		}
-		else if (silhouette == 2)
+		else if (spr->cut & SC_TOP)
 		{
-			// top sil
-			for (x = r1; x <= r2; x++)
-				if (cliptop[x] == -2)
-					cliptop[x] = ds->sprtopclip[x];
+			for (x = spr->x1; x <= spr->x2; x++)
+			{
+				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+					spr->cliptop[x] = spr->szt;
+			}
 		}
-		else if (silhouette == 3)
+		else if (spr->cut & SC_BOTTOM)
 		{
-			// both
-			for (x = r1; x <= r2; x++)
+			for (x = spr->x1; x <= spr->x2; x++)
 			{
-				if (clipbot[x] == -2)
-					clipbot[x] = ds->sprbottomclip[x];
-				if (cliptop[x] == -2)
-					cliptop[x] = ds->sprtopclip[x];
+				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+					spr->clipbot[x] = spr->sz;
 			}
 		}
-	}
 
-	// all clipping has been performed, so draw the sprite
+		// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
 
-	// check for unclipped columns
-	for (x = spr->x1; x <= spr->x2; x++)
-	{
-		if (clipbot[x] == -2)
-			clipbot[x] = (INT16)viewheight;
+		// check for unclipped columns
+		for (x = spr->x1; x <= spr->x2; x++)
+		{
+			if (spr->clipbot[x] == -2)
+				spr->clipbot[x] = (INT16)viewheight;
 
-		if (cliptop[x] == -2)
-			//Fab : 26-04-98: was -1, now clips against console bottom
-			cliptop[x] = (INT16)con_clipviewtop;
+			if (spr->cliptop[x] == -2)
+				//Fab : 26-04-98: was -1, now clips against console bottom
+				spr->cliptop[x] = (INT16)con_clipviewtop;
+		}
 	}
-
-	mfloorclip = clipbot;
-	mceilingclip = cliptop;
-	R_DrawPrecipitationVisSprite(spr);
 }
 
 //
diff --git a/src/r_things.h b/src/r_things.h
index 226b8e47621bbcbf634fedb0c3e565cf570d1af7..5a7036c6a46661962c49f925567a1498cb44b958 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -54,6 +54,7 @@ void R_DelSpriteDefs(UINT16 wadnum);
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
+void R_ClipSprites(void);
 void R_DrawMasked(void);
 
 // -----------
@@ -156,6 +157,8 @@ typedef struct vissprite_s
 
 	spritecut_e cut;
 
+	INT16 clipbot[MAXVIDWIDTH], cliptop[MAXVIDWIDTH];
+
 	boolean precip;
 	boolean vflip; // Flip vertically
 	boolean isScaled;
diff --git a/src/s_sound.c b/src/s_sound.c
index b48b60a059bcbfae169c406e03203aefcbc67f1e..14a8cc425affc371676b146cdbddcaefa430c836 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -192,10 +192,9 @@ static INT32 S_getChannel(const void *origin, sfxinfo_t *sfxinfo)
 				S_StopChannel(cnum);
 			break;
 		}
-		else if (origin && channels[cnum].origin == origin && ((channels[cnum].sfxinfo == sfxinfo)
-			|| (channels[cnum].sfxinfo->name != sfxinfo->name
-			&& channels[cnum].sfxinfo->pitch == 1 && sfxinfo->pitch == 1
-			&& channels[cnum].sfxinfo->pitch == sfxinfo->pitch)))
+		else if (origin && channels[cnum].origin == origin
+			&& channels[cnum].sfxinfo->name != sfxinfo->name
+			&& channels[cnum].sfxinfo->pitch == SF_TOTALLYSINGLE && sfxinfo->pitch == SF_TOTALLYSINGLE)
 		{
 			S_StopChannel(cnum);
 			break;
diff --git a/src/sdl/i_cdmus.c b/src/sdl/i_cdmus.c
index fc35eb9cf0bec5ac8532c6ae6ec4f685592b51ab..f3f7036677dfb1092dc23170e64bd60cd7287619 100644
--- a/src/sdl/i_cdmus.c
+++ b/src/sdl/i_cdmus.c
@@ -35,4 +35,3 @@ boolean I_SetVolumeCD(int volume)
 	(void)volume;
 	return false;
 }
-
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 336a57ea08e1acf135b087e59c295b62fe7499be..a59db26cc529a934de705fa701aa549af5d161d6 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -835,7 +835,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 		event.data2 = (evt.xrel) * (wwidth / realwidth);
 		event.data3 = -evt.yrel * (wheight / realheight);
 	}
-	
+
 	event.type = ev_mouse;
 
 	if (SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window)
@@ -1853,12 +1853,12 @@ void I_StartupGraphics(void)
 
 	// Fury: we do window initialization after GL setup to allow
 	// SDL_GL_LoadLibrary to work well on Windows
-	
+
 	// Create window
 	//Impl_CreateWindow(USE_FULLSCREEN);
 	//Impl_SetWindowName("SRB2");
 	VID_SetMode(VID_GetModeForSize(BASEVIDWIDTH, BASEVIDHEIGHT));
-	
+
 	vid.buffer = NULL;  // For software mode
 	vid.width = BASEVIDWIDTH; // Default size for startup
 	vid.height = BASEVIDHEIGHT; // BitsPerPixel is the SDL interface's
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index c34690b3b17a2310840fa29ae66bcf325879d7bb..a205d30cea0940f91ac87bf8d268f7bd835cdc62 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.11;
+				CURRENT_PROJECT_VERSION = 2.1.12;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.11;
+				CURRENT_PROJECT_VERSION = 2.1.12;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sdl12/SRB2CE/cehelp.c b/src/sdl12/SRB2CE/cehelp.c
index b9fafd040bfe6096c9658facd5f3a099c5a2eff4..7c5efdee92b0b187f007c8abf43c6141d6e8d6fc 100644
--- a/src/sdl12/SRB2CE/cehelp.c
+++ b/src/sdl12/SRB2CE/cehelp.c
@@ -444,4 +444,3 @@ VOID WINAPI OutputDebugStringA(
 
 	OutputDebugStringW(lpOutputStringW);
 }
-
diff --git a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
index c34690b3b17a2310840fa29ae66bcf325879d7bb..a205d30cea0940f91ac87bf8d268f7bd835cdc62 100644
--- a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.11;
+				CURRENT_PROJECT_VERSION = 2.1.12;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.11;
+				CURRENT_PROJECT_VERSION = 2.1.12;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sounds.c b/src/sounds.c
index c03d6cea25fc66558237903ea4bc1e52cefb0d7e..1ec86e7bc9349c4f3fb130898f467183877f4be1 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -1375,7 +1375,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k61",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k64",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"s3k64",  false,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Blue Spheres
   {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
   {"s3k67",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
diff --git a/src/st_stuff.c b/src/st_stuff.c
index ae8c2f500157c6c877ca1462bcc76b75036c21b6..0d0cb1aefd2ca2fa310472c234750031fbd641c4 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -461,6 +461,19 @@ static INT32 STRINGY(INT32 y)
 	return y;
 }
 
+static INT32 SPLITFLAGS(INT32 f)
+{
+	// Pass this V_SNAPTO(TOP|BOTTOM) and it'll trim them to account for splitscreen! -Red
+	if (splitscreen)
+	{
+		if (stplyr != &players[displayplayer])
+			f &= ~V_SNAPTOTOP;
+		else
+			f &= ~V_SNAPTOBOTTOM;
+	}
+	return f;
+}
+
 static INT32 SCX(INT32 x)
 {
 	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
@@ -508,7 +521,8 @@ static void ST_DrawNightsOverlayNum(INT32 x /* right border */, INT32 y, INT32 a
 	INT32 w = SHORT(numpat[0]->width);
 	const UINT8 *colormap;
 
-	a &= V_ALPHAMASK;
+	// I want my V_SNAPTOx flags. :< -Red
+	//a &= V_ALPHAMASK;
 
 	if (colornum == 0)
 		colormap = colormaps;
@@ -520,8 +534,8 @@ static void ST_DrawNightsOverlayNum(INT32 x /* right border */, INT32 y, INT32 a
 	// draw the number
 	do
 	{
-		x -= (w * vid.dupx);
-		V_DrawTranslucentMappedPatch(x, y, V_NOSCALESTART|a, numpat[num % 10], colormap);
+		x -= w;
+		V_DrawTranslucentMappedPatch(x, y, a, numpat[num % 10], colormap);
 		num /= 10;
 	} while (num);
 
@@ -924,7 +938,7 @@ static void ST_drawNightsRecords(void)
 		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
 		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
 		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
-		ST_DrawNightsOverlayNum(SCX(BASEVIDWIDTH/2 + 48), SCY(160), aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_STEELBLUE);
+		ST_DrawNightsOverlayNum(BASEVIDWIDTH/2 + 48, STRINGY(160), aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_STEELBLUE);
 
 		// If new record, say so!
 		if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
@@ -936,10 +950,10 @@ static void ST_drawNightsRecords(void)
 		if (P_HasGrades(gamemap, stplyr->lastmare + 1))
 		{
 			if (aflag)
-				V_DrawTranslucentPatch(SCX(BASEVIDWIDTH/2 + 60), SCY(160), V_NOSCALESTART|aflag,
+				V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), aflag,
 				                       ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
 			else
-				V_DrawScaledPatch(SCX(BASEVIDWIDTH/2 + 60), SCY(160), V_NOSCALESTART,
+				V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), 0,
 				                  ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
 		}
 	}
@@ -986,14 +1000,14 @@ static void ST_drawNiGHTSHUD(void)
 
 			if (splitscreen)
 			{
-				ST_DrawNightsOverlayNum(SCX(256), SCY(160), linktrans, (stplyr->linkcount-1), nightsnum, colornum);
-				V_DrawTranslucentMappedPatch(SCX(264), SCY(160), V_NOSCALESTART|linktrans, nightslink,
+				ST_DrawNightsOverlayNum(256, STRINGY(152), SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT|linktrans, (stplyr->linkcount-1), nightsnum, colornum);
+				V_DrawTranslucentMappedPatch(264, STRINGY(152), SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT|linktrans, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 			else
 			{
-				ST_DrawNightsOverlayNum(SCX(160), SCY(160), linktrans, (stplyr->linkcount-1), nightsnum, colornum);
-				V_DrawTranslucentMappedPatch(SCX(168), SCY(160), V_NOSCALESTART|linktrans, nightslink,
+				ST_DrawNightsOverlayNum(160, 160, V_SNAPTOBOTTOM|linktrans, (stplyr->linkcount-1), nightsnum, colornum);
+				V_DrawTranslucentMappedPatch(168, 160, V_SNAPTOBOTTOM|linktrans, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 		}
@@ -1004,28 +1018,28 @@ static void ST_drawNiGHTSHUD(void)
 			{
 				INT32 offs = 10 - (stplyr->linktimer - (2*TICRATE - 9));
 				INT32 ghosttrans = offs << V_ALPHASHIFT;
-				ST_DrawNightsOverlayNum(SCX(160), SCY(160)+(offs*2), ghosttrans, (stplyr->linkcount-2),
+				ST_DrawNightsOverlayNum(160, STRINGY(160+offs), SPLITFLAGS(V_SNAPTOBOTTOM)|ghosttrans, (stplyr->linkcount-2),
 					nightsnum, colornum);
 			}
 #endif
 
 			if (splitscreen)
 			{
-				ST_DrawNightsOverlayNum(SCX(256), SCY(160), 0, (stplyr->linkcount-1), nightsnum, colornum);
-				V_DrawMappedPatch(SCX(264), SCY(160), V_NOSCALESTART, nightslink,
+				ST_DrawNightsOverlayNum(256, STRINGY(152), SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT, (stplyr->linkcount-1), nightsnum, colornum);
+				V_DrawMappedPatch(264, STRINGY(152), SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 			else
 			{
-				ST_DrawNightsOverlayNum(SCX(160), SCY(160), 0, (stplyr->linkcount-1), nightsnum, colornum);
-				V_DrawMappedPatch(SCX(168), SCY(160), V_NOSCALESTART, nightslink,
+				ST_DrawNightsOverlayNum(160, 160, V_SNAPTOBOTTOM, (stplyr->linkcount-1), nightsnum, colornum);
+				V_DrawMappedPatch(168, 160, V_SNAPTOBOTTOM, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 		}
 
 		// Show remaining link time left in debug
 		if (cv_debug & DBG_NIGHTSBASIC)
-			V_DrawCenteredString(SCX(BASEVIDWIDTH/2), SCY(180), V_NOSCALESTART, va("End in %d.%02d", stplyr->linktimer/TICRATE, G_TicsToCentiseconds(stplyr->linktimer)));
+			V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_SNAPTOBOTTOM, va("End in %d.%02d", stplyr->linktimer/TICRATE, G_TicsToCentiseconds(stplyr->linktimer)));
 	}
 
 	// Drill meter
@@ -1039,7 +1053,7 @@ static void ST_drawNiGHTSHUD(void)
 		INT32 dfill;
 		UINT8 fillpatch;
 
-		if (splitscreen)
+		if (splitscreen || nosshack)
 		{
 			locx = 110;
 			locy = 188;
@@ -1057,25 +1071,25 @@ static void ST_drawNiGHTSHUD(void)
 			fillpatch = 0;
 
 		if (splitscreen)
-		{ // Dirty hack because V_SNAPTOBOTTOM doesn't have a way to account for splitscreen, but better than overlapping bars.
-			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_HUDTRANS, drillbar);
+		{ // 11-5-14 Replaced the old hack with a slightly better hack. -Red
+			V_DrawScaledPatch(locx, STRINGY(locy)-3, SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_HUDTRANS, drillfill[fillpatch]);
+				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillfill[fillpatch]);
 		}
 		else if (nosshack)
 		{ // Even dirtier hack-of-a-hack to draw seperate drill meters in splitscreen special stages but nothing else.
 			splitscreen = true;
-			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_HUDTRANS, drillbar);
+			V_DrawScaledPatch(locx, STRINGY(locy)-3, V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_HUDTRANS, drillfill[fillpatch]);
+				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_HUDTRANS, drillfill[fillpatch]);
 			stplyr = &players[secondarydisplayplayer];
 			if (stplyr->pflags & PF_DRILLING)
 				fillpatch = (stplyr->drillmeter & 1) + 1;
 			else
 				fillpatch = 0;
-			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
+			V_DrawScaledPatch(locx, STRINGY(locy-3), V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
+				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
 			stplyr = &players[displayplayer];
 			splitscreen = false;
 		}
@@ -1206,7 +1220,7 @@ static void ST_drawNiGHTSHUD(void)
 #endif
 	)
 	{
-		ST_DrawNightsOverlayNum(SCX(304), SCY(16), 0, stplyr->marescore, nightsnum, SKINCOLOR_STEELBLUE);
+		ST_DrawNightsOverlayNum(304, STRINGY(16), SPLITFLAGS(V_SNAPTOTOP)|V_SNAPTORIGHT, stplyr->marescore, nightsnum, SKINCOLOR_STEELBLUE);
 	}
 
 	if (!stplyr->exiting
@@ -1219,19 +1233,22 @@ static void ST_drawNiGHTSHUD(void)
 		if (modeattacking == ATTACKING_NIGHTS)
 		{
 			INT32 maretime = max(stplyr->realtime - stplyr->marebegunat, 0);
+			fixed_t cornerx = vid.width, cornery = vid.height-SCZ(20);
 
-			ST_DrawOverlayPatch(SCX(298), SCY(180), W_CachePatchName("NGRTIMER", PU_HUDGFX));
-			ST_DrawPaddedOverlayNum(SCX(298), SCY(180), G_TicsToCentiseconds(maretime), 2);
-			ST_DrawOverlayPatch(SCX(274), SCY(180), sboperiod);
+#define ASSISHHUDFIX(n) (n*vid.dupx)
+			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(22), cornery, W_CachePatchName("NGRTIMER", PU_HUDGFX));
+			ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(22), cornery, G_TicsToCentiseconds(maretime), 2);
+			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(46), cornery, sboperiod);
 			if (maretime < 60*TICRATE)
-				ST_DrawOverlayNum(SCX(274), SCY(180), G_TicsToSeconds(maretime));
+				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime));
 			else
 			{
-				ST_DrawPaddedOverlayNum(SCX(274), SCY(180), G_TicsToSeconds(maretime), 2);
-				ST_DrawOverlayPatch(SCX(250), SCY(180), sbocolon);
-				ST_DrawOverlayNum(SCX(250), SCY(180), G_TicsToMinutes(maretime, true));
+				ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime), 2);
+				ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(70), cornery, sbocolon);
+				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(70), cornery, G_TicsToMinutes(maretime, true));
 			}
 		}
+#undef ASSISHHUDFIX
 	}
 
 	// Ideya time remaining
@@ -1267,22 +1284,22 @@ static void ST_drawNiGHTSHUD(void)
 		}
 
 		if (realnightstime < 10)
-			numbersize = SCX(16)/2;
+			numbersize = 16/2;
 		else if (realnightstime < 100)
-			numbersize = SCX(32)/2;
+			numbersize = 32/2;
 		else
-			numbersize = SCX(48)/2;
+			numbersize = 48/2;
 
 		if (realnightstime < 10)
-			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), 0, realnightstime,
+			ST_DrawNightsOverlayNum(160 + numbersize, STRINGY(12), SPLITFLAGS(V_SNAPTOTOP), realnightstime,
 				nightsnum, SKINCOLOR_RED);
 		else
-			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), 0, realnightstime,
+			ST_DrawNightsOverlayNum(160 + numbersize, STRINGY(12), SPLITFLAGS(V_SNAPTOTOP), realnightstime,
 				nightsnum, SKINCOLOR_SUPER4);
 
 		// Show exact time in debug
 		if (cv_debug & DBG_NIGHTSBASIC)
-			V_DrawString(SCX(160) + numbersize + 8, SCY(24), V_NOSCALESTART|((realnightstime < 10) ? V_REDMAP : V_YELLOWMAP), va("%02d", G_TicsToCentiseconds(stplyr->nightstime)));
+			V_DrawString(160 + numbersize + 8, 24, V_SNAPTOTOP|((realnightstime < 10) ? V_REDMAP : V_YELLOWMAP), va("%02d", G_TicsToCentiseconds(stplyr->nightstime)));
 	}
 
 	// Show pickup durations
diff --git a/src/v_video.c b/src/v_video.c
index 926107ff212d4844a5ff6744b7e0a641b2e281fe..64bf825bd8d178191a79a384ecf8714ecfb8177d 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -484,7 +484,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 		INT32 topdelta, prevdelta = -1;
 		if (x < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 			continue;
-		if (x > vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
+		if (x >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
 		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
 
@@ -596,7 +596,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		INT32 topdelta, prevdelta = -1;
 		if (x < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 			continue;
-		if (x > vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
+		if (x >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
 		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
 
@@ -1609,7 +1609,7 @@ INT32 V_CreditStringWidth(const char *string)
 	{
 		c = toupper(string[i]) - CRED_FONTSTART;
 		if (c < 0 || c >= CRED_FONTSIZE)
-			w += 8;
+			w += 16;
 		else
 			w += SHORT(cred_font[c]->width);
 	}
diff --git a/src/w_wad.c b/src/w_wad.c
index b09236a2078e5fa7f24e07781a77e0ef2c9557ff..adcbb4811cca1670cc9f62a23c9f1e624efd7edd 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -418,6 +418,7 @@ UINT16 W_LoadWadFile(const char *filename)
 		free(fileinfov);
 	}
 
+#ifndef NOMD5
 	//
 	// w-waiiiit!
 	// Let's not add a wad file if the MD5 matches
@@ -433,6 +434,7 @@ UINT16 W_LoadWadFile(const char *filename)
 			return INT16_MAX;
 		}
 	}
+#endif
 
 	//
 	// link wad file to search files
diff --git a/src/win32/Makefile.cfg b/src/win32/Makefile.cfg
index b989923fe7f773efa5146f219eea62d00c682606..c369651bfbf603432eb8bd9db43de05c540d36eb 100644
--- a/src/win32/Makefile.cfg
+++ b/src/win32/Makefile.cfg
@@ -53,9 +53,9 @@ endif
 
 	# name of the exefile
 ifdef SDL
-	EXENAME?=srb2sdl2.exe
-else
 	EXENAME?=srb2win.exe
+else
+	EXENAME?=srb2dd.exe
 endif
 
 ifdef SDL
diff --git a/src/y_inter.c b/src/y_inter.c
index 35ecf62aa3d4c87670cab6022f7c8bb6eb178737..498afa7cfeddc5cd9e580c728a69b7dd77acc242 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -770,11 +770,12 @@ void Y_Ticker(void)
 }
 
 //
-// Y_UpdateReplays
+// Y_UpdateRecordReplays
 //
-// Update replay files/data, etc
+// Update replay files/data, etc. for Record Attack
+// See G_SetNightsRecords for NiGHTS Attack.
 //
-static void Y_UpdateReplays(void)
+static void Y_UpdateRecordReplays(void)
 {
 	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
 	char *gpath;
@@ -962,7 +963,7 @@ void Y_StartIntermission(void)
 					mapvisited[gamemap-1] |= MV_PERFECT;
 
 				if (modeattacking == ATTACKING_RECORD)
-					Y_UpdateReplays();
+					Y_UpdateRecordReplays();
 			}
 
 			for (i = 0; i < 4; ++i)