/*
 * =======================================================================
 *
 * Combat code like damage, death and so on.
 *
 * =======================================================================
 */

#include "header/local.h"

/*
 * Returns true if the inflictor can directly damage the
 * target. Used for explosions and melee attacks.
 */
qboolean
CanDamage(edict_t *targ, edict_t *inflictor)
{
	vec3_t dest;
	trace_t trace;

	if (!targ || !inflictor)
	{
		return false;
	}

	/* bmodels need special checking because their origin is 0,0,0 */
	if (targ->movetype == MOVETYPE_PUSH)
	{
		VectorAdd(targ->absmin, targ->absmax, dest);
		VectorScale(dest, 0.5, dest);
		trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
				dest, inflictor, MASK_SOLID);

		if (trace.fraction == 1.0)
		{
			return true;
		}

		if (trace.ent == targ)
		{
			return true;
		}

		return false;
	}

	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
			targ->s.origin, inflictor, MASK_SOLID);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	VectorCopy(targ->s.origin, dest);
	dest[0] += 15.0;
	dest[1] += 15.0;
	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
			dest, inflictor, MASK_SOLID);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	VectorCopy(targ->s.origin, dest);
	dest[0] += 15.0;
	dest[1] -= 15.0;
	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
			dest, inflictor, MASK_SOLID);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	VectorCopy(targ->s.origin, dest);
	dest[0] -= 15.0;
	dest[1] += 15.0;
	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
			dest, inflictor, MASK_SOLID);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	VectorCopy(targ->s.origin, dest);
	dest[0] -= 15.0;
	dest[1] -= 15.0;
	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
			dest, inflictor, MASK_SOLID);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	return false;
}

void
Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker,
		int damage, vec3_t point)
{
	if (!targ || !inflictor || !attacker)
	{
		return;
	}

	targ->enemy = attacker;

	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
	{
		if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
		{
			level.killed_monsters++;

			if (coop->value && attacker->client)
			{
				attacker->client->resp.score++;
			}

			/* medics won't heal monsters that they kill themselves */
			if (attacker->classname && strcmp(attacker->classname, "monster_medic") == 0)
			{
				targ->owner = attacker;
			}
		}
	}

	if ((targ->movetype == MOVETYPE_PUSH) ||
		(targ->movetype == MOVETYPE_STOP) ||
	   	(targ->movetype == MOVETYPE_NONE))
	{
		/* doors, triggers, etc */
		targ->die(targ, inflictor, attacker, damage, point);
		return;
	}

	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
	{
		targ->touch = NULL;
		monster_death_use(targ);
	}

	targ->die(targ, inflictor, attacker, damage, point);
}

void
SpawnDamage(int type, vec3_t origin, vec3_t normal, int damage)
{
	gi.WriteByte (svc_temp_entity);
	gi.WriteByte (type);
	gi.WritePosition (origin);
	gi.WriteDir (normal);
	gi.multicast (origin, MULTICAST_PVS);
}

/*
 * targ			entity that is being damaged
 * inflictor	entity that is causing the damage
 * attacker		entity that caused the inflictor to damage targ
 *  example: targ=monster, inflictor=rocket, attacker=player
 *
 * dir			direction of the attack
 * point		point at which the damage is being inflicted
 * normal		normal vector from that point
 * damage		amount of damage being inflicted
 * knockback	force to be applied against targ as a result of the damage
 *
 * dflags		these flags are used to control how T_Damage works
 *  DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
 *  DAMAGE_NO_ARMOR			armor does not protect from this damage
 *  DAMAGE_ENERGY			damage is from an energy based weapon
 *  DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
 *  DAMAGE_BULLET			damage is from a bullet (used for ricochets)
 *  DAMAGE_NO_PROTECTION	kills godmode, armor, everything
 * ============
 */
int
CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal,
		int damage, int dflags)
{
	gclient_t *client;
	int save;
	int power_armor_type;
	int index = 0;
	int damagePerCell;
	int pa_te_type;
	int power = 0;
	int power_used;

	if (!ent)
	{
		return 0;
	}

	if (!damage)
	{
		return 0;
	}

	index = 0;

	client = ent->client;

	if (dflags & DAMAGE_NO_ARMOR)
	{
		return 0;
	}

	if (client)
	{
		power_armor_type = PowerArmorType(ent);

		if (power_armor_type != POWER_ARMOR_NONE)
		{
			index = ITEM_INDEX(FindItem("Cells"));
			power = client->pers.inventory[index];
		}
	}
	else if (ent->svflags & SVF_MONSTER)
	{
		power_armor_type = ent->monsterinfo.power_armor_type;
		power = ent->monsterinfo.power_armor_power;
		index = 0;
	}
	else
	{
		return 0;
	}

	if (power_armor_type == POWER_ARMOR_NONE)
	{
		return 0;
	}

	if (!power)
	{
		return 0;
	}

	if (power_armor_type == POWER_ARMOR_SCREEN)
	{
		vec3_t vec;
		float dot;
		vec3_t forward;

		/* only works if damage point is in front */
		AngleVectors(ent->s.angles, forward, NULL, NULL);
		VectorSubtract(point, ent->s.origin, vec);
		VectorNormalize(vec);
		dot = DotProduct(vec, forward);

		if (dot <= 0.3)
		{
			return 0;
		}

		damagePerCell = 1;
		pa_te_type = TE_SCREEN_SPARKS;
		damage = damage / 3;
	}
	else
	{
		damagePerCell = 2;
		pa_te_type = TE_SHIELD_SPARKS;
		damage = (2 * damage) / 3;
	}

	save = power * damagePerCell;

	if (!save)
	{
		return 0;
	}

	if (save > damage)
	{
		save = damage;
	}

	SpawnDamage(pa_te_type, point, normal, save);
	ent->powerarmor_time = level.time + 0.2;

	power_used = save / damagePerCell;

	if (client)
	{
		client->pers.inventory[index] -= power_used;
	}
	else
	{
		ent->monsterinfo.power_armor_power -= power_used;
	}

	return save;
}

int
CheckArmor(edict_t *ent, vec3_t point, vec3_t normal,
		int damage, int te_sparks, int dflags)
{
	gclient_t *client;
	int save;
	int index;
	gitem_t *armor;

	if (!ent)
	{
		return 0;
	}

	if (!damage)
	{
		return 0;
	}

	client = ent->client;

	if (!client)
	{
		return 0;
	}

	if (dflags & DAMAGE_NO_ARMOR)
	{
		return 0;
	}

	index = ArmorIndex(ent);

	if (!index)
	{
		return 0;
	}

	armor = GetItemByIndex(index);

	if (dflags & DAMAGE_ENERGY)
	{
		save = ceil(((gitem_armor_t *)armor->info)->energy_protection * damage);
	}
	else
	{
		save = ceil(((gitem_armor_t *)armor->info)->normal_protection * damage);
	}

	if (save >= client->pers.inventory[index])
	{
		save = client->pers.inventory[index];
	}

	if (!save)
	{
		return 0;
	}

	client->pers.inventory[index] -= save;
	SpawnDamage(te_sparks, point, normal, save);

	return save;
}

void
M_ReactToDamage(edict_t *targ, edict_t *attacker)
{
	if (!targ || !attacker)
	{
		return;
	}
	
	if (targ->health <= 0)
	{
		return;
	}

	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
	{
		return;
	}

	if ((attacker == targ) || (attacker == targ->enemy))
	{
		return;
	}

	/* if we are a good guy monster and our attacker is a
	   player or another good guy, do not get mad at them */
	if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
	{
		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
		{
			return;
		}
	}

	/* if attacker is a client, get mad at
	   them because he's good and we're not */
	if (attacker->client)
	{
		targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;

		/* this can only happen in coop (both new and old
		   enemies are clients) only switch if can't see the
		   current enemy */
		if (targ->enemy && targ->enemy->client)
		{
			if (visible(targ, targ->enemy))
			{
				targ->oldenemy = attacker;
				return;
			}

			targ->oldenemy = targ->enemy;
		}

		targ->enemy = attacker;

		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
		{
			FoundTarget(targ);
		}

		return;
	}

	/* it's the same base (walk/swim/fly) type and
	   a different classname and it's not a tank
	   (they spray too much), get mad at them */
	if (((targ->flags & (FL_FLY | FL_SWIM)) ==
		 (attacker->flags & (FL_FLY | FL_SWIM))) &&
		(strcmp(targ->classname, attacker->classname) != 0) &&
		(strcmp(attacker->classname, "monster_tank") != 0) &&
		(strcmp(attacker->classname, "monster_supertank") != 0) &&
		(strcmp(attacker->classname, "monster_makron") != 0) &&
		(strcmp(attacker->classname, "monster_jorg") != 0))
	{
		if (targ->enemy && targ->enemy->client)
		{
			targ->oldenemy = targ->enemy;
		}

		targ->enemy = attacker;

		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
		{
			FoundTarget(targ);
		}
	}
	/* if they *meant* to shoot us, then shoot back */
	else if (attacker->enemy == targ)
	{
		if (targ->enemy && targ->enemy->client)
		{
			targ->oldenemy = targ->enemy;
		}

		targ->enemy = attacker;

		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
		{
			FoundTarget(targ);
		}
	}
	/* otherwise get mad at whoever they are mad at (help our buddy) unless it is us! */
	else if (attacker->enemy)
	{
		if (targ->enemy && targ->enemy->client)
		{
			targ->oldenemy = targ->enemy;
		}

		targ->enemy = attacker->enemy;

		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
		{
			FoundTarget(targ);
		}
	}
}

static void
apply_knockback(edict_t *targ, vec3_t dir, float knockback, float scale)
{
	vec3_t kvel;
	float mass;

	if (!knockback)
	{
		return;
	}

	mass = (targ->mass < 50) ? 50.0f : (float)targ->mass;

	VectorNormalize2(dir, kvel);
	VectorScale(kvel, scale * (knockback / mass), kvel);
	VectorAdd(targ->velocity, kvel, targ->velocity);
}

void
T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir,
		vec3_t point, vec3_t normal, int damage, int knockback, int dflags,
		int mod)
{
	gclient_t *client;
	int take;
	int save;
	int asave;
	int psave;
	int te_sparks;

	if (!targ || !inflictor || !attacker)
	{
		return;
	}

	if (!targ->takedamage)
	{
		return;
	}

	/* friendly fire avoidance. If enabled you can't
	   hurt teammates (but you can hurt yourself)
	   knockback still occurs */
	if ((targ != attacker) && ((deathmatch->value &&
		((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) ||
		 coop->value))
	{
		if (OnSameTeam(targ, attacker))
		{
			if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
			{
				damage = 0;
			}
			else
			{
				mod |= MOD_FRIENDLY_FIRE;
			}
		}
	}

	meansOfDeath = mod;

	/* easy mode takes half damage */
	if ((skill->value == SKILL_EASY) && (deathmatch->value == 0) && targ->client)
	{
		damage *= 0.5;

		if (!damage)
		{
			damage = 1;
		}
	}

	client = targ->client;

	if (dflags & DAMAGE_BULLET)
	{
		te_sparks = TE_BULLET_SPARKS;
	}
	else
	{
		te_sparks = TE_SPARKS;
	}

	/* bonus damage for suprising a monster */
	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) &&
		(attacker->client) && (!targ->enemy) && (targ->health > 0))
	{
		damage *= 2;
	}

	if (targ->flags & FL_NO_KNOCKBACK)
	{
		knockback = 0;
	}

	/* figure momentum add */
	if (!(dflags & DAMAGE_NO_KNOCKBACK) &&
		(targ->movetype != MOVETYPE_NONE) &&
		(targ->movetype != MOVETYPE_BOUNCE) &&
		(targ->movetype != MOVETYPE_PUSH) &&
		(targ->movetype != MOVETYPE_STOP))
	{
		apply_knockback (targ, dir, knockback,
			((client && attacker == targ) ? 1600.0f : 500.0f));
	}

	take = damage;
	save = 0;

	/* check for godmode */
	if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
	{
		take = 0;
		save = damage;
		SpawnDamage(te_sparks, point, normal, save);
	}

	/* check for invincibility */
	if ((client && (client->invincible_framenum > level.framenum)) &&
		!(dflags & DAMAGE_NO_PROTECTION) && (mod != MOD_TRAP))
	{
		if (targ->pain_debounce_time < level.time)
		{
			gi.sound(targ, CHAN_ITEM, gi.soundindex(
							"items/protect4.wav"), 1, ATTN_NORM, 0);
			targ->pain_debounce_time = level.time + 2;
		}

		take = 0;
		save = damage;
	}

	psave = CheckPowerArmor(targ, point, normal, take, dflags);
	take -= psave;

	asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
	take -= asave;

	/* treat cheat/powerup savings the same as armor */
	asave += save;

	/* do the damage */
	if (take)
	{
		if ((targ->svflags & SVF_MONSTER) || (client))
		{
			if (strcmp(targ->classname, "monster_gekk") == 0)
			{
				SpawnDamage(TE_GREENBLOOD, point, normal, take);
			}
			else
			{
				SpawnDamage(TE_BLOOD, point, normal, take);
			}
		}
		else
		{
			SpawnDamage(te_sparks, point, normal, take);
		}

		targ->health = targ->health - take;

		if (targ->health <= 0)
		{
			if ((targ->svflags & SVF_MONSTER) || (client))
			{
				targ->flags |= FL_NO_KNOCKBACK;
			}

			Killed(targ, inflictor, attacker, take, point);
			return;
		}
	}

	if (targ->svflags & SVF_MONSTER)
	{
		M_ReactToDamage(targ, attacker);

		if (!(targ->monsterinfo.aiflags & (AI_DUCKED|AI_IGNORE_PAIN)) && (take))
		{
			targ->pain(targ, attacker, knockback, take);

			/* nightmare mode monsters don't go into pain frames often */
			if (skill->value == SKILL_HARDPLUS)
			{
				targ->pain_debounce_time = level.time + 5;
			}
		}
	}
	else if (client)
	{
		if (!(targ->flags & FL_GODMODE) && (take))
		{
			targ->pain(targ, attacker, knockback, take);
		}
	}
	else if (take)
	{
		if (targ->pain)
		{
			targ->pain(targ, attacker, knockback, take);
		}
	}

	/* add to the damage inflicted on a player this frame
	   the total will be turned into screen blends and view angle kicks
	   at the end of the frame */
	if (client)
	{
		client->damage_parmor += psave;
		client->damage_armor += asave;
		client->damage_blood += take;
		client->damage_knockback += knockback;
		VectorCopy(point, client->damage_from);
	}
}

void
T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
		edict_t *ignore, float radius, int mod)
{
	float points;
	edict_t *ent = NULL;
	vec3_t v;
	vec3_t dir;

	if (!inflictor || !attacker)
	{
		return;
	}

	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
	{
		if (ent == ignore)
		{
			continue;
		}

		if (!ent->takedamage)
		{
			continue;
		}

		VectorAdd(ent->mins, ent->maxs, v);
		VectorMA(ent->s.origin, 0.5, v, v);
		VectorSubtract(inflictor->s.origin, v, v);
		points = damage - 0.5 * VectorLength(v);

		if (ent == attacker)
		{
			points = points * 0.5;
		}

		if (points > 0)
		{
			if (CanDamage(ent, inflictor))
			{
				VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
				T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
						vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
						mod);
			}
		}
	}
}

