#include "precompiled.h"

#if !defined(DOOR_ASSERT)
#undef DbgAssert
#undef DbgAssertMsg
#define DbgAssert(_exp) ((void)0)
#define DbgAssertMsg(_exp, _msg) ((void)0)
#endif

TYPEDESCRIPTION CEnvGlobal::m_SaveData[] =
{
	DEFINE_FIELD(CEnvGlobal, m_globalstate, FIELD_STRING),
	DEFINE_FIELD(CEnvGlobal, m_triggermode, FIELD_INTEGER),
	DEFINE_FIELD(CEnvGlobal, m_initialstate, FIELD_INTEGER),
};

IMPLEMENT_SAVERESTORE(CEnvGlobal, CBaseEntity)
LINK_ENTITY_TO_CLASS(env_global, CEnvGlobal, CCSEnvGlobal)

void CEnvGlobal::KeyValue(KeyValueData *pkvd)
{
	pkvd->fHandled = TRUE;

	// State name
	if (FStrEq(pkvd->szKeyName, "globalstate"))
	{
		m_globalstate = ALLOC_STRING(pkvd->szValue);
	}
	else if (FStrEq(pkvd->szKeyName, "triggermode"))
	{
		m_triggermode = Q_atoi(pkvd->szValue);
	}
	else if (FStrEq(pkvd->szKeyName, "initialstate"))
	{
		m_initialstate = Q_atoi(pkvd->szValue);
	}
	else
	{
		CPointEntity::KeyValue(pkvd);
	}
}

void CEnvGlobal::Spawn()
{
	if (!m_globalstate)
	{
		REMOVE_ENTITY(ENT(pev));
		return;
	}

	if (pev->spawnflags & SF_GLOBAL_SET)
	{
		if (!gGlobalState.EntityInTable(m_globalstate))
		{
			gGlobalState.EntityAdd(m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate);
		}
	}
}

void CEnvGlobal::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	GLOBALESTATE oldState = gGlobalState.EntityGetState(m_globalstate);
	GLOBALESTATE newState;

	switch (m_triggermode)
	{
	case 0:
		newState = GLOBAL_OFF;
		break;

	case 1:
		newState = GLOBAL_ON;
		break;

	case 2:
		newState = GLOBAL_DEAD;
		break;

	default:
	case 3:
		if (oldState == GLOBAL_ON)
		{
			newState = GLOBAL_OFF;
		}
		else if (oldState == GLOBAL_OFF)
		{
			newState = GLOBAL_ON;
		}
		else
			newState = oldState;
	}

	if (gGlobalState.EntityInTable(m_globalstate))
	{
		gGlobalState.EntitySetState(m_globalstate, newState);
	}
	else
		gGlobalState.EntityAdd(m_globalstate, gpGlobals->mapname, newState);
}

TYPEDESCRIPTION CMultiSource::m_SaveData[] =
{
	// BUGBUG FIX
	DEFINE_ARRAY(CMultiSource, m_rgEntities, FIELD_EHANDLE, MAX_MS_TARGETS),
	DEFINE_ARRAY(CMultiSource, m_rgTriggered, FIELD_INTEGER, MAX_MS_TARGETS),
	DEFINE_FIELD(CMultiSource, m_iTotal, FIELD_INTEGER),
	DEFINE_FIELD(CMultiSource, m_globalstate, FIELD_STRING),
};

IMPLEMENT_SAVERESTORE(CMultiSource, CBaseEntity)
LINK_ENTITY_TO_CLASS(multisource, CMultiSource, CCSMultiSource)

// Cache user-entity-field values until spawn is called.
void CMultiSource::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "style")
			|| FStrEq(pkvd->szKeyName, "height")
			|| FStrEq(pkvd->szKeyName, "killtarget")
			|| FStrEq(pkvd->szKeyName, "value1")
			|| FStrEq(pkvd->szKeyName, "value2")
			|| FStrEq(pkvd->szKeyName, "value3"))
		pkvd->fHandled = TRUE;

	else if (FStrEq(pkvd->szKeyName, "globalstate"))
	{
		m_globalstate = ALLOC_STRING(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
	{
		CPointEntity::KeyValue(pkvd);
	}
}

void CMultiSource::Spawn()
{
	// set up think for later registration
	pev->solid = SOLID_NOT;
	pev->movetype = MOVETYPE_NONE;
	pev->nextthink = gpGlobals->time + 0.1f;

	// Until it's initialized
	pev->spawnflags |= SF_MULTI_INIT;

	SetThink(&CMultiSource::Register);
}

#ifdef REGAMEDLL_FIXES
void CMultiSource::Restart()
{
	Q_memset(m_rgTriggered, 0, sizeof(m_rgTriggered));
	Spawn();
}
#endif

void CMultiSource::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	int i = 0;

	// Find the entity in our list
	while (i < m_iTotal)
	{
		if (m_rgEntities[i++] == pCaller)
			break;
	}

	// if we didn't find it, report error and leave
	if (i > m_iTotal)
	{
		ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname));
		return;
	}

	// CONSIDER: a Use input to the multisource always toggles.
	// Could check useType for ON/OFF/TOGGLE
	m_rgTriggered[i - 1] ^= 1;

	if (IsTriggered(pActivator))
	{
		ALERT(at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal);
		USE_TYPE useType = USE_TOGGLE;

		if (!FStringNull(m_globalstate))
		{
			useType = USE_ON;
		}

		SUB_UseTargets(nullptr, useType, 0);
	}
}

BOOL CMultiSource::IsTriggered(CBaseEntity *)
{
	// Is everything triggered?
	int i = 0;

	// Still initializing?
	if (pev->spawnflags & SF_MULTI_INIT)
		return FALSE;

	while (i < m_iTotal)
	{
		if (m_rgTriggered[i] == 0)
			break;
		i++;
	}

	if (i == m_iTotal)
	{
		if (FStringNull(m_globalstate) || gGlobalState.EntityGetState(m_globalstate) == GLOBAL_ON)
		{
			return TRUE;
		}
	}

	return FALSE;
}

void CMultiSource::Register()
{
	m_iTotal = 0;
	Q_memset(m_rgEntities, 0, MAX_MS_TARGETS * sizeof(EHANDLE));

	SetThink(&CMultiSource::SUB_DoNothing);

	// search for all entities which target this multisource (pev->targetname)
#ifdef REGAMEDLL_FIXES
	CBaseEntity *pTarget = nullptr;
	while (m_iTotal < MAX_MS_TARGETS && (pTarget = UTIL_FindEntityByString(pTarget, "target", STRING(pev->targetname)))) {
		m_rgEntities[m_iTotal++] = pTarget;
	}

	pTarget = nullptr;
	while (m_iTotal < MAX_MS_TARGETS && (pTarget = UTIL_FindEntityByClassname(pTarget, "multi_manager")))
	{
		if (pTarget->HasTarget(pev->targetname)) {
			m_rgEntities[m_iTotal++] = pTarget;
		}
	}
#else
	edict_t *pentTarget = FIND_ENTITY_BY_STRING(nullptr, "target", STRING(pev->targetname));

	while (!FNullEnt(pentTarget) && m_iTotal < MAX_MS_TARGETS)
	{
		CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget);

		if (pTarget)
		{
			m_rgEntities[m_iTotal++] = pTarget;
		}

		pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->targetname));
	}

	pentTarget = FIND_ENTITY_BY_STRING(nullptr, "classname", "multi_manager");

	while (!FNullEnt(pentTarget) && m_iTotal < MAX_MS_TARGETS)
	{
		CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget);
		if (pTarget && pTarget->HasTarget(pev->targetname))
		{
			m_rgEntities[m_iTotal++] = pTarget;
		}

		pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "classname", "multi_manager");
	}
#endif
	pev->spawnflags &= ~SF_MULTI_INIT;
}

TYPEDESCRIPTION CBaseButton::m_SaveData[] =
{
	DEFINE_FIELD(CBaseButton, m_fStayPushed, FIELD_BOOLEAN),
	DEFINE_FIELD(CBaseButton, m_fRotating, FIELD_BOOLEAN),
	DEFINE_FIELD(CBaseButton, m_sounds, FIELD_INTEGER),
	DEFINE_FIELD(CBaseButton, m_bLockedSound, FIELD_CHARACTER),
	DEFINE_FIELD(CBaseButton, m_bLockedSentence, FIELD_CHARACTER),
	DEFINE_FIELD(CBaseButton, m_bUnlockedSound, FIELD_CHARACTER),
	DEFINE_FIELD(CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER),
	DEFINE_FIELD(CBaseButton, m_strChangeTarget, FIELD_STRING),
//	DEFINE_FIELD(CBaseButton, m_ls, FIELD_???),   // This is restored in Precache()
};

IMPLEMENT_SAVERESTORE(CBaseButton, CBaseToggle)

void CBaseButton::Precache()
{
	char *pszSound;

	// this button should spark in OFF state
	if (pev->spawnflags & SF_BUTTON_SPARK_IF_OFF)
	{
		PRECACHE_SOUND("buttons/spark1.wav");
		PRECACHE_SOUND("buttons/spark2.wav");
		PRECACHE_SOUND("buttons/spark3.wav");
		PRECACHE_SOUND("buttons/spark4.wav");
		PRECACHE_SOUND("buttons/spark5.wav");
		PRECACHE_SOUND("buttons/spark6.wav");
	}

	// get door button sounds, for doors which require buttons to open
	if (m_bLockedSound)
	{
		pszSound = ButtonSound(int(m_bLockedSound));
		PRECACHE_SOUND(pszSound);
		m_ls.sLockedSound = ALLOC_STRING(pszSound);
	}

	if (m_bUnlockedSound)
	{
		pszSound = ButtonSound(int(m_bUnlockedSound));
		PRECACHE_SOUND(pszSound);
		m_ls.sUnlockedSound = ALLOC_STRING(pszSound);
	}

	// get sentence group names, for doors which are directly 'touched' to open
	switch (m_bLockedSentence)
	{
	case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break;	// access denied
	case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break;	// security lockout
	case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break;	// blast door
	case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break;	// fire door
	case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break;	// chemical door
	case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break;	// radiation door
	case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break;	// gen containment
	case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break;	// maintenance door
	case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break;	// broken door
	default: m_ls.sLockedSentence = 0; break;
	}

	switch (m_bUnlockedSentence)
	{
	case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break;	// access granted
	case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break;	// security door
	case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break;	// blast door
	case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break;	// fire door
	case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break;	// chemical door
	case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break;	// radiation door
	case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break;	// gen containment
	case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break;	// maintenance door
	default: m_ls.sUnlockedSentence = 0; break;
	}
}

// Cache user-entity-field values until spawn is called.
void CBaseButton::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "changetarget"))
	{
		m_strChangeTarget = ALLOC_STRING(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "locked_sound"))
	{
		m_bLockedSound = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "locked_sentence"))
	{
		m_bLockedSentence = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "unlocked_sound"))
	{
		m_bUnlockedSound = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "unlocked_sentence"))
	{
		m_bUnlockedSentence = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "sounds"))
	{
		m_sounds = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
	{
		CBaseToggle::KeyValue(pkvd);
	}
}

// ButtonShot
BOOL CBaseButton::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
	BUTTON_CODE code = ButtonResponseToTouch();

	if (code == BUTTON_NOTHING)
	{
		return FALSE;
	}

	// Temporarily disable the touch function, until movement is finished.
	SetTouch(nullptr);

	m_hActivator = CBaseEntity::Instance(pevAttacker);
	if (!m_hActivator)
		return FALSE;

	if (code == BUTTON_RETURN)
	{
		EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), 1, ATTN_NORM);

		// Toggle buttons fire when they get back to their "home" position
		if (!(pev->spawnflags & SF_BUTTON_TOGGLE))
		{
			SUB_UseTargets(m_hActivator, USE_TOGGLE, 0);
		}

		ButtonReturn();
	}
	else
	{
		// code == BUTTON_ACTIVATE
		ButtonActivate();
	}

	return FALSE;
}

// QUAKED func_button (0 .5 .8) ?
// When a button is touched, it moves some distance in the direction of it's angle,
// triggers all of it's targets, waits some time, then returns to it's original position
// where it can be triggered again.
//
// "angle"	determines the opening direction
// "target"	all entities with a matching targetname will be used
// "speed"	override the default 40 speed
// "wait"	override the default 1 second wait (-1 = never return)
// "lip"	override the default 4 pixel lip remaining at end of move
// "health"	if set, the button must be killed instead of touched
// "sounds"
// 0) steam metal
// 1) wooden clunk
// 2) metallic click
// 3) in-out
LINK_ENTITY_TO_CLASS(func_button, CBaseButton, CCSButton)

void CBaseButton::Spawn()
{
	char  *pszSound;

	//----------------------------------------------------
	//determine sounds for buttons
	//a sound of 0 should not make a sound
	//----------------------------------------------------
	pszSound = ButtonSound(m_sounds);
	PRECACHE_SOUND(pszSound);
	pev->noise = ALLOC_STRING(pszSound);

	Precache();

	// this button should spark in OFF state
	if (pev->spawnflags & SF_BUTTON_SPARK_IF_OFF)
	{
		SetThink(&CBaseButton::ButtonSpark);

		// no hurry, make sure everything else spawns
		pev->nextthink = gpGlobals->time + 0.5f;
	}

	SetMovedir(pev);

	pev->movetype = MOVETYPE_PUSH;
	pev->solid = SOLID_BSP;

	SET_MODEL(ENT(pev), STRING(pev->model));

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

	if (pev->health > 0)
	{
		pev->takedamage = DAMAGE_YES;
	}

	if (m_flWait == 0)
	{
		m_flWait = 1;
	}

	if (m_flLip == 0)
	{
		m_flLip = 4;
	}

	m_toggle_state = TS_AT_BOTTOM;
	m_vecPosition1 = pev->origin;

	// Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big
	m_vecPosition2 = m_vecPosition1 + (pev->movedir * (Q_fabs(real_t(pev->movedir.x * (pev->size.x - 2))) + Q_fabs(real_t(pev->movedir.y * (pev->size.y - 2))) + Q_fabs(real_t(pev->movedir.z * (pev->size.z - 2))) - m_flLip));

	// Is this a non-moving button?
	if (((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE))
	{
		m_vecPosition2 = m_vecPosition1;
	}

	m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE);
	m_fRotating = FALSE;

	// if the button is flagged for USE button activation only, take away it's touch function and add a use function
	// touchable button
	if (pev->spawnflags & SF_BUTTON_TOUCH_ONLY)
	{
		SetTouch(&CBaseButton::ButtonTouch);
	}
	else
	{
		SetTouch(nullptr);
		SetUse(&CBaseButton::ButtonUse);
	}
}

// Button sound table.
// Also used by CBaseDoor to get 'touched' door lock/unlock sounds
char *ButtonSound(int sound)
{
	char *pszSound;

	switch (sound)
	{
	case 0: pszSound = "common/null.wav"; break;
	case 1: pszSound = "buttons/button1.wav"; break;
	case 2: pszSound = "buttons/button2.wav"; break;
	case 3: pszSound = "buttons/button3.wav"; break;
	case 4: pszSound = "buttons/button4.wav"; break;
	case 5: pszSound = "buttons/button5.wav"; break;
	case 6: pszSound = "buttons/button6.wav"; break;
	case 7: pszSound = "buttons/button7.wav"; break;
	case 8: pszSound = "buttons/button8.wav"; break;
	case 9: pszSound = "buttons/button9.wav"; break;
	case 10: pszSound = "buttons/button10.wav"; break;
	case 11: pszSound = "buttons/button11.wav"; break;
	case 12: pszSound = "buttons/latchlocked1.wav"; break;
	case 13: pszSound = "buttons/latchunlocked1.wav"; break;
	case 14: pszSound = "buttons/lightswitch2.wav"; break;

	// next 6 slots reserved for any additional sliding button sounds we may add
	case 21: pszSound = "buttons/lever1.wav"; break;
	case 22: pszSound = "buttons/lever2.wav"; break;
	case 23: pszSound = "buttons/lever3.wav"; break;
	case 24: pszSound = "buttons/lever4.wav"; break;
	case 25: pszSound = "buttons/lever5.wav"; break;
	default: pszSound = "buttons/button9.wav"; break;
	}

	return pszSound;
}

// Makes flagged buttons spark when turned off
void DoSpark(entvars_t *pev, const Vector &location)
{
	Vector tmp = location + (pev->size * 0.5f);
	UTIL_Sparks(tmp);

	//random volume range
	float flVolume = RANDOM_FLOAT(0.25f, 0.75f) * 0.4f;

	// NOTE: not to change it
	switch ((int)(RANDOM_FLOAT(0, 1) * 6))
	{
	case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break;
	case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break;
	case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break;
	case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break;
	case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break;
	case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break;
	}
}

void CBaseButton::ButtonSpark()
{
	SetThink(&CBaseButton::ButtonSpark);

	// spark again at random interval
	pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT(0, 1.5));

	DoSpark(pev, pev->mins);
}

// Button's Use function
void CBaseButton::ButtonUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	// Ignore touches if button is moving, or pushed-in and waiting to auto-come-out.
	// UNDONE: Should this use ButtonResponseToTouch() too?
	if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN)
		return;

	m_hActivator = pActivator;
	if (m_toggle_state == TS_AT_TOP)
	{
		if (!m_fStayPushed && (pev->spawnflags & SF_BUTTON_TOGGLE))
		{
			EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), VOL_NORM, ATTN_NORM);
			//SUB_UseTargets(m_eoActivator);
			ButtonReturn();
		}
	}
	else
		ButtonActivate();
}

CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch()
{
	// Ignore touches if button is moving, or pushed-in and waiting to auto-come-out.
	if (m_toggle_state == TS_GOING_UP
		|| m_toggle_state == TS_GOING_DOWN
		|| (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !(pev->spawnflags & SF_BUTTON_TOGGLE)))
	{
		return BUTTON_NOTHING;
	}

	if (m_toggle_state == TS_AT_TOP)
	{
		if ((pev->spawnflags & SF_BUTTON_TOGGLE) && !m_fStayPushed)
		{
			return BUTTON_RETURN;
		}
	}
	else
		return BUTTON_ACTIVATE;

	return BUTTON_NOTHING;
}

// Touching a button simply "activates" it.
void CBaseButton::ButtonTouch(CBaseEntity *pOther)
{
	// Ignore touches by anything but players
	if (!FClassnameIs(pOther->pev, "player"))
		return;

	m_hActivator = pOther;

	BUTTON_CODE code = ButtonResponseToTouch();

	if (code == BUTTON_NOTHING)
		return;

	if (!UTIL_IsMasterTriggered(m_sMaster, pOther))
	{
		// play button locked sound
		PlayLockSounds(pev, &m_ls, TRUE, TRUE);
		return;
	}

	// Temporarily disable the touch function, until movement is finished.
	SetTouch(nullptr);

	if (code == BUTTON_RETURN)
	{
		EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), VOL_NORM, ATTN_NORM);
		SUB_UseTargets(m_hActivator, USE_TOGGLE, 0);
		ButtonReturn();
	}
	else // code == BUTTON_ACTIVATE
	{
		ButtonActivate();
	}
}

// Starts the button moving "in/up".
void CBaseButton::ButtonActivate()
{
	EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), VOL_NORM, ATTN_NORM);

	if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator))
	{
		// button is locked, play locked sound
		PlayLockSounds(pev, &m_ls, TRUE, TRUE);
		return;
	}
	else
	{
		// button is unlocked, play unlocked sound
		PlayLockSounds(pev, &m_ls, FALSE, TRUE);
	}

	DbgAssert(m_toggle_state == TS_AT_BOTTOM);
	m_toggle_state = TS_GOING_UP;

	SetMoveDone(&CBaseButton::TriggerAndWait);
	if (!m_fRotating)
	{
		LinearMove(m_vecPosition2, pev->speed);
	}
	else
	{
		AngularMove(m_vecAngle2, pev->speed);
	}
}

// Button has reached the "in/up" position.  Activate its "targets", and pause before "popping out".
void CBaseButton::TriggerAndWait()
{
	DbgAssert(m_toggle_state == TS_GOING_UP);

	if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator))
		return;

	m_toggle_state = TS_AT_TOP;

	// If button automatically comes back out, start it moving out.
	// Else re-instate touch method
	if (m_fStayPushed || (pev->spawnflags & SF_BUTTON_TOGGLE))
	{
		// this button only works if USED, not touched!
		if (!(pev->spawnflags & SF_BUTTON_TOUCH_ONLY))
		{
			// ALL buttons are now use only
			SetTouch(nullptr);
		}
		else
		{
			SetTouch(&CBaseButton::ButtonTouch);
		}
	}
	else
	{
		pev->nextthink = pev->ltime + m_flWait;
		SetThink(&CBaseButton::ButtonReturn);
	}

	// use alternate textures
	pev->frame = 1;
	SUB_UseTargets(m_hActivator, USE_TOGGLE, 0);
}

// Starts the button moving "out/down".
void CBaseButton::ButtonReturn()
{
	DbgAssert(m_toggle_state == TS_AT_TOP);
	m_toggle_state = TS_GOING_DOWN;

	SetMoveDone(&CBaseButton::ButtonBackHome);
	if (!m_fRotating)
	{
		LinearMove(m_vecPosition1, pev->speed);
	}
	else
	{
		AngularMove(m_vecAngle1, pev->speed);
	}

	// use normal textures
	pev->frame = 0;
}

#ifdef REGAMEDLL_FIXES
void CBaseButton::Restart()
{
	m_hActivator = nullptr;
	SetMovedir(pev);
	ButtonReturn();

	if (pev->spawnflags & SF_BUTTON_TOUCH_ONLY)
	{
		SetTouch(&CBaseButton::ButtonTouch);
	}
	else
	{
		SetTouch(nullptr);
		SetUse(&CBaseButton::ButtonUse);
	}
}
#endif

// Button has returned to start state. Quiesce it.
void CBaseButton::ButtonBackHome()
{
	DbgAssert(m_toggle_state == TS_GOING_DOWN);
	m_toggle_state = TS_AT_BOTTOM;

	if (pev->spawnflags & SF_BUTTON_TOGGLE
#ifdef REGAMEDLL_FIXES
		&& m_hActivator
#endif
)
	{
		//EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), 1, ATTN_NORM);
		SUB_UseTargets(m_hActivator, USE_TOGGLE, 0);
	}

	if (!FStringNull(pev->target)
#ifdef REGAMEDLL_FIXES
		&& m_hActivator
#endif
)
	{
		edict_t *pentTarget = nullptr;
		while ((pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target))))
		{
			if (FNullEnt(pentTarget))
				break;

			if (!FClassnameIs(pentTarget, "multisource"))
				continue;

			CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget);

			if (pTarget)
			{
				pTarget->Use(m_hActivator, this, USE_TOGGLE, 0);
			}
		}
	}

	// Re-instate touch method, movement cycle is complete.
	// this button only works if USED, not touched!
	if (!(pev->spawnflags & SF_BUTTON_TOUCH_ONLY))
	{
		// All buttons are now use only
		SetTouch(nullptr);
	}
	else
	{
		SetTouch(&CBaseButton::ButtonTouch);
	}

	// reset think for a sparking button
	if (pev->spawnflags & SF_BUTTON_SPARK_IF_OFF)
	{
		SetThink(&CBaseButton::ButtonSpark);

		// no hurry.
		pev->nextthink = gpGlobals->time + 0.5f;
	}
}

#ifdef REGAMEDLL_FIXES

TYPEDESCRIPTION CRotButton::m_SaveData[] =
{
	DEFINE_FIELD(CRotButton, m_vecSpawn, FIELD_VECTOR),
};

IMPLEMENT_SAVERESTORE(CRotButton, CBaseButton)

#endif

LINK_ENTITY_TO_CLASS(func_rot_button, CRotButton, CCSRotButton)

void CRotButton::Spawn()
{
	char *pszSound;

	// determine sounds for buttons
	// a sound of 0 should not make a sound
	pszSound = ButtonSound(m_sounds);
	PRECACHE_SOUND(pszSound);
	pev->noise = ALLOC_STRING(pszSound);

	// set the axis of rotation
	CBaseToggle::AxisDir(pev);

	// check for clockwise rotation
	if (pev->spawnflags & SF_ROTBUTTON_BACKWARDS)
	{
		pev->movedir = pev->movedir * -1;
	}

	pev->movetype = MOVETYPE_PUSH;

	if (pev->spawnflags & SF_ROTBUTTON_NOTSOLID)
		pev->solid = SOLID_NOT;
	else
		pev->solid = SOLID_BSP;

	SET_MODEL(ENT(pev), STRING(pev->model));

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

	if (m_flWait == 0)
	{
		m_flWait = 1;
	}

	if (pev->health > 0)
	{
		pev->takedamage = DAMAGE_YES;
	}

#ifdef REGAMEDLL_FIXES
	m_vecSpawn = pev->angles;
#endif

	m_toggle_state = TS_AT_BOTTOM;
	m_vecAngle1 = pev->angles;
	m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance;

	DbgAssertMsg(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal");

	m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE);
	m_fRotating = TRUE;

	// if the button is flagged for USE button activation only, take away it's touch function and add a use function
	if (!(pev->spawnflags & SF_BUTTON_TOUCH_ONLY))
	{
		SetTouch(nullptr);
		SetUse(&CRotButton::ButtonUse);
	}
	// touchable button
	else
	{
		SetTouch(&CRotButton::ButtonTouch);
	}
}

#ifdef REGAMEDLL_FIXES
void CRotButton::Restart()
{
	pev->angles = m_vecSpawn;
	Spawn();
}
#endif

TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] =
{
	DEFINE_FIELD(CMomentaryRotButton, m_lastUsed, FIELD_INTEGER),
	DEFINE_FIELD(CMomentaryRotButton, m_direction, FIELD_INTEGER),
	DEFINE_FIELD(CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT),
	DEFINE_FIELD(CMomentaryRotButton, m_start, FIELD_VECTOR),
	DEFINE_FIELD(CMomentaryRotButton, m_end, FIELD_VECTOR),
	DEFINE_FIELD(CMomentaryRotButton, m_sounds, FIELD_INTEGER),
};

IMPLEMENT_SAVERESTORE(CMomentaryRotButton, CBaseToggle)
LINK_ENTITY_TO_CLASS(momentary_rot_button, CMomentaryRotButton, CCSMomentaryRotButton)

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

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

	if (m_flMoveDistance < 0)
	{
		m_start = pev->angles + pev->movedir * m_flMoveDistance;
		m_end = pev->angles;

		// This will toggle to -1 on the first use()
		m_direction = 1;
		m_flMoveDistance = -m_flMoveDistance;
	}
	else
	{
		m_start = pev->angles;
		m_end = pev->angles + pev->movedir * m_flMoveDistance;

		// This will toggle to +1 on the first use()
		m_direction = -1;
	}

	if (pev->spawnflags & SF_MOMENTARY_DOOR)
		pev->solid = SOLID_BSP;
	else
		pev->solid = SOLID_NOT;

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

	char *pszSound = ButtonSound(m_sounds);
	PRECACHE_SOUND(pszSound);
	pev->noise = ALLOC_STRING(pszSound);
	m_lastUsed = 0;
}

void CMomentaryRotButton::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "returnspeed"))
	{
		m_returnSpeed = Q_atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "sounds"))
	{
		m_sounds = Q_atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
	{
		CBaseToggle::KeyValue(pkvd);
	}
}

void CMomentaryRotButton::PlaySound()
{
	EMIT_SOUND(ENT(pev), CHAN_VOICE, (char *)STRING(pev->noise), VOL_NORM, ATTN_NORM);
}

// BUGBUG: This design causes a latentcy.  When the button is retriggered, the first impulse
// will send the target in the wrong direction because the parameter is calculated based on the
// current, not future position.
void CMomentaryRotButton::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	pev->ideal_yaw = CBaseToggle::AxisDelta(pev->spawnflags, pev->angles, m_start) / m_flMoveDistance;

	UpdateAllButtons(pev->ideal_yaw, 1);
	UpdateTarget(pev->ideal_yaw);

#if 0
	// Calculate destination angle and use it to predict value, this prevents sending target in wrong direction on retriggering
	Vector dest = pev->angles + pev->avelocity * (pev->nextthink - pev->ltime);
	float value1 = CBaseToggle::AxisDelta(pev->spawnflags, dest, m_start) / m_flMoveDistance;
	UpdateTarget(value1);
#endif
}

void CMomentaryRotButton::UpdateAllButtons(float value, int start)
{
	// Update all rot buttons attached to the same target
	edict_t *pentTarget = nullptr;
	while (true)
	{

		pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target));

		if (FNullEnt(pentTarget))
			break;

		if (FClassnameIs(VARS(pentTarget), "momentary_rot_button"))
		{
			CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget);

			if (pEntity)
			{
				if (start)
					pEntity->UpdateSelf(value);
				else
					pEntity->UpdateSelfReturn(value);
			}
		}
	}
}

void CMomentaryRotButton::UpdateSelf(float value)
{
	bool fplaysound = false;

	if (!m_lastUsed)
	{
		fplaysound = true;
		m_direction = -m_direction;
	}

	m_lastUsed = 1;
	pev->nextthink = pev->ltime + 0.1f;

	if (m_direction > 0 && value >= 1.0f)
	{
		pev->avelocity = g_vecZero;
		pev->angles = m_end;
		return;
	}
	else if (m_direction < 0 && value <= 0.0f)
	{
		pev->avelocity = g_vecZero;
		pev->angles = m_start;
		return;
	}

	if (fplaysound)
	{
		PlaySound();
	}

	// HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling
	if (pev->nextthink < pev->ltime)
		pev->nextthink = pev->ltime + 0.1f;
	else
		pev->nextthink += 0.1f;

	pev->avelocity = (m_direction * pev->speed) * pev->movedir;
	SetThink(&CMomentaryRotButton::Off);
}

void CMomentaryRotButton::UpdateTarget(float value)
{
	if (!FStringNull(pev->target))
	{
		edict_t *pentTarget = nullptr;
		while ((pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target))))
		{
			if (FNullEnt(pentTarget))
				break;

			CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget);
			if (pEntity)
			{
				pEntity->Use(this, this, USE_SET, value);
			}
		}
	}
}

void CMomentaryRotButton::Off()
{
	pev->avelocity = g_vecZero;
	m_lastUsed = 0;

	if ((pev->spawnflags & SF_PENDULUM_AUTO_RETURN) && m_returnSpeed > 0)
	{
		SetThink(&CMomentaryRotButton::Return);
		pev->nextthink = pev->ltime + 0.1f;
		m_direction = -1;
	}
	else
	{
		SetThink(nullptr);
	}
}

void CMomentaryRotButton::Return()
{
	float value = CBaseToggle::AxisDelta(pev->spawnflags, pev->angles, m_start) / m_flMoveDistance;

	// This will end up calling UpdateSelfReturn() n times, but it still works right
	UpdateAllButtons(value, 0);

	if (value > 0)
	{
		UpdateTarget(value);
	}
}

void CMomentaryRotButton::UpdateSelfReturn(float value)
{
	if (value <= 0)
	{
		pev->avelocity = g_vecZero;
		pev->angles = m_start;
		pev->nextthink = -1;
		SetThink(nullptr);
	}
	else
	{
		pev->avelocity = -m_returnSpeed * pev->movedir;
		pev->nextthink = pev->ltime + 0.1f;
	}
}

TYPEDESCRIPTION CEnvSpark::m_SaveData[] =
{
	DEFINE_FIELD(CEnvSpark, m_flDelay, FIELD_FLOAT),
};

IMPLEMENT_SAVERESTORE(CEnvSpark, CBaseEntity)
LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark, CCSEnvSpark)
LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark, CCSEnvSpark)

void CEnvSpark::Spawn()
{
	SetThink(nullptr);
	SetUse(nullptr);

	// Use for on/off
	if (pev->spawnflags & SF_SPARK_TOOGLE)
	{
		// Start on
		if (pev->spawnflags & SF_SPARK_IF_OFF)
		{
			// start sparking
			SetThink(&CEnvSpark::SparkThink);

			// set up +USE to stop sparking
			SetUse(&CEnvSpark::SparkStop);
		}
		else
		{
			SetUse(&CEnvSpark::SparkStart);
		}
	}
	else
	{
		SetThink(&CEnvSpark::SparkThink);
	}

	pev->nextthink = gpGlobals->time + (0.1f + RANDOM_FLOAT(0.0f, 1.5f));

	if (m_flDelay <= 0.0f)
	{
		m_flDelay = 1.5f;
	}

	Precache();
}

#ifdef REGAMEDLL_FIXES
void CEnvSpark::Restart()
{
	SetThink(nullptr);
	SetUse(nullptr);

	// Use for on/off
	if (pev->spawnflags & SF_SPARK_TOOGLE)
	{
		// Start on
		if (pev->spawnflags & SF_SPARK_IF_OFF)
		{
			// start sparking
			SetThink(&CEnvSpark::SparkThink);

			// set up +USE to stop sparking
			SetUse(&CEnvSpark::SparkStop);
		}
		else
		{
			SetUse(&CEnvSpark::SparkStart);
		}
	}
	else
	{
		SetThink(&CEnvSpark::SparkThink);
	}

	pev->nextthink = gpGlobals->time + (0.1f + RANDOM_FLOAT(0.0f, 1.5f));

	if (m_flDelay <= 0.0f)
	{
		m_flDelay = 1.5f;
	}
}
#endif

void CEnvSpark::Precache()
{
	PRECACHE_SOUND("buttons/spark1.wav");
	PRECACHE_SOUND("buttons/spark2.wav");
	PRECACHE_SOUND("buttons/spark3.wav");
	PRECACHE_SOUND("buttons/spark4.wav");
	PRECACHE_SOUND("buttons/spark5.wav");
	PRECACHE_SOUND("buttons/spark6.wav");
}

void CEnvSpark::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "MaxDelay"))
	{
		m_flDelay = Q_atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "style")
			|| FStrEq(pkvd->szKeyName, "height")
			|| FStrEq(pkvd->szKeyName, "killtarget")
			|| FStrEq(pkvd->szKeyName, "value1")
			|| FStrEq(pkvd->szKeyName, "value2")
			|| FStrEq(pkvd->szKeyName, "value3"))
		pkvd->fHandled = TRUE;
	else
	{
		CBaseEntity::KeyValue(pkvd);
	}
}

void CEnvSpark::SparkThink()
{
	pev->nextthink = gpGlobals->time + 0.1f + RANDOM_FLOAT(0, m_flDelay);
	DoSpark(pev, pev->origin);
}

void CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	SetUse(&CEnvSpark::SparkStop);
	SetThink(&CEnvSpark::SparkThink);
	pev->nextthink = gpGlobals->time + (0.1f + RANDOM_FLOAT(0, m_flDelay));
}

void CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	SetUse(&CEnvSpark::SparkStart);
	SetThink(nullptr);
}

LINK_ENTITY_TO_CLASS(button_target, CButtonTarget, CCSButtonTarget)

void CButtonTarget::Spawn()
{
	pev->movetype = MOVETYPE_PUSH;
	pev->solid = SOLID_BSP;

	SET_MODEL(ENT(pev), STRING(pev->model));
	pev->takedamage = DAMAGE_YES;

	if (pev->spawnflags & SF_BTARGET_ON)
	{
		pev->frame = 1;
	}
}

void CButtonTarget::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (!ShouldToggle(useType, int(pev->frame)))
		return;

	pev->frame = 1 - pev->frame;

	if (pev->frame)
	{
		SUB_UseTargets(pActivator, USE_ON, 0);
	}
	else
	{
		SUB_UseTargets(pActivator, USE_OFF, 0);
	}
}

int CButtonTarget::ObjectCaps()
{
	int caps = (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION);
	if (pev->spawnflags & SF_BTARGET_USE)
	{
		caps |= FCAP_IMPULSE_USE;
	}

	return caps;
}

BOOL CButtonTarget::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
	Use(Instance(pevAttacker), this, USE_TOGGLE, 0);
	return TRUE;
}