diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index fb25dc58e..5eb6d9104 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -60,48 +60,6 @@ static const INT32 format2bpp[16] =
 	2, //14 GR_TEXFMT_AP_88
 };
 
-static RGBA_t astblendpixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
-{
-	RGBA_t output;
-	output.s.alpha = 0xFF;
-
-	if (style == AST_TRANSLUCENT)
-	{
-		if (alpha == 0)
-			output.rgba = background.rgba;
-		else if (alpha == 0xFF)
-			output.rgba = foreground.rgba;
-		else if (alpha < 0xFF)
-		{
-			UINT8 beta = (0xFF - alpha);
-			output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF;
-			output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF;
-			output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF;
-		}
-		// write foreground pixel alpha
-		// if there's no pixel in here
-		if (!background.rgba)
-			output.s.alpha = foreground.s.alpha;
-	}
-
-	return output;
-}
-
-static UINT8 astblendpixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
-{
-	if ((style == AST_TRANSLUCENT) && (alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
-	{
-		UINT8 *mytransmap;
-		if (alpha < 255/11) // Is the patch way too translucent? Don't render then.
-			return background;
-		// The equation's not exact but it works as intended. I'll call it a day for now.
-		mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
-		if (background != 0xFF)
-			return *(mytransmap + (background<<8) + foreground);
-	}
-	return background;
-}
-
 // This code was originally placed directly in HWR_DrawPatchInCache.
 // It is now split from it for my sanity! (and the sanity of others)
 // -- Monster Iestyn (13/02/19)
@@ -180,34 +138,34 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			switch (bpp)
 			{
 				case 2 : // uhhhhhhhh..........
-						 if ((originPatch != NULL) && originPatch->style)
-							 texel = astblendpixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
 				case 3 : colortemp = V_GetColor(texel);
-						 if ((originPatch != NULL) && originPatch->style)
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp = astblendpixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
 				case 4 : colortemp = V_GetColor(texel);
 						 colortemp.s.alpha = alpha;
-						 if ((originPatch != NULL) && originPatch->style)
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp = astblendpixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
-						 if ((originPatch != NULL) && originPatch->style)
-							 *dest = astblendpixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
@@ -294,34 +252,34 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			switch (bpp)
 			{
 				case 2 : // uhhhhhhhh..........
-						 if ((originPatch != NULL) && originPatch->style)
-							 texel = astblendpixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
 				case 3 : colortemp = V_GetColor(texel);
-						 if ((originPatch != NULL) && originPatch->style)
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp = astblendpixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
 				case 4 : colortemp = V_GetColor(texel);
 						 colortemp.s.alpha = alpha;
-						 if ((originPatch != NULL) && originPatch->style)
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp = astblendpixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
-						 if ((originPatch != NULL) && originPatch->style)
-							 *dest = astblendpixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
@@ -410,16 +368,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	if (texture->width <= 0 || texture->height <= 0)
 		return;
 
-	/*if ((patch->style == AST_TRANSLUCENT) && (patch->alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
-	{
-		if (patch->alpha < 255/11) // Is the patch way too translucent? Don't render then.
-			continue;
-		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawTransFlippedColumnInCache : HWR_DrawTransColumnInCache;
-	}
-	else*/
-	{
-		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
-	}
+	ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
 
 	x1 = patch->originx;
 	width = SHORT(realpatch->width);
diff --git a/src/r_data.c b/src/r_data.c
index 6889bddde..335a390d0 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -220,15 +220,110 @@ static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, tex
 	}
 }
 
+RGBA_t ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
+{
+	RGBA_t output;
+	if (style == AST_TRANSLUCENT)
+	{
+		if (alpha == 0)
+			output.rgba = background.rgba;
+		else if (alpha == 0xFF)
+			output.rgba = foreground.rgba;
+		else if (alpha < 0xFF)
+		{
+			UINT8 beta = (0xFF - alpha);
+			output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF;
+			output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF;
+			output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF;
+		}
+		// write foreground pixel alpha
+		// if there's no pixel in here
+		if (!background.rgba)
+			output.s.alpha = foreground.s.alpha;
+	}
+#define clamp(c) max(min(c, 0xFF), 0x00);
+	else
+	{
+		float falpha = ((float)alpha / 256.0f);
+		float fr = ((float)foreground.s.red * falpha);
+		float fg = ((float)foreground.s.green * falpha);
+		float fb = ((float)foreground.s.blue * falpha);
+		if (style == AST_ADD)
+		{
+			output.s.red = clamp((int)(background.s.red + fr));
+			output.s.green = clamp((int)(background.s.green + fg));
+			output.s.blue = clamp((int)(background.s.blue + fb));
+		}
+		else if (style == AST_SUBTRACT)
+		{
+			output.s.red = clamp((int)(background.s.red - fr));
+			output.s.green = clamp((int)(background.s.green - fg));
+			output.s.blue = clamp((int)(background.s.blue - fb));
+		}
+		else if (style == AST_REVERSESUBTRACT)
+		{
+			output.s.red = clamp((int)((-background.s.red) + fr));
+			output.s.green = clamp((int)((-background.s.green) + fg));
+			output.s.blue = clamp((int)((-background.s.blue) + fb));
+		}
+		else if (style == AST_MODULATE)
+		{
+			fr = ((float)foreground.s.red / 256.0f);
+			fg = ((float)foreground.s.green / 256.0f);
+			fb = ((float)foreground.s.blue / 256.0f);
+			output.s.red = clamp((int)(background.s.red * fr));
+			output.s.green = clamp((int)(background.s.green * fg));
+			output.s.blue = clamp((int)(background.s.blue * fb));
+		}
+		// just copy the pixel
+		else if (style == AST_COPY)
+			return background;
+	}
+#undef clamp
+	// unimplemented blend modes return the background pixel
+	output = background;
+	output.s.alpha = 0xFF;
+	return output;
+}
+
+UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
+{
+	if ((style == AST_TRANSLUCENT) && (alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
+	{
+		UINT8 *mytransmap;
+		if (alpha < 255/11) // Is the patch way too translucent? Don't render then.
+			return background;
+		// The equation's not exact but it works as intended. I'll call it a day for now.
+		mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
+		if (background != 0xFF)
+			return *(mytransmap + (background<<8) + foreground);
+	}
+	// just copy the pixel
+	else if (style == AST_COPY)
+		return background;
+	// use ASTBlendPixel for all other blend modes
+	// and find the nearest colour in the palette
+	else if (style != AST_TRANSLUCENT)
+	{
+		RGBA_t texel;
+		RGBA_t bg = V_GetColor(background);
+		RGBA_t fg = V_GetColor(foreground);
+		texel = ASTBlendPixel(bg, fg, style, alpha);
+		return NearestColor(texel.s.red, texel.s.green, texel.s.blue);
+	}
+	// fallback if all above fails, somehow
+	// return the background pixel
+	return background;
+}
+
 //
-// R_DrawTransColumnInCache
+// R_DrawBlendColumnInCache
 // Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
 //
-static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
 {
 	INT32 count, position;
 	UINT8 *source, *dest;
-	UINT8 *mytransmap = transtables + ((8*(originPatch->alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT); // The equation's not exact but it works as intended. I'll call it a day for now.
 	INT32 topdelta, prevdelta = -1;
 	INT32 originy = originPatch->originy;
 
@@ -258,7 +353,8 @@ static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpa
 		if (count > 0)
 		{
 			for (; dest < cache + position + count; source++, dest++)
-				if (*dest != 0xFF) *dest = *(mytransmap + ((*dest)<<8) + (*source));
+				if (*dest != 0xFF)
+					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
@@ -269,11 +365,10 @@ static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpa
 // R_DrawTransColumnInCache
 // Similar to the one above except that the column is inverted.
 //
-static inline void R_DrawTransFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
 {
 	INT32 count, position;
 	UINT8 *source, *dest;
-	UINT8 *mytransmap = transtables + ((8*(originPatch->alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT); // The equation's not exact but it works as intended. I'll call it a day for now.
 	INT32 topdelta, prevdelta = -1;
 	INT32 originy = originPatch->originy;
 
@@ -302,7 +397,8 @@ static inline void R_DrawTransFlippedColumnInCache(column_t *patch, UINT8 *cache
 		if (count > 0)
 		{
 			for (; dest < cache + position + count; --source, dest++)
-				if (*dest != 0xFF) *dest = *(mytransmap + ((*dest)<<8) + (*source));
+				if (*dest != 0xFF)
+					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
@@ -422,16 +518,10 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
 	{
 		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
-		if ((patch->style == AST_TRANSLUCENT) && (patch->alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
-		{
-			if (patch->alpha < 255/11) // Is the patch way too translucent? Don't render then.
-				continue;
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawTransFlippedColumnInCache : R_DrawTransColumnInCache;
-		}
+		if (patch->style != AST_COPY)
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
 		else
-		{
 			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
-		}
 
 		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
 		x1 = patch->originx;
@@ -848,8 +938,16 @@ static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
 				{
 					Z_Free(texturesToken);
 					texturesToken = M_GetToken(NULL);
-					if(stricmp(texturesToken, "TRANSLUCENT")==0)
+					if (stricmp(texturesToken, "TRANSLUCENT")==0)
 						style = AST_TRANSLUCENT;
+					else if (stricmp(texturesToken, "ADD")==0)
+						style = AST_ADD;
+					else if (stricmp(texturesToken, "SUBTRACT")==0)
+						style = AST_SUBTRACT;
+					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
+						style = AST_REVERSESUBTRACT;
+					else if (stricmp(texturesToken, "MODULATE")==0)
+						style = AST_MODULATE;
 				}
 				else if (stricmp(texturesToken, "FLIPX")==0)
 					flip |= 1;
@@ -1615,7 +1713,6 @@ extracolormap_t *R_ColormapForName(char *name)
 //
 static double deltas[256][3], map[256][3];
 
-static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
 static int RoundUp(double number);
 
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
@@ -2027,7 +2124,7 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 
 // Thanks to quake2 source!
 // utils3/qdata/images.c
-static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
 {
 	int dr, dg, db;
 	int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i;
diff --git a/src/r_data.h b/src/r_data.h
index b6b0a16a1..af1c9114a 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -22,7 +22,12 @@
 #endif
 
 // Possible alpha types for a patch.
-enum patchalphastyle {AST_COPY, AST_TRANSLUCENT}; // , AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
+enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
+
+RGBA_t ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
+UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
+
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
 
 // moved here for r_sky.c (texpatch_t is used)