#include "engine.h"
#include <cbase.h>

static cvar_t *sv_knockback1 = NULL;
static cvar_t *sv_knockback2 = NULL;
static cvar_t *sv_friendlyfire = NULL;
static bool g_ff_check = false;
static bool g_kb1_check = false;
static bool g_kb2_check = false;
static int gmsgDamage = 0;
static int gmsgDeathMsg = 0;
static int gmsgScoreInfo = 0;

//From VexdUM (AMX Mod 2006.2)
//This is not exposed, and is only provided as a compatibility helper.
BOOL is_breakable(edict_t* pBreak)
{
	if (FStrEq("func_breakable", STRING(pBreak->v.classname))
		|| FStrEq("func_pushable", STRING(pBreak->v.classname))
		&& pBreak->v.spawnflags & SF_PUSH_BREAKABLE)
	{
		return true;
	}

	return false;
}

//From VexdUM (AMX Mod 2006.2)
//This is not exposed, and is only provided as a compatibility helper.
BOOL is_monster(edict_t* pMonster)
{
	if(pMonster->v.flags & FL_MONSTER)
	{
		return true;
	}

	return false;
}

//From VexdUM (AMX Mod 2006.2)
// Damage Monsters
void hurt_monster(edict_t* pMonster, float dmg, int bit, const float *origin)
{
	int mdmg = (int)pMonster->v.health;
	pMonster->v.health -= dmg;

	// No need to create a trigger_hurt unless we are going to kill the monster ;)
	if((int)pMonster->v.health < 1)
	{
		int hurt = MAKE_STRING("trigger_hurt");
		char hbit[16];
		char horigin[16];
		snprintf(hbit, 15, "%i", bit);
		snprintf(horigin, 15, "%f %f %f", origin[0], origin[1], origin[2]);

		edict_t* pEntity = CREATE_NAMED_ENTITY(hurt);
		KeyValueData pkvd1;
		pkvd1.szClassName = "trigger_hurt";
		pkvd1.szKeyName = "dmg";
		pkvd1.szValue = "1.0";
		pkvd1.fHandled = FALSE;
		MDLL_KeyValue(pEntity, &pkvd1);

		KeyValueData pkvd2;
		pkvd2.szClassName = "trigger_hurt";
		pkvd2.szKeyName = "damagetype";
		pkvd2.szValue = hbit;
		pkvd2.fHandled = FALSE;
		MDLL_KeyValue(pEntity, &pkvd2);

		KeyValueData pkvd3;
		pkvd3.szClassName = "trigger_hurt";
		pkvd3.szKeyName = "origin";
		pkvd3.szValue = horigin;
		pkvd3.fHandled = FALSE;
		MDLL_KeyValue(pEntity, &pkvd3);

		MDLL_Spawn(pEntity);
		MDLL_Touch(pEntity, pMonster);
		REMOVE_ENTITY(pEntity);
	}
	mdmg -= (int)pMonster->v.health;
	//~dvander - Note, not porting the forward until this function is known to be truly wrapped
	//g_forwards.executeForward(FF_MonsterHurt, ENTINDEX(pMonster), ENTINDEX(pMonster->v.dmg_inflictor), mdmg);
}

//From VexdUM (AMX Mod 2006.2)
//This appears to be from the HLSDK CBasePlayer::TakeDamage() function.
//This is not exposed, and is only provided as a compatibility helper.
float ArmorDamage(edict_t* pVictim, float dmg, int bit)
{
	float flRatio = 0.2;
	float flBonus = 0.5;
	if(bit & DMG_BLAST)
	{
		// blasts damage armor more.
		flBonus *= 2;
	}
	if(pVictim->v.armorvalue && !(bit & (DMG_FALL | DMG_DROWN)))
	{
		// armor doesn't protect against fall or drown damage!
		float flNew = dmg * flRatio;
		float flArmor = (dmg - flNew) * flBonus;

		// Does this use more armor than we have?
		if(flArmor > pVictim->v.armorvalue)
		{
			flArmor = pVictim->v.armorvalue;
			flArmor *= (1/flBonus);
			flNew = dmg - flArmor;
			pVictim->v.armorvalue = 0;
		} else {
			pVictim->v.armorvalue -= flArmor;
		}
		dmg = flNew;
	}
	// Lets knock the view about abit
	pVictim->v.punchangle.x = -4;
	return dmg;
}

// Death emulation
//This is not exposed, and is only provided as a compatibility helper.
void Death(edict_t* pVictim, edict_t* pKiller, const char* weapon, int hs)
{

	if (!gmsgDeathMsg)
	{
		gmsgDeathMsg = GET_USER_MSG_ID(PLID, "DeathMsg", NULL);
	}

	if (!gmsgScoreInfo)
	{
		gmsgScoreInfo = GET_USER_MSG_ID(PLID, "ScoreInfo", NULL);
	}

	// Make sure an entity is allowed to take damage
	if(pVictim->v.takedamage > DAMAGE_NO)
	{
		// Breakable Check
		if(is_breakable(pVictim))
		{
			MDLL_Use(pVictim, pKiller);
		}
		// Monster Check
		if (is_monster(pVictim))
		{
			pVictim->v.dmg_inflictor = pKiller;
			float dmg = pVictim->v.health;
			int bit = DMG_BULLET;
			const float *origin = pVictim->v.origin;
			hurt_monster(pVictim, dmg, bit, origin);
		}
		// Player Check
		if (pVictim->v.flags & (FL_CLIENT | FL_FAKECLIENT))
		{
			pVictim->v.dmg_inflictor = pKiller;
			edict_t* inflictor = pKiller->v.owner;
			int inflictorId = FNullEnt(inflictor) ? ENTINDEX(inflictor) : 0;

			// See if it is a player attacking with a default weapon
			if (pKiller->v.flags & (FL_CLIENT | FL_FAKECLIENT))
			{
				// We only modify the weapon if it = 0, otherwise its been specified
				if(strcmp(weapon, "") == 0)
				{
					char view_weapon[64];
					// Get the name from the view model
					weapon = STRING(pKiller->v.viewmodel);

					// Strip out the beginning of the viewmodel (models/v_)
					if(strncmp(weapon, "models/v_", 9) == 0)
					{
						strcpy(view_weapon, weapon + 9);
					}

					// Strip out the end of viewmodel (.mdl)
					view_weapon[strlen(view_weapon) - 4] = '\0';
					weapon = view_weapon;
				}
				// See if its an entity attacking, if so lets find its owner
			} else if (inflictorId >= 1 && inflictorId <= gpGlobals->maxClients) {
				// We only modify the weapon if it = 0, otherwise its been specified
				if(strcmp(weapon, "") == 0)
				{
					weapon = STRING(pKiller->v.classname);
					// Strip out the default part of weapon name (HLSDK)
					if(strncmp(weapon, "weapon_", 7) == 0)
					{
						weapon += 7;
					} else if(strncmp(weapon, "monster_", 8) == 0) {
						weapon += 8;
					} else if(strncmp(weapon, "func_", 5) == 0) {
						weapon += 5;
					}
				}
				// Check to see if the victim is the owner
				if(inflictor == pVictim)
				{
					pKiller = pVictim;
				} else {
					pKiller = inflictor;
				}
			}
			// Send the Death Event
			int killerId = ENTINDEX(pKiller);
			int victimId = ENTINDEX(pVictim);
			MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg );
			WRITE_BYTE( killerId > gpGlobals->maxClients ? 0 : killerId );
			WRITE_BYTE( victimId );
			WRITE_BYTE( hs );
			WRITE_STRING( weapon );
			MESSAGE_END();
			// Log this kill
			if(pVictim == pKiller)
			{
				// killed self
				UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed self with \"%s\"\n",
					STRING( pVictim->v.netname ),
					GETPLAYERUSERID( pVictim ),
					GETPLAYERAUTHID( pVictim ),
					MF_GetPlayerTeam(victimId),
					weapon );
				// Killed by another player
			} else if(pKiller->v.flags & (FL_CLIENT | FL_FAKECLIENT)) {
				UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n",
					STRING( pKiller->v.netname ),
					GETPLAYERUSERID( pKiller ),
					GETPLAYERAUTHID( pKiller ),
					MF_GetPlayerTeam(killerId),
					STRING( pVictim->v.netname ),
					GETPLAYERUSERID( pVictim ),
					GETPLAYERAUTHID( pVictim ),
					MF_GetPlayerTeam(victimId),
					weapon);

				int killerTeam = MF_GetPlayerTeamID(killerId);
				int victimTeam = MF_GetPlayerTeamID(victimId);
				if (killerTeam != victimTeam)
				{
					// Give Killer credit for this kill
					pKiller->v.frags += 1;
				} else {
					pKiller->v.frags -= 1;
				}
				// Update the scoreboard for the killer
				if (gmsgScoreInfo)
				{
					MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
					WRITE_BYTE( killerId );
					WRITE_SHORT( (int)pKiller->v.frags );
					WRITE_SHORT( MF_GetPlayerDeaths(killerId) );
					WRITE_SHORT( 0 );
					WRITE_SHORT( MF_GetPlayerTeamID(killerId) );
					MESSAGE_END();
				}
				// Give Victim back 1 point since they didn't kill themselves
				pVictim->v.frags += 1;
			}
			// Killed by something else?
			else {
				UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed by \"%s\"\n",
					STRING( pVictim->v.netname ),
					GETPLAYERUSERID( pVictim ),
					GETPLAYERAUTHID( pVictim ),
					MF_GetPlayerTeam(victimId),
					weapon );
				// Give Victim back 1 point since they didn't commit suicide
				pVictim->v.frags += 1;
			}
#if 0
			//still a todo on this one
			gInfo.logBlock = true;
#endif
			int opt = BLOCK_ONCE;
			MF_MessageBlock(MSGBLOCK_SET, gmsgDeathMsg, &opt);
			// Kill the client, since the relevent logging blocks are in place
			MDLL_ClientKill(pVictim);
			// Restore the old message type
			MF_MessageBlock(MSGBLOCK_SET, gmsgDeathMsg, &opt);
			// Show the Victim the killing location
			pVictim->v.iuser3 = (killerId > gpGlobals->maxClients) ? 0 : killerId;
		}
	}
}

// Damage emulation
// From VexdUM (AMX Mod 2006.2)
//This is not exposed, and is only provided as a compatibility helper.
void Damage(edict_t *pVictim, 
			edict_t *pAttacker, 
			const float *origin, 
			float dmg, 
			int bit, 
			const char *weapon, 
			int hs)
{
	if (!g_ff_check && !sv_friendlyfire)
	{
		sv_friendlyfire = CVAR_GET_POINTER("sv_friendlyfire");
		g_ff_check = true;
	}

	if (!gmsgDamage)
	{
		gmsgDamage = GET_USER_MSG_ID(PLID, "Damage", NULL);
	}

	// Make sure an entity is allowed to take damage
	if(pVictim->v.takedamage > DAMAGE_NO)
	{
		// Breakable Check
		if(is_breakable(pVictim) && (int)dmg > 0)
		{
			MDLL_Use(pVictim, pAttacker);
		}
		// Monster Check
		if(is_monster(pVictim) && (int)dmg > 0)
		{
			pVictim->v.dmg_inflictor = pAttacker;
			hurt_monster(pVictim, dmg, bit, origin);
		}
		// Player Check
		if(pVictim->v.flags & (FL_CLIENT | FL_FAKECLIENT))
		{
			int AttackerId = ENTINDEX(pAttacker);
			int AttackerOwnerId = ENTINDEX(pAttacker->v.owner);
			int VictimId = ENTINDEX(pVictim);
			int vTeam = MF_GetPlayerTeamID(VictimId);
			int aTeam = 0;
			if (AttackerId >= 1 && AttackerId <= gpGlobals->maxClients)
			{
				aTeam = MF_GetPlayerTeamID(AttackerId);
			} else if (AttackerOwnerId >= 1 && AttackerOwnerId <= gpGlobals->maxClients) {
				aTeam = MF_GetPlayerTeamID(AttackerOwnerId);
			}
			if((sv_friendlyfire && (int)sv_friendlyfire->value) || (vTeam != aTeam))
			{
				// Recalculate the damage since we might have armor
				dmg = ArmorDamage(pVictim, dmg, bit);
				// Only allow damage to process if more than 0.5
				if((int)dmg > 0)
				{
					// Setting to externally flag who last attacked the Victim, pretty neat huh?
					pVictim->v.dmg_inflictor = pAttacker;
					pVictim->v.dmg_take += dmg;
					// Register the Damage Event
					MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pVictim );
					WRITE_BYTE( (int)pVictim->v.dmg_save );
					WRITE_BYTE( (int)pVictim->v.dmg_take );
					WRITE_LONG( bit );
					WRITE_COORD( origin[0] );
					WRITE_COORD( origin[1] );
					WRITE_COORD( origin[2] );
					MESSAGE_END();

					if((int)dmg >= (int)pVictim->v.health)
					{
						// Kill the victim
						pVictim->v.health = 0.0;
						// Send info to Death system
						Death(pVictim, pAttacker, weapon, hs);
					}else {
						// Take health away from victim
						pVictim->v.health -= dmg;
					}
				}
			}
		}
	}
}

// Radius Damage emulation - 
// From VexdUM (AMX Mod 2006.2)
//This is not exposed, and is only provided as a compatibility helper.
void RadiusDamage_AMXMod_Base(edict_t *pAttacker, 
							  float dmg, 
							  Vector vecSrc, 
							  float rad, 
							  int bit, 
							  const char *weapon, 
							  int hs)
{
	edict_t *pTarget = NULL;
	TraceResult tr;
	float falloff;
	Vector vecSpot;
	Vector vecSee;

	if (!g_kb1_check && !sv_knockback1)
	{
		sv_knockback1 = CVAR_GET_POINTER("sv_knockback1");
		g_kb1_check = true;
	}
	if (!g_kb2_check && !sv_knockback2)
	{
		sv_knockback2 = CVAR_GET_POINTER("sv_knockback2");
		g_kb2_check = true;
	}

	if(rad > 0.0)
	{
		falloff = dmg / rad;
	} else {
		falloff = 1.0;
	}
	vecSrc.z += 1; // In case grenade is lying on the ground

	int hitId;
	int targetId;

	while ((pTarget = UTIL_FindEntityInSphere(pTarget, vecSrc, rad)) != NULL)
	{
		// Make sure an entity is allowed to take damage
		if (pTarget->v.takedamage > DAMAGE_NO)
		{
			//none of this code was working so I simplified it
			//damage doesn't check for visibility now (it probably shouldn't anyway)
			//for this to work it seems like an exception needs to be made for world OR
			// the spot/see things aren't being calculated right.
#if 0
			vecSpot = (pTarget->v.absmin + pTarget->v.absmax) * 0.5;
			vecSee = (pAttacker->v.absmin + pAttacker->v.absmax) * 0.5;
			TRACE_LINE(vecSee, vecSpot, FALSE, pAttacker, &tr);
			// Explosion can 'see' this entity, so hurt them!
#endif
			TRACE_LINE(vecSrc, pTarget->v.origin, FALSE, pAttacker, &tr);
			hitId = ENTINDEX(tr.pHit);
			targetId = ENTINDEX(pTarget);
			if(tr.flFraction < 1.0 || (hitId == targetId))
			{
				// Work out the distance between impact and entity
				float dist = (tr.vecEndPos - vecSrc).Length() * falloff;
				// Damage algorithm, its just that easy :)
				dmg -= dist;
				// Knockback Effect
				if(pTarget->v.flags & (FL_CLIENT | FL_FAKECLIENT) && (bit & (DMG_BLAST | DMG_CLUB | DMG_SHOCK | DMG_SONIC | DMG_ENERGYBEAM | DMG_MORTAR)))
				{
					Vector vecPush = (pTarget->v.origin - (pAttacker->v.absmin + pAttacker->v.absmax) * 0.5).Normalize();
					if(dmg < 60.0)
					{
						pTarget->v.velocity = pTarget->v.velocity + vecPush * dmg * (sv_knockback1 ? sv_knockback1->value : 1.0f);
					} else {
						pTarget->v.velocity = pTarget->v.velocity + vecPush * dmg * (sv_knockback2 ? sv_knockback2->value : 1.0f);
					}
				}
				// Send info to Damage system
				Damage(pTarget, pAttacker, vecSrc, dmg, bit, weapon, hs);
			}
		}
	}
	pTarget = NULL;
}