From c3ffeb1e9d7a07569a2d9cee972f923ebb0f3394 Mon Sep 17 00:00:00 2001
From: MajorCooke <paul.growney22@gmail.com>
Date: Mon, 22 Feb 2016 11:30:44 -0600
Subject: [PATCH 01/13] A_RadiusGive Missile check fix - Don't use isMissile().
 Check directly for the flag at the moment of calling and not the default.
 Otherwise, things changing themselves will still be ineligible for
 non-missile checks.

---
 src/thingdef/thingdef_codeptr.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp
index 7119b577d..2cf05778b 100644
--- a/src/thingdef/thingdef_codeptr.cpp
+++ b/src/thingdef/thingdef_codeptr.cpp
@@ -5381,13 +5381,13 @@ enum RadiusGiveFlags
 static bool DoRadiusGive(AActor *self, AActor *thing, PClassActor *item, int amount, fixed_t distance, int flags, PClassActor *filter, FName species, fixed_t mindist)
 {
 	// [MC] We only want to make an exception for missiles here. Nothing else.
-	bool missilePass = !!((flags & RGF_MISSILES) && thing->isMissile());
+	bool missilePass = !!((flags & RGF_MISSILES) && thing->flags & MF_MISSILE);
 	if (thing == self)
 	{
 		if (!(flags & RGF_GIVESELF))
 			return false;
 	}
-	else if (thing->isMissile())
+	else if (thing->flags & MF_MISSILE)
 	{
 		if (!missilePass)
 			return false;

From 78552cc676d5c42f215d7dc6e805cbc1aee274d6 Mon Sep 17 00:00:00 2001
From: DaMan <daman6009@comcast.net>
Date: Mon, 15 Feb 2016 03:59:32 -0500
Subject: [PATCH 02/13] Disable UAC virtualization

---
 src/win32/zdoom.exe.manifest | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/win32/zdoom.exe.manifest b/src/win32/zdoom.exe.manifest
index 0d2074069..2ed84626b 100644
--- a/src/win32/zdoom.exe.manifest
+++ b/src/win32/zdoom.exe.manifest
@@ -5,6 +5,13 @@
       <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
     </dependentAssembly>
   </dependency>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level='asInvoker' uiAccess='false' />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
   <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
     <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
       <dpiAware>true</dpiAware>

From a8e985f4f6c29a231b5913444d4ab3f660599feb Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Mon, 22 Feb 2016 18:14:24 -0600
Subject: [PATCH 03/13] Don't use fast math for gdtoa

- gdtoa is for converting floating point numbers to and from string
  representations. I'd consider accuracy more important than speed here.
---
 gdtoa/CMakeLists.txt | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/gdtoa/CMakeLists.txt b/gdtoa/CMakeLists.txt
index 4dff4c30d..a0b76e0c3 100644
--- a/gdtoa/CMakeLists.txt
+++ b/gdtoa/CMakeLists.txt
@@ -12,9 +12,6 @@ if( ZD_CMAKE_COMPILER_IS_GNUC_COMPATIBLE )
 	set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra" )
 endif()
 
-# Enable fast flag for gdtoa
-set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ZD_FASTMATH_FLAG}" )
-
 include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 add_definitions( -DINFNAN_CHECK -DMULTIPLE_THREADS )
 

From 1ffb7ad1099f8f6c7a5d92d09f0333892a620c2e Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Tue, 23 Feb 2016 16:26:00 -0600
Subject: [PATCH 04/13] Add min and max to DECORATE

---
 src/sc_man_scanner.re                |   2 +
 src/sc_man_tokens.h                  |   2 +
 src/thingdef/thingdef_exp.cpp        |  15 ++
 src/thingdef/thingdef_exp.h          |  18 +++
 src/thingdef/thingdef_expression.cpp | 219 +++++++++++++++++++++++++--
 wadsrc/static/actors/actor.txt       |   4 +-
 6 files changed, 248 insertions(+), 12 deletions(-)

diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re
index cc051b05f..4a838f89a 100644
--- a/src/sc_man_scanner.re
+++ b/src/sc_man_scanner.re
@@ -191,6 +191,8 @@ std2:
 		'frandom'					{ RET(TK_FRandom); }
 		'randompick'				{ RET(TK_RandomPick); }
 		'frandompick'				{ RET(TK_FRandomPick); }
+		'min'						{ RET(TK_Min); }
+		'max'						{ RET(TK_Max); }
 
 		L (L|D)*					{ RET(TK_Identifier); }
 
diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h
index 1665a1cae..4dcddd332 100644
--- a/src/sc_man_tokens.h
+++ b/src/sc_man_tokens.h
@@ -128,6 +128,8 @@ xx(TK_SizeOf,				"'sizeof'")
 xx(TK_AlignOf,				"'alignof'")
 xx(TK_RandomPick,			"'randompick'")
 xx(TK_FRandomPick,			"'frandompick'")
+xx(TK_Min,					"'min'")
+xx(TK_Max,					"'max'")
 xx(TK_States,				"'states'")
 xx(TK_Loop,					"'loop'")
 xx(TK_Fail,					"'fail'")
diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp
index 17d97e4fa..2204a5652 100644
--- a/src/thingdef/thingdef_exp.cpp
+++ b/src/thingdef/thingdef_exp.cpp
@@ -349,6 +349,21 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
 		// a cheap way to get them working when people use "name" instead of 'name'.
 		return new FxConstant(FName(sc.String), scpos);
 	}
+	else if (sc.CheckToken(TK_Min) || sc.CheckToken(TK_Max))
+	{
+		int type = sc.TokenType;
+		TArray<FxExpression*> list;
+		sc.MustGetToken('(');
+		for (;;)
+		{
+			FxExpression *expr = ParseExpressionM(sc, cls);
+			list.Push(expr);
+			if (sc.CheckToken(')'))
+				break;
+			sc.MustGetToken(',');
+		}
+		return new FxMinMax(list, type, sc);
+	}
 	else if (sc.CheckToken(TK_Random))
 	{
 		FRandom *rng;
diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h
index 0802c08f8..330caf0a5 100644
--- a/src/thingdef/thingdef_exp.h
+++ b/src/thingdef/thingdef_exp.h
@@ -629,6 +629,24 @@ public:
 //
 //==========================================================================
 
+class FxMinMax : public FxExpression
+{
+	TDeletingArray<FxExpression *> choices;
+	int Type;
+
+public:
+	FxMinMax(TArray<FxExpression*> &expr, int type, const FScriptPosition &pos);
+	FxExpression *Resolve(FCompileContext&);
+
+	ExpEmit Emit(VMFunctionBuilder *build);
+};
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 class FxRandom : public FxExpression
 {
 protected:
diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp
index f9ab32b73..f5c123f4d 100644
--- a/src/thingdef/thingdef_expression.cpp
+++ b/src/thingdef/thingdef_expression.cpp
@@ -2038,6 +2038,214 @@ ExpEmit FxAbs::Emit(VMFunctionBuilder *build)
 	return out;
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+FxMinMax::FxMinMax(TArray<FxExpression*> &expr, int type, const FScriptPosition &pos)
+: FxExpression(pos), Type(type)
+{
+	assert(expr.Size() > 0);
+	assert(type == TK_Min || type == TK_Max);
+
+	ValueType = VAL_Unknown;
+	choices.Resize(expr.Size());
+	for (unsigned i = 0; i < expr.Size(); ++i)
+	{
+		choices[i] = expr[i];
+	}
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+FxExpression *FxMinMax::Resolve(FCompileContext &ctx)
+{
+	unsigned int i;
+	bool isconst;
+	int intcount, floatcount;
+
+	CHECKRESOLVED();
+
+	// Determine if float or int
+	intcount = floatcount = 0;
+	for (i = 0; i < choices.Size(); ++i)
+	{
+		RESOLVE(choices[i], ctx);
+		ABORT(choices[i]);
+
+		if (choices[i]->ValueType == VAL_Float)
+		{
+			floatcount++;
+		}
+		else if (choices[i]->ValueType == VAL_Int)
+		{
+			intcount++;
+		}
+		else
+		{
+			ScriptPosition.Message(MSG_ERROR, "Arguments must be of type int or float");
+			delete this;
+			return NULL;
+		}
+	}
+	if (floatcount != 0)
+	{
+		ValueType = VAL_Float;
+		if (intcount != 0)
+		{ // There are some ints that need to be cast to floats
+			for (i = 0; i < choices.Size(); ++i)
+			{
+				if (choices[i]->ValueType == VAL_Int)
+				{
+					choices[i] = new FxFloatCast(choices[i]);
+					RESOLVE(choices[i], ctx);
+					ABORT(choices[i]);
+				}
+			}
+		}
+	}
+	else
+	{
+		ValueType = VAL_Int;
+	}
+
+	// Determine if every argument is constant
+	isconst = true;
+	for (i = 0; i < choices.Size(); ++i)
+	{
+		if (!choices[i]->isConstant())
+		{
+			isconst = false;
+			break;
+		}
+	}
+
+	// If every argument is constant, we can decide this now.
+	if (isconst)
+	{
+		ExpVal best = static_cast<FxConstant *>(choices[0])->GetValue();
+		for (i = 1; i < choices.Size(); ++i)
+		{
+			ExpVal value = static_cast<FxConstant *>(choices[i])->GetValue();
+			assert(value.Type == ValueType.Type);
+			if (Type == TK_Min)
+			{
+				if (value.Type == VAL_Float)
+				{
+					if (value.Float < best.Float)
+					{
+						best.Float = value.Float;
+					}
+				}
+				else
+				{
+					if (value.Int < best.Int)
+					{
+						best.Int = value.Int;
+					}
+				}
+			}
+			else
+			{
+				if (value.Type == VAL_Float)
+				{
+					if (value.Float > best.Float)
+					{
+						best.Float = value.Float;
+					}
+				}
+				else
+				{
+					if (value.Int > best.Int)
+					{
+						best.Int = value.Int;
+					}
+				}
+			}
+		}
+		FxExpression *x = new FxConstant(best, ScriptPosition);
+		delete this;
+		return x;
+	}
+	return this;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+static void EmitLoad(VMFunctionBuilder *build, const ExpEmit resultreg, const ExpVal &value)
+{
+	if (resultreg.RegType == REGT_FLOAT)
+	{
+		build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(value.GetFloat()));
+	}
+	else
+	{
+		build->EmitLoadInt(resultreg.RegNum, value.GetInt());
+	}
+}
+
+ExpEmit FxMinMax::Emit(VMFunctionBuilder *build)
+{
+	unsigned i;
+	int opcode, opA;
+
+	assert(choices.Size() > 0);
+	assert(OP_LTF_RK == OP_LTF_RR+1);
+	assert(OP_LT_RK == OP_LT_RR+1);
+	assert(OP_LEF_RK == OP_LEF_RR+1);
+	assert(OP_LE_RK == OP_LE_RR+1);
+
+	if (Type == TK_Min)
+	{
+		opcode = ValueType.Type == VAL_Float ? OP_LEF_RR : OP_LE_RR;
+		opA = 1;
+	}
+	else
+	{
+		opcode = ValueType.Type == VAL_Float ? OP_LTF_RR : OP_LT_RR;
+		opA = 0;
+	}
+
+	ExpEmit bestreg;
+
+	// Get first value into a register. This will also be the result register.
+	if (choices[0]->isConstant())
+	{
+		bestreg = ExpEmit(build, ValueType.Type == VAL_Float ? REGT_FLOAT : REGT_INT);
+		EmitLoad(build, bestreg, static_cast<FxConstant *>(choices[0])->GetValue());
+	}
+	else
+	{
+		bestreg = choices[0]->Emit(build);
+	}
+
+	// Compare every choice. Better matches get copied to the bestreg.
+	for (i = 1; i < choices.Size(); ++i)
+	{
+		ExpEmit checkreg = choices[i]->Emit(build);
+		assert(checkreg.RegType == bestreg.RegType);
+		build->Emit(opcode + checkreg.Konst, opA, bestreg.RegNum, checkreg.RegNum);
+		build->Emit(OP_JMP, 1);
+		if (checkreg.Konst)
+		{
+			build->Emit(bestreg.RegType == REGT_FLOAT ? OP_LKF : OP_LK, bestreg.RegNum, checkreg.RegNum);
+		}
+		else
+		{
+			build->Emit(bestreg.RegType == REGT_FLOAT ? OP_MOVEF : OP_MOVE, bestreg.RegNum, checkreg.RegNum, 0);
+			checkreg.Free(build);
+		}
+	}
+	return bestreg;
+}
+
 //==========================================================================
 //
 //
@@ -2269,16 +2477,7 @@ ExpEmit FxRandomPick::Emit(VMFunctionBuilder *build)
 		build->BackpatchToHere(jumptable + i);
 		if (choices[i]->isConstant())
 		{
-			if (ValueType == VAL_Int)
-			{
-				int val = static_cast<FxConstant *>(choices[i])->GetValue().GetInt();
-				build->EmitLoadInt(resultreg.RegNum, val);
-			}
-			else
-			{
-				double val = static_cast<FxConstant *>(choices[i])->GetValue().GetFloat();
-				build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(val));
-			}
+			EmitLoad(build, resultreg, static_cast<FxConstant *>(choices[i])->GetValue());
 		}
 		else
 		{
diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt
index 2425ce694..642269f28 100644
--- a/wadsrc/static/actors/actor.txt
+++ b/wadsrc/static/actors/actor.txt
@@ -311,8 +311,8 @@ ACTOR Actor native //: Thinker
 	action native state A_JumpIfHigherOrLower(state high, state low, float offsethigh = 0, float offsetlow = 0, bool includeHeight = true, int ptr = AAPTR_TARGET);
 	action native A_SetSpecies(name species, int ptr = AAPTR_DEFAULT);
 	action native A_SetRipperLevel(int level);
-	action native A_SetRipMin(int min);
-	action native A_SetRipMax(int max);
+	action native A_SetRipMin(int mininum);
+	action native A_SetRipMax(int maximum);
 	action native A_SetChaseThreshold(int threshold, bool def = false, int ptr = AAPTR_DEFAULT);
 	action native state A_CheckProximity(state jump, class<Actor> classname, float distance, int count = 1, int flags = 0, int ptr = AAPTR_DEFAULT);
 	action native state A_CheckBlock(state block, int flags = 0, int ptr = AAPTR_DEFAULT);

From 1f09341d2b8020d5da2c1add45c2708bf40248cf Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Tue, 23 Feb 2016 16:37:52 -0600
Subject: [PATCH 05/13] Let min/max pre-solve for 2+ constants, even if there
 are non-constants

---
 src/thingdef/thingdef_expression.cpp | 117 +++++++++++++++------------
 1 file changed, 64 insertions(+), 53 deletions(-)

diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp
index f5c123f4d..8d0f158ba 100644
--- a/src/thingdef/thingdef_expression.cpp
+++ b/src/thingdef/thingdef_expression.cpp
@@ -2065,7 +2065,6 @@ FxMinMax::FxMinMax(TArray<FxExpression*> &expr, int type, const FScriptPosition
 FxExpression *FxMinMax::Resolve(FCompileContext &ctx)
 {
 	unsigned int i;
-	bool isconst;
 	int intcount, floatcount;
 
 	CHECKRESOLVED();
@@ -2113,64 +2112,76 @@ FxExpression *FxMinMax::Resolve(FCompileContext &ctx)
 		ValueType = VAL_Int;
 	}
 
-	// Determine if every argument is constant
-	isconst = true;
+	// If at least two arguments are constants, they can be solved now.
+
+	// Look for first constant
 	for (i = 0; i < choices.Size(); ++i)
 	{
-		if (!choices[i]->isConstant())
+		if (choices[i]->isConstant())
 		{
-			isconst = false;
+			ExpVal best = static_cast<FxConstant *>(choices[i])->GetValue();
+			// Compare against remaining constants, which are removed.
+			// The best value gets stored in this one.
+			for (unsigned j = i + 1; j < choices.Size(); )
+			{
+				if (!choices[j]->isConstant())
+				{
+					j++;
+				}
+				else
+				{
+					ExpVal value = static_cast<FxConstant *>(choices[j])->GetValue();
+					assert(value.Type == ValueType.Type);
+					if (Type == TK_Min)
+					{
+						if (value.Type == VAL_Float)
+						{
+							if (value.Float < best.Float)
+							{
+								best.Float = value.Float;
+							}
+						}
+						else
+						{
+							if (value.Int < best.Int)
+							{
+								best.Int = value.Int;
+							}
+						}
+					}
+					else
+					{
+						if (value.Type == VAL_Float)
+						{
+							if (value.Float > best.Float)
+							{
+								best.Float = value.Float;
+							}
+						}
+						else
+						{
+							if (value.Int > best.Int)
+							{
+								best.Int = value.Int;
+							}
+						}
+					}
+					delete choices[j];
+					choices[j] = NULL;
+					choices.Delete(j);
+				}
+			}
+			FxExpression *x = new FxConstant(best, ScriptPosition);
+			if (i == 0 && choices.Size() == 1)
+			{ // Every choice was constant
+				delete this;
+				return x;
+			}
+			delete choices[i];
+			choices[i] = x;
 			break;
 		}
 	}
-
-	// If every argument is constant, we can decide this now.
-	if (isconst)
-	{
-		ExpVal best = static_cast<FxConstant *>(choices[0])->GetValue();
-		for (i = 1; i < choices.Size(); ++i)
-		{
-			ExpVal value = static_cast<FxConstant *>(choices[i])->GetValue();
-			assert(value.Type == ValueType.Type);
-			if (Type == TK_Min)
-			{
-				if (value.Type == VAL_Float)
-				{
-					if (value.Float < best.Float)
-					{
-						best.Float = value.Float;
-					}
-				}
-				else
-				{
-					if (value.Int < best.Int)
-					{
-						best.Int = value.Int;
-					}
-				}
-			}
-			else
-			{
-				if (value.Type == VAL_Float)
-				{
-					if (value.Float > best.Float)
-					{
-						best.Float = value.Float;
-					}
-				}
-				else
-				{
-					if (value.Int > best.Int)
-					{
-						best.Int = value.Int;
-					}
-				}
-			}
-		}
-		FxExpression *x = new FxConstant(best, ScriptPosition);
-		delete this;
-		return x;
-	}
 	return this;
 }
 

From 58839200e577cebc88ba7ed26824031f7435e089 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@zdoom.fake>
Date: Tue, 23 Feb 2016 16:59:16 +0100
Subject: [PATCH 06/13] - fixed some line clipping issues with portals

 * Blocking lines above or below the current sector should only block if they actually intersect with the currently checking actor.
 * Sectors above a ceiling portal should not change current floor information and vice versa.
---
 src/p_map.cpp | 132 ++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 102 insertions(+), 30 deletions(-)

diff --git a/src/p_map.cpp b/src/p_map.cpp
index 0b1d78f23..d68771b22 100644
--- a/src/p_map.cpp
+++ b/src/p_map.cpp
@@ -748,6 +748,30 @@ int P_GetMoveFactor(const AActor *mo, int *frictionp)
 }
 
 
+//==========================================================================
+//
+// Checks if the line intersects with the actor
+// returns 
+// - 1 when above/below
+// - 0 when intersecting
+// - -1 when outside the portal
+//
+//==========================================================================
+
+static int LineIsAbove(line_t *line, AActor *actor)
+{
+	AActor *point = line->frontsector->SkyBoxes[sector_t::floor];
+	if (point == NULL) return -1;
+	return point->threshold >= actor->Top();
+}
+
+static int LineIsBelow(line_t *line, AActor *actor)
+{
+	AActor *point = line->frontsector->SkyBoxes[sector_t::ceiling];
+	if (point == NULL) return -1;
+	return point->threshold <= actor->Z();
+}
+
 //
 // MOVEMENT ITERATOR FUNCTIONS
 //
@@ -788,6 +812,13 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
 
 	if (!ld->backsector)
 	{ // One sided line
+		if (((cres.portalflags & FFCF_NOFLOOR) && LineIsAbove(cres.line, tm.thing) != 0) ||
+			((cres.portalflags & FFCF_NOCEILING) && LineIsBelow(cres.line, tm.thing) != 0))
+		{
+			// this blocking line is in a different vertical layer and does not intersect with the actor that is being checked.
+			// Since a one-sided line does not have an opening there's nothing left to do about it.
+			return true;
+		}
 		if (tm.thing->flags2 & MF2_BLASTED)
 		{
 			P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
@@ -818,17 +849,52 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
 			((Projectile) && (ld->flags & ML_BLOCKPROJECTILE)) ||				// block projectiles
 			((tm.thing->flags & MF_FLOAT) && (ld->flags & ML_BLOCK_FLOATERS)))	// block floaters
 		{
-			if (tm.thing->flags2 & MF2_BLASTED)
+			if (cres.portalflags & FFCF_NOFLOOR)
 			{
-				P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
+				int state = LineIsAbove(cres.line, tm.thing);
+				if (state == -1) return true;
+				if (state == 1)
+				{
+					// the line should not block but we should set the ceilingz to the portal boundary so that we can't float up into that line.
+					fixed_t portalz = cres.line->frontsector->SkyBoxes[sector_t::floor]->threshold;
+					if (portalz < tm.ceilingz)
+					{
+						tm.ceilingz = portalz;
+						tm.ceilingsector = cres.line->frontsector;
+					}
+					return true;
+				}
+			}
+			else if (cres.portalflags & FFCF_NOCEILING)
+			{
+				// same, but for downward portals
+				int state = LineIsBelow(cres.line, tm.thing);
+				if (state == -1) return true;
+				if (state == 1)
+				{
+					fixed_t portalz = cres.line->frontsector->SkyBoxes[sector_t::ceiling]->threshold;
+					if (portalz > tm.floorz)
+					{
+						tm.floorz = portalz;
+						tm.floorsector = cres.line->frontsector;
+						tm.floorterrain = 0;
+					}
+					return true;
+				}
+			}
+			else
+			{
+				if (tm.thing->flags2 & MF2_BLASTED)
+				{
+					P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
+				}
+				tm.thing->BlockingLine = ld;
+				// Calculate line side based on the actor's original position, not the new one.
+				CheckForPushSpecial(ld, P_PointOnLineSide(cres.position.x, cres.position.y, ld), tm.thing);
+				return false;
 			}
-			tm.thing->BlockingLine = ld;
-			// Calculate line side based on the actor's original position, not the new one.
-			CheckForPushSpecial(ld, P_PointOnLineSide(cres.position.x, cres.position.y, ld), tm.thing);
-			return false;
 		}
 	}
-
 	fixedvec2 ref = FindRefPoint(ld, cres.position);
 	FLineOpening open;
 
@@ -867,33 +933,39 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
 	}
 
 	// adjust floor / ceiling heights
-	if (open.top < tm.ceilingz)
+	if (!(cres.portalflags & FFCF_NOCEILING))
 	{
-		tm.ceilingz = open.top;
-		tm.ceilingsector = open.topsec;
-		tm.ceilingpic = open.ceilingpic;
-		tm.ceilingline = ld;
-		tm.thing->BlockingLine = ld;
+		if (open.top < tm.ceilingz)
+		{
+			tm.ceilingz = open.top;
+			tm.ceilingsector = open.topsec;
+			tm.ceilingpic = open.ceilingpic;
+			tm.ceilingline = ld;
+			tm.thing->BlockingLine = ld;
+		}
 	}
 
-	if (open.bottom > tm.floorz)
+	if (!(cres.portalflags & FFCF_NOFLOOR))
 	{
-		tm.floorz = open.bottom;
-		tm.floorsector = open.bottomsec;
-		tm.floorpic = open.floorpic;
-		tm.floorterrain = open.floorterrain;
-		tm.touchmidtex = open.touchmidtex;
-		tm.abovemidtex = open.abovemidtex;
-		tm.thing->BlockingLine = ld;
-	}
-	else if (open.bottom == tm.floorz)
-	{
-		tm.touchmidtex |= open.touchmidtex;
-		tm.abovemidtex |= open.abovemidtex;
-	}
+		if (open.bottom > tm.floorz)
+		{
+			tm.floorz = open.bottom;
+			tm.floorsector = open.bottomsec;
+			tm.floorpic = open.floorpic;
+			tm.floorterrain = open.floorterrain;
+			tm.touchmidtex = open.touchmidtex;
+			tm.abovemidtex = open.abovemidtex;
+			tm.thing->BlockingLine = ld;
+		}
+		else if (open.bottom == tm.floorz)
+		{
+			tm.touchmidtex |= open.touchmidtex;
+			tm.abovemidtex |= open.abovemidtex;
+		}
 
-	if (open.lowfloor < tm.dropoffz)
-		tm.dropoffz = open.lowfloor;
+		if (open.lowfloor < tm.dropoffz)
+			tm.dropoffz = open.lowfloor;
+	}
 
 	// if contacted a special line, add it to the list
 	spechit_t spec;
@@ -903,7 +975,7 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
 		spec.refpos = cres.position;
 		spechit.Push(spec);
 	}
-	if (ld->portalindex >= 0)
+	if (ld->portalindex >= 0 && ld->portalindex != UINT_MAX)
 	{
 		spec.line = ld;
 		spec.refpos = cres.position;

From 01bdd8a7da8e204fc6f2115071213ff446c893ae Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@zdoom.fake>
Date: Wed, 24 Feb 2016 01:06:48 +0100
Subject: [PATCH 07/13] - actor transition through a sector portal is working.

---
 src/actor.h       |  1 +
 src/p_mobj.cpp    | 40 ++++++++++++++++++++++++++++++++++++++++
 src/r_utility.cpp | 27 +++++++++++++++++++++++++--
 3 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/src/actor.h b/src/actor.h
index 35afef397..47f6ef44b 100644
--- a/src/actor.h
+++ b/src/actor.h
@@ -742,6 +742,7 @@ public:
 	bool IsHostile (AActor *other);
 
 	inline bool IsNoClip2() const;
+	void CheckPortalTransition();
 
 	// What species am I?
 	virtual FName GetSpecies();
diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index 7228ffbff..73683824b 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -3288,6 +3288,43 @@ void AActor::SetRoll(angle_t r, bool interpolate)
 	}
 }
 
+
+void AActor::CheckPortalTransition()
+{
+	if (!Sector->PortalBlocksMovement(sector_t::ceiling))
+	{
+		AActor *port = Sector->SkyBoxes[sector_t::ceiling];
+		if (Z() > port->threshold)
+		{
+			fixedvec3 oldpos = Pos();
+			UnlinkFromWorld();
+			SetXYZ(PosRelative(port->Sector));
+			PrevX += X() - oldpos.x;
+			PrevY += Y() - oldpos.y;
+			PrevZ += Z() - oldpos.z;
+			LinkToWorld();
+			if (player) Printf("Transitioned upwards to sector %d\n", Sector->sectornum);
+			return;
+		}
+	}
+	if (!Sector->PortalBlocksMovement(sector_t::floor))
+	{
+		AActor *port = Sector->SkyBoxes[sector_t::floor];
+		if (Z() < port->threshold && floorz < port->threshold)
+		{
+			fixedvec3 oldpos = Pos();
+			UnlinkFromWorld();
+			SetXYZ(PosRelative(port->Sector));
+			PrevX += X() - oldpos.x;
+			PrevY += Y() - oldpos.y;
+			PrevZ += Z() - oldpos.z;
+			LinkToWorld();
+			if (player) Printf("Transitioned downwards to sector %d\n", Sector->sectornum);
+			return;
+		}
+	}
+}
+
 //
 // P_MobjThinker
 //
@@ -3355,6 +3392,7 @@ void AActor::Tick ()
 		UnlinkFromWorld ();
 		flags |= MF_NOBLOCKMAP;
 		SetXYZ(Vec3Offset(velx, vely, velz));
+		CheckPortalTransition();
 		SetMovement(velx, vely, velz);
 		LinkToWorld ();
 	}
@@ -3822,6 +3860,8 @@ void AActor::Tick ()
 			Crash();
 		}
 
+		CheckPortalTransition();
+
 		UpdateWaterLevel (oldz);
 
 		// [RH] Don't advance if predicting a player
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index 10a39ba57..60a415d80 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -581,8 +581,11 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 		iview->oviewpitch = iview->nviewpitch;
 		iview->oviewangle = iview->nviewangle;
 	}
-	viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx);
-	viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy);
+	int oldgroup = R_PointInSubsector(iview->oviewx, iview->oviewy)->sector->PortalGroup;
+	int newgroup = R_PointInSubsector(iview->nviewx, iview->nviewy)->sector->PortalGroup;
+	fixedvec2 disp = Displacements(oldgroup, newgroup);
+	viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx - disp.x);
+	viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy - disp.y);
 	viewz = iview->oviewz + FixedMul (frac, iview->nviewz - iview->oviewz);
 	if (player != NULL &&
 		!(player->cheats & CF_INTERPVIEW) &&
@@ -637,6 +640,26 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 	
 	// Due to interpolation this is not necessarily the same as the sector the camera is in.
 	viewsector = R_PointInSubsector(viewx, viewy)->sector;
+	if (!viewsector->PortalBlocksMovement(sector_t::ceiling))
+	{
+		AActor *point = viewsector->SkyBoxes[sector_t::ceiling];
+		if (viewz > point->threshold)
+		{
+			viewx += point->scaleX;
+			viewy += point->scaleY;
+			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+		}
+	}
+	if (!viewsector->PortalBlocksMovement(sector_t::floor))
+	{
+		AActor *point = viewsector->SkyBoxes[sector_t::floor];
+		if (viewz < point->threshold)
+		{
+			viewx += point->scaleX;
+			viewy += point->scaleY;
+			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+		}
+	}
 }
 
 //==========================================================================

From 326907f6abeb674319d011bab4fa0ebae4118098 Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@users.noreply.github.com>
Date: Tue, 23 Feb 2016 19:36:31 -0600
Subject: [PATCH 08/13] Cleanup parsing of DECORATE intrinsics

- Split specific parsing for each intrinsic out of ParseExpression0 and
  into their own functions.
- Instead of reserving keywords for intrinsics, identify them by name
  within TK_Identifier's handling.
---
 src/namedef.h                        |   3 +
 src/sc_man_scanner.re                |   8 -
 src/sc_man_tokens.h                  |   8 -
 src/thingdef/thingdef_exp.cpp        | 291 ++++++++++++++-------------
 src/thingdef/thingdef_exp.h          |   4 +-
 src/thingdef/thingdef_expression.cpp |   8 +-
 6 files changed, 155 insertions(+), 167 deletions(-)

diff --git a/src/namedef.h b/src/namedef.h
index 323d6f29c..83f4aca9b 100644
--- a/src/namedef.h
+++ b/src/namedef.h
@@ -280,7 +280,10 @@ xx(Cast) // 'damage type' for the cast call
 
 // Special names for thingdef_exp.cpp
 xx(Random)
+xx(FRandom)
 xx(Random2)
+xx(RandomPick)
+xx(FRandomPick)
 xx(Cos)
 xx(Sin)
 xx(Alpha)
diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re
index 4a838f89a..6ebd9a413 100644
--- a/src/sc_man_scanner.re
+++ b/src/sc_man_scanner.re
@@ -185,14 +185,6 @@ std2:
 		'#include'					{ RET(TK_Include); }
 		'fixed_t'					{ RET(TK_Fixed_t); }
 		'angle_t'					{ RET(TK_Angle_t); }
-		'abs'						{ RET(TK_Abs); }
-		'random'					{ RET(TK_Random); }
-		'random2'					{ RET(TK_Random2); }
-		'frandom'					{ RET(TK_FRandom); }
-		'randompick'				{ RET(TK_RandomPick); }
-		'frandompick'				{ RET(TK_FRandomPick); }
-		'min'						{ RET(TK_Min); }
-		'max'						{ RET(TK_Max); }
 
 		L (L|D)*					{ RET(TK_Identifier); }
 
diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h
index 4dcddd332..30a8201b9 100644
--- a/src/sc_man_tokens.h
+++ b/src/sc_man_tokens.h
@@ -113,10 +113,6 @@ xx(TK_Stop,					"'stop'")
 xx(TK_Include,				"'include'")
 xx(TK_Fixed_t,				"'fixed_t'")
 xx(TK_Angle_t,				"'angle_t'")
-xx(TK_Abs,					"'abs'")
-xx(TK_Random,				"'random'")
-xx(TK_Random2,				"'random2'")
-xx(TK_FRandom,				"'frandom'")
 
 xx(TK_Is,					"'is'")
 xx(TK_Replaces,				"'replaces'")
@@ -126,10 +122,6 @@ xx(TK_Array,				"'array'")
 xx(TK_In,					"'in'")
 xx(TK_SizeOf,				"'sizeof'")
 xx(TK_AlignOf,				"'alignof'")
-xx(TK_RandomPick,			"'randompick'")
-xx(TK_FRandomPick,			"'frandompick'")
-xx(TK_Min,					"'min'")
-xx(TK_Max,					"'max'")
 xx(TK_States,				"'states'")
 xx(TK_Loop,					"'loop'")
 xx(TK_Fail,					"'fail'")
diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp
index 2204a5652..7ef93563c 100644
--- a/src/thingdef/thingdef_exp.cpp
+++ b/src/thingdef/thingdef_exp.cpp
@@ -52,6 +52,12 @@
 
 FRandom pr_exrandom ("EX_Random");
 
+static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls);
+static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls);
+static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls);
+static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls);
+static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls);
+
 //
 // ParseExpression
 // [GRB] Parses an expression and stores it into Expression array
@@ -349,161 +355,63 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
 		// a cheap way to get them working when people use "name" instead of 'name'.
 		return new FxConstant(FName(sc.String), scpos);
 	}
-	else if (sc.CheckToken(TK_Min) || sc.CheckToken(TK_Max))
-	{
-		int type = sc.TokenType;
-		TArray<FxExpression*> list;
-		sc.MustGetToken('(');
-		for (;;)
-		{
-			FxExpression *expr = ParseExpressionM(sc, cls);
-			list.Push(expr);
-			if (sc.CheckToken(')'))
-				break;
-			sc.MustGetToken(',');
-		}
-		return new FxMinMax(list, type, sc);
-	}
-	else if (sc.CheckToken(TK_Random))
-	{
-		FRandom *rng;
-
-		if (sc.CheckToken('['))
-		{
-			sc.MustGetToken(TK_Identifier);
-			rng = FRandom::StaticFindRNG(sc.String);
-			sc.MustGetToken(']');
-		}
-		else
-		{
-			rng = &pr_exrandom;
-		}
-		sc.MustGetToken('(');
-
-		FxExpression *min = ParseExpressionM (sc, cls);
-		sc.MustGetToken(',');
-		FxExpression *max = ParseExpressionM (sc, cls);
-		sc.MustGetToken(')');
-
-		return new FxRandom(rng, min, max, sc);
-	}
-	else if (sc.CheckToken(TK_RandomPick) || sc.CheckToken(TK_FRandomPick))
-	{
-		bool floaty = sc.TokenType == TK_FRandomPick;
-		FRandom *rng;
-		TArray<FxExpression*> list;
-		list.Clear();
-		int index = 0;
-
-		if (sc.CheckToken('['))
-		{
-			sc.MustGetToken(TK_Identifier);
-			rng = FRandom::StaticFindRNG(sc.String);
-			sc.MustGetToken(']');
-		}
-		else
-		{
-			rng = &pr_exrandom;
-		}
-		sc.MustGetToken('(');
-
-		for (;;)
-		{
-			FxExpression *expr = ParseExpressionM(sc, cls);
-			list.Push(expr);
-			if (sc.CheckToken(')'))
-				break;
-			sc.MustGetToken(',');
-		}
-		return new FxRandomPick(rng, list, floaty, sc);
-	}
-	else if (sc.CheckToken(TK_FRandom))
-	{
-		FRandom *rng;
-
-		if (sc.CheckToken('['))
-		{
-			sc.MustGetToken(TK_Identifier);
-			rng = FRandom::StaticFindRNG(sc.String);
-			sc.MustGetToken(']');
-		}
-		else
-		{
-			rng = &pr_exrandom;
-		}
-		sc.MustGetToken('(');
-
-		FxExpression *min = ParseExpressionM (sc, cls);
-		sc.MustGetToken(',');
-		FxExpression *max = ParseExpressionM (sc, cls);
-		sc.MustGetToken(')');
-
-		return new FxFRandom(rng, min, max, sc);
-	}
-	else if (sc.CheckToken(TK_Random2))
-	{
-		FRandom *rng;
-
-		if (sc.CheckToken('['))
-		{
-			sc.MustGetToken(TK_Identifier);
-			rng = FRandom::StaticFindRNG(sc.String);
-			sc.MustGetToken(']');
-		}
-		else
-		{
-			rng = &pr_exrandom;
-		}
-
-		sc.MustGetToken('(');
-
-		FxExpression *mask = NULL;
-
-		if (!sc.CheckToken(')'))
-		{
-			mask = ParseExpressionM(sc, cls);
-			sc.MustGetToken(')');
-		}
-		return new FxRandom2(rng, mask, sc);
-	}
-	else if (sc.CheckToken(TK_Abs))
-	{
-		sc.MustGetToken('(');
-		FxExpression *x = ParseExpressionM (sc, cls);
-		sc.MustGetToken(')');
-		return new FxAbs(x); 
-	}
 	else if (sc.CheckToken(TK_Identifier))
 	{
 		FName identifier = FName(sc.String);
+		FArgumentList *args;
+		PFunction *func;
+
+		switch (identifier)
+		{
+		case NAME_Random:
+		case NAME_FRandom:
+			return ParseRandom(sc, identifier, cls);
+		case NAME_RandomPick:
+		case NAME_FRandomPick:
+			return ParseRandomPick(sc, identifier, cls);
+		case NAME_Random2:
+			return ParseRandom2(sc, cls);
+		default:
+			break;
+		}
 		if (sc.CheckToken('('))
 		{
-			FArgumentList *args = new FArgumentList;
-			PFunction *func = dyn_cast<PFunction>(cls->Symbols.FindSymbol(identifier, true));
-			try
+			switch (identifier)
 			{
-				// There is an action function ACS_NamedExecuteWithResult which must be ignored here for this to work.
-				if (func != NULL && identifier != NAME_ACS_NamedExecuteWithResult)
+			case NAME_Min:
+			case NAME_Max:
+				return ParseMinMax(sc, identifier, cls);
+			case NAME_Abs:
+				return ParseAbs(sc, cls);
+			default:
+				args = new FArgumentList;
+				func = dyn_cast<PFunction>(cls->Symbols.FindSymbol(identifier, true));
+				try
 				{
-					sc.UnGet();
-					ParseFunctionParameters(sc, cls, *args, func, "", NULL);
-					return new FxVMFunctionCall(func, args, sc);
-				}
-				else if (!sc.CheckToken(')'))
-				{
-					do
+					// There is an action function ACS_NamedExecuteWithResult which must be ignored here for this to work.
+					if (func != NULL && identifier != NAME_ACS_NamedExecuteWithResult)
 					{
-						args->Push(ParseExpressionM (sc, cls));
+						sc.UnGet();
+						ParseFunctionParameters(sc, cls, *args, func, "", NULL);
+						return new FxVMFunctionCall(func, args, sc);
 					}
-					while (sc.CheckToken(','));
-					sc.MustGetToken(')');
+					else if (!sc.CheckToken(')'))
+					{
+						do
+						{
+							args->Push(ParseExpressionM (sc, cls));
+						}
+						while (sc.CheckToken(','));
+						sc.MustGetToken(')');
+					}
+					return new FxFunctionCall(NULL, identifier, args, sc);
 				}
-				return new FxFunctionCall(NULL, identifier, args, sc);
-			}
-			catch (...)
-			{
-				delete args;
-				throw;
+				catch (...)
+				{
+					delete args;
+					throw;
+				}
+				break;
 			}
 		}	
 		else
@@ -519,4 +427,97 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
 	return NULL;
 }
 
+static FRandom *ParseRNG(FScanner &sc)
+{
+	FRandom *rng;
 
+	if (sc.CheckToken('['))
+	{
+		sc.MustGetToken(TK_Identifier);
+		rng = FRandom::StaticFindRNG(sc.String);
+		sc.MustGetToken(']');
+	}
+	else
+	{
+		rng = &pr_exrandom;
+	}
+	return rng;
+}
+
+static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls)
+{
+	FRandom *rng = ParseRNG(sc);
+
+	sc.MustGetToken('(');
+	FxExpression *min = ParseExpressionM (sc, cls);
+	sc.MustGetToken(',');
+	FxExpression *max = ParseExpressionM (sc, cls);
+	sc.MustGetToken(')');
+
+	if (identifier == NAME_Random)
+	{
+		return new FxRandom(rng, min, max, sc);
+	}
+	else
+	{
+		return new FxFRandom(rng, min, max, sc);
+	}
+}
+
+static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls)
+{
+	bool floaty = identifier == NAME_FRandomPick;
+	FRandom *rng;
+	TArray<FxExpression*> list;
+	list.Clear();
+	int index = 0;
+
+	rng = ParseRNG(sc);
+	sc.MustGetToken('(');
+
+	for (;;)
+	{
+		FxExpression *expr = ParseExpressionM(sc, cls);
+		list.Push(expr);
+		if (sc.CheckToken(')'))
+			break;
+		sc.MustGetToken(',');
+	}
+	return new FxRandomPick(rng, list, floaty, sc);
+}
+
+static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls)
+{
+	FRandom *rng = ParseRNG(sc);
+	FxExpression *mask = NULL;
+
+	sc.MustGetToken('(');
+
+	if (!sc.CheckToken(')'))
+	{
+		mask = ParseExpressionM(sc, cls);
+		sc.MustGetToken(')');
+	}
+	return new FxRandom2(rng, mask, sc);
+}
+
+static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls)
+{
+	FxExpression *x = ParseExpressionM (sc, cls);
+	sc.MustGetToken(')');
+	return new FxAbs(x); 
+}
+
+static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls)
+{
+	TArray<FxExpression*> list;
+	for (;;)
+	{
+		FxExpression *expr = ParseExpressionM(sc, cls);
+		list.Push(expr);
+		if (sc.CheckToken(')'))
+			break;
+		sc.MustGetToken(',');
+	}
+	return new FxMinMax(list, identifier, sc);
+}
diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h
index 330caf0a5..294399d79 100644
--- a/src/thingdef/thingdef_exp.h
+++ b/src/thingdef/thingdef_exp.h
@@ -632,10 +632,10 @@ public:
 class FxMinMax : public FxExpression
 {
 	TDeletingArray<FxExpression *> choices;
-	int Type;
+	FName Type;
 
 public:
-	FxMinMax(TArray<FxExpression*> &expr, int type, const FScriptPosition &pos);
+	FxMinMax(TArray<FxExpression*> &expr, FName type, const FScriptPosition &pos);
 	FxExpression *Resolve(FCompileContext&);
 
 	ExpEmit Emit(VMFunctionBuilder *build);
diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp
index 8d0f158ba..833f3a3d5 100644
--- a/src/thingdef/thingdef_expression.cpp
+++ b/src/thingdef/thingdef_expression.cpp
@@ -2043,11 +2043,11 @@ ExpEmit FxAbs::Emit(VMFunctionBuilder *build)
 //
 //
 //==========================================================================
-FxMinMax::FxMinMax(TArray<FxExpression*> &expr, int type, const FScriptPosition &pos)
+FxMinMax::FxMinMax(TArray<FxExpression*> &expr, FName type, const FScriptPosition &pos)
 : FxExpression(pos), Type(type)
 {
 	assert(expr.Size() > 0);
-	assert(type == TK_Min || type == TK_Max);
+	assert(type == NAME_Min || type == NAME_Max);
 
 	ValueType = VAL_Unknown;
 	choices.Resize(expr.Size());
@@ -2132,7 +2132,7 @@ FxExpression *FxMinMax::Resolve(FCompileContext &ctx)
 				{
 					ExpVal value = static_cast<FxConstant *>(choices[j])->GetValue();
 					assert(value.Type == ValueType.Type);
-					if (Type == TK_Min)
+					if (Type == NAME_Min)
 					{
 						if (value.Type == VAL_Float)
 						{
@@ -2213,7 +2213,7 @@ ExpEmit FxMinMax::Emit(VMFunctionBuilder *build)
 	assert(OP_LEF_RK == OP_LEF_RR+1);
 	assert(OP_LE_RK == OP_LE_RR+1);
 
-	if (Type == TK_Min)
+	if (Type == NAME_Min)
 	{
 		opcode = ValueType.Type == VAL_Float ? OP_LEF_RR : OP_LE_RR;
 		opA = 1;

From b01e0fa06e265bc6d321d422d0fd62fccd91f1d7 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@zdoom.fake>
Date: Wed, 24 Feb 2016 10:08:23 +0100
Subject: [PATCH 09/13] - removed debug output from portal transition code. -
 handle the case where a portal transition crosses more than a single boundary
 in one move.

---
 src/actor.h       |  2 +-
 src/p_mobj.cpp    | 45 +++++++++++++++++++++++++--------------------
 src/r_utility.cpp | 22 +++++++++++++++-------
 3 files changed, 41 insertions(+), 28 deletions(-)

diff --git a/src/actor.h b/src/actor.h
index 47f6ef44b..15c2ea2ab 100644
--- a/src/actor.h
+++ b/src/actor.h
@@ -742,7 +742,7 @@ public:
 	bool IsHostile (AActor *other);
 
 	inline bool IsNoClip2() const;
-	void CheckPortalTransition();
+	void CheckPortalTransition(bool islinked);
 
 	// What species am I?
 	virtual FName GetSpecies();
diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index 73683824b..dafbaffba 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -3289,40 +3289,45 @@ void AActor::SetRoll(angle_t r, bool interpolate)
 }
 
 
-void AActor::CheckPortalTransition()
+void AActor::CheckPortalTransition(bool islinked)
 {
-	if (!Sector->PortalBlocksMovement(sector_t::ceiling))
+	bool moved = false;
+	while (!Sector->PortalBlocksMovement(sector_t::ceiling))
 	{
 		AActor *port = Sector->SkyBoxes[sector_t::ceiling];
 		if (Z() > port->threshold)
 		{
 			fixedvec3 oldpos = Pos();
-			UnlinkFromWorld();
+			if (islinked && !moved) UnlinkFromWorld();
 			SetXYZ(PosRelative(port->Sector));
 			PrevX += X() - oldpos.x;
 			PrevY += Y() - oldpos.y;
 			PrevZ += Z() - oldpos.z;
-			LinkToWorld();
-			if (player) Printf("Transitioned upwards to sector %d\n", Sector->sectornum);
-			return;
+			Sector = P_PointInSector(X(), Y());
+			moved = true;
 		}
+		else break;
 	}
-	if (!Sector->PortalBlocksMovement(sector_t::floor))
+	if (!moved)
 	{
-		AActor *port = Sector->SkyBoxes[sector_t::floor];
-		if (Z() < port->threshold && floorz < port->threshold)
+		while (!Sector->PortalBlocksMovement(sector_t::floor))
 		{
-			fixedvec3 oldpos = Pos();
-			UnlinkFromWorld();
-			SetXYZ(PosRelative(port->Sector));
-			PrevX += X() - oldpos.x;
-			PrevY += Y() - oldpos.y;
-			PrevZ += Z() - oldpos.z;
-			LinkToWorld();
-			if (player) Printf("Transitioned downwards to sector %d\n", Sector->sectornum);
-			return;
+			AActor *port = Sector->SkyBoxes[sector_t::floor];
+			if (Z() < port->threshold && floorz < port->threshold)
+			{
+				fixedvec3 oldpos = Pos();
+				if (islinked && !moved) UnlinkFromWorld();
+				SetXYZ(PosRelative(port->Sector));
+				PrevX += X() - oldpos.x;
+				PrevY += Y() - oldpos.y;
+				PrevZ += Z() - oldpos.z;
+				Sector = P_PointInSector(X(), Y());
+				moved = true;
+			}
+			else break;
 		}
 	}
+	if (islinked && moved) LinkToWorld();
 }
 
 //
@@ -3392,7 +3397,7 @@ void AActor::Tick ()
 		UnlinkFromWorld ();
 		flags |= MF_NOBLOCKMAP;
 		SetXYZ(Vec3Offset(velx, vely, velz));
-		CheckPortalTransition();
+		CheckPortalTransition(false);
 		SetMovement(velx, vely, velz);
 		LinkToWorld ();
 	}
@@ -3860,7 +3865,7 @@ void AActor::Tick ()
 			Crash();
 		}
 
-		CheckPortalTransition();
+		CheckPortalTransition(true);
 
 		UpdateWaterLevel (oldz);
 
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index 60a415d80..a97ebc65e 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -640,7 +640,8 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 	
 	// Due to interpolation this is not necessarily the same as the sector the camera is in.
 	viewsector = R_PointInSubsector(viewx, viewy)->sector;
-	if (!viewsector->PortalBlocksMovement(sector_t::ceiling))
+	bool moved = false;
+	while (!viewsector->PortalBlocksMovement(sector_t::ceiling))
 	{
 		AActor *point = viewsector->SkyBoxes[sector_t::ceiling];
 		if (viewz > point->threshold)
@@ -648,16 +649,23 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 			viewx += point->scaleX;
 			viewy += point->scaleY;
 			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+			moved = true;
 		}
+		else break;
 	}
-	if (!viewsector->PortalBlocksMovement(sector_t::floor))
+	if (!moved)
 	{
-		AActor *point = viewsector->SkyBoxes[sector_t::floor];
-		if (viewz < point->threshold)
+		while (!viewsector->PortalBlocksMovement(sector_t::floor))
 		{
-			viewx += point->scaleX;
-			viewy += point->scaleY;
-			viewsector = R_PointInSubsector(viewx, viewy)->sector;
+			AActor *point = viewsector->SkyBoxes[sector_t::floor];
+			if (viewz < point->threshold)
+			{
+				viewx += point->scaleX;
+				viewy += point->scaleY;
+				viewsector = R_PointInSubsector(viewx, viewy)->sector;
+				moved = true;
+			}
+			else break;
 		}
 	}
 }

From 51da78ba29f693260acaee459d51224569b0ca6a Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@zdoom.fake>
Date: Wed, 24 Feb 2016 10:35:29 +0100
Subject: [PATCH 10/13] - added a compatibility option to allow multiple exits
 to be triggered.

This is required by Daedalus's travel tubes which contain a faulty script with some leftover debug code.
---
 src/compatibility.cpp           | 1 +
 src/d_main.cpp                  | 4 +++-
 src/doomdef.h                   | 1 +
 src/g_level.cpp                 | 2 +-
 src/g_mapinfo.cpp               | 1 +
 wadsrc/static/compatibility.txt | 5 +++++
 wadsrc/static/language.enu      | 1 +
 wadsrc/static/menudef.txt       | 1 +
 8 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/compatibility.cpp b/src/compatibility.cpp
index 498fefb2a..c7df15027 100644
--- a/src/compatibility.cpp
+++ b/src/compatibility.cpp
@@ -146,6 +146,7 @@ static FCompatOption Options[] =
 	{ "floormove",				COMPATF2_FLOORMOVE, SLOT_COMPAT2 },
 	{ "soundcutoff",			COMPATF2_SOUNDCUTOFF, SLOT_COMPAT2 },
 	{ "pointonline",			COMPATF2_POINTONLINE, SLOT_COMPAT2 },
+	{ "multiexit",				COMPATF2_MULTIEXIT, SLOT_COMPAT2 },
 
 	{ NULL, 0, 0 }
 };
diff --git a/src/d_main.cpp b/src/d_main.cpp
index d2120f88e..bf451e408 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -572,7 +572,8 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL)
 		break;
 
 	case 4: // Old ZDoom compat mode
-		v = COMPATF_SOUNDTARGET|COMPATF_LIGHT;
+		v = COMPATF_SOUNDTARGET | COMPATF_LIGHT;
+		w = COMPATF2_MULTIEXIT;
 		break;
 
 	case 5: // MBF compat mode
@@ -627,6 +628,7 @@ CVAR (Flag, compat_badangles,			compatflags2, COMPATF2_BADANGLES);
 CVAR (Flag, compat_floormove,			compatflags2, COMPATF2_FLOORMOVE);
 CVAR (Flag, compat_soundcutoff,			compatflags2, COMPATF2_SOUNDCUTOFF);
 CVAR (Flag, compat_pointonline,			compatflags2, COMPATF2_POINTONLINE);
+CVAR (Flag, compat_multiexit,			compatflags2, COMPATF2_MULTIEXIT);
 
 //==========================================================================
 //
diff --git a/src/doomdef.h b/src/doomdef.h
index 802591386..3b066f8d8 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -341,6 +341,7 @@ enum
 	COMPATF2_FLOORMOVE		= 1 << 1,	// Use the same floor motion behavior as Doom.
 	COMPATF2_SOUNDCUTOFF	= 1 << 2,	// Cut off sounds when an actor vanishes instead of making it owner-less
 	COMPATF2_POINTONLINE	= 1 << 3,	// Use original but buggy P_PointOnLineSide() and P_PointOnDivlineSide()
+	COMPATF2_MULTIEXIT		= 1 << 4,	// Level exit can be triggered multiple times (required by Daedalus's travel tubes, thanks to a faulty script)
 };
 
 // Emulate old bugs for select maps. These are not exposed by a cvar
diff --git a/src/g_level.cpp b/src/g_level.cpp
index 5f305e5df..cfdfef9f5 100644
--- a/src/g_level.cpp
+++ b/src/g_level.cpp
@@ -535,7 +535,7 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill
 		Printf (TEXTCOLOR_RED "Unloading scripts cannot exit the level again.\n");
 		return;
 	}
-	if (gameaction == ga_completed)	// do not exit multiple times.
+	if (gameaction == ga_completed && !(i_compatflags2 & COMPATF2_MULTIEXIT))	// do not exit multiple times.
 	{
 		return;
 	}
diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp
index 1b1023fcd..b78dccf0d 100644
--- a/src/g_mapinfo.cpp
+++ b/src/g_mapinfo.cpp
@@ -1328,6 +1328,7 @@ MapFlagHandlers[] =
 	{ "compat_floormove",				MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE },
 	{ "compat_soundcutoff",				MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF },
 	{ "compat_pointonline",				MITYPE_COMPATFLAG, 0, COMPATF2_POINTONLINE },
+	{ "compat_multiexit",				MITYPE_COMPATFLAG, 0, COMPATF2_MULTIEXIT },
 	{ "cd_start_track",					MITYPE_EATNEXT,	0, 0 },
 	{ "cd_end1_track",					MITYPE_EATNEXT,	0, 0 },
 	{ "cd_end2_track",					MITYPE_EATNEXT,	0, 0 },
diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt
index 0bd924622..0e25effb8 100644
--- a/wadsrc/static/compatibility.txt
+++ b/wadsrc/static/compatibility.txt
@@ -411,6 +411,11 @@ D0139194F7817BF06F3988DFC47DB38D // Whispers of Satan map29
 	nopassover
 }
 
+5397C3B7D9B33AAF526D15A81A762828 // daedalus.wad Travel tubes (they are all identical)
+{
+	multiexit
+}
+
 D7F6E9F08C39A17026349A04F8C0B0BE // Return to Hadron, e1m9
 19D03FFC875589E21EDBB7AB74EF4AEF // Return to Hadron, e1m9, 2016.01.03 update
 {
diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu
index 1fbf83b08..12ac43fa9 100644
--- a/wadsrc/static/language.enu
+++ b/wadsrc/static/language.enu
@@ -2035,6 +2035,7 @@ CMPTMNU_SHORTTEX			= "Find shortest textures like Doom";
 CMPTMNU_STAIRS				= "Use buggier stair building";
 CMPTMNU_FLOORMOVE			= "Use Doom's floor motion behavior";
 CMPTMNU_POINTONLINE			= "Use Doom's point-on-line algorithm";
+CMPTMNU_MULTIEXIT			= "Level exit can be triggered more than once."
 CMPTMNU_PHYSICSBEHAVIOR		= "Physics Behavior";
 CMPTMNU_NOPASSOVER			= "Actors are infinitely tall";
 CMPTMNU_BOOMSCROLL			= "Boom scrollers are additive";
diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt
index ab32efa65..40b24f6e5 100644
--- a/wadsrc/static/menudef.txt
+++ b/wadsrc/static/menudef.txt
@@ -1304,6 +1304,7 @@ OptionMenu "CompatibilityOptions"
 	Option "$CMPTMNU_STAIRS",						"compat_stairs", "YesNo"
 	Option "$CMPTMNU_FLOORMOVE",					"compat_floormove", "YesNo"
 	Option "$CMPTMNU_POINTONLINE",					"compat_pointonline", "YesNo"
+	Option "$CMPTMNU_MULTIEXIT",					"compat_multiexit", "YesNo"
 	
 	StaticText " "
 	StaticText "$CMPTMNU_PHYSICSBEHAVIOR",1

From 8ba6f6ced5726a62718396fac52d5fe5857c55f9 Mon Sep 17 00:00:00 2001
From: "alexey.lysiuk" <alexey.lysiuk@gmail.com>
Date: Wed, 24 Feb 2016 11:26:42 +0200
Subject: [PATCH 11/13] Fixed crash when spawning decal without texture

See http://forum.zdoom.org/viewtopic.php?t=50977
---
 src/g_shared/a_decals.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/g_shared/a_decals.cpp b/src/g_shared/a_decals.cpp
index ebefcdff0..dcad93a3d 100644
--- a/src/g_shared/a_decals.cpp
+++ b/src/g_shared/a_decals.cpp
@@ -528,7 +528,11 @@ void DBaseDecal::Spread (const FDecalTemplate *tpl, side_t *wall, fixed_t x, fix
 	GetWallStuff (wall, v1, ldx, ldy);
 	rorg = Length (x - v1->x, y - v1->y);
 
-	tex = TexMan[PicNum];
+	if ((tex = TexMan[PicNum]) == NULL)
+	{
+		return;
+	}
+
 	int dwidth = tex->GetWidth ();
 
 	DecalWidth = dwidth * ScaleX;

From 21c55a090af4fbf2dfcd3916c692cabcef36ae2a Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@zdoom.fake>
Date: Wed, 24 Feb 2016 10:50:42 +0100
Subject: [PATCH 12/13] - fixed: "take armor" cheat should only deplete the
 armor, not destroy it. - fixed: Hexen armor cannot be depleted by the common
 function, it needs an override to achieve that.

---
 src/g_shared/a_armor.cpp | 8 ++++++++
 src/g_shared/a_pickups.h | 1 +
 src/m_cheat.cpp          | 6 +++---
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp
index c18f4172a..7be18139d 100644
--- a/src/g_shared/a_armor.cpp
+++ b/src/g_shared/a_armor.cpp
@@ -550,3 +550,11 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
 	}
 }
 
+
+void AHexenArmor::DepleteOrDestroy()
+{
+	for (int i = 0; i < 4; i++)
+	{
+		Slots[i] = 0;
+	}
+}
\ No newline at end of file
diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h
index 10391e0c8..ec16e129b 100644
--- a/src/g_shared/a_pickups.h
+++ b/src/g_shared/a_pickups.h
@@ -508,6 +508,7 @@ public:
 	virtual AInventory *CreateTossable ();
 	virtual bool HandlePickup (AInventory *item);
 	virtual void AbsorbDamage (int damage, FName damageType, int &newdamage);
+	void DepleteOrDestroy();
 
 	fixed_t Slots[5];
 	fixed_t SlotsIncrement[4];
diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp
index aa539f96b..b5da2a0a6 100644
--- a/src/m_cheat.cpp
+++ b/src/m_cheat.cpp
@@ -927,7 +927,7 @@ void cht_Take (player_t *player, const char *name, int amount)
 				AInventory *ammo = player->mo->FindInventory(static_cast<PClassActor *>(type));
 
 				if (ammo)
-					ammo->Amount = 0;
+					ammo->DepleteOrDestroy();
 			}
 		}
 
@@ -943,10 +943,10 @@ void cht_Take (player_t *player, const char *name, int amount)
 
 			if (type->IsDescendantOf (RUNTIME_CLASS (AArmor)))
 			{
-				AActor *armor = player->mo->FindInventory(static_cast<PClassActor *>(type));
+				AInventory *armor = player->mo->FindInventory(static_cast<PClassActor *>(type));
 
 				if (armor)
-					armor->Destroy ();
+					armor->DepleteOrDestroy();
 			}
 		}
 

From 8e2a629e5af7720270687d91964f4ec17f90bf10 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@zdoom.fake>
Date: Wed, 24 Feb 2016 11:19:58 +0100
Subject: [PATCH 13/13] - missed a semicolon,

---
 wadsrc/static/language.enu | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu
index 12ac43fa9..2dd1c3da2 100644
--- a/wadsrc/static/language.enu
+++ b/wadsrc/static/language.enu
@@ -2035,7 +2035,7 @@ CMPTMNU_SHORTTEX			= "Find shortest textures like Doom";
 CMPTMNU_STAIRS				= "Use buggier stair building";
 CMPTMNU_FLOORMOVE			= "Use Doom's floor motion behavior";
 CMPTMNU_POINTONLINE			= "Use Doom's point-on-line algorithm";
-CMPTMNU_MULTIEXIT			= "Level exit can be triggered more than once."
+CMPTMNU_MULTIEXIT			= "Level exit can be triggered more than once.";
 CMPTMNU_PHYSICSBEHAVIOR		= "Physics Behavior";
 CMPTMNU_NOPASSOVER			= "Actors are infinitely tall";
 CMPTMNU_BOOMSCROLL			= "Boom scrollers are additive";