#ifndef MRCLS_IN
#define MRCLS_IN

#define MAXPLAYERS 64
#define P_TIDSTART 1000
#define MCM_PCHECK_RATE 70

// just comment this out for building it for non-qcde versions
//#define COMPILE_FOR_QCDE

#ifdef COMPILE_FOR_QCDE
	#define MCM_TID_BEGIN 2664
	#define MCM_TID_END 6759
#else
	#define MCM_TID_BEGIN 66000
	#define MCM_TID_END 66000 // this isn't used for non-qcde build
#endif

int m_tid = 0;
#define MAX_MEM_BLOCKS 128
bool mem16_use[MAX_MEM_BLOCKS];
int mref;

// MONSTER CONSTANTS
#define DRAUGR_TEMP_FX 9050
#define DRAUGR_FX_COUNT 16
#define DRAUGR_R 96
#define DRAUGR_VEL_BASE 8

#define APOSTLE_FX_COUNT 32
#define APOSTLE_R 96

#define ZEALOT_SHIELD_TIME 3 * 35
#define ZEALOT_SHIELD_TIDADD 5000 // this makes the monster tid range to be outside of the others'

#define LURKER_LGDISTX 12
#define LURKER_LGDISTY 32
#define LURKER_LGSWAY 64
#define LURKER_LGSWAYF 64.0
#define LURKER_LGJAGG 1.0 / LURKER_LGSWAY
#define LURKER_LGDENSITY 2
#define LURKER_LGPTS 16
#define LURKER_ZCHANGE 16384.0
#define LURKER_TZ 16.0

enum {
	LDRAW_LURKER,
};
#define MAX_LINE_DRAW_ACTORS LDRAW_LURKER + 1
str LineDrawActors[MAX_LINE_DRAW_ACTORS] = {
	"LurkerFormFX"
};

#define DND_GOLGOTH_YOFF1 43.7
#define DND_GOLGOTH_YOFF2 39.1
#define DND_GOLGOTH_ZOFF1 94.3
#define DND_GOLGOTH_ZOFF2 69.0
#define GOLGOTH_LASER_DENSITY 6
#define GOLGOTH_ORANGESOUL_SPEED 18.0
#define GOLGOTH_METEOR_SPEED 28.0
#define GOLGOTH_METEOR_Z1 82.8
#define GOLGOTH_METEOR_Z2 55.2
#define GOLGOTH_METEOR_OFFSET 27.6
#define GOLGOTH_HPFACTOR_NUMERATOR 3
#define GOLGOTH_HPFACTOR_DENUMERATOR 5
// MONSTER CONSTANTS END

bool MCM_MonsterBehaviorToggle = false;

void SignalMonsters(bool behavior) {
	int i;
	#ifdef COMPILE_FOR_QCDE
		if(behavior) {
			for(i = MCM_TID_BEGIN; i <= MCM_TID_END; ++i)
				GiveActorInventory(i, "MCM_BehaviorToggle", 1);
		}
		else {
			for(i = MCM_TID_BEGIN; i <= MCM_TID_END; ++i)
				TakeActorInventory(i, "MCM_BehaviorToggle", 1);
		}
	#else
		if(behavior) {
			for(i = 0; i < m_tid; ++i)
				GiveActorInventory(i + MCM_TID_BEGIN, "MCM_BehaviorToggle", 1);
		}
		else {
			for(i = 0; i < m_tid; ++i)
				TakeActorInventory(i + MCM_TID_BEGIN, "MCM_BehaviorToggle", 1);
		}
	#endif
}

int abs(int x) {
	if(x < 0)
		return -x;
	return x;
}

int sqrt_z(int number)
{
	if(number <= 3)
	{
		if(number > 0)
		{
			return 1;
		}
		return 0;
	}
	
	int oldAns = number >> 1,                     
	    newAns = (oldAns + number / oldAns) >> 1; 
	
	while(newAns < oldAns)
	{
		oldAns = newAns;
		newAns = (oldAns + number / oldAns) >> 1;
	}

	return oldAns;
}

void LineDraw(int actortype, int sx, int sy, int sz, int dx, int dy, int dz, int density) {
    int vX;  int vY;   int vZ;
    int nX;  int nY;   int nZ;
    int bX;  int bY;   int bZ;
    int magnitude;
    int pointCount;
    int pointOffset;
    vX   = dx - sx;       vY   = dy - sy;       vZ   = dz - sz;

    magnitude = magnitudeThree(vX >> 16, vY >> 16, vZ >> 16);

    pointCount  = magnitude / density;
    pointOffset = magnitude - (pointCount * density);

    if (magnitude != 0) {
        nX = vX / magnitude; nY = vY / magnitude; nZ = vZ / magnitude;

        int i; int j;
        for (i = 1; i < pointCount; i++) {
            j = (i * density) + pointOffset;

            bX = (nX * j) + sx;
            bY = (nY * j) + sy;
            bZ = (nZ * j) + sz;

			SpawnForced(LineDrawActors[actortype], bX, bY, bZ);
        }
    }
}

int[]& GetMem_16() {
	static int mem[MAX_MEM_BLOCKS][16];
	for(int i = 0; i < MAX_MEM_BLOCKS; ++i)
		if(!mem16_use[i]) {
			mem16_use[i] = 1;
			mref = i;
			return mem[i];
		}
	return mem[0];
}

void FreeMem16(int i) {
	mem16_use[i] = 0;
}

void bubblesort(int[]& list) {
   int left = list.length();
   while (left) {
      int i = 0;
      int k = left - 1;
      while (i < k) {
         if (list[i] > list[i + 1]) {
            int temp = list[i + 1];
            list[i + 1] = list[i];
            list[i] = temp;
         }
         ++i;
      }
      --left;
   }
}

int magnitudeTwo(int x, int y) {
	return sqrt_z(x * x + y * y);
}

int magnitudeThree(int x, int y, int z) {
    return sqrt_z(x*x + y*y + z*z);
}

enum {
	INTF_RANDOM = 1,
	INTF_SETANGLE = 2
};

int sq(int x) {
	return FixedMul(x, x);
}

int FixedAngMod(int fAngle) {
	if (fAngle > 1.0){
		fAngle %= 65536; 
	}
	else if (fAngle < 0) {
		fAngle %= (-65536);
		fAngle = fAngle + 65536;
	}
	return fAngle;
}

void GolgothLaserTrail(int yoff, int zoff, int target, bool isFake) {
	int oX = GetActorX(0);
	int oY = GetActorY(0);
	int oZ = GetActorZ(0) + 16.0;

    int grX; int grY;  int grZ;
    int vX;  int vY;   int vZ;
    int nX;  int nY;   int nZ;
    int bX;  int bY;   int bZ;
    int magnitude;
    int pointCount;
    int pointOffset;
    grX = GetActorX(target);
    grY = GetActorY(target);
    grZ = GetActorZ(target) + zoff;
	
    vX   = grX - oX;
	vY   = grY - oY;
	vZ   = grZ - oZ;
	
	// get vectorangle for the facing, we need this for relative
	grX = GetActorAngle(target);
	//vX += FixedMul(yoff, cos(grX));
	vX += FixedMul(yoff, sin(grX));
	vY -= FixedMul(yoff, cos(grX));
	
    magnitude = magnitudeThree(vX >> 16, vY >> 16, vZ >> 16);
    pointCount  = magnitude / GOLGOTH_LASER_DENSITY;
    pointOffset = magnitude - (pointCount * GOLGOTH_LASER_DENSITY);

    if (magnitude != 0) {
        nX = vX / magnitude; nY = vY / magnitude; nZ = vZ / magnitude;

        int i; int j;
        for (i = 1; i < pointCount; i++) {
            j = (i * GOLGOTH_LASER_DENSITY) + pointOffset;
            bX = (nX * j) + oX + FixedMul(random(-6.0, 6.0), sin(grX));
            bY = (nY * j) + oY + FixedMul(random(-6.0, 6.0), cos(grX));
            bZ = (nZ * j) + oZ + random(-6.0, 6.0);

			if(isFake)
				Spawn("GolgothRailFX_Tracer", bX, bY, bZ);
			else
				Spawn("GolgothRailFX", bX, bY, bZ);
        }
    }
}

int ProjInt_Brute(int spd, int ptid, int xoff, int yoff, int zoff, str ptype, int axoff, int ayoff, int azoff, int angoff, int flags, int input_t) {
	int sX, sY, sZ, s_ang, p_ang;
	int tX, tY, tZ, tVelX, tVelY, tVelZ;
	int X_spd, Y_spd, Z_spd;
	
	bool check = 1, check2 = 1;
	int target = GetActorProperty(0, APROP_TARGETTID);
	int temp = 0, temp2 = 0;

	sX = GetActorX(0);
	sY = GetActorY(0);
	sZ = GetActorZ(0);
	s_ang = GetActorAngle(0);

	sZ += zoff;

	if(xoff > 0) {
		sX += FixedMul(cos(FixedAngMod(s_ang - 0.25)), xoff); 
		sY += FixedMul(sin(FixedAngMod(s_ang - 0.25)), xoff); 
	}
	else if(xoff < 0) {
		sX += FixedMul(cos(FixedAngMod(s_ang + 0.25)), xoff); 
		sY += FixedMul(sin(FixedAngMod(s_ang + 0.25)), xoff); 
	}

	if(yoff > 0) {
		sX += FixedMul(cos(s_ang), yoff); 
		sY += FixedMul(sin(s_ang), yoff); 
	}
	else if(yoff < 0) {
		sX -= FixedMul(cos(s_ang), yoff); 
		sY -= FixedMul(sin(s_ang), yoff); 
	}

	tX = GetActorX(target);
	tY = GetActorY(target);
	tZ = GetActorZ(target);
	tZ += 36.0;
	//tZ += 26.5; for comparison with Thing_ProjectileIntercept

	tVelX = GetActorVelX(target);
	tVelY = GetActorVelY(target);
	tVelZ = GetActorVelZ(target);

	if(!CheckFlag(target, "NOGRAVITY")) 
		tVelZ = 0;

	zoff = 0; // sml_t
	xoff = 0; // t
	yoff = 0; // nmax
	Y_spd = 0; // i
	while(check) {
		xoff = zoff - (temp / 2);
		if(!Y_spd){
			xoff = 1.0;
			temp = 10.0;
			yoff = 40; 
		}
		else if(Y_spd == 1) {
			if(zoff > 400.0) 
				check = 0; //stop trying to get closer if it's out of range, it's just a waste
			else {
				temp = 1.0;
				yoff = 10; 
			}
		}  
		else if(Y_spd == 2) {
			temp = 0.1;
			yoff = 10; 
		}       
		else if(Y_spd == 3) {
			temp = 0.01;
			yoff = 10; 
		}             
		else if(Y_spd == 4) {
			temp = 0.001;
			yoff = 10; 
		}           
		else if(Y_spd == 5) {
			temp = 1;
			yoff = 66; 
		}         
		else if(Y_spd == 6) 
			check = 0;      
		++Y_spd;
		temp2 = 0;
		check2 = 1; 

		while(check && check2) {
			target = abs(spd - VectorLength(VectorLength(FixedDiv(tX + FixedMul(xoff, tVelX) - sx, xoff), FixedDiv(tY + FixedMul(xoff, tVelY) - sy, xoff)), FixedDiv(FixedMul(xoff, tVelZ), xoff)));
			if((target < X_spd) || temp2 == 0) {
				X_spd = target;
				zoff = xoff; 
			}
			++temp2;
			xoff += temp;
			if(temp2 > yoff) 
				check2 = 0; 
		}
	}

	if(input_t) 
		xoff = input_t; 
	else {
		if(flags & INTF_RANDOM) 
			random(1, zoff);
		else 
			xoff = zoff;
	}
	
	temp = tX + FixedMul(xoff, tVelX);
	yoff = tY + FixedMul(xoff, tVelY);
	if((flags & INTF_RANDOM) || input_t) {
		Z_spd = FixedDiv(tZ + FixedMul(xoff, tVelZ) - sZ, zoff);
		p_ang = VectorAngle(temp - sX, yoff - sY);
		temp2 = FixedSqrt(sq(spd) - sq(Z_spd));
		X_spd = FixedMul(cos(p_ang), temp2);
		Y_spd = FixedMul(sin(p_ang), temp2); 
	}
	else {
		Z_spd = FixedDiv(tZ + FixedMul(xoff, tVelZ) - sZ, xoff);
		X_spd = FixedDiv(temp - sX, xoff);
		Y_spd = FixedDiv(yoff - sY, xoff);
		p_ang = VectorAngle(temp - sX, yoff - sY); 
	}
	
	if(flags & INTF_SETANGLE)
		SetActorAngle(0, p_ang);

	sZ += azoff;

	if(axoff > 0) {
		sX -= FixedMul(cos(FixedAngMod(s_ang - 0.25)), axoff); 
		sY -= FixedMul(sin(FixedAngMod(s_ang - 0.25)), axoff); 
	}
	else if(axoff < 0) {
		sX += FixedMul(cos(FixedAngMod(s_ang + 0.25)), axoff); 
		sY += FixedMul(sin(FixedAngMod(s_ang + 0.25)), axoff); 
	}

	if(ayoff > 0) {
		sX += FixedMul(cos(s_ang), ayoff); 
		sY += FixedMul(sin(s_ang), ayoff); 
	}
	else if(ayoff < 0) {
		sX -= FixedMul(cos(s_ang), ayoff); 
		sY -= FixedMul(sin(s_ang), ayoff); 
	}

	if(angoff != 0) {
		p_ang = FixedAngMod(p_ang + angoff);
		temp2 = FixedSqrt(sq(X_spd) + sq(Y_spd));
		X_spd = FixedMul(cos(FixedAngMod(p_ang + angoff)), temp2);
		Y_spd = FixedMul(sin(FixedAngMod(p_ang + angoff)), temp2); 
	}

	temp = ActivatorTID();
	target = GetActorProperty(0, APROP_TARGETTID);
	if(!ptid)
		ptid = 131072;
	SpawnProjectile (0, ptype, 0, 0, 0, 0, ptid); 
	SetActivator(ptid); 
	SetPointer(AAPTR_TARGET, temp); // so doesn't collide with it
	SetPointer(AAPTR_TRACER, target);
	SetActorPosition(ptid, sX, sY, sZ, 0);
	SetActorAngle(ptid, p_ang);
	SetActorVelocity(ptid, X_spd, Y_spd, Z_spd, 0, 0);
	Thing_ChangeTID(ptid, 0);
	SetActivator(temp);
	return xoff;
}

#endif