mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2024-11-23 12:52:41 +00:00
911 lines
23 KiB
C++
911 lines
23 KiB
C++
// Copyright (c) ZeniMax Media Inc.
|
|
// Licensed under the GNU General Public License 2.0.
|
|
// g_combat.c
|
|
|
|
#include "g_local.h"
|
|
|
|
/*
|
|
============
|
|
CanDamage
|
|
|
|
Returns true if the inflictor can directly damage the target. Used for
|
|
explosions and melee attacks.
|
|
============
|
|
*/
|
|
bool CanDamage(edict_t *targ, edict_t *inflictor)
|
|
{
|
|
vec3_t dest;
|
|
trace_t trace;
|
|
|
|
// bmodels need special checking because their origin is 0,0,0
|
|
vec3_t inflictor_center;
|
|
|
|
if (inflictor->linked)
|
|
inflictor_center = (inflictor->absmin + inflictor->absmax) * 0.5f;
|
|
else
|
|
inflictor_center = inflictor->s.origin;
|
|
|
|
if (targ->solid == SOLID_BSP)
|
|
{
|
|
dest = closest_point_to_box(inflictor_center, targ->absmin, targ->absmax);
|
|
|
|
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
}
|
|
|
|
vec3_t targ_center;
|
|
|
|
if (targ->linked)
|
|
targ_center = (targ->absmin + targ->absmax) * 0.5f;
|
|
else
|
|
targ_center = targ->s.origin;
|
|
|
|
trace = gi.traceline(inflictor_center, targ_center, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
|
|
dest = targ_center;
|
|
dest[0] += 15.0f;
|
|
dest[1] += 15.0f;
|
|
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
|
|
dest = targ_center;
|
|
dest[0] += 15.0f;
|
|
dest[1] -= 15.0f;
|
|
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
|
|
dest = targ_center;
|
|
dest[0] -= 15.0f;
|
|
dest[1] += 15.0f;
|
|
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
|
|
dest = targ_center;
|
|
dest[0] -= 15.0f;
|
|
dest[1] -= 15.0f;
|
|
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0f)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Killed
|
|
============
|
|
*/
|
|
void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod)
|
|
{
|
|
if (targ->health < -999)
|
|
targ->health = -999;
|
|
|
|
// [Paril-KEX]
|
|
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
|
|
{
|
|
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
|
|
{
|
|
cleanupHealTarget(targ->enemy);
|
|
}
|
|
|
|
// clean up self
|
|
targ->monsterinfo.aiflags &= ~AI_MEDIC;
|
|
}
|
|
|
|
targ->enemy = attacker;
|
|
targ->lastMOD = mod;
|
|
|
|
// [Paril-KEX] monsters call die in their damage handler
|
|
if (targ->svflags & SVF_MONSTER)
|
|
return;
|
|
|
|
targ->die(targ, inflictor, attacker, damage, point, mod);
|
|
|
|
if (targ->monsterinfo.setskin)
|
|
targ->monsterinfo.setskin(targ);
|
|
}
|
|
|
|
/*
|
|
================
|
|
SpawnDamage
|
|
================
|
|
*/
|
|
void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage)
|
|
{
|
|
if (damage > 255)
|
|
damage = 255;
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(type);
|
|
// gi.WriteByte (damage);
|
|
gi.WritePosition(origin);
|
|
gi.WriteDir(normal);
|
|
gi.multicast(origin, MULTICAST_PVS, false);
|
|
}
|
|
|
|
/*
|
|
============
|
|
T_Damage
|
|
|
|
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
|
|
============
|
|
*/
|
|
static int CheckPowerArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, damageflags_t dflags)
|
|
{
|
|
gclient_t *client;
|
|
int save;
|
|
item_id_t power_armor_type;
|
|
int damagePerCell;
|
|
int pa_te_type;
|
|
int *power;
|
|
int power_used;
|
|
|
|
if (ent->health <= 0)
|
|
return 0;
|
|
|
|
if (!damage)
|
|
return 0;
|
|
|
|
client = ent->client;
|
|
|
|
if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR)) // PGM
|
|
return 0;
|
|
|
|
if (client)
|
|
{
|
|
power_armor_type = PowerArmorType(ent);
|
|
power = &client->pers.inventory[IT_AMMO_CELLS];
|
|
}
|
|
else if (ent->svflags & SVF_MONSTER)
|
|
{
|
|
power_armor_type = ent->monsterinfo.power_armor_type;
|
|
power = &ent->monsterinfo.power_armor_power;
|
|
}
|
|
else
|
|
return 0;
|
|
|
|
if (power_armor_type == IT_NULL)
|
|
return 0;
|
|
if (!*power)
|
|
return 0;
|
|
|
|
if (power_armor_type == IT_ITEM_POWER_SCREEN)
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward;
|
|
|
|
// only works if damage point is in front
|
|
AngleVectors(ent->s.angles, forward, nullptr, nullptr);
|
|
vec = point - ent->s.origin;
|
|
vec.normalize();
|
|
dot = vec.dot(forward);
|
|
if (dot <= 0.3f)
|
|
return 0;
|
|
|
|
damagePerCell = 1;
|
|
pa_te_type = TE_SCREEN_SPARKS;
|
|
damage = damage / 3;
|
|
}
|
|
else
|
|
{
|
|
if (ctf->integer)
|
|
damagePerCell = 1; // power armor is weaker in CTF
|
|
else
|
|
damagePerCell = 2;
|
|
pa_te_type = TE_SCREEN_SPARKS;
|
|
damage = (2 * damage) / 3;
|
|
}
|
|
|
|
// Paril: fix small amounts of damage not
|
|
// being absorbed
|
|
damage = max(1, damage);
|
|
|
|
save = *power * damagePerCell;
|
|
|
|
if (!save)
|
|
return 0;
|
|
|
|
// [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
|
|
if (dflags & DAMAGE_ENERGY)
|
|
save = max(1, save / 2);
|
|
|
|
if (save > damage)
|
|
save = damage;
|
|
|
|
// [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
|
|
if (dflags & DAMAGE_ENERGY)
|
|
power_used = (save / damagePerCell) * 2;
|
|
else
|
|
power_used = save / damagePerCell;
|
|
|
|
power_used = max(1, power_used);
|
|
|
|
SpawnDamage(pa_te_type, point, normal, save);
|
|
ent->powerarmor_time = level.time + 200_ms;
|
|
|
|
// Paril: adjustment so that power armor
|
|
// always uses damagePerCell even if it does
|
|
// only a single point of damage
|
|
*power = max(0, *power - max(damagePerCell, power_used));
|
|
|
|
// check power armor turn-off states
|
|
if (ent->client)
|
|
G_CheckPowerArmor(ent);
|
|
else if (!*power)
|
|
{
|
|
gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/mon_power2.wav"), 1.f, ATTN_NORM, 0.f);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_POWER_SPLASH);
|
|
gi.WriteEntity(ent);
|
|
gi.WriteByte((power_armor_type == IT_ITEM_POWER_SCREEN) ? 1 : 0);
|
|
gi.multicast(ent->s.origin, MULTICAST_PHS, false);
|
|
}
|
|
|
|
return save;
|
|
}
|
|
|
|
static int CheckArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, int te_sparks,
|
|
damageflags_t dflags)
|
|
{
|
|
gclient_t *client;
|
|
int save;
|
|
item_id_t index;
|
|
gitem_t *armor;
|
|
int *power;
|
|
|
|
if (!damage)
|
|
return 0;
|
|
|
|
// ROGUE
|
|
if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR))
|
|
// ROGUE
|
|
return 0;
|
|
|
|
client = ent->client;
|
|
index = ArmorIndex(ent);
|
|
|
|
if (!index)
|
|
return 0;
|
|
|
|
armor = GetItemByIndex(index);
|
|
|
|
if (dflags & DAMAGE_ENERGY)
|
|
save = (int) ceilf(armor->armor_info->energy_protection * damage);
|
|
else
|
|
save = (int) ceilf(armor->armor_info->normal_protection * damage);
|
|
|
|
if (client)
|
|
power = &client->pers.inventory[index];
|
|
else
|
|
power = &ent->monsterinfo.armor_power;
|
|
|
|
if (save >= *power)
|
|
save = *power;
|
|
|
|
if (!save)
|
|
return 0;
|
|
|
|
*power -= save;
|
|
|
|
if (!client && !ent->monsterinfo.armor_power)
|
|
ent->monsterinfo.armor_type = IT_NULL;
|
|
|
|
SpawnDamage(te_sparks, point, normal, save);
|
|
|
|
return save;
|
|
}
|
|
|
|
void M_ReactToDamage(edict_t *targ, edict_t *attacker, edict_t *inflictor)
|
|
{
|
|
// pmm
|
|
bool new_tesla;
|
|
|
|
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
|
|
return;
|
|
|
|
//=======
|
|
// ROGUE
|
|
// logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker)
|
|
// attack the tesla
|
|
// also, target the tesla if it's a "new" tesla
|
|
if ((inflictor) && (!strcmp(inflictor->classname, "tesla_mine")))
|
|
{
|
|
new_tesla = MarkTeslaArea(targ, inflictor);
|
|
if ((new_tesla || brandom()) && (!targ->enemy || !targ->enemy->classname || strcmp(targ->enemy->classname, "tesla_mine")))
|
|
TargetTesla(targ, inflictor);
|
|
return;
|
|
}
|
|
// ROGUE
|
|
//=======
|
|
|
|
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;
|
|
}
|
|
|
|
// PGM
|
|
// if we're currently mad at something a target_anger made us mad at, ignore
|
|
// damage
|
|
if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
|
|
{
|
|
float percentHealth;
|
|
|
|
// make sure whatever we were pissed at is still around.
|
|
if (targ->enemy->inuse)
|
|
{
|
|
percentHealth = (float) (targ->health) / (float) (targ->max_health);
|
|
if (targ->enemy->inuse && percentHealth > 0.33f)
|
|
return;
|
|
}
|
|
|
|
// remove the target anger flag
|
|
targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
|
|
}
|
|
// PGM
|
|
|
|
// we recently switched from reacting to damage, don't do it
|
|
if (targ->monsterinfo.react_to_damage_time > level.time)
|
|
return;
|
|
|
|
// PMM
|
|
// if we're healing someone, do like above and try to stay with them
|
|
if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
|
|
{
|
|
float percentHealth;
|
|
|
|
percentHealth = (float) (targ->health) / (float) (targ->max_health);
|
|
// ignore it some of the time
|
|
if (targ->enemy->inuse && percentHealth > 0.25f)
|
|
return;
|
|
|
|
// remove the medic flag
|
|
cleanupHealTarget(targ->enemy);
|
|
targ->monsterinfo.aiflags &= ~AI_MEDIC;
|
|
}
|
|
// PMM
|
|
|
|
// we now know that we are not both good guys
|
|
targ->monsterinfo.react_to_damage_time = level.time + random_time(3_sec, 5_sec);
|
|
|
|
// 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 != attacker)
|
|
{
|
|
if (targ->enemy && targ->enemy->client)
|
|
{
|
|
if (visible(targ, targ->enemy))
|
|
{
|
|
targ->oldenemy = attacker;
|
|
return;
|
|
}
|
|
targ->oldenemy = targ->enemy;
|
|
}
|
|
|
|
// [Paril-KEX]
|
|
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
|
|
{
|
|
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
|
|
{
|
|
cleanupHealTarget(targ->enemy);
|
|
}
|
|
|
|
// clean up self
|
|
targ->monsterinfo.aiflags &= ~AI_MEDIC;
|
|
}
|
|
|
|
targ->enemy = attacker;
|
|
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
|
FoundTarget(targ);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (attacker->enemy == targ // if they *meant* to shoot us, then shoot back
|
|
// it's the same base (walk/swim/fly) type and both don't ignore shots,
|
|
// get mad at them
|
|
|| (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
|
|
(strcmp(targ->classname, attacker->classname) != 0) && !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
|
|
!(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS)))
|
|
{
|
|
if (targ->enemy != attacker)
|
|
{
|
|
// [Paril-KEX]
|
|
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
|
|
{
|
|
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
|
|
{
|
|
cleanupHealTarget(targ->enemy);
|
|
}
|
|
|
|
// clean up self
|
|
targ->monsterinfo.aiflags &= ~AI_MEDIC;
|
|
}
|
|
|
|
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 && attacker->enemy != targ && targ->enemy != attacker->enemy)
|
|
{
|
|
if (targ->enemy != attacker->enemy)
|
|
{
|
|
// [Paril-KEX]
|
|
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
|
|
{
|
|
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
|
|
{
|
|
cleanupHealTarget(targ->enemy);
|
|
}
|
|
|
|
// clean up self
|
|
targ->monsterinfo.aiflags &= ~AI_MEDIC;
|
|
}
|
|
|
|
if (targ->enemy && targ->enemy->client)
|
|
targ->oldenemy = targ->enemy;
|
|
targ->enemy = attacker->enemy;
|
|
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
|
|
FoundTarget(targ);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if the two given entities are on the same team
|
|
bool OnSameTeam(edict_t *ent1, edict_t *ent2)
|
|
{
|
|
// monsters are never on our team atm
|
|
if (!ent1->client || !ent2->client)
|
|
return false;
|
|
// we're never on our own team
|
|
else if (ent1 == ent2)
|
|
return false;
|
|
|
|
// [Paril-KEX] coop 'team' support
|
|
if (coop->integer)
|
|
return ent1->client && ent2->client;
|
|
// ZOID
|
|
else if (G_TeamplayEnabled() && ent1->client && ent2->client)
|
|
{
|
|
if (ent1->client->resp.ctf_team == ent2->client->resp.ctf_team)
|
|
return true;
|
|
}
|
|
// ZOID
|
|
|
|
return false;
|
|
}
|
|
|
|
// check if the two entities are on a team and that
|
|
// they wouldn't damage each other
|
|
bool CheckTeamDamage(edict_t *targ, edict_t *attacker)
|
|
{
|
|
// always damage teammates if friendly fire is enabled
|
|
if (g_friendly_fire->integer)
|
|
return false;
|
|
|
|
return OnSameTeam(targ, attacker);
|
|
}
|
|
|
|
void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point,
|
|
const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod)
|
|
{
|
|
gclient_t *client;
|
|
int take;
|
|
int save;
|
|
int asave;
|
|
int psave;
|
|
int te_sparks;
|
|
bool sphere_notified; // PGM
|
|
|
|
if (!targ->takedamage)
|
|
return;
|
|
|
|
if (g_instagib->integer && attacker->client && targ->client)
|
|
{
|
|
// [Kex] always kill no matter what on instagib
|
|
damage = 9999;
|
|
}
|
|
|
|
sphere_notified = false; // PGM
|
|
|
|
// friendly fire avoidance
|
|
// if enabled you can't hurt teammates (but you can hurt yourself)
|
|
// knockback still occurs
|
|
if ((targ != attacker) && !(dflags & DAMAGE_NO_PROTECTION))
|
|
{
|
|
// mark as friendly fire
|
|
if (OnSameTeam(targ, attacker))
|
|
{
|
|
mod.friendly_fire = true;
|
|
|
|
// if we're not a nuke & friendly fire is disabled, just kill the damage
|
|
if (!g_friendly_fire->integer && (mod.id != MOD_NUKE))
|
|
damage = 0;
|
|
}
|
|
}
|
|
|
|
// ROGUE
|
|
// allow the deathmatch game to change values
|
|
if (deathmatch->integer && gamerules->integer)
|
|
{
|
|
if (DMGame.ChangeDamage)
|
|
damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
|
|
if (DMGame.ChangeKnockback)
|
|
knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
|
|
|
|
if (!damage)
|
|
return;
|
|
}
|
|
// ROGUE
|
|
|
|
// easy mode takes half damage
|
|
if (skill->integer == 0 && deathmatch->integer == 0 && targ->client && damage)
|
|
{
|
|
damage /= 2;
|
|
if (!damage)
|
|
damage = 1;
|
|
}
|
|
|
|
if ( ( targ->svflags & SVF_MONSTER ) != 0 ) {
|
|
damage *= ai_damage_scale->integer;
|
|
} else {
|
|
damage *= g_damage_scale->integer;
|
|
} // mal: just for debugging...
|
|
|
|
client = targ->client;
|
|
|
|
// PMM - defender sphere takes half damage
|
|
if (damage && (client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == SPHERE_DEFENDER))
|
|
{
|
|
damage /= 2;
|
|
if (!damage)
|
|
damage = 1;
|
|
}
|
|
|
|
if (dflags & DAMAGE_BULLET)
|
|
te_sparks = TE_BULLET_SPARKS;
|
|
else
|
|
te_sparks = TE_SPARKS;
|
|
|
|
// bonus damage for surprising a monster
|
|
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) &&
|
|
(!targ->enemy || targ->monsterinfo.surprise_time == level.time) && (targ->health > 0))
|
|
{
|
|
damage *= 2;
|
|
targ->monsterinfo.surprise_time = level.time;
|
|
}
|
|
|
|
// ZOID
|
|
// strength tech
|
|
damage = CTFApplyStrength(attacker, damage);
|
|
// ZOID
|
|
|
|
if ((targ->flags & FL_NO_KNOCKBACK) ||
|
|
((targ->flags & FL_ALIVE_KNOCKBACK_ONLY) && (!targ->deadflag || targ->dead_time != level.time)))
|
|
knockback = 0;
|
|
|
|
// figure momentum add
|
|
if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
|
{
|
|
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) &&
|
|
(targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
|
|
{
|
|
vec3_t normalized = dir.normalized();
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
if (targ->mass < 50)
|
|
mass = 50;
|
|
else
|
|
mass = (float) targ->mass;
|
|
|
|
if (targ->client && attacker == targ)
|
|
kvel = normalized * (1600.0f * knockback / mass); // the rocket jump hack...
|
|
else
|
|
kvel = normalized * (500.0f * knockback / mass);
|
|
|
|
targ->velocity += kvel;
|
|
}
|
|
}
|
|
|
|
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
|
|
// ROGUE
|
|
if (!(dflags & DAMAGE_NO_PROTECTION) &&
|
|
(((client && client->invincible_time > level.time)) ||
|
|
((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_time > level.time)))
|
|
// ROGUE
|
|
{
|
|
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_sec;
|
|
}
|
|
take = 0;
|
|
save = damage;
|
|
}
|
|
|
|
// ZOID
|
|
// team armor protect
|
|
if (G_TeamplayEnabled() && targ->client && attacker->client &&
|
|
targ->client->resp.ctf_team == attacker->client->resp.ctf_team && targ != attacker &&
|
|
g_teamplay_armor_protect->integer)
|
|
{
|
|
psave = asave = 0;
|
|
}
|
|
else
|
|
{
|
|
// ZOID
|
|
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;
|
|
|
|
// ZOID
|
|
// resistance tech
|
|
take = CTFApplyResistance(targ, take);
|
|
// ZOID
|
|
|
|
// ZOID
|
|
CTFCheckHurtCarrier(targ, attacker);
|
|
// ZOID
|
|
|
|
// ROGUE - this option will do damage both to the armor and person. originally for DPU rounds
|
|
if (dflags & DAMAGE_DESTROY_ARMOR)
|
|
{
|
|
if (!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
|
|
!(client && client->invincible_time > level.time))
|
|
{
|
|
take = damage;
|
|
}
|
|
}
|
|
// ROGUE
|
|
|
|
// [Paril-KEX] player hit markers
|
|
if (targ != attacker && attacker->client && targ->health > 0 && !((targ->svflags & SVF_DEADMONSTER) || (targ->flags & FL_NO_DAMAGE_EFFECTS)) && mod.id != MOD_TARGET_LASER)
|
|
attacker->client->ps.stats[STAT_HIT_MARKER] += take + psave + asave;
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
if (!(targ->flags & FL_NO_DAMAGE_EFFECTS))
|
|
{
|
|
// ROGUE
|
|
if (targ->flags & FL_MECHANICAL)
|
|
SpawnDamage(TE_ELECTRIC_SPARKS, point, normal, take);
|
|
// ROGUE
|
|
else if ((targ->svflags & SVF_MONSTER) || (client))
|
|
{
|
|
// XATRIX
|
|
if (strcmp(targ->classname, "monster_gekk") == 0)
|
|
SpawnDamage(TE_GREENBLOOD, point, normal, take);
|
|
// XATRIX
|
|
// ROGUE
|
|
else if (mod.id == MOD_CHAINFIST)
|
|
SpawnDamage(TE_MOREBLOOD, point, normal, 255);
|
|
// ROGUE
|
|
else
|
|
SpawnDamage(TE_BLOOD, point, normal, take);
|
|
}
|
|
else
|
|
SpawnDamage(te_sparks, point, normal, take);
|
|
}
|
|
|
|
if (!CTFMatchSetup())
|
|
targ->health = targ->health - take;
|
|
|
|
if ((targ->flags & FL_IMMORTAL) && targ->health <= 0)
|
|
targ->health = 1;
|
|
|
|
// PGM - spheres need to know who to shoot at
|
|
if (client && client->owned_sphere)
|
|
{
|
|
sphere_notified = true;
|
|
if (client->owned_sphere->pain)
|
|
client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
|
|
}
|
|
// PGM
|
|
|
|
if (targ->health <= 0)
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
{
|
|
targ->flags |= FL_ALIVE_KNOCKBACK_ONLY;
|
|
targ->dead_time = level.time;
|
|
}
|
|
targ->monsterinfo.damage_blood += take;
|
|
targ->monsterinfo.damage_attacker = attacker;
|
|
targ->monsterinfo.damage_inflictor = inflictor;
|
|
targ->monsterinfo.damage_from = point;
|
|
targ->monsterinfo.damage_mod = mod;
|
|
targ->monsterinfo.damage_knockback += knockback;
|
|
Killed(targ, inflictor, attacker, take, point, mod);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// PGM - spheres need to know who to shoot at
|
|
if (!sphere_notified)
|
|
{
|
|
if (client && client->owned_sphere)
|
|
{
|
|
sphere_notified = true;
|
|
if (client->owned_sphere->pain)
|
|
client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
|
|
}
|
|
}
|
|
// PGM
|
|
|
|
if ( targ->client ) {
|
|
targ->client->last_attacker_time = level.time;
|
|
}
|
|
|
|
if (targ->svflags & SVF_MONSTER)
|
|
{
|
|
if (damage > 0)
|
|
{
|
|
M_ReactToDamage(targ, attacker, inflictor);
|
|
|
|
targ->monsterinfo.damage_attacker = attacker;
|
|
targ->monsterinfo.damage_inflictor = inflictor;
|
|
targ->monsterinfo.damage_blood += take;
|
|
targ->monsterinfo.damage_from = point;
|
|
targ->monsterinfo.damage_mod = mod;
|
|
targ->monsterinfo.damage_knockback += knockback;
|
|
}
|
|
|
|
if (targ->monsterinfo.setskin)
|
|
targ->monsterinfo.setskin(targ);
|
|
}
|
|
else if (take && targ->pain)
|
|
targ->pain(targ, attacker, (float) knockback, take, mod);
|
|
|
|
// 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;
|
|
client->damage_from = point;
|
|
client->last_damage_time = level.time + COOP_DAMAGE_RESPAWN_TIME;
|
|
|
|
if (!(dflags & DAMAGE_NO_INDICATOR) && inflictor != world && attacker != world && (take || psave || asave))
|
|
{
|
|
damage_indicator_t *indicator = nullptr;
|
|
size_t i;
|
|
|
|
for (i = 0; i < client->num_damage_indicators; i++)
|
|
{
|
|
if ((point - client->damage_indicators[i].from).length() < 32.f)
|
|
{
|
|
indicator = &client->damage_indicators[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!indicator && i != MAX_DAMAGE_INDICATORS)
|
|
{
|
|
indicator = &client->damage_indicators[i];
|
|
// for projectile direct hits, use the attacker; otherwise
|
|
// use the inflictor (rocket splash should point to the rocket)
|
|
indicator->from = (dflags & DAMAGE_RADIUS) ? inflictor->s.origin : attacker->s.origin;
|
|
indicator->health = indicator->armor = indicator->power = 0;
|
|
client->num_damage_indicators++;
|
|
}
|
|
|
|
if (indicator)
|
|
{
|
|
indicator->health += take;
|
|
indicator->power += psave;
|
|
indicator->armor += asave;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
T_RadiusDamage
|
|
============
|
|
*/
|
|
void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod)
|
|
{
|
|
float points;
|
|
edict_t *ent = nullptr;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
vec3_t inflictor_center;
|
|
|
|
if (inflictor->linked)
|
|
inflictor_center = (inflictor->absmax + inflictor->absmin) * 0.5f;
|
|
else
|
|
inflictor_center = inflictor->s.origin;
|
|
|
|
while ((ent = findradius(ent, inflictor_center, radius)) != nullptr)
|
|
{
|
|
if (ent == ignore)
|
|
continue;
|
|
if (!ent->takedamage)
|
|
continue;
|
|
|
|
if (ent->solid == SOLID_BSP && ent->linked)
|
|
v = closest_point_to_box(inflictor_center, ent->absmin, ent->absmax);
|
|
else
|
|
{
|
|
v = ent->mins + ent->maxs;
|
|
v = ent->s.origin + (v * 0.5f);
|
|
}
|
|
v = inflictor_center - v;
|
|
points = damage - 0.5f * v.length();
|
|
if (ent == attacker)
|
|
points = points * 0.5f;
|
|
if (points > 0)
|
|
{
|
|
if (CanDamage(ent, inflictor))
|
|
{
|
|
dir = (ent->s.origin - inflictor_center).normalized();
|
|
// [Paril-KEX] use closest point on bbox to explosion position
|
|
// to spawn damage effect
|
|
|
|
T_Damage(ent, inflictor, attacker, dir, closest_point_to_box(inflictor_center, ent->absmin, ent->absmax), dir, (int) points, (int) points,
|
|
dflags | DAMAGE_RADIUS, mod);
|
|
}
|
|
}
|
|
}
|
|
}
|