#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "doors.h"

extern DLL_GLOBAL Vector g_vecAttackDir;

#define SF_BRUSH_ACCDCC 16
#define SF_BRUSH_HURT 32
#define SF_ROTATING_NOT_SOLID 64

#define noiseStart noise1
#define noiseStop noise2
#define noiseRunning noise3

#define SF_PENDULUM_SWING 2

Vector VecBModelOrigin(entvars_t *pevBModel)
{
	return pevBModel->absmin + (pevBModel->size * 0.5);
}

class CFuncWall : public CBaseEntity
{
public:
	void Spawn(void);
	void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
	int ObjectCaps(void) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
};

LINK_ENTITY_TO_CLASS(func_wall, CFuncWall);

void CFuncWall::Spawn(void)
{
	pev->angles = g_vecZero;
	pev->movetype = MOVETYPE_PUSH;
	pev->solid = SOLID_BSP;
	SET_MODEL(ENT(pev), STRING(pev->model));

	if (pev->team > 3 || pev->team < TEAM_UNASSIGNED)
	{
		ALERT(at_console, "Bad team number (%i) in %s\n", pev->team, STRING(pev->classname));
		pev->team = 0;
	}

	pev->flags |= FL_WORLDBRUSH;
}

void CFuncWall::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (ShouldToggle(useType, (int)(pev->frame)))
		pev->frame = 1 - pev->frame;
}

#define SF_WALL_START_OFF 0x0001

class CFuncWallToggle : public CFuncWall
{
public:
	void Spawn(void);
	void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
	void Restart(void);

public:
	void TurnOff(void);
	void TurnOn(void);
	BOOL IsOn(void);
};

LINK_ENTITY_TO_CLASS(func_wall_toggle, CFuncWallToggle);

void CFuncWallToggle::Spawn(void)
{
	CFuncWall::Spawn();

	if (pev->spawnflags & SF_WALL_START_OFF)
		TurnOff();
}

void CFuncWallToggle::TurnOff(void)
{
	pev->solid = SOLID_NOT;
	pev->effects |= EF_NODRAW;
	UTIL_SetOrigin(pev, pev->origin);
}

void CFuncWallToggle::TurnOn(void)
{
	pev->solid = SOLID_BSP;
	pev->effects &= ~EF_NODRAW;
	UTIL_SetOrigin(pev, pev->origin);
}

BOOL CFuncWallToggle::IsOn(void)
{
	if (pev->solid == SOLID_NOT)
		return FALSE;

	return TRUE;
}

void CFuncWallToggle::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	int status = IsOn();

	if (ShouldToggle(useType, status))
	{
		if (status)
			TurnOff();
		else
			TurnOn();
	}
}

void CFuncWallToggle::Restart(void)
{
	CFuncWall::Spawn();
	if ( pev->spawnflags & SF_WALL_START_OFF )
		TurnOff();
}

#define SF_CONVEYOR_VISUAL 0x0001
#define SF_CONVEYOR_NOTSOLID 0x0002

class CFuncConveyor : public CFuncWall
{
public:
	void Spawn(void);
	void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
	void UpdateSpeed(float speed);
};

LINK_ENTITY_TO_CLASS(func_conveyor, CFuncConveyor);

void CFuncConveyor::Spawn(void)
{
	SetMovedir(pev);
	CFuncWall::Spawn();

	if (!(pev->spawnflags & SF_CONVEYOR_VISUAL))
		SetBits(pev->flags, FL_CONVEYOR);

	if (pev->spawnflags & SF_CONVEYOR_NOTSOLID)
	{
		pev->solid = SOLID_NOT;
		pev->skin = 0;
	}

	if (!pev->speed)
		pev->speed = 100;

	UpdateSpeed(pev->speed);
}

void CFuncConveyor::UpdateSpeed(float speed)
{
	int speedCode = (int)(fabs(speed) * 16.0);

	if (speed < 0)
		pev->rendercolor.x = 1;
	else
		pev->rendercolor.x = 0;

	pev->rendercolor.y = (speedCode >> 8);
	pev->rendercolor.z = (speedCode & 0xFF);
}

void CFuncConveyor::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	pev->speed = -pev->speed;
	UpdateSpeed(pev->speed);
}

class CFuncIllusionary : public CBaseToggle 
{
public:
	void Spawn(void);
	void KeyValue(KeyValueData *pkvd);
	int ObjectCaps(void) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	int Classify(void) { return CLASS_MOVEABLE; }
public:
	void EXPORT SloshTouch(CBaseEntity *pOther);
};

LINK_ENTITY_TO_CLASS(func_illusionary, CFuncIllusionary);

void CFuncIllusionary::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "skin"))
	{
		pev->skin = atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
		CBaseToggle::KeyValue(pkvd);
}

void CFuncIllusionary::Spawn(void)
{
	pev->angles = g_vecZero;
	pev->movetype = MOVETYPE_NONE;
	pev->solid = SOLID_NOT;
	SET_MODEL(ENT(pev), STRING(pev->model));
}

class CFuncRotating : public CBaseEntity
{
public:
	void Spawn(void);
	void Precache(void);
	void KeyValue(KeyValueData *pkvd);
	void RampPitchVol(int fUp);
	void Blocked(CBaseEntity *pOther);
	int ObjectCaps(void) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	int Classify(void) { return CLASS_MOVEABLE; }

public:
	void EXPORT SpinUp(void);
	void EXPORT SpinDown(void);
	void EXPORT HurtTouch(CBaseEntity *pOther);
	void EXPORT RotatingUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
	void EXPORT Rotate(void);

public:
	float m_flFanFriction;
	float m_flAttenuation;
	float m_flVolume;
	float m_pitch;
	int m_sounds;
};

LINK_ENTITY_TO_CLASS(func_rotating, CFuncRotating);

void CFuncRotating::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "fanfriction"))
	{
		m_flFanFriction = atof(pkvd->szValue) / 100;
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "Volume"))
	{
		m_flVolume = atof(pkvd->szValue) / 10;

		if (m_flVolume > 1)
			m_flVolume = 1;

		if (m_flVolume < 0)
			m_flVolume = 0;

		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "spawnorigin"))
	{
		Vector tmp;
		UTIL_StringToVector((float *)tmp, pkvd->szValue);

		if (tmp != g_vecZero)
			pev->origin = tmp;
	}
	else if (FStrEq(pkvd->szKeyName, "sounds"))
	{
		m_sounds = atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "axes"))
	{
		UTIL_StringToVector( (float *)(pev->movedir), pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
		CBaseEntity::KeyValue(pkvd);
}

void CFuncRotating::Spawn(void)
{
	m_pitch = PITCH_NORM - 1;

	if (m_flVolume == 0.0)
		m_flVolume = 1;

	m_flAttenuation = ATTN_NORM;

	if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS))
		m_flAttenuation = ATTN_IDLE;
	else if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS))
		m_flAttenuation = ATTN_STATIC;
	else if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS))
		m_flAttenuation = ATTN_NORM;

	if (m_flFanFriction <= 0)//sohl fix
		m_flFanFriction = 1;

	if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS))
		pev->movedir = Vector(0, 0, 1);
	else if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS))
		pev->movedir = Vector(1, 0, 0);
	else
		pev->movedir = Vector(0, 1, 0);

	if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS))
		pev->movedir = pev->movedir * -1;

	if (FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID))
	{
		pev->solid = SOLID_NOT;
		pev->skin = CONTENTS_EMPTY;
		pev->movetype = MOVETYPE_PUSH;
	}
	else
	{
		pev->solid = SOLID_BSP;
		pev->movetype = MOVETYPE_PUSH;
	}

	UTIL_SetOrigin(pev, pev->origin);
	SET_MODEL(ENT(pev), STRING(pev->model));
	SetUse(&CFuncRotating::RotatingUse);

	if (pev->speed <= 0)
		pev->speed = 0;

	if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_INSTANT))
	{
		SetThink(&CBaseEntity::SUB_CallUseToggle);
		pev->nextthink = pev->ltime + 1.5;
	}

	if (FBitSet(pev->spawnflags, SF_BRUSH_HURT))
		SetTouch(&CFuncRotating::HurtTouch);

	Precache();
}

void CFuncRotating::Precache(void)
{
	char *szSoundFile = (char *)STRING(pev->message);

	if (!FStringNull(pev->message) && strlen(szSoundFile) > 0)
	{
		PRECACHE_SOUND(szSoundFile);
		pev->noiseRunning = ALLOC_STRING(szSoundFile);
	}
	else
	{
		switch (m_sounds)
		{
			case 1:
			{
				PRECACHE_SOUND("fans/fan1.wav");
				pev->noiseRunning = ALLOC_STRING("fans/fan1.wav");
				break;
			}

			case 2:
			{
				PRECACHE_SOUND("fans/fan2.wav");
				pev->noiseRunning = ALLOC_STRING("fans/fan2.wav");
				break;
			}

			case 3:
			{
				PRECACHE_SOUND("fans/fan3.wav");
				pev->noiseRunning = ALLOC_STRING("fans/fan3.wav");
				break;
			}

			case 4:
			{
				PRECACHE_SOUND("fans/fan4.wav");
				pev->noiseRunning = ALLOC_STRING("fans/fan4.wav");
				break;
			}

			case 5:
			{
				PRECACHE_SOUND("fans/fan5.wav");
				pev->noiseRunning = ALLOC_STRING("fans/fan5.wav");
				break;
			}

			case 0:
			default:
			{
				if (!FStringNull(pev->message) && strlen(szSoundFile) > 0)
				{
					PRECACHE_SOUND(szSoundFile);
					pev->noiseRunning = ALLOC_STRING(szSoundFile);
					break;
				}
				else
				{
					pev->noiseRunning = ALLOC_STRING("common/null.wav");
					break;
				}
			}
		}
	}

	if (pev->avelocity != g_vecZero)
	{
		SetThink(&CFuncRotating::SpinUp);
		pev->nextthink = pev->ltime + 1.5;
	}
}

void CFuncRotating::HurtTouch(CBaseEntity *pOther)
{
	entvars_t *pevOther = pOther->pev;

	if (!pevOther->takedamage)
		return;

	pev->dmg = pev->avelocity.Length() / 10;
	pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH, 0);
	pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev)).Normalize() * pev->dmg;
}

#define FANPITCHMIN 30
#define FANPITCHMAX 100

void CFuncRotating::RampPitchVol(int fUp)
{
	Vector vecAVel = pev->avelocity;
	vec_t vecCur = abs(vecAVel.x != 0 ? (int)vecAVel.x : (int)(vecAVel.y != 0 ? vecAVel.y : vecAVel.z));
	vec_t vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z));
	vecFinal *= pev->speed;
	vecFinal = abs((int)vecFinal);

	float fpct = vecCur / vecFinal;
	float fvol = m_flVolume * fpct;
	float fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct;
	int pitch = (int)fpitch;

	if (pitch == PITCH_NORM)
		pitch = PITCH_NORM - 1;

	EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch);
}

void CFuncRotating::SpinUp(void)
{
	pev->nextthink = pev->ltime + 0.1;
	pev->avelocity = pev->avelocity + (pev->movedir * (pev->speed * m_flFanFriction));
	Vector vecAVel = pev->avelocity;

	if (abs((int)vecAVel.x) >= abs((int)pev->movedir.x * pev->speed) && abs((int)vecAVel.y) >= abs((int)pev->movedir.y * pev->speed) && abs((int)vecAVel.z) >= abs((int)pev->movedir.z * pev->speed))
	{
		pev->avelocity = pev->movedir * pev->speed;
		EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX);
		SetThink(&CFuncRotating::Rotate);
		Rotate();
	}
	else
		RampPitchVol(TRUE);
}

void CFuncRotating::SpinDown(void)
{
	pev->nextthink = pev->ltime + 0.1;
	pev->avelocity = pev->avelocity - (pev->movedir *(pev->speed * m_flFanFriction));
	Vector vecAVel = pev->avelocity;
	vec_t vecdir = (pev->movedir.x != 0) ? vecdir = pev->movedir.x : (pev->movedir.y != 0) ? vecdir = pev->movedir.y : vecdir = pev->movedir.z;

	if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0)))
	{
		pev->avelocity = g_vecZero;
		EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), 0, 0, SND_STOP, (int)m_pitch);
		SetThink(&CFuncRotating::Rotate);
		Rotate();
	}
	else
		RampPitchVol(FALSE);
}

void CFuncRotating::Rotate(void)
{
	pev->nextthink = pev->ltime + 10;
}

void CFuncRotating::RotatingUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (FBitSet(pev->spawnflags, SF_BRUSH_ACCDCC))
	{
		if (pev->avelocity != g_vecZero)
		{
			SetThink(&CFuncRotating::SpinDown);
			pev->nextthink = pev->ltime + 0.1;
		}
		else
		{
			SetThink(&CFuncRotating::SpinUp);
			EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), 0.01, m_flAttenuation, 0, FANPITCHMIN);
			pev->nextthink = pev->ltime + 0.1;
		}
	}
	else if (!FBitSet(pev->spawnflags, SF_BRUSH_ACCDCC))
	{
		if (pev->avelocity != g_vecZero)
		{
			SetThink(&CFuncRotating::SpinDown);
			pev->nextthink = pev->ltime + 0.1;
		}
		else
		{
			EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), m_flVolume, m_flAttenuation, 0, FANPITCHMAX);
			pev->avelocity = pev->movedir * pev->speed;
			SetThink(&CFuncRotating::Rotate);
			Rotate();
		}
	}
}

void CFuncRotating::Blocked(CBaseEntity *pOther)
{
	pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH, 0);
}

class CPendulum : public CBaseEntity
{
public:
	void Spawn(void);
	void KeyValue(KeyValueData *pkvd);
	void Touch(CBaseEntity *pOther);
	int ObjectCaps(void) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	void Blocked(CBaseEntity *pOther);

public:
	void EXPORT Swing(void);
	void EXPORT PendulumUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
	void EXPORT Stop(void);
	void EXPORT RopeTouch(CBaseEntity *pOther);

public:
	float m_accel;
	float m_distance;
	float m_time;
	float m_damp;
	float m_maxSpeed;
	float m_dampSpeed;
	vec3_t m_center;
	vec3_t m_start;
};

LINK_ENTITY_TO_CLASS(func_pendulum, CPendulum);

void CPendulum::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "distance"))
	{
		m_distance = atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "axes"))
	{
		UTIL_StringToVector( (float*)(pev->movedir), pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "damp"))
	{
		m_damp = atof(pkvd->szValue) * 0.001;
		pkvd->fHandled = TRUE;
	}
	else
		CBaseEntity::KeyValue(pkvd);
}

void CPendulum::Spawn(void)
{
	CBaseToggle::AxisDir(pev);

	if (FBitSet(pev->spawnflags, SF_DOOR_PASSABLE))
		pev->solid = SOLID_NOT;
	else
		pev->solid = SOLID_BSP;

	pev->movetype = MOVETYPE_PUSH;
	UTIL_SetOrigin(pev, pev->origin);
	SET_MODEL(ENT(pev), STRING(pev->model));

	if (m_distance == 0)
		return;

	if (pev->speed == 0)
		pev->speed = 100;

	m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance));
	m_maxSpeed = pev->speed;
	m_start = pev->angles;
	m_center = pev->angles + (m_distance * 0.5) * pev->movedir;

	if (FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_INSTANT))
	{
		SetThink(&CBaseEntity::SUB_CallUseToggle);
		pev->nextthink = gpGlobals->time + 0.1;
	}

	pev->speed = 0;
	SetUse(&CPendulum::PendulumUse);

	if (FBitSet(pev->spawnflags, SF_PENDULUM_SWING))
		SetTouch(&CPendulum::RopeTouch);
}

void CPendulum::PendulumUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (pev->speed)
	{
		if (FBitSet(pev->spawnflags, SF_PENDULUM_AUTO_RETURN))
		{
			float delta = CBaseToggle::AxisDelta(pev->spawnflags, pev->angles, m_start);
			pev->avelocity = m_maxSpeed * pev->movedir;
			pev->nextthink = pev->ltime + (delta / m_maxSpeed);
			SetThink(&CPendulum::Stop);
		}
		else
		{
			pev->speed = 0;
			SetThink(NULL);
			pev->avelocity = g_vecZero;
		}
	}
	else
	{
		pev->nextthink = pev->ltime + 0.1;
		m_time = gpGlobals->time;
		SetThink(&CPendulum::Swing);
		m_dampSpeed = m_maxSpeed;
	}
}

void CPendulum::Stop(void)
{
	pev->angles = m_start;
	pev->speed = 0;
	SetThink(NULL);
	pev->avelocity = g_vecZero;
}

void CPendulum::Blocked(CBaseEntity *pOther)
{
	m_time = gpGlobals->time;
}

void CPendulum::Swing(void)
{
	float delta = CBaseToggle::AxisDelta(pev->spawnflags, pev->angles, m_center);
	float dt = gpGlobals->time - m_time;
	m_time = gpGlobals->time;

	if (delta > 0 && m_accel > 0)
		pev->speed -= m_accel * dt;
	else
		pev->speed += m_accel * dt;

	if (pev->speed > m_maxSpeed)
		pev->speed = m_maxSpeed;
	else if (pev->speed < -m_maxSpeed)
		pev->speed = -m_maxSpeed;

	pev->avelocity = pev->speed * pev->movedir;
	pev->nextthink = pev->ltime + 0.1;

	if (m_damp)
	{
		m_dampSpeed -= m_damp * m_dampSpeed * dt;

		if (m_dampSpeed < 30)
		{
			pev->angles = m_center;
			pev->speed = 0;
			SetThink(NULL);
			pev->avelocity = g_vecZero;
		}
		else if (pev->speed > m_dampSpeed)
			pev->speed = m_dampSpeed;
		else if (pev->speed < -m_dampSpeed)
			pev->speed = -m_dampSpeed;
	}
}

void CPendulum::Touch(CBaseEntity *pOther)
{
	entvars_t *pevOther = pOther->pev;

	if (pev->dmg <= 0)
		return;

	if (!pevOther->takedamage)
		return;

	float damage = pev->dmg * pev->speed * 0.01;

	if (damage < 0)
		damage = -damage;

	pOther->TakeDamage( pev, pev, damage, DMG_CRUSH, 0 );

	pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev)).Normalize() * damage;
}

void CPendulum::RopeTouch(CBaseEntity *pOther)
{
	entvars_t *pevOther = pOther->pev;

	if (!pOther->IsPlayer())
	{
		ALERT(at_console, "Not a client\n");
		return;
	}

	if (ENT(pevOther) == pev->enemy)
		return;

	pev->enemy = pOther->edict();
	pevOther->velocity = g_vecZero;
	pevOther->movetype = MOVETYPE_NONE;
}