
//**************************************************************************
//**
//** TEMPLATE.C
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "p_local.h"
#include "m_swap.h"
#include "w_wad.h"
#include "templates.h"
#include "r_sky.h"
#include "r_defs.h"
#include "p_setup.h"
#include "g_level.h"
#include "r_data/colormaps.h"
#include "gi.h"

// MACROS ------------------------------------------------------------------

//#define SHADE2LIGHT(s) (160-2*(s))
#define SHADE2LIGHT(s) (255-2*s)

// TYPES -------------------------------------------------------------------

//ceilingstat/floorstat:
//   bit 0: 1 = parallaxing, 0 = not                                 "P"
//   bit 1: 1 = groudraw, 0 = not
//   bit 2: 1 = swap x&y, 0 = not                                    "F"
//   bit 3: 1 = double smooshiness                                   "E"
//   bit 4: 1 = x-flip                                               "F"
//   bit 5: 1 = y-flip                                               "F"
//   bit 6: 1 = Align texture to first wall of sector                "R"
//   bits 7-8:                                                       "T"
//          00 = normal floors
//          01 = masked floors
//          10 = transluscent masked floors
//          11 = reverse transluscent masked floors
//   bits 9-15: reserved

	//40 bytes
struct sectortype
{
	SWORD wallptr, wallnum;
	SDWORD ceilingz, floorz;
	SWORD ceilingstat, floorstat;
	SWORD ceilingpicnum, ceilingheinum;
	SBYTE ceilingshade;
	BYTE ceilingpal, ceilingxpanning, ceilingypanning;
	SWORD floorpicnum, floorheinum;
	SBYTE floorshade;
	BYTE floorpal, floorxpanning, floorypanning;
	BYTE visibility, filler;
	SWORD lotag, hitag, extra;
};

//cstat:
//   bit 0: 1 = Blocking wall (use with clipmove, getzrange)         "B"
//   bit 1: 1 = bottoms of invisible walls swapped, 0 = not          "2"
//   bit 2: 1 = align picture on bottom (for doors), 0 = top         "O"
//   bit 3: 1 = x-flipped, 0 = normal                                "F"
//   bit 4: 1 = masking wall, 0 = not                                "M"
//   bit 5: 1 = 1-way wall, 0 = not                                  "1"
//   bit 6: 1 = Blocking wall (use with hitscan / cliptype 1)        "H"
//   bit 7: 1 = Transluscence, 0 = not                               "T"
//   bit 8: 1 = y-flipped, 0 = normal                                "F"
//   bit 9: 1 = Transluscence reversing, 0 = normal                  "T"
//   bits 10-15: reserved

	//32 bytes
struct walltype
{
	SDWORD x, y;
	SWORD point2, nextwall, nextsector, cstat;
	SWORD picnum, overpicnum;
	SBYTE shade;
	BYTE pal, xrepeat, yrepeat, xpanning, ypanning;
	SWORD lotag, hitag, extra;
};

//cstat:
//   bit 0: 1 = Blocking sprite (use with clipmove, getzrange)       "B"
//   bit 1: 1 = transluscence, 0 = normal                            "T"
//   bit 2: 1 = x-flipped, 0 = normal                                "F"
//   bit 3: 1 = y-flipped, 0 = normal                                "F"
//   bits 5-4: 00 = FACE sprite (default)                            "R"
//             01 = WALL sprite (like masked walls)
//             10 = FLOOR sprite (parallel to ceilings&floors)
//   bit 6: 1 = 1-sided sprite, 0 = normal                           "1"
//   bit 7: 1 = Real centered centering, 0 = foot center             "C"
//   bit 8: 1 = Blocking sprite (use with hitscan / cliptype 1)      "H"
//   bit 9: 1 = Transluscence reversing, 0 = normal                  "T"
//   bits 10-14: reserved
//   bit 15: 1 = Invisible sprite, 0 = not invisible

	//44 bytes
struct spritetype
{
	SDWORD x, y, z;
	SWORD cstat, picnum;
	SBYTE shade;
	BYTE pal, clipdist, filler;
	BYTE xrepeat, yrepeat;
	SBYTE xoffset, yoffset;
	SWORD sectnum, statnum;
	SWORD ang, owner, xvel, yvel, zvel;
	SWORD lotag, hitag, extra;
};

// I used to have all the Xobjects mapped out. Not anymore.
// (Thanks for the great firmware, Seagate!)
struct Xsprite
{
	BYTE NotReallyPadding[16];
	WORD Data1;
	WORD Data2;
	WORD Data3;
	WORD ThisIsntPaddingEither;
	DWORD NorThis:2;
	DWORD Data4:16;
	DWORD WhatIsThisIDontEven:14;
	BYTE ThisNeedsToBe56Bytes[28];
};

struct SlopeWork
{
	walltype *wal;
	walltype *wal2;
	long dx, dy, i, x[3], y[3], z[3];
	long heinum;
};

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

void P_AdjustLine (line_t *line);

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static bool P_LoadBloodMap (BYTE *data, size_t len, FMapThing **sprites, int *numsprites);
static void LoadSectors (sectortype *bsectors);
static void LoadWalls (walltype *walls, int numwalls, sectortype *bsectors);
static int LoadSprites (spritetype *sprites, Xsprite *xsprites, int numsprites, sectortype *bsectors, FMapThing *mapthings);
static vertex_t *FindVertex (fixed_t x, fixed_t y);
static void CreateStartSpot (fixed_t *pos, FMapThing *start);
static void CalcPlane (SlopeWork &slope, secplane_t &plane);
static void Decrypt (void *to, const void *from, int len, int key);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// CODE --------------------------------------------------------------------

bool P_IsBuildMap(MapData *map)
{
	DWORD len = map->Size(ML_LABEL);
	if (len < 4)
	{
		return false;
	}
	BYTE *data = new BYTE[len];

	map->Seek(ML_LABEL);
	map->Read(ML_LABEL, data);

	// Check for a Blood map.
	if (*(DWORD *)data == MAKE_ID('B','L','M','\x1a'))
	{
		delete[] data;
		return true;
	}

	const int numsec = LittleShort(*(WORD *)(data + 20));
	int numwalls;

	if (len < 26 + numsec*sizeof(sectortype) ||
		(numwalls = LittleShort(*(WORD *)(data + 22 + numsec*sizeof(sectortype))),
			len < 24 + numsec*sizeof(sectortype) + numwalls*sizeof(walltype)) ||
		LittleLong(*(DWORD *)data) != 7 ||
		LittleShort(*(WORD *)(data + 16)) >= 2048)
	{ // Can't possibly be a version 7 BUILD map
		delete[] data;
		return false;
	}
	delete[] data;
	return true;
}

//==========================================================================
//
// P_LoadBuildMap
//
//==========================================================================

bool P_LoadBuildMap (BYTE *data, size_t len, FMapThing **sprites, int *numspr)
{
	if (len < 26)
	{
		return false;
	}

	// Check for a Blood map.
	if (*(DWORD *)data == MAKE_ID('B','L','M','\x1a'))
	{
		return P_LoadBloodMap (data, len, sprites, numspr);
	}

	const int numsec = LittleShort(*(WORD *)(data + 20));
	int numwalls;
	int numsprites;

	if (len < 26 + numsec*sizeof(sectortype) ||
		(numwalls = LittleShort(*(WORD *)(data + 22 + numsec*sizeof(sectortype))),
			len < 24 + numsec*sizeof(sectortype) + numwalls*sizeof(walltype)) ||
		LittleLong(*(DWORD *)data) != 7 ||
		LittleShort(*(WORD *)(data + 16)) >= 2048)
	{ // Can't possibly be a version 7 BUILD map
		return false;
	}

	numsectors = numsec;
	LoadSectors ((sectortype *)(data + 22));
	LoadWalls ((walltype *)(data + 24 + numsectors*sizeof(sectortype)), numwalls,
		(sectortype *)(data + 22));

	numsprites = *(WORD *)(data + 24 + numsectors*sizeof(sectortype) + numwalls*sizeof(walltype));
	*sprites = new FMapThing[numsprites + 1];
	CreateStartSpot ((fixed_t *)(data + 4), *sprites);
	*numspr = 1 + LoadSprites ((spritetype *)(data + 26 + numsectors*sizeof(sectortype) + numwalls*sizeof(walltype)),
		NULL, numsprites, (sectortype *)(data + 22), *sprites + 1);

	return true;
}

//==========================================================================
//
// P_LoadBloodMap
//
//==========================================================================

static bool P_LoadBloodMap (BYTE *data, size_t len, FMapThing **mapthings, int *numspr)
{
	BYTE infoBlock[37];
	int mapver = data[5];
	DWORD matt;
	int numRevisions, numWalls, numsprites, skyLen, visibility, parallaxType;
	int i;
	int k;

	if (mapver != 6 && mapver != 7)
	{
		return false;
	}

	matt = *(DWORD *)(data + 28);
	if (matt != 0 &&
		matt != MAKE_ID('M','a','t','t') &&
		matt != MAKE_ID('t','t','a','M'))
	{
		Decrypt (infoBlock, data + 6, 37, 0x7474614d);
	}
	else
	{
		memcpy (infoBlock, data + 6, 37);
	}
	skyLen = 2 << LittleShort(*(WORD *)(infoBlock + 16));
	visibility = LittleLong(*(DWORD *)(infoBlock + 18));
	parallaxType = infoBlock[26];
	numRevisions = LittleLong(*(DWORD *)(infoBlock + 27));
	numsectors = LittleShort(*(WORD *)(infoBlock + 31));
	numWalls = LittleShort(*(WORD *)(infoBlock + 33));
	numsprites = LittleShort(*(WORD *)(infoBlock + 35));
	Printf("Visibility: %d\n", visibility);

	if (mapver == 7)
	{
		// Version 7 has some extra stuff after the info block. This
		// includes a copyright, and I have no idea what the rest of
		// it is.
		data += 171;
	}
	else
	{
		data += 43;
	}

	// Skip the sky info.
	data += skyLen;

	sectortype *bsec = new sectortype[numsectors];
	walltype *bwal = new walltype[numWalls];
	spritetype *bspr = new spritetype[numsprites];
	Xsprite *xspr = new Xsprite[numsprites];

	// Read sectors
	k = numRevisions * sizeof(sectortype);
	for (i = 0; i < numsectors; ++i)
	{
		if (mapver == 7)
		{
			Decrypt (&bsec[i], data, sizeof(sectortype), k);
		}
		else
		{
			memcpy (&bsec[i], data, sizeof(sectortype));
		}
		data += sizeof(sectortype);
		if (bsec[i].extra > 0)	// skip Xsector
		{
			data += 60;
		}
	}

	// Read walls
	k |= 0x7474614d;
	for (i = 0; i < numWalls; ++i)
	{
		if (mapver == 7)
		{
			Decrypt (&bwal[i], data, sizeof(walltype), k);
		}
		else
		{
			memcpy (&bwal[i], data, sizeof(walltype));
		}
		data += sizeof(walltype);
		if (bwal[i].extra > 0)	// skip Xwall
		{
			data += 24;
		}
	}

	// Read sprites
	k = (numRevisions * sizeof(spritetype)) | 0x7474614d;
	for (i = 0; i < numsprites; ++i)
	{
		if (mapver == 7)
		{
			Decrypt (&bspr[i], data, sizeof(spritetype), k);
		}
		else
		{
			memcpy (&bspr[i], data, sizeof(spritetype));
		}
		data += sizeof(spritetype);
		if (bspr[i].extra > 0)	// copy Xsprite
		{
			assert(sizeof(Xsprite) == 56);
			memcpy(&xspr[i], data, sizeof(Xsprite));
			data += sizeof(Xsprite);
		}
		else
		{
			memset(&xspr[i], 0, sizeof(Xsprite));
		}
	}

	// Now convert to Doom format, since we've extracted all the standard
	// BUILD info from the map we need. (Sprites are ignored.)
	LoadSectors (bsec);
	LoadWalls (bwal, numWalls, bsec);
	*mapthings = new FMapThing[numsprites];
	*numspr = LoadSprites (bspr, xspr, numsprites, bsec, *mapthings);

	delete[] bsec;
	delete[] bwal;
	delete[] bspr;
	delete[] xspr;

	return true;
}

//==========================================================================
//
// LoadSectors
//
//==========================================================================

static void LoadSectors (sectortype *bsec)
{
	FDynamicColormap *map = GetSpecialLights (PalEntry (255,255,255), level.fadeto, 0);
	sector_t *sec;
	char tnam[9];

	sec = sectors = new sector_t[numsectors];
	memset (sectors, 0, sizeof(sector_t)*numsectors);

	sectors[0].e = new extsector_t[numsectors];

	for (int i = 0; i < numsectors; ++i, ++bsec, ++sec)
	{
		bsec->wallptr = WORD(bsec->wallptr);
		bsec->wallnum = WORD(bsec->wallnum);
		bsec->ceilingstat = WORD(bsec->ceilingstat);
		bsec->floorstat = WORD(bsec->floorstat);

		sec->e = &sectors[0].e[i];
		sec->SetPlaneTexZ(sector_t::floor, -(LittleLong(bsec->floorz) << 8));
		sec->floorplane.d = -sec->GetPlaneTexZ(sector_t::floor);
		sec->floorplane.c = FRACUNIT;
		sec->floorplane.ic = FRACUNIT;
		mysnprintf (tnam, countof(tnam), "BTIL%04d", LittleShort(bsec->floorpicnum));
		sec->SetTexture(sector_t::floor, TexMan.GetTexture (tnam, FTexture::TEX_Build));
		sec->SetXScale(sector_t::floor, (bsec->floorstat & 8) ? FRACUNIT*2 : FRACUNIT);
		sec->SetYScale(sector_t::floor, (bsec->floorstat & 8) ? FRACUNIT*2 : FRACUNIT);
		sec->SetXOffset(sector_t::floor, (bsec->floorxpanning << FRACBITS) + (32 << FRACBITS));
		sec->SetYOffset(sector_t::floor, bsec->floorypanning << FRACBITS);
		sec->SetPlaneLight(sector_t::floor, SHADE2LIGHT (bsec->floorshade));
		sec->ChangeFlags(sector_t::floor, 0, PLANEF_ABSLIGHTING);

		sec->SetPlaneTexZ(sector_t::ceiling, -(LittleLong(bsec->ceilingz) << 8));
		sec->ceilingplane.d = sec->GetPlaneTexZ(sector_t::ceiling);
		sec->ceilingplane.c = -FRACUNIT;
		sec->ceilingplane.ic = -FRACUNIT;
		mysnprintf (tnam, countof(tnam), "BTIL%04d", LittleShort(bsec->ceilingpicnum));
		sec->SetTexture(sector_t::ceiling, TexMan.GetTexture (tnam, FTexture::TEX_Build));
		if (bsec->ceilingstat & 1)
		{
			sky1texture = sky2texture = sec->GetTexture(sector_t::ceiling);
			sec->SetTexture(sector_t::ceiling, skyflatnum);
		}
		sec->SetXScale(sector_t::ceiling, (bsec->ceilingstat & 8) ? FRACUNIT*2 : FRACUNIT);
		sec->SetYScale(sector_t::ceiling, (bsec->ceilingstat & 8) ? FRACUNIT*2 : FRACUNIT);
		sec->SetXOffset(sector_t::ceiling, (bsec->ceilingxpanning << FRACBITS) + (32 << FRACBITS));
		sec->SetYOffset(sector_t::ceiling, bsec->ceilingypanning << FRACBITS);
		sec->SetPlaneLight(sector_t::ceiling, SHADE2LIGHT (bsec->ceilingshade));
		sec->ChangeFlags(sector_t::ceiling, 0, PLANEF_ABSLIGHTING);

		sec->lightlevel = (sec->GetPlaneLight(sector_t::floor) + sec->GetPlaneLight(sector_t::ceiling)) / 2;

		sec->seqType = -1;
		sec->SeqName = NAME_None;
		sec->nextsec = -1;
		sec->prevsec = -1;
		sec->gravity = 1.f;
		sec->friction = ORIG_FRICTION;
		sec->movefactor = ORIG_FRICTION_FACTOR;
		sec->ColorMap = map;
		sec->ZoneNumber = 0xFFFF;
		sec->terrainnum[sector_t::ceiling] = sec->terrainnum[sector_t::floor] = -1;

		if (bsec->floorstat & 4)
		{
			sec->SetAngle(sector_t::floor, ANGLE_90);
			sec->SetXScale(sector_t::floor, -sec->GetXScale(sector_t::floor));
		}
		if (bsec->floorstat & 16)
		{
			sec->SetXScale(sector_t::floor, -sec->GetXScale(sector_t::floor));
		}
		if (bsec->floorstat & 32)
		{
			sec->SetYScale(sector_t::floor, -sec->GetYScale(sector_t::floor));
		}

		if (bsec->ceilingstat & 4)
		{
			sec->SetAngle(sector_t::ceiling, ANGLE_90);
			sec->SetYScale(sector_t::ceiling, -sec->GetYScale(sector_t::ceiling));
		}
		if (bsec->ceilingstat & 16)
		{
			sec->SetXScale(sector_t::ceiling, -sec->GetXScale(sector_t::ceiling));
		}
		if (bsec->ceilingstat & 32)
		{
			sec->SetYScale(sector_t::ceiling, -sec->GetYScale(sector_t::ceiling));
		}
	}
}

//==========================================================================
//
// LoadWalls
//
//==========================================================================

static void LoadWalls (walltype *walls, int numwalls, sectortype *bsec)
{
	int i, j;

	// Setting numvertexes to the same as numwalls is overly conservative,
	// but the extra vertices will be removed during the BSP building pass.
	numsides = numvertexes = numwalls;
	numlines = 0;

	sides = new side_t[numsides];
	memset (sides, 0, numsides*sizeof(side_t));

	vertexes = new vertex_t[numvertexes];
	numvertexes = 0;

	// First mark each sidedef with the sector it belongs to
	for (i = 0; i < numsectors; ++i)
	{
		if (bsec[i].wallptr >= 0)
		{
			for (j = 0; j < bsec[i].wallnum; ++j)
			{
				sides[j + bsec[i].wallptr].sector = sectors + i;
			}
		}
	}

	// Now copy wall properties to their matching sidedefs
	for (i = 0; i < numwalls; ++i)
	{
		char tnam[9];
		FTextureID overpic, pic;

		mysnprintf (tnam, countof(tnam), "BTIL%04d", LittleShort(walls[i].picnum));
		pic = TexMan.GetTexture (tnam, FTexture::TEX_Build);
		mysnprintf (tnam, countof(tnam), "BTIL%04d", LittleShort(walls[i].overpicnum));
		overpic = TexMan.GetTexture (tnam, FTexture::TEX_Build);

		walls[i].x = LittleLong(walls[i].x);
		walls[i].y = LittleLong(walls[i].y);
		walls[i].point2 = LittleShort(walls[i].point2);
		walls[i].cstat = LittleShort(walls[i].cstat);
		walls[i].nextwall = LittleShort(walls[i].nextwall);
		walls[i].nextsector = LittleShort(walls[i].nextsector);

		sides[i].SetTextureXOffset(walls[i].xpanning << FRACBITS);
		sides[i].SetTextureYOffset(walls[i].ypanning << FRACBITS);

		sides[i].SetTexture(side_t::top, pic);
		sides[i].SetTexture(side_t::bottom, pic);
		if (walls[i].nextsector < 0 || (walls[i].cstat & 32))
		{
			sides[i].SetTexture(side_t::mid, pic);
		}
		else if (walls[i].cstat & 16)
		{
			sides[i].SetTexture(side_t::mid, overpic);
		}
		else
		{
			sides[i].SetTexture(side_t::mid, FNullTextureID());
		}

		sides[i].TexelLength = walls[i].xrepeat * 8;
		sides[i].SetTextureYScale(walls[i].yrepeat << (FRACBITS - 3));
		sides[i].SetTextureXScale(FRACUNIT);
		sides[i].SetLight(SHADE2LIGHT(walls[i].shade));
		sides[i].Flags = WALLF_ABSLIGHTING;
		sides[i].RightSide = walls[i].point2;
		sides[walls[i].point2].LeftSide = i;

		if (walls[i].nextwall >= 0 && walls[i].nextwall <= i)
		{
			sides[i].linedef = sides[walls[i].nextwall].linedef;
		}
		else
		{
			sides[i].linedef = (line_t*)(intptr_t)(numlines++);
		}
	}

	// Set line properties that Doom doesn't store per-sidedef
	lines = new line_t[numlines];
	memset (lines, 0, numlines*sizeof(line_t));

	for (i = 0, j = -1; i < numwalls; ++i)
	{
		if (walls[i].nextwall >= 0 && walls[i].nextwall <= i)
		{
			continue;
		}

		j = int(intptr_t(sides[i].linedef));
		lines[j].sidedef[0] = (side_t*)(intptr_t)i;
		lines[j].sidedef[1] = (side_t*)(intptr_t)walls[i].nextwall;
		lines[j].v1 = FindVertex (walls[i].x, walls[i].y);
		lines[j].v2 = FindVertex (walls[walls[i].point2].x, walls[walls[i].point2].y);
		lines[j].frontsector = sides[i].sector;
		lines[j].flags |= ML_WRAP_MIDTEX;
		if (walls[i].nextsector >= 0)
		{
			lines[j].backsector = sectors + walls[i].nextsector;
			lines[j].flags |= ML_TWOSIDED;
		}
		else
		{
			lines[j].backsector = NULL;
		}
		P_AdjustLine (&lines[j]);
		if (walls[i].cstat & 128)
		{
			if (walls[i].cstat & 512)
			{
				lines[j].Alpha = FRACUNIT/3;
			}
			else
			{
				lines[j].Alpha = FRACUNIT*2/3;
			}
		}
		if (walls[i].cstat & 1)
		{
			lines[j].flags |= ML_BLOCKING;
		}
		if (walls[i].nextwall < 0)
		{
			if (walls[i].cstat & 4)
			{
				lines[j].flags |= ML_DONTPEGBOTTOM;
			}
		}
		else
		{
			if (walls[i].cstat & 4)
			{
				lines[j].flags |= ML_DONTPEGTOP;
			}
			else
			{
				lines[j].flags |= ML_DONTPEGBOTTOM;
			}
		}
		if (walls[i].cstat & 64)
		{
			lines[j].flags |= ML_BLOCKEVERYTHING;
		}
	}

	// Finish setting sector properties that depend on walls
	for (i = 0; i < numsectors; ++i, ++bsec)
	{
		SlopeWork slope;

		slope.wal = &walls[bsec->wallptr];
		slope.wal2 = &walls[slope.wal->point2];
		slope.dx = slope.wal2->x - slope.wal->x;
		slope.dy = slope.wal2->y - slope.wal->y;
		slope.i = long (sqrt ((double)(slope.dx*slope.dx+slope.dy*slope.dy))) << 5;
		if (slope.i == 0)
		{
			continue;
		}
		if ((bsec->floorstat & 2) && (bsec->floorheinum != 0))
		{ // floor is sloped
			slope.heinum = -LittleShort(bsec->floorheinum);
			slope.z[0] = slope.z[1] = slope.z[2] = -bsec->floorz;
			CalcPlane (slope, sectors[i].floorplane);
		}
		if ((bsec->ceilingstat & 2) && (bsec->ceilingheinum != 0))
		{ // ceiling is sloped
			slope.heinum = -LittleShort(bsec->ceilingheinum);
			slope.z[0] = slope.z[1] = slope.z[2] = -bsec->ceilingz;
			CalcPlane (slope, sectors[i].ceilingplane);
		}
		int linenum = int(intptr_t(sides[bsec->wallptr].linedef));
		int sidenum = int(intptr_t(lines[linenum].sidedef[1]));
		if (bsec->floorstat & 64)
		{ // floor is aligned to first wall
			P_AlignFlat (linenum, sidenum == bsec->wallptr, 0);
		}
		if (bsec->ceilingstat & 64)
		{ // ceiling is aligned to first wall
			P_AlignFlat (linenum, sidenum == bsec->wallptr, 0);
		}
	}
	for (i = 0; i < numlines; i++)
	{
		intptr_t front = intptr_t(lines[i].sidedef[0]);
		intptr_t back = intptr_t(lines[i].sidedef[1]);
		lines[i].sidedef[0] = front >= 0 ? &sides[front] : NULL;
		lines[i].sidedef[1] = back >= 0 ? &sides[back] : NULL;
	}
	for (i = 0; i < numsides; i++)
	{
		assert(sides[i].sector != NULL);
		sides[i].linedef = &lines[intptr_t(sides[i].linedef)];
	}
}

//==========================================================================
//
// LoadSprites
//
//==========================================================================

static int LoadSprites (spritetype *sprites, Xsprite *xsprites, int numsprites,
	sectortype *bsectors, FMapThing *mapthings)
{
	int count = 0;

	memset(mapthings, 0, sizeof(*mapthings)*numsprites);

	for (int i = 0; i < numsprites; ++i)
	{
		mapthings[count].thingid = 0;
		mapthings[count].x = (sprites[i].x << 12);
		mapthings[count].y = -(sprites[i].y << 12);
		mapthings[count].z = (bsectors[sprites[i].sectnum].floorz - sprites[i].z) << 8;
		mapthings[count].angle = (((2048-sprites[i].ang) & 2047) * 360) >> 11;
		mapthings[count].ClassFilter = 0xffff;
		mapthings[count].SkillFilter = 0xffff;
		mapthings[count].flags = MTF_SINGLE|MTF_COOPERATIVE|MTF_DEATHMATCH;
		mapthings[count].special = 0;
		mapthings[count].gravity = FRACUNIT;
		mapthings[count].RenderStyle = STYLE_Count;
		mapthings[count].alpha = -1;
		mapthings[count].health = -1;
		mapthings[count].FloatbobPhase = -1;

		if (xsprites != NULL && sprites[i].lotag == 710)
		{ // Blood ambient sound
			mapthings[count].args[0] = xsprites[i].Data3;
			// I am totally guessing about the volume level. 50 seems to be a pretty
			// typical value for Blood's standard maps, so I assume it's 100-based.
			mapthings[count].args[1] = xsprites[i].Data4;
			mapthings[count].args[2] = xsprites[i].Data1;
			mapthings[count].args[3] = xsprites[i].Data2;
			mapthings[count].EdNum = 14065;
		}
		else if (xsprites != NULL && sprites[i].lotag == 1)
		{ // Blood player start
			if (xsprites[i].Data1 < 4)
				mapthings[count].EdNum= 1 + xsprites[i].Data1;
			else
				mapthings[count].EdNum = 4001 + xsprites[i].Data1 - 4;
		}
		else if (xsprites != NULL && sprites[i].lotag == 2)
		{ // Bloodbath start
			mapthings[count].EdNum = 11;
		}
		else
		{
			if (sprites[i].cstat & 32768) continue;
			if (sprites[i].xrepeat == 0 || sprites[i].yrepeat == 0) continue;

			mapthings[count].EdNum = 9988;
			mapthings[count].args[0] = sprites[i].picnum;
			mapthings[count].args[2] = sprites[i].xrepeat;
			mapthings[count].args[3] = sprites[i].yrepeat;
			mapthings[count].args[4] = sprites[i].cstat;
		}
		mapthings[count].info = DoomEdMap.CheckKey(mapthings[count].EdNum);
		count++;
	}
	return count;
}

//==========================================================================
//
// FindVertex
//
//==========================================================================

vertex_t *FindVertex (fixed_t x, fixed_t y)
{
	int i;

	x <<= 12;
	y = -(y << 12);

	for (i = 0; i < numvertexes; ++i)
	{
		if (vertexes[i].x == x && vertexes[i].y == y)
		{
			return &vertexes[i];
		}
	}
	vertexes[i].x = x;
	vertexes[i].y = y;
	numvertexes++;
	return &vertexes[i];
}

//==========================================================================
//
// CreateStartSpot
//
//==========================================================================

static void CreateStartSpot (fixed_t *pos, FMapThing *start)
{
	short angle = LittleShort(*(WORD *)(&pos[3]));
	FMapThing mt = { 0, };

	mt.x = LittleLong(pos[0])<<12;
	mt.y = (-LittleLong(pos[1]))<<12;
	mt.angle = short(Scale((2048-angle)&2047, 360, 2048));
	mt.info = DoomEdMap.CheckKey(1);
	mt.EdNum = 1;
	mt.flags = 7|MTF_SINGLE|224;

	*start = mt;
}

//==========================================================================
//
// CalcPlane
//
//==========================================================================

static void CalcPlane (SlopeWork &slope, secplane_t &plane)
{
	FVector3 pt[3];
	long j;

	slope.x[0] = slope.wal->x;  slope.y[0] = slope.wal->y;
	slope.x[1] = slope.wal2->x; slope.y[1] = slope.wal2->y;
	if (slope.dx == 0)
	{
		slope.x[2] = slope.x[0] + 64;
		slope.y[2] = slope.y[0];
	}
	else
	{
		slope.x[2] = slope.x[0];
		slope.y[2] = slope.y[0] + 64;
	}
	j = DMulScale3 (slope.dx, slope.y[2]-slope.wal->y,
		-slope.dy, slope.x[2]-slope.wal->x);
	slope.z[2] += Scale (slope.heinum, j, slope.i);

	pt[0] = FVector3(slope.dx, -slope.dy, 0);
	pt[1] = FVector3(slope.x[2] - slope.x[0], slope.y[0] - slope.y[2], (slope.z[2] - slope.z[0]) / 16);
	pt[2] = (pt[0] ^ pt[1]).Unit();

	if ((pt[2][2] < 0 && plane.c > 0) || (pt[2][2] > 0 && plane.c < 0))
	{
		pt[2] = -pt[2];
	}

	plane.a = fixed_t(pt[2][0]*65536.f);
	plane.b = fixed_t(pt[2][1]*65536.f);
	plane.c = fixed_t(pt[2][2]*65536.f);
	plane.ic = fixed_t(65536.f/pt[2][2]);
	plane.d = -TMulScale8
		(plane.a, slope.x[0]<<4, plane.b, (-slope.y[0])<<4, plane.c, slope.z[0]);
}

//==========================================================================
//
// Decrypt
//
// Note that this is different from the general RFF encryption.
//
//==========================================================================

static void Decrypt (void *to_, const void *from_, int len, int key)
{
	BYTE *to = (BYTE *)to_;
	const BYTE *from = (const BYTE *)from_;

	for (int i = 0; i < len; ++i, ++key)
	{
		to[i] = from[i] ^ key;
	}
}

//==========================================================================
//
// Just an actor to make the Build sprites show up. It doesn't do anything
// with them other than display them.
//
//==========================================================================

class ACustomSprite : public AActor
{
	DECLARE_CLASS (ACustomSprite, AActor);
public:
	void BeginPlay ();
};

IMPLEMENT_CLASS (ACustomSprite)

void ACustomSprite::BeginPlay ()
{
	char name[9];
	Super::BeginPlay ();

	mysnprintf (name, countof(name), "BTIL%04d", args[0] & 0xffff);
	picnum = TexMan.GetTexture (name, FTexture::TEX_Build);

	scaleX = args[2] * (FRACUNIT/64);
	scaleY = args[3] * (FRACUNIT/64);

	int cstat = args[4];
	if (cstat & 2)
	{
		RenderStyle = STYLE_Translucent;
		alpha = (cstat & 512) ? TRANSLUC66 : TRANSLUC33;
	}
	if (cstat & 4)
		renderflags |= RF_XFLIP;
	if (cstat & 8)
		renderflags |= RF_YFLIP;
	// set face/wall/floor flags
	renderflags |= ActorRenderFlags::FromInt (((cstat >> 4) & 3) << 12);
}
